@circle-fin/provider-cctp-v2 1.5.0 → 1.6.1
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/CHANGELOG.md +17 -0
- package/index.cjs +269 -26
- package/index.d.ts +81 -0
- package/index.mjs +269 -26
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @circle-fin/provider-cctp-v2
|
|
2
2
|
|
|
3
|
+
## 1.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add support for Solana as a forwarder destination
|
|
8
|
+
|
|
9
|
+
## 1.6.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- Add support for EIP-5792 batched transactions in the bridge flow
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Enable forwarder destination support for Codex, Plume, and XDC chains on both mainnet and testnet
|
|
18
|
+
- Update HyperEVM explorer URLs to use the official Hyperliquid explorer
|
|
19
|
+
|
|
3
20
|
## 1.5.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
package/index.cjs
CHANGED
|
@@ -1066,7 +1066,7 @@ const Codex = defineChain({
|
|
|
1066
1066
|
},
|
|
1067
1067
|
forwarderSupported: {
|
|
1068
1068
|
source: true,
|
|
1069
|
-
destination:
|
|
1069
|
+
destination: true,
|
|
1070
1070
|
},
|
|
1071
1071
|
},
|
|
1072
1072
|
kitContracts: {
|
|
@@ -1109,7 +1109,7 @@ const CodexTestnet = defineChain({
|
|
|
1109
1109
|
},
|
|
1110
1110
|
forwarderSupported: {
|
|
1111
1111
|
source: true,
|
|
1112
|
-
destination:
|
|
1112
|
+
destination: true,
|
|
1113
1113
|
},
|
|
1114
1114
|
},
|
|
1115
1115
|
kitContracts: {
|
|
@@ -1371,7 +1371,7 @@ const HyperEVM = defineChain({
|
|
|
1371
1371
|
},
|
|
1372
1372
|
chainId: 999,
|
|
1373
1373
|
isTestnet: false,
|
|
1374
|
-
explorerUrl: 'https://
|
|
1374
|
+
explorerUrl: 'https://app.hyperliquid.xyz/explorer/tx/{hash}',
|
|
1375
1375
|
rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
|
|
1376
1376
|
eurcAddress: null,
|
|
1377
1377
|
usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
|
|
@@ -1416,7 +1416,7 @@ const HyperEVMTestnet = defineChain({
|
|
|
1416
1416
|
},
|
|
1417
1417
|
chainId: 998,
|
|
1418
1418
|
isTestnet: true,
|
|
1419
|
-
explorerUrl: 'https://
|
|
1419
|
+
explorerUrl: 'https://app.hyperliquid-testnet.xyz/explorer/tx/{hash}',
|
|
1420
1420
|
rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
|
|
1421
1421
|
eurcAddress: null,
|
|
1422
1422
|
usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
|
|
@@ -2062,7 +2062,7 @@ const Plume = defineChain({
|
|
|
2062
2062
|
},
|
|
2063
2063
|
forwarderSupported: {
|
|
2064
2064
|
source: true,
|
|
2065
|
-
destination:
|
|
2065
|
+
destination: true,
|
|
2066
2066
|
},
|
|
2067
2067
|
},
|
|
2068
2068
|
kitContracts: {
|
|
@@ -2107,7 +2107,7 @@ const PlumeTestnet = defineChain({
|
|
|
2107
2107
|
},
|
|
2108
2108
|
forwarderSupported: {
|
|
2109
2109
|
source: true,
|
|
2110
|
-
destination:
|
|
2110
|
+
destination: true,
|
|
2111
2111
|
},
|
|
2112
2112
|
},
|
|
2113
2113
|
kitContracts: {
|
|
@@ -2479,7 +2479,7 @@ const Solana = defineChain({
|
|
|
2479
2479
|
},
|
|
2480
2480
|
forwarderSupported: {
|
|
2481
2481
|
source: true,
|
|
2482
|
-
destination:
|
|
2482
|
+
destination: true,
|
|
2483
2483
|
},
|
|
2484
2484
|
},
|
|
2485
2485
|
kitContracts: {
|
|
@@ -2526,7 +2526,7 @@ const SolanaDevnet = defineChain({
|
|
|
2526
2526
|
},
|
|
2527
2527
|
forwarderSupported: {
|
|
2528
2528
|
source: true,
|
|
2529
|
-
destination:
|
|
2529
|
+
destination: true,
|
|
2530
2530
|
},
|
|
2531
2531
|
},
|
|
2532
2532
|
kitContracts: {
|
|
@@ -2885,7 +2885,7 @@ const XDC = defineChain({
|
|
|
2885
2885
|
},
|
|
2886
2886
|
forwarderSupported: {
|
|
2887
2887
|
source: true,
|
|
2888
|
-
destination:
|
|
2888
|
+
destination: true,
|
|
2889
2889
|
},
|
|
2890
2890
|
},
|
|
2891
2891
|
kitContracts: {
|
|
@@ -2929,7 +2929,7 @@ const XDCApothem = defineChain({
|
|
|
2929
2929
|
},
|
|
2930
2930
|
forwarderSupported: {
|
|
2931
2931
|
source: true,
|
|
2932
|
-
destination:
|
|
2932
|
+
destination: true,
|
|
2933
2933
|
},
|
|
2934
2934
|
},
|
|
2935
2935
|
kitContracts: {
|
|
@@ -9509,7 +9509,184 @@ zod.z
|
|
|
9509
9509
|
})
|
|
9510
9510
|
.passthrough();
|
|
9511
9511
|
|
|
9512
|
-
|
|
9512
|
+
/**
|
|
9513
|
+
* Check whether the source adapter supports EIP-5792 atomic batching and
|
|
9514
|
+
* the consumer has not explicitly opted out via `config.batchTransactions`.
|
|
9515
|
+
*
|
|
9516
|
+
* @param params - Bridge parameters (used for adapter and config access).
|
|
9517
|
+
* @returns `true` when batched execution should be attempted.
|
|
9518
|
+
*
|
|
9519
|
+
* @example
|
|
9520
|
+
* ```typescript
|
|
9521
|
+
* const useBatched = await shouldUseBatchedExecution(params)
|
|
9522
|
+
* if (useBatched) {
|
|
9523
|
+
* // take the batched approve + burn path
|
|
9524
|
+
* }
|
|
9525
|
+
* ```
|
|
9526
|
+
*/
|
|
9527
|
+
async function shouldUseBatchedExecution(params) {
|
|
9528
|
+
if (params.config?.batchTransactions === false) {
|
|
9529
|
+
return false;
|
|
9530
|
+
}
|
|
9531
|
+
const { chain } = params.source;
|
|
9532
|
+
if (chain.type !== 'evm') {
|
|
9533
|
+
return false;
|
|
9534
|
+
}
|
|
9535
|
+
const adapter = params.source
|
|
9536
|
+
.adapter;
|
|
9537
|
+
if (typeof adapter.supportsAtomicBatch !== 'function' ||
|
|
9538
|
+
typeof adapter.batchExecute !== 'function') {
|
|
9539
|
+
return false;
|
|
9540
|
+
}
|
|
9541
|
+
try {
|
|
9542
|
+
return await adapter.supportsAtomicBatch(chain);
|
|
9543
|
+
}
|
|
9544
|
+
catch {
|
|
9545
|
+
return false;
|
|
9546
|
+
}
|
|
9547
|
+
}
|
|
9548
|
+
/**
|
|
9549
|
+
* Execute the approve and burn steps as a single EIP-5792 batched call.
|
|
9550
|
+
*
|
|
9551
|
+
* Prepare both `PreparedChainRequest` objects upfront, extract their raw
|
|
9552
|
+
* call data via `getCallData()`, submit both via `adapter.batchExecute()`,
|
|
9553
|
+
* then map the individual receipts back to standard {@link BridgeStep}
|
|
9554
|
+
* objects so downstream consumers (event callbacks, result tracking) are
|
|
9555
|
+
* unaffected.
|
|
9556
|
+
*
|
|
9557
|
+
* @param params - The CCTP v2 bridge parameters.
|
|
9558
|
+
* @param provider - The CCTP v2 bridging provider.
|
|
9559
|
+
* @returns Approve and burn steps with a shared context containing the burn tx hash.
|
|
9560
|
+
* @throws {@link KitError} when the source chain is not EVM.
|
|
9561
|
+
* @throws {@link KitError} when calldata extraction (`getCallData`) is not supported
|
|
9562
|
+
* by the prepared requests.
|
|
9563
|
+
* @remarks
|
|
9564
|
+
* Errors that occur after the batch has been submitted to the wallet
|
|
9565
|
+
* (e.g. polling timeout, insufficient receipts) are **not thrown** — they
|
|
9566
|
+
* are captured as `state: 'error'` on the returned steps to prevent
|
|
9567
|
+
* accidental double-spend on retry.
|
|
9568
|
+
*
|
|
9569
|
+
* @example
|
|
9570
|
+
* ```typescript
|
|
9571
|
+
* const { approveStep, burnStep, context } = await executeBatchedApproveAndBurn(
|
|
9572
|
+
* params,
|
|
9573
|
+
* provider,
|
|
9574
|
+
* )
|
|
9575
|
+
* result.steps.push(approveStep, burnStep)
|
|
9576
|
+
* ```
|
|
9577
|
+
*/
|
|
9578
|
+
async function executeBatchedApproveAndBurn(params, provider) {
|
|
9579
|
+
// Double-unknown cast: Adapter<TFrom> has no structural overlap with
|
|
9580
|
+
// BatchCapableAdapter (a duck-typed interface for EIP-5792 methods).
|
|
9581
|
+
// A direct cast fails because TS cannot prove the intersection; the
|
|
9582
|
+
// runtime capability check below guards against unsupported adapters.
|
|
9583
|
+
const adapter = params.source.adapter;
|
|
9584
|
+
const sourceChain = params.source.chain;
|
|
9585
|
+
if (sourceChain.type !== 'evm') {
|
|
9586
|
+
throw new KitError({
|
|
9587
|
+
...InputError.INVALID_CHAIN,
|
|
9588
|
+
recoverability: 'FATAL',
|
|
9589
|
+
message: 'Batched execution is only supported on EVM chains.',
|
|
9590
|
+
});
|
|
9591
|
+
}
|
|
9592
|
+
const chain = sourceChain;
|
|
9593
|
+
// customFee.value is in base units (integer string) at this point.
|
|
9594
|
+
const customFee = BigInt(params.config?.customFee?.value ?? '0');
|
|
9595
|
+
const amountBigInt = BigInt(params.amount);
|
|
9596
|
+
const approvalAmount = (amountBigInt + customFee).toString();
|
|
9597
|
+
const [approveRequest, burnRequest] = await Promise.all([
|
|
9598
|
+
provider.approve(params.source, approvalAmount),
|
|
9599
|
+
provider.burn(params),
|
|
9600
|
+
]);
|
|
9601
|
+
if (approveRequest.type !== 'evm' ||
|
|
9602
|
+
burnRequest.type !== 'evm' ||
|
|
9603
|
+
!approveRequest.getCallData ||
|
|
9604
|
+
!burnRequest.getCallData) {
|
|
9605
|
+
throw new KitError({
|
|
9606
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
9607
|
+
recoverability: 'FATAL',
|
|
9608
|
+
message: 'Batched execution requires EVM prepared requests with getCallData() support.',
|
|
9609
|
+
});
|
|
9610
|
+
}
|
|
9611
|
+
const approveCallData = approveRequest.getCallData();
|
|
9612
|
+
const burnCallData = burnRequest.getCallData();
|
|
9613
|
+
// batchExecute may throw before submission (wallet declined) but never
|
|
9614
|
+
// after — post-submission errors are returned as empty receipts.
|
|
9615
|
+
const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
|
|
9616
|
+
const approveReceipt = batchResult.receipts[0];
|
|
9617
|
+
const burnReceipt = batchResult.receipts[1];
|
|
9618
|
+
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
|
|
9619
|
+
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
|
|
9620
|
+
if (burnStep.state !== 'error' && !burnStep.txHash) {
|
|
9621
|
+
burnStep.state = 'error';
|
|
9622
|
+
burnStep.errorMessage =
|
|
9623
|
+
'Batched burn step completed but no transaction hash was returned.';
|
|
9624
|
+
}
|
|
9625
|
+
const context = { burnTxHash: burnStep.txHash ?? '' };
|
|
9626
|
+
return { approveStep, burnStep, context };
|
|
9627
|
+
}
|
|
9628
|
+
/**
|
|
9629
|
+
* Build a {@link BridgeStep} from a single receipt within a batch.
|
|
9630
|
+
*
|
|
9631
|
+
* Map the raw receipt from `batchExecute` into a standard `BridgeStep`,
|
|
9632
|
+
* waiting for on-chain confirmation via `adapter.waitForTransaction`.
|
|
9633
|
+
* All errors are captured on the step (never thrown) so the caller
|
|
9634
|
+
* can inspect each step independently.
|
|
9635
|
+
*
|
|
9636
|
+
* @param name - Human-readable step name (e.g. `'approve'`, `'burn'`).
|
|
9637
|
+
* @param receipt - Per-call receipt from `batchExecute`, or `undefined`
|
|
9638
|
+
* when the wallet returned fewer receipts than submitted calls.
|
|
9639
|
+
* @param batchId - Wallet-assigned batch identifier.
|
|
9640
|
+
* @param adapter - The batch-capable adapter (used for confirmation).
|
|
9641
|
+
* @param chain - The EVM chain the batch was executed on.
|
|
9642
|
+
* @returns A fully-populated bridge step with state, tx hash and explorer URL.
|
|
9643
|
+
*
|
|
9644
|
+
* @internal
|
|
9645
|
+
*/
|
|
9646
|
+
async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
9647
|
+
const step = {
|
|
9648
|
+
name,
|
|
9649
|
+
state: 'pending',
|
|
9650
|
+
batched: true,
|
|
9651
|
+
batchId,
|
|
9652
|
+
};
|
|
9653
|
+
if (!receipt) {
|
|
9654
|
+
step.state = 'error';
|
|
9655
|
+
step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
|
|
9656
|
+
return step;
|
|
9657
|
+
}
|
|
9658
|
+
step.txHash = receipt.txHash;
|
|
9659
|
+
if (receipt.txHash) {
|
|
9660
|
+
step.explorerUrl = buildExplorerUrl(chain, receipt.txHash);
|
|
9661
|
+
}
|
|
9662
|
+
if (receipt.status !== 'success') {
|
|
9663
|
+
step.state = 'error';
|
|
9664
|
+
step.errorMessage = `${name} call failed within batch ${batchId}.`;
|
|
9665
|
+
return step;
|
|
9666
|
+
}
|
|
9667
|
+
if (!receipt.txHash) {
|
|
9668
|
+
step.state = 'error';
|
|
9669
|
+
step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
|
|
9670
|
+
return step;
|
|
9671
|
+
}
|
|
9672
|
+
try {
|
|
9673
|
+
const transaction = await adapter.waitForTransaction(receipt.txHash, { confirmations: 1 }, chain);
|
|
9674
|
+
step.state = transaction.blockNumber === undefined ? 'error' : 'success';
|
|
9675
|
+
step.data = transaction;
|
|
9676
|
+
if (transaction.blockNumber === undefined) {
|
|
9677
|
+
step.errorMessage = 'Transaction was not confirmed on-chain.';
|
|
9678
|
+
}
|
|
9679
|
+
}
|
|
9680
|
+
catch (err) {
|
|
9681
|
+
step.state = 'error';
|
|
9682
|
+
step.error = err;
|
|
9683
|
+
step.errorMessage =
|
|
9684
|
+
err instanceof Error ? err.message : 'Unknown error during confirmation.';
|
|
9685
|
+
}
|
|
9686
|
+
return step;
|
|
9687
|
+
}
|
|
9688
|
+
|
|
9689
|
+
var version = "1.6.1";
|
|
9513
9690
|
var pkg = {
|
|
9514
9691
|
version: version};
|
|
9515
9692
|
|
|
@@ -9538,6 +9715,67 @@ function resolveBridgeInvocation(invocationMeta) {
|
|
|
9538
9715
|
};
|
|
9539
9716
|
return extendInvocationContext(resolveInvocationContext(invocationMeta, defaults), BRIDGE_CALLER);
|
|
9540
9717
|
}
|
|
9718
|
+
/**
|
|
9719
|
+
* Execute the batched approve + burn path via EIP-5792.
|
|
9720
|
+
*
|
|
9721
|
+
* Mutate `result` with step data and error state as needed. Return the
|
|
9722
|
+
* batch context on success, or `undefined` when the batch failed (in
|
|
9723
|
+
* which case `result.state` is set to `'error'`).
|
|
9724
|
+
*
|
|
9725
|
+
* @internal
|
|
9726
|
+
* @param params - Bridge parameters (read-only).
|
|
9727
|
+
* @param provider - The CCTP v2 bridging provider (read-only).
|
|
9728
|
+
* @param result - Bridge result object — **mutated in place** with step
|
|
9729
|
+
* data and, on failure, `state: 'error'` plus an `error` payload.
|
|
9730
|
+
* @param invocation - Invocation context for telemetry (read-only).
|
|
9731
|
+
* @returns The step context on success, or `undefined` when the batch failed.
|
|
9732
|
+
*/
|
|
9733
|
+
async function executeBatchedPath(params, provider, result, invocation) {
|
|
9734
|
+
// IMPORTANT: once executeBatchedApproveAndBurn is called, we NEVER
|
|
9735
|
+
// fall back to sequential. The wallet may have already signed &
|
|
9736
|
+
// submitted the batch; retrying as individual txs would double-spend.
|
|
9737
|
+
try {
|
|
9738
|
+
const { approveStep, burnStep, context: batchContext, } = await executeBatchedApproveAndBurn(params, provider);
|
|
9739
|
+
for (const step of [approveStep, burnStep]) {
|
|
9740
|
+
const stepName = step.name;
|
|
9741
|
+
if (step.state === 'error') {
|
|
9742
|
+
ensureStepErrorMessage(step.name, step);
|
|
9743
|
+
result.steps.push(step);
|
|
9744
|
+
result.state = 'error';
|
|
9745
|
+
dispatchStepEvent(stepName, step, provider, invocation);
|
|
9746
|
+
return undefined;
|
|
9747
|
+
}
|
|
9748
|
+
dispatchStepEvent(stepName, step, provider, invocation);
|
|
9749
|
+
result.steps.push(step);
|
|
9750
|
+
}
|
|
9751
|
+
return batchContext;
|
|
9752
|
+
}
|
|
9753
|
+
catch (error_) {
|
|
9754
|
+
// Only handles pre-submission failures (prepare rejected, wallet
|
|
9755
|
+
// declined, etc.). batchExecute never throws after sendCalls succeeds.
|
|
9756
|
+
result.state = 'error';
|
|
9757
|
+
result.steps.push({
|
|
9758
|
+
name: 'batch',
|
|
9759
|
+
state: 'error',
|
|
9760
|
+
batched: true,
|
|
9761
|
+
error: error_,
|
|
9762
|
+
errorMessage: error_ instanceof Error
|
|
9763
|
+
? error_.message
|
|
9764
|
+
: 'Batched approve + burn failed.',
|
|
9765
|
+
});
|
|
9766
|
+
return undefined;
|
|
9767
|
+
}
|
|
9768
|
+
}
|
|
9769
|
+
/**
|
|
9770
|
+
* Ensure `step.errorMessage` is populated when an error object exists.
|
|
9771
|
+
*
|
|
9772
|
+
* @internal
|
|
9773
|
+
*/
|
|
9774
|
+
function ensureStepErrorMessage(name, step) {
|
|
9775
|
+
if (!step.errorMessage && step.error) {
|
|
9776
|
+
step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
|
|
9777
|
+
}
|
|
9778
|
+
}
|
|
9541
9779
|
/**
|
|
9542
9780
|
* Execute a cross-chain USDC bridge using the CCTP v2 protocol.
|
|
9543
9781
|
*
|
|
@@ -9571,9 +9809,7 @@ function resolveBridgeInvocation(invocationMeta) {
|
|
|
9571
9809
|
* ```
|
|
9572
9810
|
*/
|
|
9573
9811
|
async function bridge(params, provider) {
|
|
9574
|
-
// Check if forwarder is enabled (on destination)
|
|
9575
9812
|
const useForwarder = params.destination.useForwarder === true;
|
|
9576
|
-
// Resolve invocation metadata to full context for event dispatching
|
|
9577
9813
|
const invocation = resolveBridgeInvocation(params.invocationMeta);
|
|
9578
9814
|
const result = {
|
|
9579
9815
|
state: 'pending',
|
|
@@ -9586,11 +9822,9 @@ async function bridge(params, provider) {
|
|
|
9586
9822
|
destination: {
|
|
9587
9823
|
address: params.destination.address,
|
|
9588
9824
|
chain: params.destination.chain,
|
|
9589
|
-
// Preserve recipientAddress
|
|
9590
9825
|
...(params.destination.recipientAddress && {
|
|
9591
9826
|
recipientAddress: params.destination.recipientAddress,
|
|
9592
9827
|
}),
|
|
9593
|
-
// Preserve useForwarder
|
|
9594
9828
|
...(useForwarder && {
|
|
9595
9829
|
useForwarder: true,
|
|
9596
9830
|
}),
|
|
@@ -9599,29 +9833,38 @@ async function bridge(params, provider) {
|
|
|
9599
9833
|
config: params.config,
|
|
9600
9834
|
provider: provider.name,
|
|
9601
9835
|
};
|
|
9602
|
-
// Context shared between steps
|
|
9603
9836
|
let context = undefined;
|
|
9604
|
-
|
|
9837
|
+
let useBatched = false;
|
|
9838
|
+
try {
|
|
9839
|
+
useBatched = await shouldUseBatchedExecution(params);
|
|
9840
|
+
}
|
|
9841
|
+
catch {
|
|
9842
|
+
// Silently fall back to sequential
|
|
9843
|
+
}
|
|
9605
9844
|
const executors = createStepExecutors(useForwarder);
|
|
9606
|
-
|
|
9607
|
-
|
|
9845
|
+
if (useBatched) {
|
|
9846
|
+
const batchContext = await executeBatchedPath(params, provider, result, invocation);
|
|
9847
|
+
if (result.state === 'error') {
|
|
9848
|
+
return result;
|
|
9849
|
+
}
|
|
9850
|
+
context = batchContext;
|
|
9851
|
+
}
|
|
9852
|
+
const stepsToRun = useBatched
|
|
9853
|
+
? executors.filter(({ name }) => name !== 'approve' && name !== 'burn')
|
|
9854
|
+
: executors;
|
|
9855
|
+
for (const { name, executor, updateContext } of stepsToRun) {
|
|
9608
9856
|
try {
|
|
9609
9857
|
const step = await executor(params, provider, context);
|
|
9610
9858
|
if (step.state === 'error') {
|
|
9611
|
-
|
|
9612
|
-
if (!step.errorMessage && step.error) {
|
|
9613
|
-
step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
|
|
9614
|
-
}
|
|
9859
|
+
ensureStepErrorMessage(name, step);
|
|
9615
9860
|
result.steps.push(step);
|
|
9616
9861
|
result.state = 'error';
|
|
9617
|
-
// Dispatch event even for error steps
|
|
9618
9862
|
dispatchStepEvent(name, step, provider, invocation);
|
|
9619
9863
|
return result;
|
|
9620
9864
|
}
|
|
9621
|
-
// Merge new context with existing context to preserve data from previous steps
|
|
9622
9865
|
const newContext = updateContext?.(step);
|
|
9623
9866
|
if (newContext) {
|
|
9624
|
-
context = { ...
|
|
9867
|
+
context = { ...context, ...newContext };
|
|
9625
9868
|
}
|
|
9626
9869
|
dispatchStepEvent(name, step, provider, invocation);
|
|
9627
9870
|
result.steps.push(step);
|
package/index.d.ts
CHANGED
|
@@ -665,6 +665,32 @@ interface EvmExecuteOverrides extends EvmEstimateOverrides {
|
|
|
665
665
|
*/
|
|
666
666
|
nonce?: number;
|
|
667
667
|
}
|
|
668
|
+
/**
|
|
669
|
+
* Raw EVM call data tuple for a single contract interaction.
|
|
670
|
+
*
|
|
671
|
+
* Represents the minimal data needed to submit an EVM transaction:
|
|
672
|
+
* the target contract address, the ABI-encoded calldata, and an
|
|
673
|
+
* optional native token value. Used by EIP-5792 batched execution
|
|
674
|
+
* to compose multiple calls into a single `wallet_sendCalls` request.
|
|
675
|
+
*
|
|
676
|
+
* @interface EvmCallData
|
|
677
|
+
*
|
|
678
|
+
* @example
|
|
679
|
+
* ```typescript
|
|
680
|
+
* const callData: EvmCallData = {
|
|
681
|
+
* to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
|
|
682
|
+
* data: '0x095ea7b3000000000000000000000000...',
|
|
683
|
+
* }
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
interface EvmCallData {
|
|
687
|
+
/** The target contract address. */
|
|
688
|
+
to: `0x${string}`;
|
|
689
|
+
/** The ABI-encoded function calldata. */
|
|
690
|
+
data: `0x${string}`;
|
|
691
|
+
/** Optional native token value to send with the call. */
|
|
692
|
+
value?: bigint | undefined;
|
|
693
|
+
}
|
|
668
694
|
/**
|
|
669
695
|
* Prepared contract execution for EVM chains.
|
|
670
696
|
*
|
|
@@ -693,6 +719,28 @@ interface EvmPreparedChainRequest {
|
|
|
693
719
|
* @throws If the execution fails
|
|
694
720
|
*/
|
|
695
721
|
execute(overrides?: EvmExecuteOverrides): Promise<string>;
|
|
722
|
+
/**
|
|
723
|
+
* Return the raw call tuple without executing or estimating.
|
|
724
|
+
*
|
|
725
|
+
* Expose the `{ to, data, value }` triple that would be sent on-chain so
|
|
726
|
+
* callers can feed it into EIP-5792 `wallet_sendCalls` or other batching
|
|
727
|
+
* mechanisms. This method is optional -- adapters that do not support
|
|
728
|
+
* calldata extraction (e.g. Ethers v6) may omit it.
|
|
729
|
+
*
|
|
730
|
+
* @returns The raw EVM call data for this prepared request.
|
|
731
|
+
* @throws Never — synchronous accessor with no failure path.
|
|
732
|
+
* @since 2.0.0
|
|
733
|
+
*
|
|
734
|
+
* @example
|
|
735
|
+
* ```typescript
|
|
736
|
+
* const prepared = await adapter.prepare(params, ctx)
|
|
737
|
+
* if (prepared.getCallData) {
|
|
738
|
+
* const { to, data, value } = prepared.getCallData()
|
|
739
|
+
* console.log('Target:', to, 'Data:', data)
|
|
740
|
+
* }
|
|
741
|
+
* ```
|
|
742
|
+
*/
|
|
743
|
+
getCallData?(): EvmCallData;
|
|
696
744
|
}
|
|
697
745
|
/**
|
|
698
746
|
* Union type for all supported prepared contract executions.
|
|
@@ -4417,6 +4465,21 @@ interface BridgeStep {
|
|
|
4417
4465
|
* - `undefined`: Not applicable (non-mint steps)
|
|
4418
4466
|
*/
|
|
4419
4467
|
forwarded?: boolean;
|
|
4468
|
+
/**
|
|
4469
|
+
* Whether this step was executed as part of an EIP-5792 batched
|
|
4470
|
+
* `wallet_sendCalls` request.
|
|
4471
|
+
*
|
|
4472
|
+
* - `true`: The step was included in a batched call bundle
|
|
4473
|
+
* - `undefined`: The step was executed individually (sequential flow)
|
|
4474
|
+
*/
|
|
4475
|
+
batched?: boolean | undefined;
|
|
4476
|
+
/**
|
|
4477
|
+
* The wallet-assigned batch identifier from `wallet_sendCalls`.
|
|
4478
|
+
*
|
|
4479
|
+
* Present only when {@link batched} is `true`. Can be used with
|
|
4480
|
+
* `wallet_getCallsStatus` to query the status of the entire bundle.
|
|
4481
|
+
*/
|
|
4482
|
+
batchId?: string | undefined;
|
|
4420
4483
|
/** Optional human-readable error message */
|
|
4421
4484
|
errorMessage?: string;
|
|
4422
4485
|
/** Optional raw error object (can be Viem/Ethers/Chain error) */
|
|
@@ -4561,6 +4624,24 @@ interface BridgeConfig {
|
|
|
4561
4624
|
* @defaultValue TransferSpeed.FAST
|
|
4562
4625
|
*/
|
|
4563
4626
|
transferSpeed?: TransferSpeed | `${TransferSpeed}` | undefined;
|
|
4627
|
+
/**
|
|
4628
|
+
* Enable or disable EIP-5792 batched transaction execution.
|
|
4629
|
+
*
|
|
4630
|
+
* When `true` (or `undefined` / omitted), the bridge will attempt to batch
|
|
4631
|
+
* the approve and burn calls into a single `wallet_sendCalls` request if
|
|
4632
|
+
* the connected wallet supports it. Set to `false` to explicitly opt out
|
|
4633
|
+
* and always use the sequential approve -> burn flow.
|
|
4634
|
+
*
|
|
4635
|
+
* @defaultValue `undefined` (batching attempted when the wallet supports it)
|
|
4636
|
+
*
|
|
4637
|
+
* @example
|
|
4638
|
+
* ```typescript
|
|
4639
|
+
* const config: BridgeConfig = {
|
|
4640
|
+
* batchTransactions: false, // force sequential flow
|
|
4641
|
+
* }
|
|
4642
|
+
* ```
|
|
4643
|
+
*/
|
|
4644
|
+
batchTransactions?: boolean | undefined;
|
|
4564
4645
|
/**
|
|
4565
4646
|
* The maximum fee to pay for the burn operation.
|
|
4566
4647
|
*
|
package/index.mjs
CHANGED
|
@@ -1059,7 +1059,7 @@ const Codex = defineChain({
|
|
|
1059
1059
|
},
|
|
1060
1060
|
forwarderSupported: {
|
|
1061
1061
|
source: true,
|
|
1062
|
-
destination:
|
|
1062
|
+
destination: true,
|
|
1063
1063
|
},
|
|
1064
1064
|
},
|
|
1065
1065
|
kitContracts: {
|
|
@@ -1102,7 +1102,7 @@ const CodexTestnet = defineChain({
|
|
|
1102
1102
|
},
|
|
1103
1103
|
forwarderSupported: {
|
|
1104
1104
|
source: true,
|
|
1105
|
-
destination:
|
|
1105
|
+
destination: true,
|
|
1106
1106
|
},
|
|
1107
1107
|
},
|
|
1108
1108
|
kitContracts: {
|
|
@@ -1364,7 +1364,7 @@ const HyperEVM = defineChain({
|
|
|
1364
1364
|
},
|
|
1365
1365
|
chainId: 999,
|
|
1366
1366
|
isTestnet: false,
|
|
1367
|
-
explorerUrl: 'https://
|
|
1367
|
+
explorerUrl: 'https://app.hyperliquid.xyz/explorer/tx/{hash}',
|
|
1368
1368
|
rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
|
|
1369
1369
|
eurcAddress: null,
|
|
1370
1370
|
usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
|
|
@@ -1409,7 +1409,7 @@ const HyperEVMTestnet = defineChain({
|
|
|
1409
1409
|
},
|
|
1410
1410
|
chainId: 998,
|
|
1411
1411
|
isTestnet: true,
|
|
1412
|
-
explorerUrl: 'https://
|
|
1412
|
+
explorerUrl: 'https://app.hyperliquid-testnet.xyz/explorer/tx/{hash}',
|
|
1413
1413
|
rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
|
|
1414
1414
|
eurcAddress: null,
|
|
1415
1415
|
usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
|
|
@@ -2055,7 +2055,7 @@ const Plume = defineChain({
|
|
|
2055
2055
|
},
|
|
2056
2056
|
forwarderSupported: {
|
|
2057
2057
|
source: true,
|
|
2058
|
-
destination:
|
|
2058
|
+
destination: true,
|
|
2059
2059
|
},
|
|
2060
2060
|
},
|
|
2061
2061
|
kitContracts: {
|
|
@@ -2100,7 +2100,7 @@ const PlumeTestnet = defineChain({
|
|
|
2100
2100
|
},
|
|
2101
2101
|
forwarderSupported: {
|
|
2102
2102
|
source: true,
|
|
2103
|
-
destination:
|
|
2103
|
+
destination: true,
|
|
2104
2104
|
},
|
|
2105
2105
|
},
|
|
2106
2106
|
kitContracts: {
|
|
@@ -2472,7 +2472,7 @@ const Solana = defineChain({
|
|
|
2472
2472
|
},
|
|
2473
2473
|
forwarderSupported: {
|
|
2474
2474
|
source: true,
|
|
2475
|
-
destination:
|
|
2475
|
+
destination: true,
|
|
2476
2476
|
},
|
|
2477
2477
|
},
|
|
2478
2478
|
kitContracts: {
|
|
@@ -2519,7 +2519,7 @@ const SolanaDevnet = defineChain({
|
|
|
2519
2519
|
},
|
|
2520
2520
|
forwarderSupported: {
|
|
2521
2521
|
source: true,
|
|
2522
|
-
destination:
|
|
2522
|
+
destination: true,
|
|
2523
2523
|
},
|
|
2524
2524
|
},
|
|
2525
2525
|
kitContracts: {
|
|
@@ -2878,7 +2878,7 @@ const XDC = defineChain({
|
|
|
2878
2878
|
},
|
|
2879
2879
|
forwarderSupported: {
|
|
2880
2880
|
source: true,
|
|
2881
|
-
destination:
|
|
2881
|
+
destination: true,
|
|
2882
2882
|
},
|
|
2883
2883
|
},
|
|
2884
2884
|
kitContracts: {
|
|
@@ -2922,7 +2922,7 @@ const XDCApothem = defineChain({
|
|
|
2922
2922
|
},
|
|
2923
2923
|
forwarderSupported: {
|
|
2924
2924
|
source: true,
|
|
2925
|
-
destination:
|
|
2925
|
+
destination: true,
|
|
2926
2926
|
},
|
|
2927
2927
|
},
|
|
2928
2928
|
kitContracts: {
|
|
@@ -9502,7 +9502,184 @@ z
|
|
|
9502
9502
|
})
|
|
9503
9503
|
.passthrough();
|
|
9504
9504
|
|
|
9505
|
-
|
|
9505
|
+
/**
|
|
9506
|
+
* Check whether the source adapter supports EIP-5792 atomic batching and
|
|
9507
|
+
* the consumer has not explicitly opted out via `config.batchTransactions`.
|
|
9508
|
+
*
|
|
9509
|
+
* @param params - Bridge parameters (used for adapter and config access).
|
|
9510
|
+
* @returns `true` when batched execution should be attempted.
|
|
9511
|
+
*
|
|
9512
|
+
* @example
|
|
9513
|
+
* ```typescript
|
|
9514
|
+
* const useBatched = await shouldUseBatchedExecution(params)
|
|
9515
|
+
* if (useBatched) {
|
|
9516
|
+
* // take the batched approve + burn path
|
|
9517
|
+
* }
|
|
9518
|
+
* ```
|
|
9519
|
+
*/
|
|
9520
|
+
async function shouldUseBatchedExecution(params) {
|
|
9521
|
+
if (params.config?.batchTransactions === false) {
|
|
9522
|
+
return false;
|
|
9523
|
+
}
|
|
9524
|
+
const { chain } = params.source;
|
|
9525
|
+
if (chain.type !== 'evm') {
|
|
9526
|
+
return false;
|
|
9527
|
+
}
|
|
9528
|
+
const adapter = params.source
|
|
9529
|
+
.adapter;
|
|
9530
|
+
if (typeof adapter.supportsAtomicBatch !== 'function' ||
|
|
9531
|
+
typeof adapter.batchExecute !== 'function') {
|
|
9532
|
+
return false;
|
|
9533
|
+
}
|
|
9534
|
+
try {
|
|
9535
|
+
return await adapter.supportsAtomicBatch(chain);
|
|
9536
|
+
}
|
|
9537
|
+
catch {
|
|
9538
|
+
return false;
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
/**
|
|
9542
|
+
* Execute the approve and burn steps as a single EIP-5792 batched call.
|
|
9543
|
+
*
|
|
9544
|
+
* Prepare both `PreparedChainRequest` objects upfront, extract their raw
|
|
9545
|
+
* call data via `getCallData()`, submit both via `adapter.batchExecute()`,
|
|
9546
|
+
* then map the individual receipts back to standard {@link BridgeStep}
|
|
9547
|
+
* objects so downstream consumers (event callbacks, result tracking) are
|
|
9548
|
+
* unaffected.
|
|
9549
|
+
*
|
|
9550
|
+
* @param params - The CCTP v2 bridge parameters.
|
|
9551
|
+
* @param provider - The CCTP v2 bridging provider.
|
|
9552
|
+
* @returns Approve and burn steps with a shared context containing the burn tx hash.
|
|
9553
|
+
* @throws {@link KitError} when the source chain is not EVM.
|
|
9554
|
+
* @throws {@link KitError} when calldata extraction (`getCallData`) is not supported
|
|
9555
|
+
* by the prepared requests.
|
|
9556
|
+
* @remarks
|
|
9557
|
+
* Errors that occur after the batch has been submitted to the wallet
|
|
9558
|
+
* (e.g. polling timeout, insufficient receipts) are **not thrown** — they
|
|
9559
|
+
* are captured as `state: 'error'` on the returned steps to prevent
|
|
9560
|
+
* accidental double-spend on retry.
|
|
9561
|
+
*
|
|
9562
|
+
* @example
|
|
9563
|
+
* ```typescript
|
|
9564
|
+
* const { approveStep, burnStep, context } = await executeBatchedApproveAndBurn(
|
|
9565
|
+
* params,
|
|
9566
|
+
* provider,
|
|
9567
|
+
* )
|
|
9568
|
+
* result.steps.push(approveStep, burnStep)
|
|
9569
|
+
* ```
|
|
9570
|
+
*/
|
|
9571
|
+
async function executeBatchedApproveAndBurn(params, provider) {
|
|
9572
|
+
// Double-unknown cast: Adapter<TFrom> has no structural overlap with
|
|
9573
|
+
// BatchCapableAdapter (a duck-typed interface for EIP-5792 methods).
|
|
9574
|
+
// A direct cast fails because TS cannot prove the intersection; the
|
|
9575
|
+
// runtime capability check below guards against unsupported adapters.
|
|
9576
|
+
const adapter = params.source.adapter;
|
|
9577
|
+
const sourceChain = params.source.chain;
|
|
9578
|
+
if (sourceChain.type !== 'evm') {
|
|
9579
|
+
throw new KitError({
|
|
9580
|
+
...InputError.INVALID_CHAIN,
|
|
9581
|
+
recoverability: 'FATAL',
|
|
9582
|
+
message: 'Batched execution is only supported on EVM chains.',
|
|
9583
|
+
});
|
|
9584
|
+
}
|
|
9585
|
+
const chain = sourceChain;
|
|
9586
|
+
// customFee.value is in base units (integer string) at this point.
|
|
9587
|
+
const customFee = BigInt(params.config?.customFee?.value ?? '0');
|
|
9588
|
+
const amountBigInt = BigInt(params.amount);
|
|
9589
|
+
const approvalAmount = (amountBigInt + customFee).toString();
|
|
9590
|
+
const [approveRequest, burnRequest] = await Promise.all([
|
|
9591
|
+
provider.approve(params.source, approvalAmount),
|
|
9592
|
+
provider.burn(params),
|
|
9593
|
+
]);
|
|
9594
|
+
if (approveRequest.type !== 'evm' ||
|
|
9595
|
+
burnRequest.type !== 'evm' ||
|
|
9596
|
+
!approveRequest.getCallData ||
|
|
9597
|
+
!burnRequest.getCallData) {
|
|
9598
|
+
throw new KitError({
|
|
9599
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
9600
|
+
recoverability: 'FATAL',
|
|
9601
|
+
message: 'Batched execution requires EVM prepared requests with getCallData() support.',
|
|
9602
|
+
});
|
|
9603
|
+
}
|
|
9604
|
+
const approveCallData = approveRequest.getCallData();
|
|
9605
|
+
const burnCallData = burnRequest.getCallData();
|
|
9606
|
+
// batchExecute may throw before submission (wallet declined) but never
|
|
9607
|
+
// after — post-submission errors are returned as empty receipts.
|
|
9608
|
+
const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
|
|
9609
|
+
const approveReceipt = batchResult.receipts[0];
|
|
9610
|
+
const burnReceipt = batchResult.receipts[1];
|
|
9611
|
+
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
|
|
9612
|
+
const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
|
|
9613
|
+
if (burnStep.state !== 'error' && !burnStep.txHash) {
|
|
9614
|
+
burnStep.state = 'error';
|
|
9615
|
+
burnStep.errorMessage =
|
|
9616
|
+
'Batched burn step completed but no transaction hash was returned.';
|
|
9617
|
+
}
|
|
9618
|
+
const context = { burnTxHash: burnStep.txHash ?? '' };
|
|
9619
|
+
return { approveStep, burnStep, context };
|
|
9620
|
+
}
|
|
9621
|
+
/**
|
|
9622
|
+
* Build a {@link BridgeStep} from a single receipt within a batch.
|
|
9623
|
+
*
|
|
9624
|
+
* Map the raw receipt from `batchExecute` into a standard `BridgeStep`,
|
|
9625
|
+
* waiting for on-chain confirmation via `adapter.waitForTransaction`.
|
|
9626
|
+
* All errors are captured on the step (never thrown) so the caller
|
|
9627
|
+
* can inspect each step independently.
|
|
9628
|
+
*
|
|
9629
|
+
* @param name - Human-readable step name (e.g. `'approve'`, `'burn'`).
|
|
9630
|
+
* @param receipt - Per-call receipt from `batchExecute`, or `undefined`
|
|
9631
|
+
* when the wallet returned fewer receipts than submitted calls.
|
|
9632
|
+
* @param batchId - Wallet-assigned batch identifier.
|
|
9633
|
+
* @param adapter - The batch-capable adapter (used for confirmation).
|
|
9634
|
+
* @param chain - The EVM chain the batch was executed on.
|
|
9635
|
+
* @returns A fully-populated bridge step with state, tx hash and explorer URL.
|
|
9636
|
+
*
|
|
9637
|
+
* @internal
|
|
9638
|
+
*/
|
|
9639
|
+
async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
|
|
9640
|
+
const step = {
|
|
9641
|
+
name,
|
|
9642
|
+
state: 'pending',
|
|
9643
|
+
batched: true,
|
|
9644
|
+
batchId,
|
|
9645
|
+
};
|
|
9646
|
+
if (!receipt) {
|
|
9647
|
+
step.state = 'error';
|
|
9648
|
+
step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
|
|
9649
|
+
return step;
|
|
9650
|
+
}
|
|
9651
|
+
step.txHash = receipt.txHash;
|
|
9652
|
+
if (receipt.txHash) {
|
|
9653
|
+
step.explorerUrl = buildExplorerUrl(chain, receipt.txHash);
|
|
9654
|
+
}
|
|
9655
|
+
if (receipt.status !== 'success') {
|
|
9656
|
+
step.state = 'error';
|
|
9657
|
+
step.errorMessage = `${name} call failed within batch ${batchId}.`;
|
|
9658
|
+
return step;
|
|
9659
|
+
}
|
|
9660
|
+
if (!receipt.txHash) {
|
|
9661
|
+
step.state = 'error';
|
|
9662
|
+
step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
|
|
9663
|
+
return step;
|
|
9664
|
+
}
|
|
9665
|
+
try {
|
|
9666
|
+
const transaction = await adapter.waitForTransaction(receipt.txHash, { confirmations: 1 }, chain);
|
|
9667
|
+
step.state = transaction.blockNumber === undefined ? 'error' : 'success';
|
|
9668
|
+
step.data = transaction;
|
|
9669
|
+
if (transaction.blockNumber === undefined) {
|
|
9670
|
+
step.errorMessage = 'Transaction was not confirmed on-chain.';
|
|
9671
|
+
}
|
|
9672
|
+
}
|
|
9673
|
+
catch (err) {
|
|
9674
|
+
step.state = 'error';
|
|
9675
|
+
step.error = err;
|
|
9676
|
+
step.errorMessage =
|
|
9677
|
+
err instanceof Error ? err.message : 'Unknown error during confirmation.';
|
|
9678
|
+
}
|
|
9679
|
+
return step;
|
|
9680
|
+
}
|
|
9681
|
+
|
|
9682
|
+
var version = "1.6.1";
|
|
9506
9683
|
var pkg = {
|
|
9507
9684
|
version: version};
|
|
9508
9685
|
|
|
@@ -9531,6 +9708,67 @@ function resolveBridgeInvocation(invocationMeta) {
|
|
|
9531
9708
|
};
|
|
9532
9709
|
return extendInvocationContext(resolveInvocationContext(invocationMeta, defaults), BRIDGE_CALLER);
|
|
9533
9710
|
}
|
|
9711
|
+
/**
|
|
9712
|
+
* Execute the batched approve + burn path via EIP-5792.
|
|
9713
|
+
*
|
|
9714
|
+
* Mutate `result` with step data and error state as needed. Return the
|
|
9715
|
+
* batch context on success, or `undefined` when the batch failed (in
|
|
9716
|
+
* which case `result.state` is set to `'error'`).
|
|
9717
|
+
*
|
|
9718
|
+
* @internal
|
|
9719
|
+
* @param params - Bridge parameters (read-only).
|
|
9720
|
+
* @param provider - The CCTP v2 bridging provider (read-only).
|
|
9721
|
+
* @param result - Bridge result object — **mutated in place** with step
|
|
9722
|
+
* data and, on failure, `state: 'error'` plus an `error` payload.
|
|
9723
|
+
* @param invocation - Invocation context for telemetry (read-only).
|
|
9724
|
+
* @returns The step context on success, or `undefined` when the batch failed.
|
|
9725
|
+
*/
|
|
9726
|
+
async function executeBatchedPath(params, provider, result, invocation) {
|
|
9727
|
+
// IMPORTANT: once executeBatchedApproveAndBurn is called, we NEVER
|
|
9728
|
+
// fall back to sequential. The wallet may have already signed &
|
|
9729
|
+
// submitted the batch; retrying as individual txs would double-spend.
|
|
9730
|
+
try {
|
|
9731
|
+
const { approveStep, burnStep, context: batchContext, } = await executeBatchedApproveAndBurn(params, provider);
|
|
9732
|
+
for (const step of [approveStep, burnStep]) {
|
|
9733
|
+
const stepName = step.name;
|
|
9734
|
+
if (step.state === 'error') {
|
|
9735
|
+
ensureStepErrorMessage(step.name, step);
|
|
9736
|
+
result.steps.push(step);
|
|
9737
|
+
result.state = 'error';
|
|
9738
|
+
dispatchStepEvent(stepName, step, provider, invocation);
|
|
9739
|
+
return undefined;
|
|
9740
|
+
}
|
|
9741
|
+
dispatchStepEvent(stepName, step, provider, invocation);
|
|
9742
|
+
result.steps.push(step);
|
|
9743
|
+
}
|
|
9744
|
+
return batchContext;
|
|
9745
|
+
}
|
|
9746
|
+
catch (error_) {
|
|
9747
|
+
// Only handles pre-submission failures (prepare rejected, wallet
|
|
9748
|
+
// declined, etc.). batchExecute never throws after sendCalls succeeds.
|
|
9749
|
+
result.state = 'error';
|
|
9750
|
+
result.steps.push({
|
|
9751
|
+
name: 'batch',
|
|
9752
|
+
state: 'error',
|
|
9753
|
+
batched: true,
|
|
9754
|
+
error: error_,
|
|
9755
|
+
errorMessage: error_ instanceof Error
|
|
9756
|
+
? error_.message
|
|
9757
|
+
: 'Batched approve + burn failed.',
|
|
9758
|
+
});
|
|
9759
|
+
return undefined;
|
|
9760
|
+
}
|
|
9761
|
+
}
|
|
9762
|
+
/**
|
|
9763
|
+
* Ensure `step.errorMessage` is populated when an error object exists.
|
|
9764
|
+
*
|
|
9765
|
+
* @internal
|
|
9766
|
+
*/
|
|
9767
|
+
function ensureStepErrorMessage(name, step) {
|
|
9768
|
+
if (!step.errorMessage && step.error) {
|
|
9769
|
+
step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
|
|
9770
|
+
}
|
|
9771
|
+
}
|
|
9534
9772
|
/**
|
|
9535
9773
|
* Execute a cross-chain USDC bridge using the CCTP v2 protocol.
|
|
9536
9774
|
*
|
|
@@ -9564,9 +9802,7 @@ function resolveBridgeInvocation(invocationMeta) {
|
|
|
9564
9802
|
* ```
|
|
9565
9803
|
*/
|
|
9566
9804
|
async function bridge(params, provider) {
|
|
9567
|
-
// Check if forwarder is enabled (on destination)
|
|
9568
9805
|
const useForwarder = params.destination.useForwarder === true;
|
|
9569
|
-
// Resolve invocation metadata to full context for event dispatching
|
|
9570
9806
|
const invocation = resolveBridgeInvocation(params.invocationMeta);
|
|
9571
9807
|
const result = {
|
|
9572
9808
|
state: 'pending',
|
|
@@ -9579,11 +9815,9 @@ async function bridge(params, provider) {
|
|
|
9579
9815
|
destination: {
|
|
9580
9816
|
address: params.destination.address,
|
|
9581
9817
|
chain: params.destination.chain,
|
|
9582
|
-
// Preserve recipientAddress
|
|
9583
9818
|
...(params.destination.recipientAddress && {
|
|
9584
9819
|
recipientAddress: params.destination.recipientAddress,
|
|
9585
9820
|
}),
|
|
9586
|
-
// Preserve useForwarder
|
|
9587
9821
|
...(useForwarder && {
|
|
9588
9822
|
useForwarder: true,
|
|
9589
9823
|
}),
|
|
@@ -9592,29 +9826,38 @@ async function bridge(params, provider) {
|
|
|
9592
9826
|
config: params.config,
|
|
9593
9827
|
provider: provider.name,
|
|
9594
9828
|
};
|
|
9595
|
-
// Context shared between steps
|
|
9596
9829
|
let context = undefined;
|
|
9597
|
-
|
|
9830
|
+
let useBatched = false;
|
|
9831
|
+
try {
|
|
9832
|
+
useBatched = await shouldUseBatchedExecution(params);
|
|
9833
|
+
}
|
|
9834
|
+
catch {
|
|
9835
|
+
// Silently fall back to sequential
|
|
9836
|
+
}
|
|
9598
9837
|
const executors = createStepExecutors(useForwarder);
|
|
9599
|
-
|
|
9600
|
-
|
|
9838
|
+
if (useBatched) {
|
|
9839
|
+
const batchContext = await executeBatchedPath(params, provider, result, invocation);
|
|
9840
|
+
if (result.state === 'error') {
|
|
9841
|
+
return result;
|
|
9842
|
+
}
|
|
9843
|
+
context = batchContext;
|
|
9844
|
+
}
|
|
9845
|
+
const stepsToRun = useBatched
|
|
9846
|
+
? executors.filter(({ name }) => name !== 'approve' && name !== 'burn')
|
|
9847
|
+
: executors;
|
|
9848
|
+
for (const { name, executor, updateContext } of stepsToRun) {
|
|
9601
9849
|
try {
|
|
9602
9850
|
const step = await executor(params, provider, context);
|
|
9603
9851
|
if (step.state === 'error') {
|
|
9604
|
-
|
|
9605
|
-
if (!step.errorMessage && step.error) {
|
|
9606
|
-
step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
|
|
9607
|
-
}
|
|
9852
|
+
ensureStepErrorMessage(name, step);
|
|
9608
9853
|
result.steps.push(step);
|
|
9609
9854
|
result.state = 'error';
|
|
9610
|
-
// Dispatch event even for error steps
|
|
9611
9855
|
dispatchStepEvent(name, step, provider, invocation);
|
|
9612
9856
|
return result;
|
|
9613
9857
|
}
|
|
9614
|
-
// Merge new context with existing context to preserve data from previous steps
|
|
9615
9858
|
const newContext = updateContext?.(step);
|
|
9616
9859
|
if (newContext) {
|
|
9617
|
-
context = { ...
|
|
9860
|
+
context = { ...context, ...newContext };
|
|
9618
9861
|
}
|
|
9619
9862
|
dispatchStepEvent(name, step, provider, invocation);
|
|
9620
9863
|
result.steps.push(step);
|