Skip to content

Commit d2d4243

Browse files
committed
feat: integrate Thirdweb Engine into Request Node
1 parent 650c343 commit d2d4243

File tree

4 files changed

+185
-77
lines changed

4 files changed

+185
-77
lines changed

packages/request-node/README.md

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -221,42 +221,25 @@ Default values correspond to the basic configuration used to run a server in a t
221221

222222
#### Options:
223223

224-
- `--port` Port for the server to listen for API requests
225-
- Default value: `3000`
226-
- Environment variable name: `$PORT`
227-
- `--networkId` Id of the Ethereum network used
228-
- Default value: `0`
229-
- Environment variable name: `$ETHEREUM_NETWORK_ID`
230-
- `--providerUrl` URL of the web3 provider for Ethereum
231-
- Default value: `http://localhost:8545`
232-
- Environment variable name: `$WEB3_PROVIDER_URL`
233-
- `--ipfsUrl` URL of the IPFS gateway
234-
- Default value: `http://localhost:5001`
235-
- Environment variable name: `$IPFS_URL`
236-
- `--ipfsTimeout` Timeout threshold to connect to the IPFS gateway
237-
- Default value: `10000`
238-
- Environment variable name: `$IPFS_TIMEOUT`
239-
- `--blockConfirmations` The number of block confirmations to consider a transaction successful
240-
- Default value: `2`
241-
- Environment variable name: `$BLOCK_CONFIRMATIONS`
242-
- `--storageConcurrency` Maximum number of concurrent calls to Ethereum or IPFS
243-
- Default value: `'200'`
244-
- Environment variable name: `$STORAGE_MAX_CONCURRENCY`
245-
- `--logLevel` The maximum level of messages we will log
246-
- Environment variable name: `$LOG_LEVEL`
247-
- Available levels: ERROR, WARN, INFO and DEBUG
248-
- `--logMode` Defines the log format to use
249-
- Environment variable name: `$LOG_MODE`
250-
- Available modes:
251-
- `human` is a more human readable log to display during development
252-
- `machine` is better for parsing on CI or deployments
253-
- `--persistTransactionTimeout` Defines the delay in seconds to wait before sending a timeout when creating or updating a request
254-
- Default value: 600
255-
- Environment variable name: `$PERSIST_TRANSACTION_TIMEOUT`
256-
- `--externalUrl` External url of the node (used to identified where the buffer data are stored before being broadcasted on ethereum)
257-
- Environment variable name: `$EXTERNAL_URL`
258-
- `--graphNodeUrl` External url of the Graph node, if any. If specified, this will replace the traditional data access with the Graph implementation. Default is undefined. See [TheGraph mode](#thegraph-mode).
259-
- Environment variable name: `$GRAPH_NODE_URL`
224+
| Option | Environment Variable | Description | Required | Default Value |
225+
| -------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------------------------------- |
226+
| `--port` | `PORT` | Port for the server to listen for API requests | No | `3000` |
227+
| `--networkId` | `ETHEREUM_NETWORK_ID` | Id of the Ethereum network used | No | `0` |
228+
| `--providerUrl` | `WEB3_PROVIDER_URL` | URL of the web3 provider for Ethereum | No | `http://localhost:8545` |
229+
| `--ipfsUrl` | `IPFS_URL` | URL of the IPFS gateway | No | `http://localhost:5001` |
230+
| `--ipfsTimeout` | `IPFS_TIMEOUT` | Timeout threshold to connect to the IPFS gateway | No | `10000` |
231+
| `--blockConfirmations` | `BLOCK_CONFIRMATIONS` | The number of block confirmations to consider a transaction successful | No | `2` |
232+
| `--storageConcurrency` | `STORAGE_MAX_CONCURRENCY` | Maximum number of concurrent calls to Ethereum or IPFS | No | `200` |
233+
| `--logLevel` | `LOG_LEVEL` | The maximum level of messages we will log (ERROR, WARN, INFO or DEBUG) | No | `INFO` |
234+
| `--logMode` | `LOG_MODE` | The log format to use (human or machine) | No | `human` |
235+
| `--persistTransactionTimeout` | `PERSIST_TRANSACTION_TIMEOUT` | Defines the delay in seconds to wait before sending a timeout when creating or updating a request | No | `600` |
236+
| `--externalUrl` | `EXTERNAL_URL` | External url of the node (used to identify where the buffer data are stored) | No | - |
237+
| `--graphNodeUrl` | `GRAPH_NODE_URL` | External url of the Graph node. See [TheGraph mode](#thegraph-mode) | No | - |
238+
| `--thirdwebEngineUrl` | `THIRDWEB_ENGINE_URL` | URL of your Thirdweb Engine instance | **Yes** | - |
239+
| `--thirdwebAccessToken` | `THIRDWEB_ACCESS_TOKEN` | Access token for Thirdweb Engine | **Yes** | - |
240+
| `--thirdwebBackendWalletAddress` | `THIRDWEB_BACKEND_WALLET_ADDRESS` | Address of the wallet configured in Thirdweb Engine | **Yes** | - |
241+
| `--thirdwebWebhookSecret` | `THIRDWEB_WEBHOOK_SECRET` | Secret for verifying webhook signatures | No | - |
242+
| - | `MNEMONIC` | The mnemonic for generating the wallet private key | **Yes** (except on private networks) | `candy maple...` (only for testing) |
260243

261244
#### Mnemonic
262245

@@ -336,6 +319,25 @@ yarn start
336319
Open a browser and navigate towards: http://localhost:3000/status
337320
You can see the details of your local Request & IPFS nodes.
338321

322+
## Thirdweb Engine Integration
323+
324+
The Request Node uses Thirdweb Engine for transaction submission, which offers several advantages:
325+
326+
- No need to manage private keys in the Request Node
327+
- Better transaction management and monitoring
328+
- Automated gas price optimization and retry mechanisms
329+
- Webhook notifications for transaction status
330+
331+
### Setting Up Thirdweb Engine
332+
333+
1. Deploy Thirdweb Engine by following the [official documentation](https://portal.thirdweb.com/engine/getting-started)
334+
2. Create a wallet in Thirdweb Engine for the Request Node
335+
3. Ensure the wallet has sufficient funds for gas costs
336+
4. Generate an access token with appropriate permissions
337+
5. Configure the Request Node with the required environment variables (see Options table above)
338+
339+
**Note:** The Request Node no longer supports transaction submission through local wallets. All transactions are processed through Thirdweb Engine.
340+
339341
## Contributing
340342

341343
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

packages/request-node/src/config.ts

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ const defaultValues = {
4141
mode: LogMode.human,
4242
},
4343
server: {
44-
headers: '{}',
4544
port: 3000,
4645
},
4746
wallet: {
@@ -51,6 +50,12 @@ const defaultValues = {
5150
litProtocolRPC: 'https://yellowstone-rpc.litprotocol.com',
5251
litProtocolCapacityCreditsUsage: '1',
5352
litProtocolCapacityCreditsExpirationInSeconds: 10 * 60, // 10 minutes
53+
thirdweb: {
54+
engineUrl: '',
55+
accessToken: '',
56+
backendWalletAddress: '',
57+
webhookSecret: '',
58+
},
5459
};
5560

5661
const getOption = <T extends string | number>(
@@ -83,6 +88,42 @@ export const getLitProtocolNetwork = makeOption(
8388
defaultValues.litProtocolNetwork,
8489
);
8590

91+
/**
92+
* Get Thirdweb Engine URL from command line argument, environment variables or default values
93+
*/
94+
export const getThirdwebEngineUrl = makeOption(
95+
'thirdwebEngineUrl',
96+
'THIRDWEB_ENGINE_URL',
97+
defaultValues.thirdweb.engineUrl,
98+
);
99+
100+
/**
101+
* Get Thirdweb Access Token from command line argument, environment variables or default values
102+
*/
103+
export const getThirdwebAccessToken = makeOption(
104+
'thirdwebAccessToken',
105+
'THIRDWEB_ACCESS_TOKEN',
106+
defaultValues.thirdweb.accessToken,
107+
);
108+
109+
/**
110+
* Get Thirdweb Backend Wallet Address from command line argument, environment variables or default values
111+
*/
112+
export const getThirdwebBackendWalletAddress = makeOption(
113+
'thirdwebBackendWalletAddress',
114+
'THIRDWEB_BACKEND_WALLET_ADDRESS',
115+
defaultValues.thirdweb.backendWalletAddress,
116+
);
117+
118+
/**
119+
* Get Thirdweb Webhook Secret from command line argument, environment variables or default values
120+
*/
121+
export const getThirdwebWebhookSecret = makeOption(
122+
'thirdwebWebhookSecret',
123+
'THIRDWEB_WEBHOOK_SECRET',
124+
defaultValues.thirdweb.webhookSecret,
125+
);
126+
86127
/**
87128
* Get the litProtocolNetwork from command line argument, environment variables or default values to send with the API responses
88129
*/
@@ -240,9 +281,12 @@ export function getHelpMessage(): string {
240281
OPTIONS
241282
SERVER OPTIONS
242283
port (${defaultValues.server.port})\t\t\t\tPort for the server to listen for API requests
243-
headers (${
244-
defaultValues.server.headers
245-
})\t\t\t\tCustom headers to send with the API responses
284+
285+
THIRDWEB ENGINE OPTIONS
286+
thirdwebEngineUrl\t\t\t\tURL of your Thirdweb Engine instance (REQUIRED)
287+
thirdwebAccessToken\t\t\t\tAccess token for Thirdweb Engine (REQUIRED)
288+
thirdwebBackendWalletAddress\t\tAddress of the wallet configured in Thirdweb Engine (REQUIRED)
289+
thirdwebWebhookSecret\t\t\tSecret for verifying webhook signatures (optional)
246290
247291
THE GRAPH OPTIONS
248292
graphNodeUrl (${defaultValues.storage.thegraph.nodeUrl})\t\t\t\tURL of the Graph node
@@ -280,13 +324,13 @@ export function getHelpMessage(): string {
280324
logMode (${defaultValues.log.mode})\t\t\tThe node log mode (human or machine)
281325
282326
EXAMPLE
283-
yarn start --port 5000 --networkId 1
327+
yarn start --port 5000 --networkId 1 --thirdwebEngineUrl=https://engine.thirdweb.io --thirdwebAccessToken=your_token --thirdwebBackendWalletAddress=0x123...
284328
285-
All options are optional, not specified options are read from environment variables
286-
If the environment variable is not specified, default value is used
329+
All options except Thirdweb Engine options are optional. Thirdweb Engine options are required and can be set via environment variables if not specified in command line.
287330
288331
Default mnemonic is:
289332
${defaultValues.wallet.mnemonic}
333+
NOTE: This mnemonic should ONLY be used for testing on private networks.
290334
`;
291335

292336
return message;
@@ -306,5 +350,35 @@ export const getConfigDisplay = (): string => {
306350
Lit Protocol RPC: ${getLitProtocolRPC()}
307351
Lit Protocol Capacity Credits Uses: ${getLitProtocolCapacityCreditsUsage()}
308352
Lit Protocol Capacity Credits Expiration in seconds: ${getLitProtocolCapacityCreditsExpirationInSeconds()}
353+
Thirdweb Engine URL: ${getThirdwebEngineUrl()}
354+
Thirdweb Backend Wallet: ${getThirdwebBackendWalletAddress()}
309355
`;
310356
};
357+
358+
/**
359+
* Check if all required Thirdweb configuration values are provided
360+
* @throws Error if required configuration is missing
361+
*/
362+
export function validateThirdwebConfig(): void {
363+
const engineUrl = getThirdwebEngineUrl();
364+
const accessToken = getThirdwebAccessToken();
365+
const backendWalletAddress = getThirdwebBackendWalletAddress();
366+
367+
if (!engineUrl) {
368+
throw new Error(
369+
'Thirdweb Engine URL is required. Set THIRDWEB_ENGINE_URL environment variable or use --thirdwebEngineUrl option.',
370+
);
371+
}
372+
373+
if (!accessToken) {
374+
throw new Error(
375+
'Thirdweb Access Token is required. Set THIRDWEB_ACCESS_TOKEN environment variable or use --thirdwebAccessToken option.',
376+
);
377+
}
378+
379+
if (!backendWalletAddress) {
380+
throw new Error(
381+
'Thirdweb Backend Wallet Address is required. Set THIRDWEB_BACKEND_WALLET_ADDRESS environment variable or use --thirdwebBackendWalletAddress option.',
382+
);
383+
}
384+
}
Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,57 @@
1-
import { providers, Wallet } from 'ethers';
2-
import { NonceManager } from '@ethersproject/experimental';
3-
import { CurrencyTypes, DataAccessTypes, LogTypes, StorageTypes } from '@requestnetwork/types';
4-
5-
import * as config from './config';
61
import { TheGraphDataAccess } from '@requestnetwork/thegraph-data-access';
2+
import { EthereumStorage, ThirdwebTransactionSubmitter } from '@requestnetwork/ethereum-storage';
73
import { PendingStore } from '@requestnetwork/data-access';
8-
import { EthereumStorage, EthereumTransactionSubmitter } from '@requestnetwork/ethereum-storage';
4+
import { LogTypes, StorageTypes } from '@requestnetwork/types';
5+
import * as config from './config';
96

10-
export function getDataAccess(
11-
network: CurrencyTypes.EvmChainName,
7+
/**
8+
* Creates and returns a data access instance
9+
* @param network The Ethereum network to use
10+
* @param ipfsStorage The IPFS storage instance
11+
* @param logger Logger instance
12+
* @param graphqlUrl GraphQL endpoint URL
13+
* @param blockConfirmations Number of block confirmations to wait for
14+
* @returns A data access instance
15+
*/
16+
export async function getDataAccess(
17+
network: string,
1218
ipfsStorage: StorageTypes.IIpfsStorage,
1319
logger: LogTypes.ILogger,
14-
): DataAccessTypes.IDataAccess {
15-
const graphNodeUrl = config.getGraphNodeUrl();
20+
graphqlUrl: string,
21+
blockConfirmations: number,
22+
): Promise<TheGraphDataAccess> {
23+
// Validate that all required Thirdweb config options are set
24+
config.validateThirdwebConfig();
1625

17-
const wallet = Wallet.fromMnemonic(config.getMnemonic()).connect(
18-
new providers.StaticJsonRpcProvider(config.getStorageWeb3ProviderUrl()),
19-
);
26+
logger.info('Using Thirdweb Engine for transaction submission');
2027

21-
const signer = new NonceManager(wallet);
22-
23-
const gasPriceMin = config.getGasPriceMin();
24-
const gasPriceMax = config.getGasPriceMax();
25-
const gasPriceMultiplier = config.getGasPriceMultiplier();
26-
const blockConfirmations = config.getBlockConfirmations();
27-
const txSubmitter = new EthereumTransactionSubmitter({
28+
// Create ThirdwebTransactionSubmitter
29+
const txSubmitter = new ThirdwebTransactionSubmitter({
30+
engineUrl: config.getThirdwebEngineUrl(),
31+
accessToken: config.getThirdwebAccessToken(),
32+
backendWalletAddress: config.getThirdwebBackendWalletAddress(),
2833
network,
2934
logger,
30-
gasPriceMin,
31-
gasPriceMax,
32-
gasPriceMultiplier,
33-
signer,
3435
});
35-
const pendingStore = new PendingStore();
36+
37+
// Initialize the transaction submitter
38+
await txSubmitter.initialize();
39+
40+
// Create Ethereum Storage with the transaction submitter
3641
const storage = new EthereumStorage({
3742
ipfsStorage,
3843
txSubmitter,
39-
logger,
4044
blockConfirmations,
45+
logger,
4146
});
47+
48+
// Create and return TheGraphDataAccess
4249
return new TheGraphDataAccess({
43-
graphql: { url: graphNodeUrl },
50+
graphql: {
51+
url: graphqlUrl,
52+
},
4453
storage,
45-
network,
54+
pendingStore: new PendingStore(),
4655
logger,
47-
pendingStore,
4856
});
4957
}

packages/request-node/src/server.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ConfirmedTransactionStore from './request/confirmedTransactionStore';
88
import { EvmChains } from '@requestnetwork/currency';
99
import { getEthereumStorageNetworkNameFromId } from '@requestnetwork/ethereum-storage';
1010
import { SubgraphClient } from '@requestnetwork/thegraph-data-access';
11+
import { StorageTypes } from '@requestnetwork/types';
1112

1213
// Initialize the node logger
1314
const logger = new Logger(config.getLogLevel(), config.getLogMode());
@@ -21,10 +22,32 @@ const getNetwork = () => {
2122
return network;
2223
};
2324

24-
export const getRequestNode = (): RequestNode => {
25+
// Initialize the data access layer
26+
async function initializeDataAccess(ipfsStorage: StorageTypes.IIpfsStorage) {
27+
// Get configuration values
2528
const network = getNetwork();
26-
const storage = getDataStorage(logger);
27-
const dataAccess = getDataAccess(network, storage, logger);
29+
const graphqlUrl = config.getGraphNodeUrl();
30+
const blockConfirmations = config.getBlockConfirmations();
31+
32+
// Create data access with Thirdweb transaction submitter
33+
const dataAccess = await getDataAccess(
34+
network,
35+
ipfsStorage,
36+
logger,
37+
graphqlUrl,
38+
blockConfirmations,
39+
);
40+
41+
return dataAccess;
42+
}
43+
44+
export const getRequestNode = async (): Promise<RequestNode> => {
45+
const network = getNetwork();
46+
const ipfsStorage = getDataStorage(logger);
47+
await ipfsStorage.initialize();
48+
49+
// Use the initialized data access
50+
const dataAccess = await initializeDataAccess(ipfsStorage);
2851

2952
// we access the subgraph client directly, not through the data access,
3053
// because this feature is specific to RN use with Request Node. Without a node,
@@ -34,12 +57,13 @@ export const getRequestNode = (): RequestNode => {
3457
network,
3558
);
3659

37-
return new RequestNode(dataAccess, storage, confirmedTransactionStore, logger);
60+
return new RequestNode(dataAccess, ipfsStorage, confirmedTransactionStore, logger);
3861
};
3962

63+
// Main server setup
4064
export const startNode = async (): Promise<void> => {
4165
const port = config.getServerPort();
42-
const requestNode = getRequestNode();
66+
const requestNode = await getRequestNode();
4367
const server = withShutdown(
4468
requestNode.listen(port, () => {
4569
logger.info(`Listening on port ${port}`);

0 commit comments

Comments
 (0)