@atxp/worldchain 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -0
- package/dist/cache.js +11 -0
- package/dist/cache.js.map +1 -0
- package/dist/index.cjs +882 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +235 -0
- package/dist/index.js +869 -0
- package/dist/index.js.map +1 -0
- package/dist/mainWalletPaymentMaker.js +136 -0
- package/dist/mainWalletPaymentMaker.js.map +1 -0
- package/dist/minikit.js +254 -0
- package/dist/minikit.js.map +1 -0
- package/dist/smartWalletHelpers.js +46 -0
- package/dist/smartWalletHelpers.js.map +1 -0
- package/dist/spendPermissionShim.js +119 -0
- package/dist/spendPermissionShim.js.map +1 -0
- package/dist/worldchainAccount.js +136 -0
- package/dist/worldchainAccount.js.map +1 -0
- package/dist/worldchainPaymentMaker.js +206 -0
- package/dist/worldchainPaymentMaker.js.map +1 -0
- package/package.json +58 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var client = require('@atxp/client');
|
|
4
|
+
var common = require('@atxp/common');
|
|
5
|
+
var viem = require('viem');
|
|
6
|
+
var accounts = require('viem/accounts');
|
|
7
|
+
var accountAbstraction = require('viem/account-abstraction');
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
This shim replaces spend permission functionality with ERC20 approvals for World Chain.
|
|
11
|
+
This approach will work with any World Chain wallet/connector that supports ERC20 approvals.
|
|
12
|
+
|
|
13
|
+
The implementation is based on the Base shim but adapted for World Chain networks.
|
|
14
|
+
*/
|
|
15
|
+
// Minimal ERC20 ABI for approve and transferFrom functions
|
|
16
|
+
const ERC20_ABI$2 = [
|
|
17
|
+
{
|
|
18
|
+
"constant": false,
|
|
19
|
+
"inputs": [
|
|
20
|
+
{
|
|
21
|
+
"name": "_spender",
|
|
22
|
+
"type": "address"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "_value",
|
|
26
|
+
"type": "uint256"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"name": "approve",
|
|
30
|
+
"outputs": [
|
|
31
|
+
{
|
|
32
|
+
"name": "",
|
|
33
|
+
"type": "bool"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"payable": false,
|
|
37
|
+
"stateMutability": "nonpayable",
|
|
38
|
+
"type": "function"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"constant": false,
|
|
42
|
+
"inputs": [
|
|
43
|
+
{
|
|
44
|
+
"name": "_from",
|
|
45
|
+
"type": "address"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "_to",
|
|
49
|
+
"type": "address"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "_value",
|
|
53
|
+
"type": "uint256"
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"name": "transferFrom",
|
|
57
|
+
"outputs": [
|
|
58
|
+
{
|
|
59
|
+
"name": "",
|
|
60
|
+
"type": "bool"
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"payable": false,
|
|
64
|
+
"stateMutability": "nonpayable",
|
|
65
|
+
"type": "function"
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
async function requestSpendPermission(params) {
|
|
69
|
+
// Support World Chain mainnet only
|
|
70
|
+
if (params.chainId !== client.WORLD_CHAIN_MAINNET.id) {
|
|
71
|
+
throw new Error(`Chain ID ${params.chainId} is not supported. Only World Chain mainnet (${client.WORLD_CHAIN_MAINNET.id}) is supported.`);
|
|
72
|
+
}
|
|
73
|
+
// Use World Chain mainnet config
|
|
74
|
+
const chainConfig = client.WORLD_CHAIN_MAINNET;
|
|
75
|
+
const client$1 = viem.createWalletClient({
|
|
76
|
+
chain: chainConfig,
|
|
77
|
+
transport: viem.custom(params.provider)
|
|
78
|
+
});
|
|
79
|
+
// Send ERC20 approve transaction
|
|
80
|
+
const hash = await client$1.sendTransaction({
|
|
81
|
+
account: params.account,
|
|
82
|
+
to: params.token,
|
|
83
|
+
data: viem.encodeFunctionData({
|
|
84
|
+
abi: ERC20_ABI$2,
|
|
85
|
+
functionName: "approve",
|
|
86
|
+
args: [params.spender, params.allowance]
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
permission: {
|
|
91
|
+
account: params.account,
|
|
92
|
+
spender: params.spender,
|
|
93
|
+
token: params.token,
|
|
94
|
+
allowance: params.allowance.toString(),
|
|
95
|
+
period: params.periodInDays * 24 * 60 * 60,
|
|
96
|
+
start: Math.floor(Date.now() / 1000),
|
|
97
|
+
end: Math.floor(Date.now() / 1000) + params.periodInDays * 24 * 60 * 60,
|
|
98
|
+
salt: '0x0',
|
|
99
|
+
extraData: '0x0'
|
|
100
|
+
},
|
|
101
|
+
signature: hash
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function prepareSpendCallData(params) {
|
|
105
|
+
// Creates transferFrom call data to move tokens from user wallet to ephemeral wallet
|
|
106
|
+
return [
|
|
107
|
+
{
|
|
108
|
+
to: params.permission.permission.token,
|
|
109
|
+
data: viem.encodeFunctionData({
|
|
110
|
+
abi: ERC20_ABI$2,
|
|
111
|
+
functionName: 'transferFrom',
|
|
112
|
+
args: [
|
|
113
|
+
params.permission.permission.account,
|
|
114
|
+
params.permission.permission.spender,
|
|
115
|
+
params.amount
|
|
116
|
+
]
|
|
117
|
+
}),
|
|
118
|
+
value: BigInt(0)
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const USDC_DECIMALS$1 = 6;
|
|
124
|
+
// Minimal ERC20 ABI for transfer function
|
|
125
|
+
const ERC20_ABI$1 = [
|
|
126
|
+
{
|
|
127
|
+
inputs: [
|
|
128
|
+
{ name: 'to', type: 'address' },
|
|
129
|
+
{ name: 'amount', type: 'uint256' }
|
|
130
|
+
],
|
|
131
|
+
name: 'transfer',
|
|
132
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
133
|
+
stateMutability: 'nonpayable',
|
|
134
|
+
type: 'function'
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
/**
|
|
138
|
+
* Validates confirmation delays configuration
|
|
139
|
+
*/
|
|
140
|
+
function validateConfirmationDelays(delays) {
|
|
141
|
+
if (delays.networkPropagationMs < 0) {
|
|
142
|
+
throw new Error('networkPropagationMs must be non-negative');
|
|
143
|
+
}
|
|
144
|
+
if (delays.confirmationFailedMs < 0) {
|
|
145
|
+
throw new Error('confirmationFailedMs must be non-negative');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const DEFAULT_CONFIRMATION_DELAYS = {
|
|
149
|
+
networkPropagationMs: 5000, // 5 seconds for production
|
|
150
|
+
confirmationFailedMs: 15000 // 15 seconds for production
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Gets default confirmation delays based on environment
|
|
154
|
+
*/
|
|
155
|
+
const getDefaultConfirmationDelays = () => {
|
|
156
|
+
if (process.env.NODE_ENV === 'test') {
|
|
157
|
+
return { networkPropagationMs: 10, confirmationFailedMs: 20 };
|
|
158
|
+
}
|
|
159
|
+
return DEFAULT_CONFIRMATION_DELAYS;
|
|
160
|
+
};
|
|
161
|
+
async function waitForTransactionConfirmations(smartWallet, txHash, confirmations, logger, delays = DEFAULT_CONFIRMATION_DELAYS) {
|
|
162
|
+
try {
|
|
163
|
+
const publicClient = smartWallet.client.account?.client;
|
|
164
|
+
if (publicClient && 'waitForTransactionReceipt' in publicClient) {
|
|
165
|
+
logger.info(`Waiting for ${confirmations} confirmations...`);
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
167
|
+
await publicClient.waitForTransactionReceipt({
|
|
168
|
+
hash: txHash,
|
|
169
|
+
confirmations: 1, // Reduce confirmations to speed up
|
|
170
|
+
timeout: 60000 // 60 second timeout
|
|
171
|
+
});
|
|
172
|
+
logger.info(`Transaction confirmed with 1 confirmation`);
|
|
173
|
+
// Add extra delay for network propagation
|
|
174
|
+
logger.info(`Adding ${delays.networkPropagationMs}ms delay for network propagation...`);
|
|
175
|
+
await new Promise(resolve => setTimeout(resolve, delays.networkPropagationMs));
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
logger.warn('Unable to wait for confirmations: client does not support waitForTransactionReceipt');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
logger.warn(`Could not wait for additional confirmations: ${error}`);
|
|
183
|
+
// Add longer delay if confirmation failed
|
|
184
|
+
logger.info(`Confirmation failed, adding ${delays.confirmationFailedMs}ms delay for transaction to propagate...`);
|
|
185
|
+
await new Promise(resolve => setTimeout(resolve, delays.confirmationFailedMs));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Payment maker for World Chain transactions using smart wallets and spend permissions
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```typescript
|
|
193
|
+
* // For production use (default delays)
|
|
194
|
+
* const paymentMaker = new WorldchainPaymentMaker(permission, wallet);
|
|
195
|
+
*
|
|
196
|
+
* // For testing (fast delays)
|
|
197
|
+
* const paymentMaker = new WorldchainPaymentMaker(permission, wallet, {
|
|
198
|
+
* confirmationDelays: { networkPropagationMs: 10, confirmationFailedMs: 20 }
|
|
199
|
+
* });
|
|
200
|
+
*
|
|
201
|
+
* // With custom configuration
|
|
202
|
+
* const paymentMaker = new WorldchainPaymentMaker(permission, wallet, {
|
|
203
|
+
* chainId: 480, // World Chain Mainnet
|
|
204
|
+
* customRpcUrl: 'https://my-rpc.com',
|
|
205
|
+
* logger: myLogger
|
|
206
|
+
* });
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
class WorldchainPaymentMaker {
|
|
210
|
+
/**
|
|
211
|
+
* Creates a new WorldchainPaymentMaker instance
|
|
212
|
+
*
|
|
213
|
+
* @param spendPermission - The spend permission for transactions
|
|
214
|
+
* @param smartWallet - The smart wallet instance to use
|
|
215
|
+
* @param options - Optional configuration
|
|
216
|
+
*/
|
|
217
|
+
constructor(spendPermission, smartWallet, options = {}) {
|
|
218
|
+
if (!spendPermission) {
|
|
219
|
+
throw new Error('Spend permission is required');
|
|
220
|
+
}
|
|
221
|
+
if (!smartWallet) {
|
|
222
|
+
throw new Error('Smart wallet is required');
|
|
223
|
+
}
|
|
224
|
+
// Extract and validate options
|
|
225
|
+
const { logger, chainId = client.WORLD_CHAIN_MAINNET.id, customRpcUrl, confirmationDelays } = options;
|
|
226
|
+
const finalDelays = confirmationDelays ?? getDefaultConfirmationDelays();
|
|
227
|
+
validateConfirmationDelays(finalDelays);
|
|
228
|
+
this.logger = logger ?? new common.ConsoleLogger();
|
|
229
|
+
this.spendPermission = spendPermission;
|
|
230
|
+
this.smartWallet = smartWallet;
|
|
231
|
+
this.chainId = chainId;
|
|
232
|
+
this.customRpcUrl = customRpcUrl;
|
|
233
|
+
this.confirmationDelays = finalDelays;
|
|
234
|
+
}
|
|
235
|
+
async generateJWT({ paymentRequestId, codeChallenge }) {
|
|
236
|
+
// Generate EIP-1271 auth data for smart wallet authentication
|
|
237
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
238
|
+
const message = common.constructEIP1271Message({
|
|
239
|
+
walletAddress: this.smartWallet.account.address,
|
|
240
|
+
timestamp,
|
|
241
|
+
codeChallenge,
|
|
242
|
+
paymentRequestId
|
|
243
|
+
});
|
|
244
|
+
// Sign the message - this will return an ABI-encoded signature from the smart wallet
|
|
245
|
+
const signature = await this.smartWallet.account.signMessage({
|
|
246
|
+
message: message
|
|
247
|
+
});
|
|
248
|
+
const authData = common.createEIP1271AuthData({
|
|
249
|
+
walletAddress: this.smartWallet.account.address,
|
|
250
|
+
message,
|
|
251
|
+
signature,
|
|
252
|
+
timestamp,
|
|
253
|
+
codeChallenge,
|
|
254
|
+
paymentRequestId
|
|
255
|
+
});
|
|
256
|
+
return common.createEIP1271JWT(authData);
|
|
257
|
+
}
|
|
258
|
+
async makePayment(amount, currency, receiver, memo) {
|
|
259
|
+
if (currency !== 'USDC') {
|
|
260
|
+
throw new Error('Only usdc currency is supported; received ' + currency);
|
|
261
|
+
}
|
|
262
|
+
// Use World Chain Mainnet configuration
|
|
263
|
+
const usdcAddress = client.USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
|
|
264
|
+
// Convert amount to USDC units (6 decimals) as BigInt for spendPermission
|
|
265
|
+
const amountInUSDCUnits = BigInt(amount.multipliedBy(10 ** USDC_DECIMALS$1).toFixed(0));
|
|
266
|
+
const spendCalls = await prepareSpendCallData({ permission: this.spendPermission, amount: amountInUSDCUnits });
|
|
267
|
+
// Add a second call to transfer USDC from the smart wallet to the receiver
|
|
268
|
+
let transferCallData = viem.encodeFunctionData({
|
|
269
|
+
abi: ERC20_ABI$1,
|
|
270
|
+
functionName: "transfer",
|
|
271
|
+
args: [receiver, amountInUSDCUnits],
|
|
272
|
+
});
|
|
273
|
+
// Append memo to transfer call data if present
|
|
274
|
+
// This works because the EVM ignores extra calldata beyond what a function expects.
|
|
275
|
+
// The ERC20 transfer() function only reads the first 68 bytes (4-byte selector + 32-byte address + 32-byte amount).
|
|
276
|
+
// Any additional data appended after those 68 bytes is safely ignored by the USDC contract
|
|
277
|
+
// but remains accessible in the transaction data for payment verification.
|
|
278
|
+
// This is a well-established pattern used by OpenSea, Uniswap, and other major protocols.
|
|
279
|
+
if (memo && memo.trim()) {
|
|
280
|
+
const memoHex = Buffer.from(memo.trim(), 'utf8').toString('hex');
|
|
281
|
+
transferCallData = (transferCallData + memoHex);
|
|
282
|
+
this.logger.info(`Added memo "${memo.trim()}" to transfer call`);
|
|
283
|
+
}
|
|
284
|
+
const transferCall = {
|
|
285
|
+
to: usdcAddress,
|
|
286
|
+
data: transferCallData,
|
|
287
|
+
value: '0x0'
|
|
288
|
+
};
|
|
289
|
+
// Combine spend permission calls with the transfer call
|
|
290
|
+
const allCalls = [...spendCalls, transferCall];
|
|
291
|
+
this.logger.info(`Executing ${allCalls.length} calls (${spendCalls.length} spend permission + 1 transfer)`);
|
|
292
|
+
const hash = await this.smartWallet.client.sendUserOperation({
|
|
293
|
+
account: this.smartWallet.account,
|
|
294
|
+
calls: allCalls.map(call => {
|
|
295
|
+
return {
|
|
296
|
+
to: call.to,
|
|
297
|
+
data: call.data,
|
|
298
|
+
value: BigInt(call.value || '0x0')
|
|
299
|
+
};
|
|
300
|
+
}),
|
|
301
|
+
maxPriorityFeePerGas: viem.parseEther('0.000000001')
|
|
302
|
+
});
|
|
303
|
+
const receipt = await this.smartWallet.client.waitForUserOperationReceipt({ hash });
|
|
304
|
+
if (!receipt) {
|
|
305
|
+
throw new Error('User operation failed');
|
|
306
|
+
}
|
|
307
|
+
// The receipt contains the actual transaction hash that was mined on chain
|
|
308
|
+
const txHash = receipt.receipt.transactionHash;
|
|
309
|
+
if (!txHash) {
|
|
310
|
+
throw new Error('User operation was executed but no transaction hash was returned. This should not happen.');
|
|
311
|
+
}
|
|
312
|
+
this.logger.info(`Spend permission executed successfully. UserOp: ${receipt.userOpHash}, TxHash: ${txHash}`);
|
|
313
|
+
// Wait for additional confirmations to ensure the transaction is well-propagated
|
|
314
|
+
// This helps avoid the "Transaction receipt could not be found" error
|
|
315
|
+
await waitForTransactionConfirmations(this.smartWallet, txHash, 2, this.logger, this.confirmationDelays);
|
|
316
|
+
// Return the actual transaction hash, not the user operation hash
|
|
317
|
+
// The payment verification system needs the on-chain transaction hash
|
|
318
|
+
return txHash;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const USDC_DECIMALS = 6;
|
|
323
|
+
// Minimal ERC20 ABI for transfer function
|
|
324
|
+
const ERC20_ABI = [
|
|
325
|
+
{
|
|
326
|
+
inputs: [
|
|
327
|
+
{ name: 'to', type: 'address' },
|
|
328
|
+
{ name: 'amount', type: 'uint256' }
|
|
329
|
+
],
|
|
330
|
+
name: 'transfer',
|
|
331
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
332
|
+
stateMutability: 'nonpayable',
|
|
333
|
+
type: 'function'
|
|
334
|
+
}
|
|
335
|
+
];
|
|
336
|
+
class MainWalletPaymentMaker {
|
|
337
|
+
constructor(walletAddress, provider, logger, chainId = client.WORLD_CHAIN_MAINNET.id, customRpcUrl) {
|
|
338
|
+
this.walletAddress = walletAddress;
|
|
339
|
+
this.provider = provider;
|
|
340
|
+
this.logger = logger ?? new common.ConsoleLogger();
|
|
341
|
+
this.chainId = chainId;
|
|
342
|
+
this.customRpcUrl = customRpcUrl;
|
|
343
|
+
}
|
|
344
|
+
async generateJWT({ paymentRequestId, codeChallenge }) {
|
|
345
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
346
|
+
// Create proper JWT header and payload for ES256K
|
|
347
|
+
const header = {
|
|
348
|
+
alg: 'ES256K',
|
|
349
|
+
typ: 'JWT'
|
|
350
|
+
};
|
|
351
|
+
const payload = {
|
|
352
|
+
sub: this.walletAddress,
|
|
353
|
+
iss: 'accounts.atxp.ai',
|
|
354
|
+
aud: 'https://auth.atxp.ai',
|
|
355
|
+
iat: timestamp,
|
|
356
|
+
exp: timestamp + 3600, // 1 hour expiration
|
|
357
|
+
payment_request_id: paymentRequestId,
|
|
358
|
+
code_challenge: codeChallenge,
|
|
359
|
+
chain_id: this.chainId
|
|
360
|
+
};
|
|
361
|
+
// Encode header and payload to base64url
|
|
362
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
363
|
+
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
364
|
+
// Create the message that ES256K should actually sign (header.payload)
|
|
365
|
+
const messageToSign = `${encodedHeader}.${encodedPayload}`;
|
|
366
|
+
// Sign the actual JWT header.payload string using personal_sign
|
|
367
|
+
const signature = await this.provider.request({
|
|
368
|
+
method: 'personal_sign',
|
|
369
|
+
params: [messageToSign, this.walletAddress]
|
|
370
|
+
});
|
|
371
|
+
// Use the legacy format (base64url of hex string with 0x prefix)
|
|
372
|
+
// This matches what the ES256K verification expects
|
|
373
|
+
const encodedSignature = Buffer.from(signature).toString('base64url');
|
|
374
|
+
const jwt = `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
|
375
|
+
this.logger.info(`Generated ES256K JWT for main wallet: ${this.walletAddress}`);
|
|
376
|
+
return jwt;
|
|
377
|
+
}
|
|
378
|
+
async makePayment(amount, currency, receiver, memo) {
|
|
379
|
+
if (currency !== 'USDC') {
|
|
380
|
+
throw new Error('Only USDC currency is supported; received ' + currency);
|
|
381
|
+
}
|
|
382
|
+
// Use World Chain Mainnet configuration
|
|
383
|
+
const usdcAddress = client.USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
|
|
384
|
+
const chainConfig = this.customRpcUrl
|
|
385
|
+
? client.getWorldChainMainnetWithRPC(this.customRpcUrl)
|
|
386
|
+
: client.WORLD_CHAIN_MAINNET;
|
|
387
|
+
const chainName = chainConfig.name;
|
|
388
|
+
this.logger.info(`Making direct wallet payment of ${amount} ${currency} to ${receiver} on ${chainName} with memo: ${memo}`);
|
|
389
|
+
// Convert amount to USDC units (6 decimals)
|
|
390
|
+
const amountInUSDCUnits = BigInt(amount.multipliedBy(10 ** USDC_DECIMALS).toFixed(0));
|
|
391
|
+
// Prepare transfer call data
|
|
392
|
+
let transferCallData = viem.encodeFunctionData({
|
|
393
|
+
abi: ERC20_ABI,
|
|
394
|
+
functionName: 'transfer',
|
|
395
|
+
args: [receiver, amountInUSDCUnits]
|
|
396
|
+
});
|
|
397
|
+
// Append memo to call data if present
|
|
398
|
+
if (memo && memo.trim()) {
|
|
399
|
+
const memoHex = Buffer.from(memo.trim(), 'utf8').toString('hex');
|
|
400
|
+
transferCallData = (transferCallData + memoHex);
|
|
401
|
+
this.logger.info(`Added memo "${memo.trim()}" to transfer call`);
|
|
402
|
+
}
|
|
403
|
+
// Create wallet client
|
|
404
|
+
const walletClient = viem.createWalletClient({
|
|
405
|
+
chain: chainConfig,
|
|
406
|
+
transport: viem.custom(this.provider)
|
|
407
|
+
});
|
|
408
|
+
// Send transaction directly from main wallet
|
|
409
|
+
const txHash = await walletClient.sendTransaction({
|
|
410
|
+
account: this.walletAddress,
|
|
411
|
+
to: usdcAddress,
|
|
412
|
+
data: transferCallData,
|
|
413
|
+
value: 0n,
|
|
414
|
+
chain: chainConfig
|
|
415
|
+
});
|
|
416
|
+
this.logger.info(`Payment sent successfully. TxHash: ${txHash}`);
|
|
417
|
+
this.logger.info(`Transaction URL: https://worldscan.org/tx/${txHash}`);
|
|
418
|
+
// Wait for transaction confirmation to ensure it's mined
|
|
419
|
+
// This prevents "Transaction receipt could not be found" errors
|
|
420
|
+
try {
|
|
421
|
+
this.logger.info(`Waiting for transaction confirmation...`);
|
|
422
|
+
// Create a public client to wait for the transaction receipt
|
|
423
|
+
// Use custom RPC URL if provided for better consistency with auth server
|
|
424
|
+
const rpcUrl = this.customRpcUrl || chainConfig.rpcUrls.default.http[0];
|
|
425
|
+
const publicClient = viem.createPublicClient({
|
|
426
|
+
chain: chainConfig,
|
|
427
|
+
transport: viem.http(rpcUrl, {
|
|
428
|
+
timeout: 30000, // 30 second timeout for individual RPC calls
|
|
429
|
+
retryCount: 3, // Retry failed requests
|
|
430
|
+
retryDelay: 1000 // 1 second delay between retries
|
|
431
|
+
})
|
|
432
|
+
});
|
|
433
|
+
await publicClient.waitForTransactionReceipt({
|
|
434
|
+
hash: txHash,
|
|
435
|
+
confirmations: 1, // Reduce to 1 confirmation to speed up
|
|
436
|
+
timeout: 120000 // 2 minute timeout - World Chain can be slow
|
|
437
|
+
});
|
|
438
|
+
this.logger.info(`Transaction confirmed with 1 confirmation`);
|
|
439
|
+
// Add extra delay to ensure the transaction is well propagated across network
|
|
440
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
441
|
+
}
|
|
442
|
+
catch (error) {
|
|
443
|
+
this.logger.warn(`Could not wait for confirmations: ${error}`);
|
|
444
|
+
// Add a much longer delay if confirmation failed - World Chain can be slow
|
|
445
|
+
this.logger.info('Confirmation failed, adding delay for transaction to propagate...');
|
|
446
|
+
await new Promise(resolve => setTimeout(resolve, 30000));
|
|
447
|
+
}
|
|
448
|
+
return txHash;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Type-safe cache wrapper for permission data
|
|
454
|
+
*/
|
|
455
|
+
class IntermediaryCache extends common.JsonCache {
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// For now, we'll use a generic approach for World Chain
|
|
459
|
+
// This may need to be updated when World Chain provides specific infrastructure
|
|
460
|
+
const DEFAULT_WORLD_CHAIN_RPC = 'https://worldchain-mainnet.g.alchemy.com/public';
|
|
461
|
+
/**
|
|
462
|
+
* Creates an ephemeral smart wallet for World Chain
|
|
463
|
+
* Note: This implementation uses Coinbase's smart wallet infrastructure
|
|
464
|
+
* adapted for World Chain. This may need updates when World Chain
|
|
465
|
+
* provides their own account abstraction infrastructure.
|
|
466
|
+
*/
|
|
467
|
+
async function toEphemeralSmartWallet(privateKey, rpcUrl) {
|
|
468
|
+
const signer = accounts.privateKeyToAccount(privateKey);
|
|
469
|
+
const publicClient = viem.createPublicClient({
|
|
470
|
+
chain: client.WORLD_CHAIN_MAINNET,
|
|
471
|
+
transport: viem.http(DEFAULT_WORLD_CHAIN_RPC)
|
|
472
|
+
});
|
|
473
|
+
// Create the smart wallet using Coinbase's smart account SDK
|
|
474
|
+
// This will need to be adapted when World Chain provides their own solution
|
|
475
|
+
const account = await accountAbstraction.toCoinbaseSmartAccount({
|
|
476
|
+
client: publicClient,
|
|
477
|
+
owners: [signer],
|
|
478
|
+
version: '1'
|
|
479
|
+
});
|
|
480
|
+
// Create bundler client
|
|
481
|
+
// Note: World Chain may not have paymaster support initially
|
|
482
|
+
const bundlerClient = accountAbstraction.createBundlerClient({
|
|
483
|
+
account,
|
|
484
|
+
client: publicClient,
|
|
485
|
+
transport: viem.http(DEFAULT_WORLD_CHAIN_RPC),
|
|
486
|
+
chain: client.WORLD_CHAIN_MAINNET
|
|
487
|
+
// Paymaster omitted - World Chain infrastructure may not support it yet
|
|
488
|
+
});
|
|
489
|
+
return {
|
|
490
|
+
address: account.address,
|
|
491
|
+
client: bundlerClient,
|
|
492
|
+
account,
|
|
493
|
+
signer,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const DEFAULT_ALLOWANCE = 10n;
|
|
498
|
+
const DEFAULT_PERIOD_IN_DAYS = 7;
|
|
499
|
+
class WorldchainAccount {
|
|
500
|
+
static toCacheKey(userWalletAddress) {
|
|
501
|
+
return `atxp-world-permission-${userWalletAddress}`;
|
|
502
|
+
}
|
|
503
|
+
static async initialize(config) {
|
|
504
|
+
const logger = config.logger || new common.ConsoleLogger();
|
|
505
|
+
const useEphemeralWallet = config.useEphemeralWallet ?? true;
|
|
506
|
+
const chainId = config.chainId || client.WORLD_CHAIN_MAINNET.id;
|
|
507
|
+
// Use World Chain Mainnet USDC address
|
|
508
|
+
const usdcAddress = client.USDC_CONTRACT_ADDRESS_WORLD_MAINNET;
|
|
509
|
+
// Some wallets don't support wallet_connect, so
|
|
510
|
+
// will just continue if it fails
|
|
511
|
+
try {
|
|
512
|
+
await config.provider.request({ method: 'wallet_connect' });
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
logger.warn(`wallet_connect not supported, continuing with initialization. ${error}`);
|
|
516
|
+
}
|
|
517
|
+
// If using main wallet mode, return early with main wallet payment maker
|
|
518
|
+
if (!useEphemeralWallet) {
|
|
519
|
+
logger.info(`Using main wallet mode for address: ${config.walletAddress}`);
|
|
520
|
+
return new WorldchainAccount(null, // No spend permission in main wallet mode
|
|
521
|
+
null, // No ephemeral wallet in main wallet mode
|
|
522
|
+
logger, config.walletAddress, config.provider, chainId, config.customRpcUrl);
|
|
523
|
+
}
|
|
524
|
+
// Initialize cache
|
|
525
|
+
const baseCache = config?.cache || new common.BrowserCache();
|
|
526
|
+
const cache = new IntermediaryCache(baseCache);
|
|
527
|
+
const cacheKey = this.toCacheKey(config.walletAddress);
|
|
528
|
+
// Try to load existing permission
|
|
529
|
+
const existingData = this.loadSavedWalletAndPermission(cache, cacheKey);
|
|
530
|
+
if (existingData) {
|
|
531
|
+
const ephemeralSmartWallet = await toEphemeralSmartWallet(existingData.privateKey);
|
|
532
|
+
return new WorldchainAccount(existingData.permission, ephemeralSmartWallet, logger, undefined, undefined, chainId, config.customRpcUrl);
|
|
533
|
+
}
|
|
534
|
+
const privateKey = accounts.generatePrivateKey();
|
|
535
|
+
const smartWallet = await toEphemeralSmartWallet(privateKey);
|
|
536
|
+
logger.info(`Generated ephemeral wallet: ${smartWallet.address}`);
|
|
537
|
+
await this.deploySmartWallet(smartWallet);
|
|
538
|
+
logger.info(`Deployed smart wallet: ${smartWallet.address}`);
|
|
539
|
+
const permission = await requestSpendPermission({
|
|
540
|
+
account: config.walletAddress,
|
|
541
|
+
spender: smartWallet.address,
|
|
542
|
+
token: usdcAddress,
|
|
543
|
+
chainId: chainId,
|
|
544
|
+
allowance: config?.allowance ?? DEFAULT_ALLOWANCE,
|
|
545
|
+
periodInDays: config?.periodInDays ?? DEFAULT_PERIOD_IN_DAYS,
|
|
546
|
+
provider: config.provider,
|
|
547
|
+
});
|
|
548
|
+
// Save wallet and permission
|
|
549
|
+
cache.set(cacheKey, { privateKey, permission });
|
|
550
|
+
return new WorldchainAccount(permission, smartWallet, logger, undefined, undefined, chainId, config.customRpcUrl);
|
|
551
|
+
}
|
|
552
|
+
static loadSavedWalletAndPermission(permissionCache, cacheKey) {
|
|
553
|
+
const cachedData = permissionCache.get(cacheKey);
|
|
554
|
+
if (!cachedData)
|
|
555
|
+
return null;
|
|
556
|
+
// Check if permission is not expired
|
|
557
|
+
const now = Math.floor(Date.now() / 1000);
|
|
558
|
+
const permissionEnd = parseInt(cachedData.permission.permission.end.toString());
|
|
559
|
+
if (permissionEnd <= now) {
|
|
560
|
+
permissionCache.delete(cacheKey);
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
return cachedData;
|
|
564
|
+
}
|
|
565
|
+
static async deploySmartWallet(smartWallet) {
|
|
566
|
+
const deployTx = await smartWallet.client.sendUserOperation({
|
|
567
|
+
calls: [{
|
|
568
|
+
to: smartWallet.address,
|
|
569
|
+
value: 0n,
|
|
570
|
+
data: '0x'
|
|
571
|
+
}]
|
|
572
|
+
// Note: World Chain may not have paymaster support initially
|
|
573
|
+
// paymaster omitted
|
|
574
|
+
});
|
|
575
|
+
const receipt = await smartWallet.client.waitForUserOperationReceipt({
|
|
576
|
+
hash: deployTx
|
|
577
|
+
});
|
|
578
|
+
if (!receipt.success) {
|
|
579
|
+
throw new Error(`Smart wallet deployment failed. Receipt: ${JSON.stringify(receipt)}`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
constructor(spendPermission, ephemeralSmartWallet, logger, mainWalletAddress, provider, chainId = client.WORLD_CHAIN_MAINNET.id, customRpcUrl) {
|
|
583
|
+
if (ephemeralSmartWallet) {
|
|
584
|
+
// Ephemeral wallet mode
|
|
585
|
+
if (!spendPermission) {
|
|
586
|
+
throw new Error('Spend permission is required for ephemeral wallet mode');
|
|
587
|
+
}
|
|
588
|
+
this.accountId = ephemeralSmartWallet.address;
|
|
589
|
+
this.paymentMakers = {
|
|
590
|
+
'world': new WorldchainPaymentMaker(spendPermission, ephemeralSmartWallet, {
|
|
591
|
+
logger,
|
|
592
|
+
chainId,
|
|
593
|
+
customRpcUrl
|
|
594
|
+
}),
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
// Main wallet mode
|
|
599
|
+
if (!mainWalletAddress || !provider) {
|
|
600
|
+
throw new Error('Main wallet address and provider are required for main wallet mode');
|
|
601
|
+
}
|
|
602
|
+
this.accountId = mainWalletAddress;
|
|
603
|
+
this.paymentMakers = {
|
|
604
|
+
'world': new MainWalletPaymentMaker(mainWalletAddress, provider, logger, chainId, customRpcUrl),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
static clearAllCachedData(userWalletAddress, cache) {
|
|
609
|
+
// In non-browser environments, require an explicit cache parameter
|
|
610
|
+
if (!cache) {
|
|
611
|
+
const browserCache = new common.BrowserCache();
|
|
612
|
+
// Check if BrowserCache would work (i.e., we're in a browser)
|
|
613
|
+
if (typeof window === 'undefined') {
|
|
614
|
+
throw new Error('clearAllCachedData requires a cache to be provided outside of browser environments');
|
|
615
|
+
}
|
|
616
|
+
cache = browserCache;
|
|
617
|
+
}
|
|
618
|
+
cache.delete(this.toCacheKey(userWalletAddress));
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Loads and initializes a Worldchain account with MiniKit integration
|
|
624
|
+
*
|
|
625
|
+
* This function creates a Worldchain account that can interact with the World Chain network
|
|
626
|
+
* using MiniKit for transaction signing and wallet operations. It sets up a custom provider
|
|
627
|
+
* that handles various Ethereum JSON-RPC methods through MiniKit's interface.
|
|
628
|
+
*
|
|
629
|
+
* @param walletAddress - The wallet address to use for the account
|
|
630
|
+
* @param logger - Optional logger instance for debugging and monitoring
|
|
631
|
+
* @param customRpcUrl - Optional custom RPC URL for Worldchain. If not provided, uses the public RPC endpoint
|
|
632
|
+
* @param miniKit - The MiniKit instance to use for transactions and signing
|
|
633
|
+
* @returns Promise that resolves to an initialized WorldchainAccount instance
|
|
634
|
+
*
|
|
635
|
+
* @example
|
|
636
|
+
* ```typescript
|
|
637
|
+
* // Using public RPC endpoint
|
|
638
|
+
* const account = await createMiniKitWorldchainAccount({
|
|
639
|
+
* walletAddress: "0x1234...",
|
|
640
|
+
* logger: new ConsoleLogger(),
|
|
641
|
+
* miniKit: MiniKit
|
|
642
|
+
* });
|
|
643
|
+
*
|
|
644
|
+
* // Using custom RPC endpoint
|
|
645
|
+
* const account = await createMiniKitWorldchainAccount({
|
|
646
|
+
* walletAddress: "0x1234...",
|
|
647
|
+
* logger: new ConsoleLogger(),
|
|
648
|
+
* customRpcUrl: "https://your-custom-rpc-endpoint.com",
|
|
649
|
+
* miniKit: MiniKit
|
|
650
|
+
* });
|
|
651
|
+
* ```
|
|
652
|
+
*
|
|
653
|
+
* @remarks
|
|
654
|
+
* The function creates a custom provider that supports:
|
|
655
|
+
* - `eth_accounts`: Returns the wallet address
|
|
656
|
+
* - `eth_chainId`: Returns Worldchain chain ID (0x1e0 / 480)
|
|
657
|
+
* - `eth_requestAccounts`: Returns the wallet address
|
|
658
|
+
* - `eth_sendTransaction`: Handles USDC transfers and other transactions via MiniKit
|
|
659
|
+
* - `personal_sign`: Signs messages using MiniKit
|
|
660
|
+
*
|
|
661
|
+
* The account is configured with:
|
|
662
|
+
* - 10 USDC allowance
|
|
663
|
+
* - 30-day period for permissions
|
|
664
|
+
* - Worldchain mainnet RPC endpoint (public or custom)
|
|
665
|
+
* - Regular wallet mode (no ephemeral wallet)
|
|
666
|
+
*
|
|
667
|
+
* @throws {Error} When MiniKit operations fail or unsupported transaction types are encountered
|
|
668
|
+
*/
|
|
669
|
+
const createMiniKitWorldchainAccount = async ({ walletAddress, logger: loggerParam, customRpcUrl, miniKit }) => {
|
|
670
|
+
const logger = loggerParam || new common.ConsoleLogger();
|
|
671
|
+
// If no connector client from wagmi, create a simple MiniKit provider
|
|
672
|
+
const provider = {
|
|
673
|
+
request: async (args) => {
|
|
674
|
+
const { method, params } = args;
|
|
675
|
+
switch (method) {
|
|
676
|
+
case 'eth_accounts':
|
|
677
|
+
return [walletAddress];
|
|
678
|
+
case 'eth_chainId':
|
|
679
|
+
return '0x1e0'; // Worldchain chain ID (480)
|
|
680
|
+
case 'eth_requestAccounts':
|
|
681
|
+
return [walletAddress];
|
|
682
|
+
case 'eth_sendTransaction':
|
|
683
|
+
return await handleSendTransaction(params, logger, miniKit);
|
|
684
|
+
case 'personal_sign':
|
|
685
|
+
return await signMessageWithMiniKit(params, miniKit);
|
|
686
|
+
default:
|
|
687
|
+
throw new Error(`Method ${method} not supported in MiniKit context`);
|
|
688
|
+
}
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
const worldchainAccount = await WorldchainAccount.initialize({
|
|
692
|
+
walletAddress,
|
|
693
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
694
|
+
provider: provider, // Type cast needed for client compatibility
|
|
695
|
+
allowance: viem.parseUnits("10", 6), // 10 USDC
|
|
696
|
+
useEphemeralWallet: false, // Regular wallet mode (smart wallet infrastructure not available on World Chain)
|
|
697
|
+
periodInDays: 30,
|
|
698
|
+
customRpcUrl: customRpcUrl || DEFAULT_WORLD_CHAIN_RPC // Public RPC URL as default
|
|
699
|
+
});
|
|
700
|
+
return worldchainAccount;
|
|
701
|
+
};
|
|
702
|
+
/**
|
|
703
|
+
* Handles eth_sendTransaction requests by processing different transaction types
|
|
704
|
+
* @param params Array containing the transaction object
|
|
705
|
+
* @param logger Logger instance for debugging
|
|
706
|
+
* @returns Transaction hash or throws error
|
|
707
|
+
*/
|
|
708
|
+
async function handleSendTransaction(params, logger, miniKit) {
|
|
709
|
+
const transaction = params[0];
|
|
710
|
+
// Handle USDC transfer (ERC20 transfer function)
|
|
711
|
+
if (transaction.data && transaction.data.startsWith('0xa9059cbb')) {
|
|
712
|
+
// This is a transfer(address,uint256) call - decode the parameters
|
|
713
|
+
const data = transaction.data.slice(10); // Remove function selector
|
|
714
|
+
// Extract recipient address (first 32 bytes, last 20 bytes are the address)
|
|
715
|
+
const recipientHex = '0x' + data.slice(24, 64);
|
|
716
|
+
// Extract amount (next 32 bytes)
|
|
717
|
+
const amountHex = '0x' + data.slice(64, 128);
|
|
718
|
+
const amount = BigInt(amountHex).toString();
|
|
719
|
+
// Validate transaction parameter
|
|
720
|
+
// Check for memo data (any data after the standard 128 characters)
|
|
721
|
+
let memo = '';
|
|
722
|
+
if (data.length > 128) {
|
|
723
|
+
const memoHex = data.slice(128);
|
|
724
|
+
try {
|
|
725
|
+
memo = Buffer.from(memoHex, 'hex').toString('utf8');
|
|
726
|
+
}
|
|
727
|
+
catch (e) {
|
|
728
|
+
logger.warn(`[MiniKit] Failed to decode memo data: ${e}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
// ERC20 ABI for transfer function
|
|
732
|
+
const ERC20_ABI = [
|
|
733
|
+
{
|
|
734
|
+
inputs: [
|
|
735
|
+
{ name: 'to', type: 'address' },
|
|
736
|
+
{ name: 'amount', type: 'uint256' }
|
|
737
|
+
],
|
|
738
|
+
name: 'transfer',
|
|
739
|
+
outputs: [{ name: '', type: 'bool' }],
|
|
740
|
+
stateMutability: 'nonpayable',
|
|
741
|
+
type: 'function'
|
|
742
|
+
}
|
|
743
|
+
];
|
|
744
|
+
const input = {
|
|
745
|
+
transaction: [
|
|
746
|
+
{
|
|
747
|
+
address: transaction.to, // USDC contract address
|
|
748
|
+
abi: ERC20_ABI,
|
|
749
|
+
functionName: 'transfer',
|
|
750
|
+
args: [recipientHex, amount],
|
|
751
|
+
value: transaction.value || "0"
|
|
752
|
+
}
|
|
753
|
+
]
|
|
754
|
+
};
|
|
755
|
+
// TODO: MiniKit doesn't have a standard way to include memo data in ERC20 transfers
|
|
756
|
+
// The memo is extracted and logged but not included in the transaction
|
|
757
|
+
if (memo) {
|
|
758
|
+
logger.debug(`[MiniKit] Memo "${memo}" will be lost in MiniKit transaction - consider alternative approach`);
|
|
759
|
+
}
|
|
760
|
+
const sentResult = await miniKit.commandsAsync.sendTransaction(input);
|
|
761
|
+
if (sentResult.finalPayload?.status === 'success') {
|
|
762
|
+
const transactionId = sentResult.finalPayload.transaction_id;
|
|
763
|
+
// Wait for the transaction to be confirmed and get the actual transaction hash
|
|
764
|
+
const confirmed = await waitForTransactionConfirmation(transactionId, logger, 120000); // 2 minute timeout
|
|
765
|
+
if (confirmed && confirmed.transactionHash) {
|
|
766
|
+
logger.debug(`[MiniKit] Transaction confirmed with hash: ${confirmed.transactionHash}`);
|
|
767
|
+
return confirmed.transactionHash; // Return the actual blockchain transaction hash
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
logger.error(`[MiniKit] Transaction confirmation failed for ID: ${transactionId}`);
|
|
771
|
+
throw new Error(`Transaction confirmation failed. Transaction may still be pending.`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// Enhanced error logging for debugging
|
|
775
|
+
const errorCode = sentResult.finalPayload?.error_code;
|
|
776
|
+
const simulationError = sentResult.finalPayload?.details?.simulationError;
|
|
777
|
+
logger.error(`[MiniKit] Transaction failed: ${JSON.stringify({
|
|
778
|
+
errorCode,
|
|
779
|
+
simulationError,
|
|
780
|
+
fullPayload: sentResult.finalPayload
|
|
781
|
+
})}`);
|
|
782
|
+
// Provide more user-friendly error messages
|
|
783
|
+
let userFriendlyError = `MiniKit sendTransaction failed: ${errorCode}`;
|
|
784
|
+
if (simulationError?.includes('transfer amount exceeds balance')) {
|
|
785
|
+
const amountUSDC = (Number(amount) / 1000000).toFixed(6);
|
|
786
|
+
userFriendlyError = `💳 Insufficient USDC Balance\n\n` +
|
|
787
|
+
`You're trying to send ${amountUSDC} USDC, but your wallet doesn't have enough funds.\n\n` +
|
|
788
|
+
`To complete this payment:\n` +
|
|
789
|
+
`• Add USDC to your World App wallet\n` +
|
|
790
|
+
`• Bridge USDC from another chain\n` +
|
|
791
|
+
`• Buy USDC directly in World App\n\n` +
|
|
792
|
+
`Wallet: ${transaction.from?.slice(0, 6)}...${transaction.from?.slice(-4)}`;
|
|
793
|
+
}
|
|
794
|
+
else if (simulationError) {
|
|
795
|
+
userFriendlyError += ` - ${simulationError}`;
|
|
796
|
+
}
|
|
797
|
+
throw new Error(userFriendlyError);
|
|
798
|
+
}
|
|
799
|
+
// Handle simple ETH transfers (no data or empty data)
|
|
800
|
+
if (!transaction.data || transaction.data === '0x') {
|
|
801
|
+
// For ETH transfers, you'd need to use the Forward contract
|
|
802
|
+
throw new Error('ETH transfers require Forward contract - not implemented yet');
|
|
803
|
+
}
|
|
804
|
+
// For other transaction types
|
|
805
|
+
throw new Error(`Unsupported transaction type. Data: ${transaction.data.slice(0, 10)}`);
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Signs a message using MiniKit
|
|
809
|
+
* @param params Array containing the message to sign
|
|
810
|
+
* @returns The signature string
|
|
811
|
+
* @throws Error if signing fails
|
|
812
|
+
*/
|
|
813
|
+
async function signMessageWithMiniKit(params, miniKit) {
|
|
814
|
+
const [message] = params;
|
|
815
|
+
const signResult = await miniKit.commandsAsync.signMessage({ message: message });
|
|
816
|
+
if (signResult?.finalPayload?.status === 'success') {
|
|
817
|
+
return signResult.finalPayload.signature;
|
|
818
|
+
}
|
|
819
|
+
throw new Error(`MiniKit signing failed: ${signResult?.finalPayload?.error_code}`);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Resolves a MiniKit transaction ID to the actual blockchain transaction hash
|
|
823
|
+
* using the World API
|
|
824
|
+
*/
|
|
825
|
+
async function resolveTransactionHash(transactionId, logger) {
|
|
826
|
+
try {
|
|
827
|
+
const response = await fetch('/api/resolve-transaction', {
|
|
828
|
+
method: 'POST',
|
|
829
|
+
headers: {
|
|
830
|
+
'Content-Type': 'application/json',
|
|
831
|
+
},
|
|
832
|
+
body: JSON.stringify({ transactionId })
|
|
833
|
+
});
|
|
834
|
+
if (!response.ok) {
|
|
835
|
+
const error = await response.text();
|
|
836
|
+
logger.error(`[WorldTransaction] API error: ${response.status} ${error}`);
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
const transaction = await response.json();
|
|
840
|
+
return {
|
|
841
|
+
transactionHash: transaction.transactionHash,
|
|
842
|
+
status: transaction.transactionStatus
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
logger.error(`[WorldTransaction] Error resolving transaction: ${error}`);
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Waits for a MiniKit transaction to be confirmed and returns the transaction hash
|
|
852
|
+
* Polls the World API until the transaction is confirmed or times out
|
|
853
|
+
*/
|
|
854
|
+
async function waitForTransactionConfirmation(transactionId, logger, timeoutMs = 120000, // 2 minutes
|
|
855
|
+
pollIntervalMs = 2000) {
|
|
856
|
+
const startTime = Date.now();
|
|
857
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
858
|
+
const result = await resolveTransactionHash(transactionId, logger);
|
|
859
|
+
if (result && result.transactionHash && result.status !== 'pending') {
|
|
860
|
+
return result;
|
|
861
|
+
}
|
|
862
|
+
// Wait before next poll
|
|
863
|
+
await new Promise(resolve => setTimeout(resolve, pollIntervalMs));
|
|
864
|
+
}
|
|
865
|
+
logger.warn(`[WorldTransaction] Timeout waiting for transaction confirmation: ${transactionId}`);
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
Object.defineProperty(exports, "BrowserCache", {
|
|
870
|
+
enumerable: true,
|
|
871
|
+
get: function () { return common.BrowserCache; }
|
|
872
|
+
});
|
|
873
|
+
Object.defineProperty(exports, "MemoryCache", {
|
|
874
|
+
enumerable: true,
|
|
875
|
+
get: function () { return common.MemoryCache; }
|
|
876
|
+
});
|
|
877
|
+
exports.MainWalletPaymentMaker = MainWalletPaymentMaker;
|
|
878
|
+
exports.PermissionCache = IntermediaryCache;
|
|
879
|
+
exports.WorldchainAccount = WorldchainAccount;
|
|
880
|
+
exports.WorldchainPaymentMaker = WorldchainPaymentMaker;
|
|
881
|
+
exports.createMiniKitWorldchainAccount = createMiniKitWorldchainAccount;
|
|
882
|
+
//# sourceMappingURL=index.cjs.map
|