@pushchain/core 2.0.1 → 2.0.3

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.
Files changed (44) hide show
  1. package/package.json +4 -3
  2. package/src/lib/constants/abi/erc20.evm.d.ts +36 -0
  3. package/src/lib/constants/abi/erc20.evm.js +26 -0
  4. package/src/lib/constants/abi/erc20.evm.js.map +1 -0
  5. package/src/lib/constants/abi/index.d.ts +3 -0
  6. package/src/lib/constants/abi/index.js +7 -1
  7. package/src/lib/constants/abi/index.js.map +1 -1
  8. package/src/lib/constants/abi/pushsolanagateway.json +1004 -0
  9. package/src/lib/constants/abi/universalGatewayV0.evm.d.ts +31 -0
  10. package/src/lib/constants/abi/universalGatewayV0.evm.js +141 -0
  11. package/src/lib/constants/abi/universalGatewayV0.evm.js.map +1 -0
  12. package/src/lib/constants/index.d.ts +1 -0
  13. package/src/lib/constants/index.js.map +1 -1
  14. package/src/lib/constants/tokens.d.ts +50 -0
  15. package/src/lib/constants/tokens.js +167 -0
  16. package/src/lib/constants/tokens.js.map +1 -0
  17. package/src/lib/generated/uexecutor/v1/query.d.ts +23 -0
  18. package/src/lib/generated/uexecutor/v1/query.js +79 -0
  19. package/src/lib/generated/uexecutor/v1/query.js.map +1 -0
  20. package/src/lib/generated/uexecutor/v1/types.d.ts +91 -0
  21. package/src/lib/generated/uexecutor/v1/types.js +856 -0
  22. package/src/lib/generated/uexecutor/v1/types.js.map +1 -0
  23. package/src/lib/generated/v1/tx.d.ts +4 -4
  24. package/src/lib/generated/v1/tx.js +1 -1
  25. package/src/lib/index.d.ts +1 -0
  26. package/src/lib/index.js.map +1 -1
  27. package/src/lib/orchestrator/orchestrator.d.ts +18 -0
  28. package/src/lib/orchestrator/orchestrator.js +720 -4
  29. package/src/lib/orchestrator/orchestrator.js.map +1 -1
  30. package/src/lib/orchestrator/orchestrator.types.d.ts +13 -0
  31. package/src/lib/progress-hook/progress-hook.js +66 -0
  32. package/src/lib/progress-hook/progress-hook.js.map +1 -1
  33. package/src/lib/progress-hook/progress-hook.types.d.ts +9 -0
  34. package/src/lib/progress-hook/progress-hook.types.js +12 -0
  35. package/src/lib/progress-hook/progress-hook.types.js.map +1 -1
  36. package/src/lib/push-chain/push-chain.d.ts +17 -0
  37. package/src/lib/push-chain/push-chain.js +190 -0
  38. package/src/lib/push-chain/push-chain.js.map +1 -1
  39. package/src/lib/push-client/push-client.d.ts +5 -0
  40. package/src/lib/push-client/push-client.js +15 -0
  41. package/src/lib/push-client/push-client.js.map +1 -1
  42. package/src/lib/utils.d.ts +98 -2
  43. package/src/lib/utils.js +264 -8
  44. package/src/lib/utils.js.map +1 -1
@@ -18,6 +18,7 @@ const anchor_1 = require("@coral-xyz/anchor");
18
18
  const progress_hook_types_1 = require("../progress-hook/progress-hook.types");
19
19
  const progress_hook_1 = tslib_1.__importDefault(require("../progress-hook/progress-hook"));
20
20
  const uea_evm_1 = require("../constants/abi/uea.evm");
21
+ const tokens_1 = require("../constants/tokens");
21
22
  class Orchestrator {
22
23
  constructor(universalSigner, pushNetwork, rpcUrls = {}, printTraces = false, progressHook) {
23
24
  this.universalSigner = universalSigner;
@@ -62,7 +63,555 @@ class Orchestrator {
62
63
  */
63
64
  execute(execute) {
64
65
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
66
+ var _a, _b, _c;
65
67
  try {
68
+ // FUNDS_TX short-circuit: Bridge tokens from origin chain to Push Chain
69
+ // - EVM (Sepolia): UniversalGatewayV0
70
+ // - SVM (Solana Devnet): pushsolanagateway
71
+ if (execute.funds) {
72
+ if (!execute.data || execute.data === '0x') {
73
+ // Disallow user-provided `value` for funds-only bridging. The SDK derives
74
+ // origin-chain msg.value automatically from the funds input:
75
+ // - Native path: msg.value = bridgeAmount
76
+ // - ERC-20 path: msg.value = 0
77
+ if (execute.value !== undefined && execute.value !== BigInt(0)) {
78
+ throw new Error('Do not set `value` when using funds bridging; the SDK sets origin msg.value from `funds.amount` automatically');
79
+ }
80
+ const chain = this.universalSigner.account.chain;
81
+ const { vm } = chain_1.CHAIN_INFO[chain];
82
+ if (!(chain === enums_1.CHAIN.ETHEREUM_SEPOLIA)) {
83
+ throw new Error('Funds bridging is only supported on Ethereum Sepolia for now');
84
+ }
85
+ // Progress: Origin chain detected
86
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_01, chain);
87
+ const { defaultRPC, lockerContract } = chain_1.CHAIN_INFO[chain];
88
+ const rpcUrls = this.rpcUrls[chain] || defaultRPC;
89
+ // Resolve token: default to native token based on VM (ETH for EVM, SOL for SVM)
90
+ if (!execute.funds.token) {
91
+ const available = tokens_1.MOVEABLE_TOKENS[chain] || [];
92
+ const vm = chain_1.CHAIN_INFO[chain].vm;
93
+ const preferredSymbol = vm === enums_1.VM.EVM ? 'ETH' : vm === enums_1.VM.SVM ? 'SOL' : undefined;
94
+ const nativeToken = preferredSymbol
95
+ ? available.find((t) => t.symbol === preferredSymbol)
96
+ : undefined;
97
+ if (!nativeToken) {
98
+ throw new Error('Native token not configured for this chain');
99
+ }
100
+ execute.funds.token = nativeToken;
101
+ }
102
+ const amount = execute.funds.amount;
103
+ const symbol = execute.funds.token.symbol;
104
+ // Funds Flow: Preparing funds transfer
105
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_01, amount, execute.funds.token.decimals, symbol);
106
+ if (vm === enums_1.VM.EVM) {
107
+ const evmClient = new evm_client_1.EvmClient({ rpcUrls });
108
+ const gatewayAddress = lockerContract;
109
+ const tokenAddr = execute.funds.token.address;
110
+ // Approve gateway to pull tokens if ERC-20 (not native sentinel)
111
+ if (execute.funds.token.mechanism === 'approve') {
112
+ yield this.ensureErc20Allowance(evmClient, tokenAddr, gatewayAddress, amount);
113
+ }
114
+ else if (execute.funds.token.mechanism === 'permit2') {
115
+ throw new Error('Permit2 is not supported yet');
116
+ }
117
+ else if (execute.funds.token.mechanism === 'native') {
118
+ // Native flow uses msg.value == bridgeAmount and bridgeToken = address(0)
119
+ }
120
+ }
121
+ const bridgeAmount = amount;
122
+ const revertCFG = {
123
+ fundRecipient: this.universalSigner.account
124
+ .address,
125
+ revertMsg: '0x',
126
+ }; // typed by viem via ABI
127
+ if (vm === enums_1.VM.EVM) {
128
+ const evmClient = new evm_client_1.EvmClient({ rpcUrls });
129
+ const gatewayAddress = lockerContract;
130
+ const tokenAddr = execute.funds.token.address;
131
+ // Call UniversalGatewayV0.sendFunds(recipient, bridgeToken, bridgeAmount, revertCFG)
132
+ const recipient = execute.to; // funds to recipient on Push Chain
133
+ const isNative = execute.funds.token.mechanism === 'native';
134
+ const bridgeToken = execute.funds.token.mechanism === 'approve'
135
+ ? tokenAddr
136
+ : '0x0000000000000000000000000000000000000000';
137
+ let txHash;
138
+ try {
139
+ txHash = yield evmClient.writeContract({
140
+ abi: abi_1.UNIVERSAL_GATEWAY_V0,
141
+ address: gatewayAddress,
142
+ functionName: 'sendFunds',
143
+ args: [recipient, bridgeToken, bridgeAmount, revertCFG],
144
+ signer: this.universalSigner,
145
+ value: isNative ? bridgeAmount : BigInt(0),
146
+ });
147
+ }
148
+ catch (err) {
149
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_04);
150
+ throw err;
151
+ }
152
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_03);
153
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_02, txHash, bridgeAmount, execute.funds.token.decimals, symbol);
154
+ yield this.waitForEvmConfirmationsWithCountdown(evmClient, txHash, 14, 210000);
155
+ // After origin confirmations, query Push Chain for UniversalTx status
156
+ try {
157
+ const receipt = yield evmClient.publicClient.getTransactionReceipt({
158
+ hash: txHash,
159
+ });
160
+ const gatewayLogs = (receipt.logs || []).filter((l) => (l.address || '').toLowerCase() ===
161
+ gatewayAddress.toLowerCase());
162
+ const firstLog = (gatewayLogs[0] || ((_a = receipt.logs) === null || _a === void 0 ? void 0 : _a[0]));
163
+ const logIndexVal = (_b = firstLog === null || firstLog === void 0 ? void 0 : firstLog.logIndex) !== null && _b !== void 0 ? _b : 0;
164
+ const logIndexStr = typeof logIndexVal === 'bigint'
165
+ ? logIndexVal.toString()
166
+ : String(logIndexVal);
167
+ const sourceChain = `${chain_1.VM_NAMESPACE[vm]}:${chain_1.CHAIN_INFO[this.universalSigner.account.chain].chainId}`;
168
+ // ID = sha256("${sourceChain}:${txHash}:${logIndex}") as hex string (no 0x)
169
+ const idInput = `${sourceChain}:${txHash}:${logIndexStr}`;
170
+ const idHex = (0, viem_1.sha256)((0, viem_1.stringToBytes)(idInput)).slice(2);
171
+ // Fetch UniversalTx via gRPC with a brief retry window
172
+ let universalTxObj;
173
+ for (let attempt = 0; attempt < 10; attempt++) {
174
+ try {
175
+ const universalTxResp = yield this.pushClient.getUniversalTxById(idHex);
176
+ universalTxObj = universalTxResp === null || universalTxResp === void 0 ? void 0 : universalTxResp.universalTx;
177
+ if (universalTxObj)
178
+ break;
179
+ }
180
+ catch (_d) {
181
+ // ignore and retry
182
+ }
183
+ yield new Promise((r) => setTimeout(r, 1500));
184
+ }
185
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_06, (universalTxObj === null || universalTxObj === void 0 ? void 0 : universalTxObj.universalStatus) ||
186
+ (universalTxObj === null || universalTxObj === void 0 ? void 0 : universalTxObj.universal_status));
187
+ }
188
+ catch (_e) {
189
+ // Best-effort; do not fail flow if PC query is unavailable
190
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_06);
191
+ }
192
+ const tx = yield evmClient.getTransaction(txHash);
193
+ return yield this.transformToUniversalTxResponse(tx);
194
+ }
195
+ else {
196
+ // SVM path (Solana Devnet)
197
+ const svmClient = new svm_client_1.SvmClient({ rpcUrls });
198
+ const programId = new web3_js_1.PublicKey(abi_1.SVM_GATEWAY_IDL.metadata.address);
199
+ const [configPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('config')], programId);
200
+ const [vaultPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('vault')], programId);
201
+ const userPk = new web3_js_1.PublicKey(this.universalSigner.account.address);
202
+ let txSignature;
203
+ // SVM-specific RevertSettings: bytes must be a Buffer
204
+ const revertSvm = {
205
+ fundRecipient: userPk,
206
+ revertMsg: Buffer.alloc(0),
207
+ };
208
+ if (execute.funds.token.mechanism === 'native') {
209
+ // Native SOL funds-only
210
+ const recipientPk = userPk; // must be non-default per program checks
211
+ console.log('recipientPk', recipientPk);
212
+ console.log('bridgeAmount', bridgeAmount);
213
+ console.log('userPk', userPk);
214
+ console.log('configPda', configPda);
215
+ console.log('vaultPda', vaultPda);
216
+ console.log('systemProgram', web3_js_1.SystemProgram.programId);
217
+ console.log('programId', programId.toBase58());
218
+ console.log('SVM_GATEWAY_IDL', abi_1.SVM_GATEWAY_IDL);
219
+ console.log('sendFundsNative', 'sendFundsNative');
220
+ console.log('args', [
221
+ recipientPk,
222
+ bridgeAmount,
223
+ { fundRecipient: userPk, revertMsg: new Uint8Array([]) },
224
+ ]);
225
+ console.log('signer', this.universalSigner);
226
+ console.log('accounts', {
227
+ config: configPda,
228
+ vault: vaultPda,
229
+ user: userPk,
230
+ systemProgram: web3_js_1.SystemProgram.programId,
231
+ });
232
+ txSignature = yield svmClient.writeContract({
233
+ abi: abi_1.SVM_GATEWAY_IDL,
234
+ address: programId.toBase58(),
235
+ functionName: 'sendFundsNative',
236
+ args: [recipientPk, bridgeAmount, revertSvm],
237
+ signer: this.universalSigner,
238
+ accounts: {
239
+ config: configPda,
240
+ vault: vaultPda,
241
+ user: userPk,
242
+ systemProgram: web3_js_1.SystemProgram.programId,
243
+ },
244
+ });
245
+ }
246
+ else if (execute.funds.token.mechanism === 'approve') {
247
+ // SPL token funds-only (requires pre-existing ATAs)
248
+ const mintPk = new web3_js_1.PublicKey(execute.funds.token.address);
249
+ const [whitelistPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('whitelist')], programId);
250
+ // Associated Token Accounts
251
+ const TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
252
+ const ASSOCIATED_TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
253
+ const userAta = web3_js_1.PublicKey.findProgramAddressSync([
254
+ userPk.toBuffer(),
255
+ TOKEN_PROGRAM_ID.toBuffer(),
256
+ mintPk.toBuffer(),
257
+ ], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
258
+ const vaultAta = web3_js_1.PublicKey.findProgramAddressSync([
259
+ vaultPda.toBuffer(),
260
+ TOKEN_PROGRAM_ID.toBuffer(),
261
+ mintPk.toBuffer(),
262
+ ], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
263
+ const recipientPk = userPk; // must be non-default per program checks
264
+ txSignature = yield svmClient.writeContract({
265
+ abi: abi_1.SVM_GATEWAY_IDL,
266
+ address: programId.toBase58(),
267
+ functionName: 'sendFunds',
268
+ args: [recipientPk, mintPk, bridgeAmount, revertSvm],
269
+ signer: this.universalSigner,
270
+ accounts: {
271
+ config: configPda,
272
+ vault: vaultPda,
273
+ tokenWhitelist: whitelistPda,
274
+ userTokenAccount: userAta,
275
+ gatewayTokenAccount: vaultAta,
276
+ user: userPk,
277
+ bridgeToken: mintPk,
278
+ tokenProgram: TOKEN_PROGRAM_ID,
279
+ systemProgram: web3_js_1.SystemProgram.programId,
280
+ },
281
+ });
282
+ }
283
+ else {
284
+ throw new Error('Unsupported token mechanism on Solana');
285
+ }
286
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_03);
287
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_02, txSignature, bridgeAmount, execute.funds.token.decimals, symbol);
288
+ yield svmClient.waitForConfirmations({
289
+ txSignature,
290
+ confirmations: chain_1.CHAIN_INFO[chain].confirmations,
291
+ timeoutMs: chain_1.CHAIN_INFO[chain].timeout,
292
+ });
293
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_06);
294
+ // Build a minimal UniversalTxResponse for SVM origin
295
+ const chainId = chain_1.CHAIN_INFO[chain].chainId;
296
+ const origin = `${chain_1.VM_NAMESPACE[vm]}:${chainId}:${this.universalSigner.account.address}`;
297
+ const universalTxResponse = {
298
+ hash: txSignature,
299
+ origin,
300
+ blockNumber: BigInt(0),
301
+ blockHash: '',
302
+ transactionIndex: 0,
303
+ chainId,
304
+ from: this.universalSigner.account.address,
305
+ to: '0x0000000000000000000000000000000000000000',
306
+ nonce: 0,
307
+ data: '0x',
308
+ value: BigInt(0),
309
+ gasLimit: BigInt(0),
310
+ gasPrice: undefined,
311
+ maxFeePerGas: undefined,
312
+ maxPriorityFeePerGas: undefined,
313
+ accessList: [],
314
+ wait: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
315
+ return ({
316
+ hash: txSignature,
317
+ blockNumber: BigInt(0),
318
+ blockHash: '',
319
+ transactionIndex: 0,
320
+ from: this.universalSigner.account.address,
321
+ to: '0x0000000000000000000000000000000000000000',
322
+ contractAddress: null,
323
+ gasPrice: BigInt(0),
324
+ gasUsed: BigInt(0),
325
+ cumulativeGasUsed: BigInt(0),
326
+ logs: [],
327
+ logsBloom: '0x',
328
+ status: 1,
329
+ raw: {
330
+ from: this.universalSigner.account.address,
331
+ to: '0x0000000000000000000000000000000000000000',
332
+ },
333
+ });
334
+ }),
335
+ type: '99',
336
+ typeVerbose: 'universal',
337
+ signature: { r: '0x0', s: '0x0', v: 0 },
338
+ raw: {
339
+ from: this.universalSigner.account.address,
340
+ to: '0x0000000000000000000000000000000000000000',
341
+ nonce: 0,
342
+ data: '0x',
343
+ value: BigInt(0),
344
+ },
345
+ };
346
+ return universalTxResponse;
347
+ }
348
+ }
349
+ else {
350
+ // Bridge funds + execute payload. Support:
351
+ // - EVM (Sepolia): ERC-20 approve path + native gas via msg.value
352
+ // - SVM (Solana Devnet): SPL or native SOL with gas_amount
353
+ const { chain, evmClient, gatewayAddress } = this.ensureSepoliaGateway();
354
+ // Default token to native ETH if none provided
355
+ if (!execute.funds.token) {
356
+ const available = tokens_1.MOVEABLE_TOKENS[chain] || [];
357
+ const vm = chain_1.CHAIN_INFO[chain].vm;
358
+ const preferredSymbol = vm === enums_1.VM.EVM ? 'ETH' : vm === enums_1.VM.SVM ? 'SOL' : undefined;
359
+ const nativeToken = preferredSymbol
360
+ ? available.find((t) => t.symbol === preferredSymbol)
361
+ : undefined;
362
+ if (!nativeToken) {
363
+ throw new Error('Native token not configured for this chain');
364
+ }
365
+ execute.funds.token = nativeToken;
366
+ }
367
+ const mechanism = execute.funds.token.mechanism;
368
+ const { deployed, nonce } = yield this.getUeaStatusAndNonce();
369
+ const { payload: universalPayload } = yield this.buildGatewayPayloadAndGas(execute, nonce);
370
+ // Compute required gas funding on Push Chain and current UEA balance
371
+ const gasEstimate = execute.gasLimit || BigInt(1e7);
372
+ const gasPrice = yield this.pushClient.getGasPrice();
373
+ const requiredGasFee = gasEstimate * gasPrice;
374
+ const payloadValue = (_c = execute.value) !== null && _c !== void 0 ? _c : BigInt(0);
375
+ const requiredFunds = requiredGasFee + payloadValue;
376
+ const ueaAddress = this.computeUEAOffchain();
377
+ const [ueaBalance] = yield Promise.all([
378
+ this.pushClient.getBalance(ueaAddress),
379
+ ]);
380
+ // Determine USD to deposit via gateway (8 decimals) with caps: min=$1, max=$10
381
+ const oneUsd = push_chain_1.PushChain.utils.helpers.parseUnits('1', 8);
382
+ const tenUsd = push_chain_1.PushChain.utils.helpers.parseUnits('10', 8);
383
+ const deficit = requiredFunds > ueaBalance ? requiredFunds - ueaBalance : BigInt(0);
384
+ let depositUsd = deficit > BigInt(0) ? this.pushClient.pushToUSDC(deficit) : oneUsd;
385
+ if (depositUsd < oneUsd)
386
+ depositUsd = oneUsd;
387
+ if (depositUsd > tenUsd)
388
+ throw new Error('Deposit value exceeds max $10 worth of native token');
389
+ // Convert USD(8) -> native wei using the same pricing path as fee locking
390
+ const nativeTokenUsdPrice = yield new price_fetch_1.PriceFetch(this.rpcUrls).getPrice(chain); // 8 decimals
391
+ const oneEthWei = push_chain_1.PushChain.utils.helpers.parseUnits('1', 18);
392
+ const nativeAmount = (depositUsd * oneEthWei) / nativeTokenUsdPrice;
393
+ const revertCFG = {
394
+ fundRecipient: this.universalSigner.account
395
+ .address,
396
+ revertMsg: '0x',
397
+ };
398
+ const bridgeAmount = execute.funds.amount;
399
+ const symbol = execute.funds.token.symbol;
400
+ // Funds Flow: Preparing funds transfer
401
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_01, bridgeAmount, execute.funds.token.decimals, symbol);
402
+ if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.EVM) {
403
+ const tokenAddr = execute.funds.token.address;
404
+ if (mechanism !== 'approve') {
405
+ throw new Error('Only ERC-20 tokens are supported for funds+payload on EVM; native and permit2 are not supported yet');
406
+ }
407
+ yield this.ensureErc20Allowance(evmClient, tokenAddr, gatewayAddress, bridgeAmount);
408
+ }
409
+ let txHash;
410
+ try {
411
+ if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.EVM) {
412
+ const tokenAddr = execute.funds.token.address;
413
+ // Compute EIP-712 signature for the universal payload and hash to bytes32
414
+ const ueaAddress = this.computeUEAOffchain();
415
+ const eip712Signature = yield this.signUniversalPayload(universalPayload,
416
+ // execute.to
417
+ ueaAddress);
418
+ const eip712SignatureHex = typeof eip712Signature === 'string'
419
+ ? eip712Signature
420
+ : (0, viem_1.bytesToHex)(eip712Signature);
421
+ const signatureData = (0, viem_1.keccak256)(eip712SignatureHex);
422
+ txHash = yield evmClient.writeContract({
423
+ abi: abi_1.UNIVERSAL_GATEWAY_V0,
424
+ address: gatewayAddress,
425
+ functionName: 'sendTxWithFunds',
426
+ args: [
427
+ tokenAddr,
428
+ bridgeAmount,
429
+ universalPayload,
430
+ revertCFG,
431
+ signatureData,
432
+ ],
433
+ signer: this.universalSigner,
434
+ value: nativeAmount,
435
+ });
436
+ }
437
+ else {
438
+ // SVM funds+payload path
439
+ const svmClient = new svm_client_1.SvmClient({
440
+ rpcUrls: this.rpcUrls[enums_1.CHAIN.SOLANA_DEVNET] ||
441
+ chain_1.CHAIN_INFO[enums_1.CHAIN.SOLANA_DEVNET].defaultRPC,
442
+ });
443
+ const programId = new web3_js_1.PublicKey(abi_1.SVM_GATEWAY_IDL.metadata.address);
444
+ const [configPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('config')], programId);
445
+ const [vaultPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('vault')], programId);
446
+ const [whitelistPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('whitelist')], programId);
447
+ const userPk = new web3_js_1.PublicKey(this.universalSigner.account.address);
448
+ const priceUpdatePk = new web3_js_1.PublicKey('7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE');
449
+ const isNative = mechanism === 'native' || execute.funds.token.symbol === 'SOL';
450
+ const revertSvm2 = {
451
+ fundRecipient: userPk,
452
+ revertMsg: Buffer.alloc(0),
453
+ };
454
+ if (isNative) {
455
+ // Native SOL as bridge + gas
456
+ txHash = yield svmClient.writeContract({
457
+ abi: abi_1.SVM_GATEWAY_IDL,
458
+ address: programId.toBase58(),
459
+ functionName: 'sendTxWithFunds',
460
+ args: [
461
+ web3_js_1.PublicKey.default, // bridge_token = default for native SOL
462
+ bridgeAmount,
463
+ universalPayload,
464
+ revertSvm2,
465
+ nativeAmount,
466
+ Buffer.alloc(32),
467
+ ],
468
+ signer: this.universalSigner,
469
+ accounts: {
470
+ config: configPda,
471
+ vault: vaultPda,
472
+ tokenWhitelist: whitelistPda,
473
+ userTokenAccount: web3_js_1.PublicKey.default, // not used for native
474
+ gatewayTokenAccount: web3_js_1.PublicKey.default, // not used for native
475
+ user: userPk,
476
+ priceUpdate: priceUpdatePk,
477
+ bridgeToken: web3_js_1.PublicKey.default,
478
+ tokenProgram: new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
479
+ systemProgram: web3_js_1.SystemProgram.programId,
480
+ },
481
+ });
482
+ }
483
+ else {
484
+ // SPL token as bridge + native SOL lamports as gas_amount
485
+ const mintPk = new web3_js_1.PublicKey(execute.funds.token.address);
486
+ const TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
487
+ const ASSOCIATED_TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
488
+ const userAta = web3_js_1.PublicKey.findProgramAddressSync([
489
+ userPk.toBuffer(),
490
+ TOKEN_PROGRAM_ID.toBuffer(),
491
+ mintPk.toBuffer(),
492
+ ], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
493
+ const vaultAta = web3_js_1.PublicKey.findProgramAddressSync([
494
+ vaultPda.toBuffer(),
495
+ TOKEN_PROGRAM_ID.toBuffer(),
496
+ mintPk.toBuffer(),
497
+ ], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
498
+ txHash = yield svmClient.writeContract({
499
+ abi: abi_1.SVM_GATEWAY_IDL,
500
+ address: programId.toBase58(),
501
+ functionName: 'sendTxWithFunds',
502
+ args: [
503
+ mintPk,
504
+ bridgeAmount,
505
+ universalPayload,
506
+ revertSvm2,
507
+ nativeAmount,
508
+ Buffer.alloc(32),
509
+ ],
510
+ signer: this.universalSigner,
511
+ accounts: {
512
+ config: configPda,
513
+ vault: vaultPda,
514
+ tokenWhitelist: whitelistPda,
515
+ userTokenAccount: userAta,
516
+ gatewayTokenAccount: vaultAta,
517
+ user: userPk,
518
+ priceUpdate: priceUpdatePk,
519
+ bridgeToken: mintPk,
520
+ tokenProgram: TOKEN_PROGRAM_ID,
521
+ systemProgram: web3_js_1.SystemProgram.programId,
522
+ },
523
+ });
524
+ }
525
+ }
526
+ }
527
+ catch (err) {
528
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_04);
529
+ throw err;
530
+ }
531
+ // Payload Flow: Verification Success
532
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_03);
533
+ // Funds Flow: Funds lock submitted
534
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_02, txHash, bridgeAmount, execute.funds.token.decimals, symbol);
535
+ // Awaiting confirmations
536
+ if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.EVM) {
537
+ yield this.waitForEvmConfirmationsWithCountdown(evmClient, txHash, 14, 300000);
538
+ }
539
+ else {
540
+ const svmClient = new svm_client_1.SvmClient({
541
+ rpcUrls: this.rpcUrls[enums_1.CHAIN.SOLANA_DEVNET] ||
542
+ chain_1.CHAIN_INFO[enums_1.CHAIN.SOLANA_DEVNET].defaultRPC,
543
+ });
544
+ yield svmClient.waitForConfirmations({
545
+ txSignature: txHash,
546
+ confirmations: chain_1.CHAIN_INFO[enums_1.CHAIN.SOLANA_DEVNET].confirmations,
547
+ timeoutMs: chain_1.CHAIN_INFO[enums_1.CHAIN.SOLANA_DEVNET].timeout,
548
+ });
549
+ }
550
+ // Funds Flow: Confirmed on origin
551
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_06);
552
+ // If UEA is not deployed yet, deploy it now using the gateway tx hash
553
+ yield this.sendUniversalTx(deployed, txHash);
554
+ if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.EVM) {
555
+ const evmTx = yield evmClient.getTransaction(txHash);
556
+ return yield this.transformToUniversalTxResponse(evmTx);
557
+ }
558
+ else {
559
+ const chainId = chain_1.CHAIN_INFO[chain].chainId;
560
+ const vm = chain_1.CHAIN_INFO[chain].vm;
561
+ const origin = `${chain_1.VM_NAMESPACE[vm]}:${chainId}:${this.universalSigner.account.address}`;
562
+ const universalTxResponse = {
563
+ hash: txHash,
564
+ origin,
565
+ blockNumber: BigInt(0),
566
+ blockHash: '',
567
+ transactionIndex: 0,
568
+ chainId,
569
+ from: this.universalSigner.account.address,
570
+ to: '0x0000000000000000000000000000000000000000',
571
+ nonce: 0,
572
+ data: '0x',
573
+ value: BigInt(0),
574
+ gasLimit: BigInt(0),
575
+ gasPrice: undefined,
576
+ maxFeePerGas: undefined,
577
+ maxPriorityFeePerGas: undefined,
578
+ accessList: [],
579
+ wait: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
580
+ return ({
581
+ hash: txHash,
582
+ blockNumber: BigInt(0),
583
+ blockHash: '',
584
+ transactionIndex: 0,
585
+ from: this.universalSigner.account.address,
586
+ to: '0x0000000000000000000000000000000000000000',
587
+ contractAddress: null,
588
+ gasPrice: BigInt(0),
589
+ gasUsed: BigInt(0),
590
+ cumulativeGasUsed: BigInt(0),
591
+ logs: [],
592
+ logsBloom: '0x',
593
+ status: 1,
594
+ raw: {
595
+ from: this.universalSigner.account.address,
596
+ to: '0x0000000000000000000000000000000000000000',
597
+ },
598
+ });
599
+ }),
600
+ type: '99',
601
+ typeVerbose: 'universal',
602
+ signature: { r: '0x0', s: '0x0', v: 0 },
603
+ raw: {
604
+ from: this.universalSigner.account.address,
605
+ to: '0x0000000000000000000000000000000000000000',
606
+ nonce: 0,
607
+ data: '0x',
608
+ value: BigInt(0),
609
+ },
610
+ };
611
+ return universalTxResponse;
612
+ }
613
+ }
614
+ }
66
615
  // Set default value for value if undefined
67
616
  if (execute.value === undefined) {
68
617
  execute.value = BigInt(0);
@@ -123,8 +672,9 @@ class Orchestrator {
123
672
  if (!allowedChains.includes(this.universalSigner.account.chain)) {
124
673
  throw new Error('Multicall is only enabled for Ethereum Sepolia and Solana Devnet');
125
674
  }
126
- // Validate `to` is a 0x-prefixed address
127
- if (typeof execute.to !== 'string' || !execute.to.startsWith('0x')) {
675
+ // For multicall payloads, ExecuteParams.to must be the sentinel '0x'
676
+ // Note: This is distinct from each call's `to` inside MulticallCall[]
677
+ if (execute.to !== '0x') {
128
678
  throw new Error('When using multicall, "to" must be a 0x-prefixed address');
129
679
  }
130
680
  // Normalize and validate calls
@@ -152,8 +702,12 @@ class Orchestrator {
152
702
  else {
153
703
  payloadData = (execute.data || '0x');
154
704
  }
705
+ // Determine payload `to` value. For multicall sentinel '0x', encode as zero address.
706
+ const payloadTo = Array.isArray(execute.data)
707
+ ? '0x0000000000000000000000000000000000000000'
708
+ : execute.to;
155
709
  const universalPayload = JSON.parse(JSON.stringify({
156
- to: execute.to,
710
+ to: payloadTo,
157
711
  value: execute.value,
158
712
  data: payloadData,
159
713
  gasLimit: execute.gasLimit || BigInt(1e7),
@@ -219,7 +773,19 @@ class Orchestrator {
219
773
  return transactions[transactions.length - 1];
220
774
  }
221
775
  catch (err) {
222
- this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_02, err);
776
+ const errMessage = err instanceof Error
777
+ ? err.message
778
+ : typeof err === 'string'
779
+ ? err
780
+ : (() => {
781
+ try {
782
+ return JSON.stringify(err);
783
+ }
784
+ catch (_a) {
785
+ return 'Unknown error';
786
+ }
787
+ })();
788
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_02, errMessage);
223
789
  throw err;
224
790
  }
225
791
  });
@@ -365,6 +931,7 @@ class Orchestrator {
365
931
  universalAccountId,
366
932
  txHash: feeLockTxHash,
367
933
  }));
934
+ // TODO: pchaind q uexecutor all-universal-tx --node https://rpc-testnet-donut-node1.push.org/
368
935
  }
369
936
  if (feeLockTxHash) {
370
937
  msgs.push(this.pushClient.createMsgMintPC({
@@ -635,6 +1202,129 @@ class Orchestrator {
635
1202
  }
636
1203
  });
637
1204
  }
1205
+ ensureErc20Allowance(evmClient, tokenAddress, spender, requiredAmount) {
1206
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1207
+ const chain = this.universalSigner.account.chain;
1208
+ const owner = this.universalSigner.account.address;
1209
+ const currentAllowance = yield evmClient.readContract({
1210
+ abi: abi_1.ERC20_EVM,
1211
+ address: tokenAddress,
1212
+ functionName: 'allowance',
1213
+ args: [owner, spender],
1214
+ });
1215
+ if (currentAllowance >= requiredAmount)
1216
+ return;
1217
+ // Some ERC-20s like USDT require setting allowance to 0 before changing
1218
+ // an existing non-zero allowance to a different non-zero value.
1219
+ if (currentAllowance > BigInt(0)) {
1220
+ this.printLog(`Resetting existing allowance from ${currentAllowance.toString()} to 0 for spender ${spender}`);
1221
+ const resetTxHash = yield evmClient.writeContract({
1222
+ abi: abi_1.ERC20_EVM,
1223
+ address: tokenAddress,
1224
+ functionName: 'approve',
1225
+ args: [spender, BigInt(0)],
1226
+ signer: this.universalSigner,
1227
+ });
1228
+ yield evmClient.waitForConfirmations({
1229
+ txHash: resetTxHash,
1230
+ confirmations: 1,
1231
+ timeoutMs: chain_1.CHAIN_INFO[chain].timeout,
1232
+ });
1233
+ }
1234
+ const setTxHash = yield evmClient.writeContract({
1235
+ abi: abi_1.ERC20_EVM,
1236
+ address: tokenAddress,
1237
+ functionName: 'approve',
1238
+ args: [spender, requiredAmount],
1239
+ signer: this.universalSigner,
1240
+ });
1241
+ yield evmClient.waitForConfirmations({
1242
+ txHash: setTxHash,
1243
+ confirmations: 1,
1244
+ timeoutMs: chain_1.CHAIN_INFO[chain].timeout,
1245
+ });
1246
+ try {
1247
+ const updated = yield evmClient.readContract({
1248
+ abi: abi_1.ERC20_EVM,
1249
+ address: tokenAddress,
1250
+ functionName: 'allowance',
1251
+ args: [owner, spender],
1252
+ });
1253
+ if (updated < requiredAmount) {
1254
+ this.printLog('Warning: allowance not updated yet; proceeding');
1255
+ }
1256
+ }
1257
+ catch (_a) {
1258
+ // ignore
1259
+ }
1260
+ });
1261
+ }
1262
+ /**
1263
+ * Ensures we're on Sepolia, returns EVM client and gateway address.
1264
+ */
1265
+ ensureSepoliaGateway() {
1266
+ const chain = this.universalSigner.account.chain;
1267
+ if (chain !== enums_1.CHAIN.ETHEREUM_SEPOLIA) {
1268
+ throw new Error('Funds + payload bridging is only supported on Ethereum Sepolia for now');
1269
+ }
1270
+ const { defaultRPC, lockerContract } = chain_1.CHAIN_INFO[chain];
1271
+ const rpcUrls = this.rpcUrls[chain] || defaultRPC;
1272
+ const evmClient = new evm_client_1.EvmClient({ rpcUrls });
1273
+ const gatewayAddress = lockerContract;
1274
+ if (!gatewayAddress) {
1275
+ throw new Error('Universal Gateway address not configured');
1276
+ }
1277
+ return { chain, evmClient, gatewayAddress };
1278
+ }
1279
+ /**
1280
+ * Computes UEA and fetches its nonce if deployed; returns 0 otherwise.
1281
+ */
1282
+ getUeaNonceForExecution() {
1283
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1284
+ const UEA = this.computeUEAOffchain();
1285
+ const [code] = yield Promise.all([
1286
+ this.pushClient.publicClient.getCode({ address: UEA }),
1287
+ ]);
1288
+ return code !== undefined ? yield this.getUEANonce(UEA) : BigInt(0);
1289
+ });
1290
+ }
1291
+ /**
1292
+ * Returns UEA deployment status and nonce (0 if not deployed).
1293
+ */
1294
+ getUeaStatusAndNonce() {
1295
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1296
+ const UEA = this.computeUEAOffchain();
1297
+ const [code] = yield Promise.all([
1298
+ this.pushClient.publicClient.getCode({ address: UEA }),
1299
+ ]);
1300
+ const deployed = code !== undefined;
1301
+ const nonce = deployed ? yield this.getUEANonce(UEA) : BigInt(0);
1302
+ return { deployed, nonce };
1303
+ });
1304
+ }
1305
+ /**
1306
+ * Builds UniversalPayload for the gateway and computes the native gas deposit.
1307
+ */
1308
+ buildGatewayPayloadAndGas(execute, nonce) {
1309
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1310
+ var _a, _b;
1311
+ const gasEstimate = execute.gasLimit || BigInt(1e7);
1312
+ const payloadValue = (_a = execute.value) !== null && _a !== void 0 ? _a : BigInt(0);
1313
+ const gasAmount = (_b = execute.value) !== null && _b !== void 0 ? _b : BigInt(0);
1314
+ const universalPayload = {
1315
+ to: execute.to,
1316
+ value: payloadValue,
1317
+ data: execute.data || '0x',
1318
+ gasLimit: gasEstimate,
1319
+ maxFeePerGas: execute.maxFeePerGas || BigInt(1e10),
1320
+ maxPriorityFeePerGas: execute.maxPriorityFeePerGas || BigInt(0),
1321
+ nonce,
1322
+ deadline: execute.deadline || BigInt(9999999999),
1323
+ vType: tx_1.VerificationType.universalTxVerification,
1324
+ };
1325
+ return { payload: universalPayload, gasAmount };
1326
+ });
1327
+ }
638
1328
  /********************************** HELPER FUNCTIONS **************************************************/
639
1329
  /**
640
1330
  * Transforms a TransactionReceipt to UniversalTxReceipt format
@@ -855,6 +1545,32 @@ class Orchestrator {
855
1545
  // invoke the user-provided callback
856
1546
  this.progressHook(hookPayload);
857
1547
  }
1548
+ // Emit countdown updates while waiting for EVM confirmations
1549
+ waitForEvmConfirmationsWithCountdown(evmClient, txHash, confirmations, timeoutMs) {
1550
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1551
+ // initial emit
1552
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_03, confirmations);
1553
+ const start = Date.now();
1554
+ // Wait for receipt to get included block
1555
+ const receipt = yield evmClient.publicClient.waitForTransactionReceipt({
1556
+ hash: txHash,
1557
+ });
1558
+ const targetBlock = receipt.blockNumber + BigInt(confirmations);
1559
+ // Poll blocks and emit remaining confirmations
1560
+ // eslint-disable-next-line no-constant-condition
1561
+ while (true) {
1562
+ const currentBlock = yield evmClient.publicClient.getBlockNumber();
1563
+ if (currentBlock >= targetBlock)
1564
+ return;
1565
+ const remaining = Number(targetBlock - currentBlock);
1566
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_03, remaining);
1567
+ if (Date.now() - start > timeoutMs) {
1568
+ throw new Error(`Timeout: transaction ${txHash} not confirmed with ${confirmations} confirmations within ${timeoutMs} ms`);
1569
+ }
1570
+ yield new Promise((r) => setTimeout(r, 12000));
1571
+ }
1572
+ });
1573
+ }
858
1574
  }
859
1575
  exports.Orchestrator = Orchestrator;
860
1576
  //# sourceMappingURL=orchestrator.js.map