@circle-fin/provider-cctp-v2 1.6.0 → 1.6.2

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 (4) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/index.cjs +1026 -14
  3. package/index.mjs +1026 -14
  4. package/package.json +6 -2
package/index.mjs CHANGED
@@ -182,8 +182,9 @@ var Blockchain;
182
182
  /**
183
183
  * Enum representing chains that support same-chain swaps through the Swap Kit.
184
184
  *
185
- * Unlike the full {@link Blockchain} enum, SwapChain includes only mainnet
186
- * networks where adapter contracts are deployed (CCTPv2 support).
185
+ * Unlike the full {@link Blockchain} enum, SwapChain includes mainnet
186
+ * networks and explicitly whitelisted testnets (e.g., {@link Arc_Testnet})
187
+ * where adapter contracts are deployed (CCTPv2 support).
187
188
  *
188
189
  * Dynamic validation via {@link isSwapSupportedChain} ensures chains
189
190
  * automatically work when adapter contracts and supported tokens are deployed.
@@ -228,6 +229,8 @@ var SwapChain;
228
229
  SwapChain["XDC"] = "XDC";
229
230
  SwapChain["HyperEVM"] = "HyperEVM";
230
231
  SwapChain["Monad"] = "Monad";
232
+ // Testnet chains with swap support
233
+ SwapChain["Arc_Testnet"] = "Arc_Testnet";
231
234
  })(SwapChain || (SwapChain = {}));
232
235
  // -----------------------------------------------------------------------------
233
236
  // Bridge Chain Enum (CCTPv2 Supported Chains)
@@ -324,6 +327,31 @@ var BridgeChain;
324
327
  BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
325
328
  BridgeChain["XDC_Apothem"] = "XDC_Apothem";
326
329
  })(BridgeChain || (BridgeChain = {}));
330
+ // -----------------------------------------------------------------------------
331
+ // Earn Chain Enum
332
+ // -----------------------------------------------------------------------------
333
+ /**
334
+ * Enumeration of blockchains that support earn (vault deposit/withdraw)
335
+ * operations through the Earn Kit.
336
+ *
337
+ * Currently only Ethereum mainnet is supported. Additional chains
338
+ * will be added as vault protocol support expands.
339
+ *
340
+ * @example
341
+ * ```typescript
342
+ * import { EarnChain } from '@core/chains'
343
+ *
344
+ * const result = await earnKit.deposit({
345
+ * from: { adapter, chain: EarnChain.Ethereum },
346
+ * vaultAddress: '0x...',
347
+ * amount: '100',
348
+ * })
349
+ * ```
350
+ */
351
+ var EarnChain;
352
+ (function (EarnChain) {
353
+ EarnChain["Ethereum"] = "Ethereum";
354
+ })(EarnChain || (EarnChain = {}));
327
355
 
328
356
  /**
329
357
  * Helper function to define a chain with proper TypeScript typing.
@@ -626,6 +654,14 @@ const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0'
626
654
  * on EVM-compatible chains. Use this address for mainnet adapter integrations.
627
655
  */
628
656
  const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845';
657
+ /**
658
+ * The adapter contract address for EVM testnet networks.
659
+ *
660
+ * This contract serves as an adapter for integrating with various protocols
661
+ * on EVM-compatible testnet chains. Use this address for testnet adapter
662
+ * integrations (e.g., Arc Testnet).
663
+ */
664
+ const ADAPTER_CONTRACT_EVM_TESTNET = '0xBBD70b01a1CAbc96d5b7b129Ae1AAabdf50dd40b';
629
665
 
630
666
  /**
631
667
  * Arc Testnet chain definition
@@ -674,6 +710,7 @@ const ArcTestnet = defineChain({
674
710
  },
675
711
  kitContracts: {
676
712
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
713
+ adapter: ADAPTER_CONTRACT_EVM_TESTNET,
677
714
  },
678
715
  });
679
716
 
@@ -1364,7 +1401,7 @@ const HyperEVM = defineChain({
1364
1401
  },
1365
1402
  chainId: 999,
1366
1403
  isTestnet: false,
1367
- explorerUrl: 'https://app.hyperliquid.xyz/explorer/tx/{hash}',
1404
+ explorerUrl: 'https://hyperevmscan.io/tx/{hash}',
1368
1405
  rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
1369
1406
  eurcAddress: null,
1370
1407
  usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
@@ -2472,7 +2509,7 @@ const Solana = defineChain({
2472
2509
  },
2473
2510
  forwarderSupported: {
2474
2511
  source: true,
2475
- destination: false,
2512
+ destination: true,
2476
2513
  },
2477
2514
  },
2478
2515
  kitContracts: {
@@ -2519,7 +2556,7 @@ const SolanaDevnet = defineChain({
2519
2556
  },
2520
2557
  forwarderSupported: {
2521
2558
  source: true,
2522
- destination: false,
2559
+ destination: true,
2523
2560
  },
2524
2561
  },
2525
2562
  kitContracts: {
@@ -3329,6 +3366,41 @@ z.union([
3329
3366
  message: `Chain "${chainDef.name}" (${chainDef.chain}) is not supported for bridging. Only chains in the BridgeChain enum support CCTPv2 bridging.`,
3330
3367
  })),
3331
3368
  ]);
3369
+ /**
3370
+ * Zod schema for validating earn-specific chain identifiers.
3371
+ *
3372
+ * Validate that the provided chain is supported for earn (vault
3373
+ * deposit/withdraw) operations. Currently only Ethereum is
3374
+ * supported.
3375
+ *
3376
+ * Accept an EarnChain enum value, a matching string literal, or
3377
+ * a ChainDefinition for a supported chain.
3378
+ *
3379
+ * @example
3380
+ * ```typescript
3381
+ * import { earnChainIdentifierSchema } from '@core/chains'
3382
+ * import { EarnChain, Ethereum } from '@core/chains'
3383
+ *
3384
+ * // Valid
3385
+ * earnChainIdentifierSchema.parse(EarnChain.Ethereum)
3386
+ * earnChainIdentifierSchema.parse('Ethereum')
3387
+ * earnChainIdentifierSchema.parse(Ethereum)
3388
+ *
3389
+ * // Invalid (throws ZodError)
3390
+ * earnChainIdentifierSchema.parse('Solana')
3391
+ * ```
3392
+ */
3393
+ z.union([
3394
+ z.string().refine((val) => val in EarnChain, (val) => ({
3395
+ message: `"${val}" is not a supported earn chain. ` +
3396
+ `Supported chains: ${Object.values(EarnChain).join(', ')}`,
3397
+ })),
3398
+ z.nativeEnum(EarnChain),
3399
+ chainDefinitionSchema$2.refine((chain) => chain.chain in EarnChain, (chain) => ({
3400
+ message: `"${chain.chain}" is not a supported earn chain. ` +
3401
+ `Supported chains: ${Object.values(EarnChain).join(', ')}`,
3402
+ })),
3403
+ ]);
3332
3404
 
3333
3405
  /**
3334
3406
  * @packageDocumentation
@@ -4205,6 +4277,12 @@ const InputError = {
4205
4277
  name: 'INPUT_UNSUPPORTED_ACTION',
4206
4278
  type: 'INPUT',
4207
4279
  },
4280
+ /** No route satisfies the slippage or minimum-output constraint */
4281
+ SLIPPAGE_CONSTRAINT_NOT_MET: {
4282
+ code: 1009,
4283
+ name: 'INPUT_SLIPPAGE_CONSTRAINT_NOT_MET',
4284
+ type: 'INPUT',
4285
+ },
4208
4286
  /** General validation failure for complex validation rules */
4209
4287
  VALIDATION_FAILED: {
4210
4288
  code: 1098,
@@ -4268,11 +4346,61 @@ const BalanceError = {
4268
4346
  * ```
4269
4347
  */
4270
4348
  const OnchainError = {
4349
+ /** Transaction reverted on-chain after execution */
4350
+ TRANSACTION_REVERTED: {
4351
+ code: 5001,
4352
+ name: 'ONCHAIN_TRANSACTION_REVERTED',
4353
+ type: 'ONCHAIN',
4354
+ },
4271
4355
  /** Pre-flight transaction simulation failed */
4272
4356
  SIMULATION_FAILED: {
4273
4357
  code: 5002,
4274
4358
  name: 'ONCHAIN_SIMULATION_FAILED',
4275
4359
  type: 'ONCHAIN',
4360
+ },
4361
+ /** Transaction ran out of gas during execution */
4362
+ OUT_OF_GAS: {
4363
+ code: 5003,
4364
+ name: 'ONCHAIN_OUT_OF_GAS',
4365
+ type: 'ONCHAIN',
4366
+ },
4367
+ /** Transaction size exceeds blockchain limit */
4368
+ TRANSACTION_TOO_LARGE: {
4369
+ code: 5005,
4370
+ name: 'ONCHAIN_TRANSACTION_TOO_LARGE',
4371
+ type: 'ONCHAIN',
4372
+ },
4373
+ /** Unknown blockchain error that cannot be categorized */
4374
+ UNKNOWN_BLOCKCHAIN_ERROR: {
4375
+ code: 5099,
4376
+ name: 'ONCHAIN_UNKNOWN_BLOCKCHAIN_ERROR',
4377
+ type: 'ONCHAIN',
4378
+ },
4379
+ };
4380
+ /**
4381
+ * Standardized error definitions for RPC type errors.
4382
+ *
4383
+ * RPC errors occur when communicating with blockchain RPC providers,
4384
+ * including endpoint failures, invalid responses, and provider-specific issues.
4385
+ *
4386
+ * @example
4387
+ * ```typescript
4388
+ * import { RpcError } from '@core/errors'
4389
+ *
4390
+ * const error = new KitError({
4391
+ * ...RpcError.ENDPOINT_ERROR,
4392
+ * recoverability: 'RETRYABLE',
4393
+ * message: 'RPC endpoint unavailable on Ethereum',
4394
+ * cause: { trace: { endpoint: 'https://mainnet.infura.io' } }
4395
+ * })
4396
+ * ```
4397
+ */
4398
+ const RpcError = {
4399
+ /** RPC endpoint returned error or is unavailable */
4400
+ ENDPOINT_ERROR: {
4401
+ code: 4001,
4402
+ name: 'RPC_ENDPOINT_ERROR',
4403
+ type: 'RPC',
4276
4404
  }};
4277
4405
  /**
4278
4406
  * Standardized error definitions for NETWORK type errors.
@@ -4293,6 +4421,12 @@ const OnchainError = {
4293
4421
  * ```
4294
4422
  */
4295
4423
  const NetworkError = {
4424
+ /** Network connection failed or unreachable */
4425
+ CONNECTION_FAILED: {
4426
+ code: 3001,
4427
+ name: 'NETWORK_CONNECTION_FAILED',
4428
+ type: 'NETWORK',
4429
+ },
4296
4430
  /** Circle relayer failed to process the forwarding/mint transaction */
4297
4431
  RELAYER_FORWARD_FAILED: {
4298
4432
  code: 3003,
@@ -4653,6 +4787,687 @@ function createSimulationFailedError(chain, reason, trace) {
4653
4787
  },
4654
4788
  });
4655
4789
  }
4790
+ /**
4791
+ * Creates error for transaction reverts.
4792
+ *
4793
+ * This error is thrown when a transaction is submitted and confirmed
4794
+ * but reverts on-chain. The error is FATAL as it indicates the
4795
+ * transaction executed but failed.
4796
+ *
4797
+ * @param chain - The blockchain network where the transaction reverted
4798
+ * @param reason - The reason for the revert (e.g., revert message)
4799
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
4800
+ * @param txHash - The transaction hash if the transaction was submitted (optional)
4801
+ * @param explorerUrl - The block explorer URL for the transaction (optional)
4802
+ * @returns KitError with transaction revert details
4803
+ *
4804
+ * @example
4805
+ * ```typescript
4806
+ * import { createTransactionRevertedError } from '@core/errors'
4807
+ *
4808
+ * throw createTransactionRevertedError('Base', 'Slippage exceeded')
4809
+ * // Message: "Transaction reverted on Base: Slippage exceeded"
4810
+ * ```
4811
+ *
4812
+ * @example
4813
+ * ```typescript
4814
+ * // With trace context and transaction details for debugging
4815
+ * throw createTransactionRevertedError(
4816
+ * 'Base',
4817
+ * 'Slippage exceeded',
4818
+ * { rawError: error },
4819
+ * '0x123...',
4820
+ * 'https://basescan.org/tx/0x123...'
4821
+ * )
4822
+ * ```
4823
+ */
4824
+ function createTransactionRevertedError(chain, reason, trace, txHash, explorerUrl) {
4825
+ return new KitError({
4826
+ ...OnchainError.TRANSACTION_REVERTED,
4827
+ recoverability: 'FATAL',
4828
+ message: `Transaction reverted on ${chain}: ${reason}`,
4829
+ cause: {
4830
+ trace: {
4831
+ ...trace,
4832
+ chain,
4833
+ reason,
4834
+ ...(txHash !== undefined && txHash !== '' && { txHash }),
4835
+ ...(explorerUrl !== undefined && explorerUrl !== '' && { explorerUrl }),
4836
+ },
4837
+ },
4838
+ });
4839
+ }
4840
+ /**
4841
+ * Creates error for out of gas failures.
4842
+ *
4843
+ * This error is thrown when a transaction runs out of gas during execution.
4844
+ * The error is FATAL as it requires adjusting gas limits or transaction logic.
4845
+ *
4846
+ * @param chain - The blockchain network where the transaction ran out of gas
4847
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
4848
+ * @param txHash - The transaction hash if the transaction was submitted (optional)
4849
+ * @param explorerUrl - The block explorer URL for the transaction (optional)
4850
+ * @returns KitError with out of gas details
4851
+ *
4852
+ * @example
4853
+ * ```typescript
4854
+ * import { createOutOfGasError } from '@core/errors'
4855
+ *
4856
+ * throw createOutOfGasError('Polygon')
4857
+ * // Message: "Transaction ran out of gas on Polygon"
4858
+ * ```
4859
+ *
4860
+ * @example
4861
+ * ```typescript
4862
+ * // With trace context for debugging
4863
+ * throw createOutOfGasError('Polygon', {
4864
+ * gasUsed: '50000',
4865
+ * gasLimit: '45000',
4866
+ * },
4867
+ * '0xabc...',
4868
+ * 'https://polygonscan.com/tx/0xabc...'
4869
+ * )
4870
+ * ```
4871
+ */
4872
+ function createOutOfGasError(chain, trace, txHash, explorerUrl) {
4873
+ return new KitError({
4874
+ ...OnchainError.OUT_OF_GAS,
4875
+ recoverability: 'FATAL',
4876
+ message: `Transaction ran out of gas on ${chain}`,
4877
+ cause: {
4878
+ trace: {
4879
+ ...trace,
4880
+ chain,
4881
+ ...(txHash !== undefined && txHash !== '' && { txHash }),
4882
+ ...(explorerUrl !== undefined && explorerUrl !== '' && { explorerUrl }),
4883
+ },
4884
+ },
4885
+ });
4886
+ }
4887
+ /**
4888
+ * Creates error for transaction size exceeding blockchain limits.
4889
+ *
4890
+ * This error is thrown when a transaction's serialized size exceeds
4891
+ * the blockchain's maximum transaction size limit. The error is FATAL
4892
+ * as it requires reducing the transaction size (e.g., fewer instructions,
4893
+ * smaller data payloads, or splitting into multiple transactions).
4894
+ *
4895
+ * Common on Solana where transactions have strict size limits (e.g., max 1232 bytes).
4896
+ *
4897
+ * @param chain - The blockchain network where the size limit was exceeded
4898
+ * @param rawError - The original error from the underlying system (optional)
4899
+ * @returns KitError with transaction size exceeded details
4900
+ *
4901
+ * @example
4902
+ * ```typescript
4903
+ * import { createTransactionTooLargeError } from '@core/errors'
4904
+ *
4905
+ * throw createTransactionTooLargeError('Solana')
4906
+ * // Message: "Transaction size exceeds limit on Solana"
4907
+ * ```
4908
+ *
4909
+ * @example
4910
+ * ```typescript
4911
+ * import { createTransactionTooLargeError } from '@core/errors'
4912
+ *
4913
+ * // With raw error containing size details
4914
+ * throw createTransactionTooLargeError(
4915
+ * 'Solana',
4916
+ * new Error('transaction too large: max: encoded/raw 1644/1232')
4917
+ * )
4918
+ * // Error includes size information in cause.trace
4919
+ * ```
4920
+ */
4921
+ function createTransactionTooLargeError(chain, rawError) {
4922
+ return new KitError({
4923
+ ...OnchainError.TRANSACTION_TOO_LARGE,
4924
+ recoverability: 'FATAL',
4925
+ message: `Transaction size exceeds limit on ${chain}`,
4926
+ cause: {
4927
+ trace: {
4928
+ chain,
4929
+ rawError,
4930
+ },
4931
+ },
4932
+ });
4933
+ }
4934
+ /**
4935
+ * Creates error for unknown blockchain errors that cannot be categorized.
4936
+ *
4937
+ * This error is used as a fallback when a blockchain error doesn't match
4938
+ * any known patterns (balance, simulation, gas, network, RPC, etc.).
4939
+ * The error is FATAL as we cannot determine if it's retriable without
4940
+ * understanding the underlying cause.
4941
+ *
4942
+ * The original error message and context are preserved in the trace for
4943
+ * debugging purposes.
4944
+ *
4945
+ * @param chain - The blockchain network where the error occurred
4946
+ * @param originalMessage - The original error message from the blockchain
4947
+ * @param rawError - The original error object from the underlying system (optional)
4948
+ * @returns KitError with unknown blockchain error details
4949
+ *
4950
+ * @example
4951
+ * ```typescript
4952
+ * import { createUnknownBlockchainError } from '@core/errors'
4953
+ *
4954
+ * throw createUnknownBlockchainError('Ethereum', 'Unknown protocol error')
4955
+ * // Message: "Unknown blockchain error on Ethereum: Unknown protocol error"
4956
+ * ```
4957
+ *
4958
+ * @example
4959
+ * ```typescript
4960
+ * import { createUnknownBlockchainError } from '@core/errors'
4961
+ *
4962
+ * // With raw error for debugging
4963
+ * const rawError = new Error('Unexpected blockchain state')
4964
+ * throw createUnknownBlockchainError('Solana', rawError.message, rawError)
4965
+ * // Error preserves full context in cause.trace
4966
+ * ```
4967
+ */
4968
+ function createUnknownBlockchainError(chain, originalMessage, rawError) {
4969
+ const errorMessage = originalMessage
4970
+ ? 'Unknown blockchain error on ' + chain + ': ' + originalMessage
4971
+ : 'Unknown blockchain error on ' + chain;
4972
+ return new KitError({
4973
+ ...OnchainError.UNKNOWN_BLOCKCHAIN_ERROR,
4974
+ recoverability: 'FATAL',
4975
+ message: errorMessage,
4976
+ cause: {
4977
+ trace: {
4978
+ chain,
4979
+ rawError,
4980
+ },
4981
+ },
4982
+ });
4983
+ }
4984
+
4985
+ /**
4986
+ * Create error for RPC endpoint failures.
4987
+ *
4988
+ * Throw when an RPC provider endpoint fails, returns an error,
4989
+ * or is unavailable. The error is RETRYABLE as RPC issues are often temporary.
4990
+ *
4991
+ * @param chain - The blockchain network where the RPC error occurred.
4992
+ * @param trace - Optional trace context (can include rawError and debugging data).
4993
+ * @returns KitError with RPC endpoint error details.
4994
+ *
4995
+ * @example
4996
+ * ```typescript
4997
+ * import { createRpcEndpointError } from '@core/errors'
4998
+ *
4999
+ * throw createRpcEndpointError('Ethereum')
5000
+ * // Message: "RPC endpoint error on Ethereum"
5001
+ * ```
5002
+ *
5003
+ * @example
5004
+ * ```typescript
5005
+ * throw createRpcEndpointError('Ethereum', {
5006
+ * rawError: error,
5007
+ * endpoint: 'https://mainnet.infura.io/v3/...',
5008
+ * statusCode: 429,
5009
+ * })
5010
+ * ```
5011
+ */
5012
+ function createRpcEndpointError(chain, trace) {
5013
+ return new KitError({
5014
+ ...RpcError.ENDPOINT_ERROR,
5015
+ recoverability: 'RETRYABLE',
5016
+ message: `RPC endpoint error on ${chain}`,
5017
+ cause: {
5018
+ trace: {
5019
+ ...trace,
5020
+ chain,
5021
+ },
5022
+ },
5023
+ });
5024
+ }
5025
+
5026
+ /**
5027
+ * Create error for network connection failures.
5028
+ *
5029
+ * Throw when network connectivity issues prevent reaching
5030
+ * the blockchain network. The error is RETRYABLE as network issues are
5031
+ * often temporary.
5032
+ *
5033
+ * @param chain - The blockchain network where the connection failed.
5034
+ * @param trace - Optional trace context (can include rawError and debugging data).
5035
+ * @returns KitError with network connection error details.
5036
+ *
5037
+ * @example
5038
+ * ```typescript
5039
+ * import { createNetworkConnectionError } from '@core/errors'
5040
+ *
5041
+ * throw createNetworkConnectionError('Ethereum')
5042
+ * // Message: "Network connection failed for Ethereum"
5043
+ * ```
5044
+ *
5045
+ * @example
5046
+ * ```typescript
5047
+ * throw createNetworkConnectionError('Ethereum', {
5048
+ * rawError: error,
5049
+ * endpoint: 'https://eth-mainnet.g.alchemy.com/v2/...',
5050
+ * retryCount: 3,
5051
+ * })
5052
+ * ```
5053
+ */
5054
+ function createNetworkConnectionError(chain, trace) {
5055
+ return new KitError({
5056
+ ...NetworkError.CONNECTION_FAILED,
5057
+ recoverability: 'RETRYABLE',
5058
+ message: `Network connection failed for ${chain}`,
5059
+ cause: {
5060
+ trace: {
5061
+ ...trace,
5062
+ chain,
5063
+ },
5064
+ },
5065
+ });
5066
+ }
5067
+
5068
+ /**
5069
+ * Normalizes optional transaction details for error factories.
5070
+ *
5071
+ * Converts empty strings to undefined to ensure clean error traces.
5072
+ *
5073
+ * @param txHash - The transaction hash.
5074
+ * @param explorerUrl - The explorer URL.
5075
+ * @returns Normalized values or undefined.
5076
+ */
5077
+ function normalizeTransactionDetails(txHash, explorerUrl) {
5078
+ const normalized = {};
5079
+ if (txHash !== undefined && txHash !== '') {
5080
+ normalized.txHash = txHash;
5081
+ }
5082
+ if (explorerUrl !== undefined && explorerUrl !== '') {
5083
+ normalized.explorerUrl = explorerUrl;
5084
+ }
5085
+ return normalized;
5086
+ }
5087
+ /**
5088
+ * Pattern that matches on-chain revert reasons caused by slippage or
5089
+ * price constraints. When a revert matches, the error is surfaced as
5090
+ * {@link InputError.SLIPPAGE_CONSTRAINT_NOT_MET} (RETRYABLE) instead of
5091
+ * a generic simulation-failed / transaction-reverted error.
5092
+ *
5093
+ * @internal
5094
+ */
5095
+ const SLIPPAGE_REVERT_PATTERN = /slippage|lower than the minimum required|less than the initial balance|minimum.?output|price.?impact|stop.?limit|InsufficientOutput|InsufficientFinalBalance/i;
5096
+ /**
5097
+ * Handle simulation / execution revert errors, distinguishing slippage
5098
+ * constraint failures from generic reverts and simulation failures.
5099
+ *
5100
+ * @internal
5101
+ */
5102
+ function handleRevertError(msg, error, context) {
5103
+ const reason = extractRevertReason(msg, error) ?? 'Transaction reverted';
5104
+ if (SLIPPAGE_REVERT_PATTERN.test(reason)) {
5105
+ return new KitError({
5106
+ ...InputError.SLIPPAGE_CONSTRAINT_NOT_MET,
5107
+ recoverability: 'RETRYABLE',
5108
+ message: `Transaction on ${context.chain} reverted: "${reason}". ` +
5109
+ 'Try increasing slippageBps or adjusting stopLimit.',
5110
+ cause: { trace: { rawError: error, chain: context.chain, reason } },
5111
+ });
5112
+ }
5113
+ if (/simulation failed/i.test(msg) || context.operation === 'simulation') {
5114
+ return createSimulationFailedError(context.chain, reason, {
5115
+ rawError: error,
5116
+ });
5117
+ }
5118
+ const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
5119
+ return createTransactionRevertedError(context.chain, reason, { rawError: error }, txHash, explorerUrl);
5120
+ }
5121
+ /**
5122
+ * Parses raw blockchain errors into structured KitError instances.
5123
+ *
5124
+ * This function uses pattern matching to identify common blockchain error
5125
+ * types and converts them into standardized KitError format. It handles
5126
+ * errors from viem, ethers, Solana web3.js, and other blockchain libraries.
5127
+ *
5128
+ * The parser recognizes 6 main error patterns:
5129
+ * 1. Insufficient balance errors
5130
+ * 2. Simulation/execution reverted errors
5131
+ * 3. Gas-related errors
5132
+ * 4. Network connectivity errors
5133
+ * 5. RPC provider errors
5134
+ * 6. Transaction size limit errors
5135
+ *
5136
+ * When errors don't match known patterns, the parser uses operation context
5137
+ * (e.g., 'simulation', 'estimateGas') to categorize them appropriately.
5138
+ * Unrecognized errors without context are categorized as UNKNOWN_BLOCKCHAIN_ERROR.
5139
+ *
5140
+ * @param error - The raw error from the blockchain library
5141
+ * @param context - Context information including chain and optional token
5142
+ * @returns A structured KitError instance
5143
+ *
5144
+ * @example
5145
+ * ```typescript
5146
+ * import { parseBlockchainError } from '@core/errors'
5147
+ *
5148
+ * try {
5149
+ * await walletClient.sendTransaction(...)
5150
+ * } catch (error) {
5151
+ * throw parseBlockchainError(error, {
5152
+ * chain: 'Ethereum',
5153
+ * token: 'USDC',
5154
+ * operation: 'transfer'
5155
+ * })
5156
+ * }
5157
+ * ```
5158
+ *
5159
+ * @example
5160
+ * ```typescript
5161
+ * // Minimal usage
5162
+ * try {
5163
+ * await connection.sendTransaction(...)
5164
+ * } catch (error) {
5165
+ * throw parseBlockchainError(error, { chain: 'Solana' })
5166
+ * }
5167
+ * ```
5168
+ *
5169
+ * @example
5170
+ * ```typescript
5171
+ * // With transaction hash and explorer URL
5172
+ * import { buildExplorerUrl } from '@core/utils'
5173
+ * import { Ethereum } from '@core/chains'
5174
+ *
5175
+ * try {
5176
+ * const txHash = await walletClient.sendTransaction(...)
5177
+ * await waitForTransaction(txHash)
5178
+ * } catch (error) {
5179
+ * const explorerUrl = buildExplorerUrl(Ethereum, txHash)
5180
+ * throw parseBlockchainError(error, {
5181
+ * chain: 'Ethereum',
5182
+ * txHash,
5183
+ * explorerUrl
5184
+ * })
5185
+ * }
5186
+ * ```
5187
+ */
5188
+ function parseBlockchainError(error, context) {
5189
+ const msg = extractMessage(error);
5190
+ const token = context.token ?? 'token';
5191
+ // Pattern 1: Insufficient balance errors
5192
+ // Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
5193
+ if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
5194
+ return createInsufficientTokenBalanceError(context.chain, token, {
5195
+ rawError: error,
5196
+ });
5197
+ }
5198
+ // Pattern 2: Simulation and execution reverts
5199
+ // Matches contract revert errors and simulation failures
5200
+ if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
5201
+ return handleRevertError(msg, error, context);
5202
+ }
5203
+ // Pattern 3: Gas-related errors
5204
+ // Matches gas estimation failures and gas exhaustion
5205
+ // Check specific patterns first, then generic "gas" patterns
5206
+ // Gas estimation failures are RPC issues
5207
+ if (/gas estimation failed|cannot estimate gas/i.test(msg)) {
5208
+ return createRpcEndpointError(context.chain, { rawError: error });
5209
+ }
5210
+ // Gas exhaustion errors
5211
+ // Use specific patterns without wildcards to avoid ReDoS
5212
+ if (/out of gas|gas limit exceeded|exceeds block gas limit/i.test(msg)) {
5213
+ const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
5214
+ return createOutOfGasError(context.chain, { rawError: error }, txHash, explorerUrl);
5215
+ }
5216
+ // Insufficient funds for gas
5217
+ if (/insufficient funds for gas/i.test(msg)) {
5218
+ return createInsufficientGasError(context.chain, { rawError: error });
5219
+ }
5220
+ // Pattern 4: Network connectivity errors
5221
+ // Matches connection failures, DNS errors, and timeouts
5222
+ if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
5223
+ return createNetworkConnectionError(context.chain, { rawError: error });
5224
+ }
5225
+ // Pattern 5: RPC provider errors
5226
+ // Matches RPC endpoint errors, invalid responses, rate limits, and
5227
+ // transient JSON-RPC internal errors (e.g. ethers.js "could not coalesce error").
5228
+ // Note: "internal error" alone is too broad — contracts like USDT emit
5229
+ // "An internal error was received" for on-chain assertion failures.
5230
+ // We require JSON-RPC context (codes -32603/-32000) instead.
5231
+ if (/rpc|invalid response|rate limit|too many requests|could not coalesce|no response|server error|json-rpc\s+internal|internal json-rpc|-32603|-32000/i.test(msg)) {
5232
+ return createRpcEndpointError(context.chain, { rawError: error });
5233
+ }
5234
+ // Pattern 6: Transaction size limit errors
5235
+ // Matches transaction size errors from various blockchains:
5236
+ // - Solana: "transaction too large: max: encoded/raw 1644/1232"
5237
+ // - Generic: "transaction exceeds size limit", "max transaction size"
5238
+ // Split into two checks to reduce regex complexity for SonarQube
5239
+ const isSizeError = /transaction (?:too large|exceeds size limit|size exceeds)|encoded\/raw\s+\d+\/\d+|max transaction size/i.test(msg) || /(?:tx|transaction) size is too big:\s*\d+,\s*max:\s*\d+/i.test(msg);
5240
+ if (isSizeError) {
5241
+ return createTransactionTooLargeError(context.chain, error);
5242
+ }
5243
+ // Fallback based on operation context
5244
+ // Gas-related operations are RPC calls
5245
+ if (context.operation === 'estimateGas' ||
5246
+ context.operation === 'getGasPrice') {
5247
+ return createRpcEndpointError(context.chain, { rawError: error });
5248
+ }
5249
+ // Simulation operations that don't match standard patterns should still be
5250
+ // categorized as simulation failures. This handles cases where blockchain
5251
+ // libraries throw generic errors (e.g., "fail", "error") during staticCall
5252
+ // operations without descriptive messages. The operation context is authoritative
5253
+ // about what was being attempted, even when error messages are unclear.
5254
+ //
5255
+ // Example: ethers.js staticCall rejection with message "fail"
5256
+ // - Message doesn't match /execution reverted|simulation failed/
5257
+ // - But context.operation === 'simulation' tells us it was a simulation
5258
+ // - Result: SIMULATION_FAILED (accurate) vs UNKNOWN_BLOCKCHAIN_ERROR (misleading)
5259
+ if (context.operation === 'simulation') {
5260
+ return createSimulationFailedError(context.chain, msg.length > 0 ? msg : 'Simulation failed', { rawError: error });
5261
+ }
5262
+ // Final fallback for truly unrecognized errors
5263
+ // Only errors that don't match any pattern AND lack operation context
5264
+ // are categorized as unknown.
5265
+ return createUnknownBlockchainError(context.chain, msg.length > 0 ? msg : 'Unknown error', { rawError: error });
5266
+ }
5267
+ /**
5268
+ * Type guard to check if error has Solana-Kit structure with logs.
5269
+ *
5270
+ * Checks if the error object contains a context with logs array,
5271
+ * which is the structure used by Solana Kit errors.
5272
+ *
5273
+ * @param error - Unknown error to check
5274
+ * @returns True if error has Solana-Kit logs structure
5275
+ */
5276
+ function hasSolanaLogs(error) {
5277
+ return (error !== null &&
5278
+ typeof error === 'object' &&
5279
+ 'context' in error &&
5280
+ error.context !== null &&
5281
+ typeof error.context === 'object' &&
5282
+ 'logs' in error.context &&
5283
+ Array.isArray(error.context.logs));
5284
+ }
5285
+ /**
5286
+ * Extracts a human-readable error message from various error types.
5287
+ *
5288
+ * Handles Error objects, string errors, objects with message properties,
5289
+ * Solana-Kit errors with context logs, and falls back to string representation.
5290
+ * For Solana-Kit errors, extracts Anchor error messages from transaction logs.
5291
+ *
5292
+ * @param error - Unknown error to extract message from
5293
+ * @returns Extracted error message string
5294
+ *
5295
+ * @example
5296
+ * ```typescript
5297
+ * const msg1 = extractMessage(new Error('test')) // 'test'
5298
+ * const msg2 = extractMessage('string error') // 'string error'
5299
+ * const msg3 = extractMessage({ message: 'obj' }) // 'obj'
5300
+ * ```
5301
+ */
5302
+ function extractMessage(error) {
5303
+ // Check for Solana-Kit errors with context.logs
5304
+ if (hasSolanaLogs(error)) {
5305
+ // Extract Anchor error message from logs
5306
+ const anchorLog = error.context.logs.find((log) => log.includes('AnchorError') || log.includes('Error Message'));
5307
+ if (anchorLog !== undefined) {
5308
+ // Return the anchor error log which contains the detailed message
5309
+ return anchorLog;
5310
+ }
5311
+ }
5312
+ if (error instanceof Error) {
5313
+ return error.message;
5314
+ }
5315
+ if (typeof error === 'string') {
5316
+ return error;
5317
+ }
5318
+ if (typeof error === 'object' && error !== null && 'message' in error) {
5319
+ return String(error.message);
5320
+ }
5321
+ return String(error);
5322
+ }
5323
+ /**
5324
+ * Mapping of custom error selectors to human-readable error names.
5325
+ *
5326
+ * These selectors correspond to custom errors from the contracts.
5327
+ * When a transaction reverts with a custom error, the 4-byte selector
5328
+ * (first 4 bytes of keccak256 of the error signature) is included in the error message.
5329
+ *
5330
+ */
5331
+ const CUSTOM_ERROR_SELECTORS = {
5332
+ '0xd93c0665': 'This contract is currently paused', // EnforcedPause()
5333
+ '0x3ee5aeb5': 'Security check failed due to a reentrancy attempt', // ReentrancyGuardReentrantCall()
5334
+ '0x8baa579f': 'The provided signature is invalid or could not be verified', // InvalidSignature()
5335
+ '0x1ab7da6b': 'This request has expired', // DeadlineExpired()
5336
+ '0x64eee62e': 'No instructions provided', // EmptyInstructions()
5337
+ '0x3c46992e': 'Too many execution steps were provided', // TooManyInstructions()
5338
+ '0xe066e60e': 'Exceeds maximum token inputs limit', // TooManyTokenInputs()
5339
+ '0x5566df5c': 'The beneficiary address is invalid', // InvalidBeneficiary()
5340
+ '0xf41d7bc6': 'Execution ID already used', // ExecIdUsed()
5341
+ '0x948726e2': 'The account balance does not meet the required minimum', // InvalidBalanceState()
5342
+ '0x01aa0452': 'The permit type provided is not supported', // UnsupportedPermitType()
5343
+ '0x13be252b': 'Token approval failed and the allowance is insufficient', // InsufficientAllowance()
5344
+ '0x5274afe7': 'Token transfer or approval failed', // SafeERC20FailedOperation(address)
5345
+ '0x00bc1dcb': 'One of the execution targets is invalid', // InvalidInstruction(uint256)
5346
+ '0x5d693841': 'Cannot approve a zero address or a native token', // InvalidApprovalConfig(uint256)
5347
+ '0x3ec5cfe1': 'Cannot validate output for a zero address token', // InvalidOutputConfig(uint256)
5348
+ '0x492fd70e': 'Instruction call failed without revert data', // ExecutionFailed(uint256)
5349
+ '0xba235e1a': 'The received token amount is lower than the minimum required', // InsufficientOutput(uint256,address,uint256,uint256)
5350
+ '0x9147d463': 'Final balance is less than the initial balance', // InsufficientFinalBalance(address,uint256,uint256)
5351
+ '0xf4b3b1bc': 'Native token transfer to the beneficiary failed', // NativeTransferFailed()
5352
+ };
5353
+ /**
5354
+ * Attempts to extract an error selector from an error object or message.
5355
+ *
5356
+ * Checks multiple sources in priority order:
5357
+ * 1. `error.data` field
5358
+ * 2. Message pattern: "custom error 0x8baa579f"
5359
+ *
5360
+ * @param msg - The error message
5361
+ * @param error - The error object (optional)
5362
+ * @returns The 4 bytes selector , or undefined if not found
5363
+ */
5364
+ function extractErrorSelector(msg, error) {
5365
+ // Check error.data field
5366
+ if (error !== null && typeof error === 'object' && 'data' in error) {
5367
+ if (typeof error.data === 'string' && /^0x[0-9a-f]{8}$/i.test(error.data)) {
5368
+ return error.data.toLowerCase();
5369
+ }
5370
+ }
5371
+ // Check for "custom error 0x8baa579f" in the message
5372
+ const match = /custom error (0x[0-9a-f]{8})\b/i.exec(msg);
5373
+ const selector = match?.[1];
5374
+ if (selector !== undefined && selector.length > 0) {
5375
+ return selector.toLowerCase();
5376
+ }
5377
+ return undefined;
5378
+ }
5379
+ /**
5380
+ * Extracts the revert reason from an error message and error object.
5381
+ *
5382
+ * Attempts to parse out the meaningful reason from execution revert errors,
5383
+ * removing common prefixes like "execution reverted:" or "reverted:".
5384
+ * If the error contains a custom error selector, it will be resolved to its
5385
+ * human-readable description with the selector included for developer reference.
5386
+ *
5387
+ * Supports multiple error formats:
5388
+ * - Custom error with selector in `error.data` field (e.g., `{ data: "0x8baa579f", ... }`)
5389
+ * - Message with selector pattern: `"Execution reverted with reason: custom error 0x8baa579f"`
5390
+ * - Standard revert: `"execution reverted: Insufficient funds"`
5391
+ *
5392
+ * @param msg - The error message to extract from
5393
+ * @param error - The full error object (optional, used to extract selector from `data` field)
5394
+ * @returns The extracted revert reason, or null if not found
5395
+ *
5396
+ * @example
5397
+ * ```typescript
5398
+ * const reason = extractRevertReason(
5399
+ * 'execution reverted: ERC20: transfer amount exceeds balance'
5400
+ * )
5401
+ * // Returns: 'ERC20: transfer amount exceeds balance'
5402
+ * ```
5403
+ *
5404
+ * @example
5405
+ * ```typescript
5406
+ * const reason = extractRevertReason(
5407
+ * 'Simulation failed: Execution reverted with reason: Insufficient allowance'
5408
+ * )
5409
+ * // Returns: 'Insufficient allowance'
5410
+ * ```
5411
+ *
5412
+ * @example
5413
+ * ```typescript
5414
+ * // Custom error with selector in message
5415
+ * const reason = extractRevertReason(
5416
+ * 'Execution reverted with reason: custom error 0x8baa579f'
5417
+ * )
5418
+ * // Returns: 'The provided signature is invalid or could not be verified (0x8baa579f)'
5419
+ * ```
5420
+ *
5421
+ * @example
5422
+ * ```typescript
5423
+ * // Error with data field
5424
+ * const reason = extractRevertReason(
5425
+ * 'execution reverted (unknown custom error)',
5426
+ * { data: '0x8baa579f' }
5427
+ * )
5428
+ * // Returns: 'The provided signature is invalid or could not be verified (0x8baa579f)'
5429
+ * ```
5430
+ *
5431
+ * @example
5432
+ * ```typescript
5433
+ * // Unrecognized selector preserved for debugging
5434
+ * const reason = extractRevertReason(
5435
+ * 'execution reverted (unknown custom error)',
5436
+ * { data: '0xdeadbeef' }
5437
+ * )
5438
+ * // Returns: 'Transaction reverted with error (0xdeadbeef)'
5439
+ * ```
5440
+ */
5441
+ function extractRevertReason(msg, error) {
5442
+ // Try to extract and decode custom error selector
5443
+ const selector = extractErrorSelector(msg, error);
5444
+ if (selector !== undefined) {
5445
+ const errorMessage = CUSTOM_ERROR_SELECTORS[selector];
5446
+ if (errorMessage !== undefined) {
5447
+ return `${errorMessage} (${selector})`;
5448
+ }
5449
+ }
5450
+ // Try to extract reason after "execution reverted:" or "reason:"
5451
+ // Use [^\n.]+ instead of .+? to avoid ReDoS vulnerability
5452
+ // Fall back to standard revert reason extraction
5453
+ const patterns = [
5454
+ /(?:execution reverted|reverted):\s*([^\n.]+)/i,
5455
+ /reason:\s*([^\n.]+)/i,
5456
+ /with reason:\s*([^\n.]+)/i,
5457
+ ];
5458
+ for (const pattern of patterns) {
5459
+ const match = pattern.exec(msg);
5460
+ const extractedReason = match?.at(1);
5461
+ if (extractedReason !== undefined && extractedReason.length > 0) {
5462
+ return extractedReason.trim();
5463
+ }
5464
+ }
5465
+ // Preserve unrecognized selector so it appears in error output for debugging
5466
+ if (selector !== undefined) {
5467
+ return `Transaction reverted with error (${selector})`;
5468
+ }
5469
+ return null;
5470
+ }
4656
5471
 
4657
5472
  /**
4658
5473
  * Type guard to check if an error is a KitError instance.
@@ -4710,6 +5525,102 @@ function isKitError(error) {
4710
5525
  function isFatalError(error) {
4711
5526
  return isKitError(error) && error.recoverability === 'FATAL';
4712
5527
  }
5528
+ /**
5529
+ * Error codes that are considered retryable by default.
5530
+ *
5531
+ * @remarks
5532
+ * These are typically transient errors that may succeed on retry:
5533
+ * - Network connectivity issues (3001, 3002)
5534
+ * - Provider unavailability (4001, 4002)
5535
+ * - RPC nonce errors (4003)
5536
+ */
5537
+ const DEFAULT_RETRYABLE_ERROR_CODES = [
5538
+ // Network errors
5539
+ 3001, // NETWORK_CONNECTION_FAILED
5540
+ 3002, // NETWORK_TIMEOUT
5541
+ // Provider errors
5542
+ 4001, // PROVIDER_UNAVAILABLE
5543
+ 4002, // PROVIDER_TIMEOUT
5544
+ 4003, // RPC_NONCE_ERROR
5545
+ ];
5546
+ /**
5547
+ * Checks if an error is retryable.
5548
+ *
5549
+ * @remarks
5550
+ * Check order for KitError instances:
5551
+ * 1. If `recoverability === 'RETRYABLE'`, return `true` immediately (priority check).
5552
+ * 2. Otherwise, check if `error.code` is in `DEFAULT_RETRYABLE_ERROR_CODES` (fallback check).
5553
+ * 3. Non-KitError instances always return `false`.
5554
+ *
5555
+ * This two-tier approach allows both explicit recoverability control and
5556
+ * backward-compatible code-based retry logic.
5557
+ *
5558
+ * RETRYABLE errors indicate transient failures that may succeed on
5559
+ * subsequent attempts, such as network timeouts or temporary service
5560
+ * unavailability. These errors are safe to retry after a delay.
5561
+ *
5562
+ * @param error - Unknown error to check
5563
+ * @returns True if error is retryable
5564
+ *
5565
+ * @example
5566
+ * ```typescript
5567
+ * import { isRetryableError } from '@core/errors'
5568
+ *
5569
+ * try {
5570
+ * await kit.bridge(params)
5571
+ * } catch (error) {
5572
+ * if (isRetryableError(error)) {
5573
+ * // Implement retry logic with exponential backoff
5574
+ * setTimeout(() => retryOperation(), 5000)
5575
+ * }
5576
+ * }
5577
+ * ```
5578
+ *
5579
+ * @example
5580
+ * ```typescript
5581
+ * import { isRetryableError, createNetworkConnectionError, KitError } from '@core/errors'
5582
+ *
5583
+ * // KitError with RETRYABLE recoverability (priority check)
5584
+ * const error1 = createNetworkConnectionError('Ethereum')
5585
+ * isRetryableError(error1) // true
5586
+ *
5587
+ * // KitError with default retryable code (fallback check)
5588
+ * const error2 = new KitError({
5589
+ * code: 3002, // NETWORK_TIMEOUT - in DEFAULT_RETRYABLE_ERROR_CODES
5590
+ * name: 'NETWORK_TIMEOUT',
5591
+ * type: 'NETWORK',
5592
+ * recoverability: 'FATAL', // Not RETRYABLE
5593
+ * message: 'Timeout',
5594
+ * })
5595
+ * isRetryableError(error2) // true (code 3002 is in default list)
5596
+ *
5597
+ * // KitError with non-retryable code and FATAL recoverability
5598
+ * const error3 = new KitError({
5599
+ * code: 1001,
5600
+ * name: 'INPUT_NETWORK_MISMATCH',
5601
+ * type: 'INPUT',
5602
+ * recoverability: 'FATAL',
5603
+ * message: 'Invalid input',
5604
+ * })
5605
+ * isRetryableError(error3) // false
5606
+ *
5607
+ * // Non-KitError
5608
+ * const error4 = new Error('Standard error')
5609
+ * isRetryableError(error4) // false
5610
+ * ```
5611
+ */
5612
+ function isRetryableError$1(error) {
5613
+ // Use proper type guard to check if it's a KitError
5614
+ if (isKitError(error)) {
5615
+ // Priority check: explicit recoverability
5616
+ if (error.recoverability === 'RETRYABLE') {
5617
+ return true;
5618
+ }
5619
+ // Fallback check: error code against default retryable codes
5620
+ return DEFAULT_RETRYABLE_ERROR_CODES.includes(error.code);
5621
+ }
5622
+ return false;
5623
+ }
4713
5624
  /**
4714
5625
  * Safely extracts error message from any error type.
4715
5626
  *
@@ -5088,8 +5999,15 @@ const pollApiWithValidation = async (url, method, isValidType, config = {}, body
5088
5999
  }
5089
6000
  }
5090
6001
  }
5091
- // After the loop: we're guaranteed to have a lastError
5092
- throw new Error(`Maximum retry attempts (${String(effectiveConfig.maxRetries)}) exceeded: ${String(lastError?.message)}`);
6002
+ // Preserve responseBody from the last attempt so upstream parsers
6003
+ // (e.g. parseApiError) can inspect the server's structured response.
6004
+ const retryError = new Error(`Maximum retry attempts (${String(effectiveConfig.maxRetries)}) exceeded: ${String(lastError?.message)}`);
6005
+ if (lastError !== undefined &&
6006
+ 'responseBody' in lastError &&
6007
+ lastError.responseBody !== undefined) {
6008
+ retryError.responseBody = lastError.responseBody;
6009
+ }
6010
+ throw retryError;
5093
6011
  };
5094
6012
  /**
5095
6013
  * Convenience function for making GET requests with validation.
@@ -5630,6 +6548,7 @@ const USDC = {
5630
6548
  // =========================================================================
5631
6549
  // Testnets (alphabetically sorted)
5632
6550
  // =========================================================================
6551
+ [Blockchain.Arc_Testnet]: '0x3600000000000000000000000000000000000000',
5633
6552
  [Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
5634
6553
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
5635
6554
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
@@ -5706,6 +6625,8 @@ const EURC = {
5706
6625
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
5707
6626
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
5708
6627
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
6628
+ // Testnets
6629
+ [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
5709
6630
  },
5710
6631
  };
5711
6632
 
@@ -6396,6 +7317,86 @@ function buildForwardingHookData() {
6396
7317
  return cachedHookDataHex;
6397
7318
  }
6398
7319
 
7320
+ const DEFAULTS = {
7321
+ maxRetries: 3,
7322
+ baseDelayMs: 1000,
7323
+ maxDelayMs: 15_000,
7324
+ deadlineMs: undefined,
7325
+ jitter: true,
7326
+ isRetryable: () => true,
7327
+ };
7328
+ function resolveOptions(options) {
7329
+ if (options === undefined)
7330
+ return DEFAULTS;
7331
+ return {
7332
+ maxRetries: options.maxRetries ?? DEFAULTS.maxRetries,
7333
+ baseDelayMs: options.baseDelayMs ?? DEFAULTS.baseDelayMs,
7334
+ maxDelayMs: options.maxDelayMs ?? DEFAULTS.maxDelayMs,
7335
+ deadlineMs: options.deadlineMs ?? DEFAULTS.deadlineMs,
7336
+ jitter: options.jitter ?? DEFAULTS.jitter,
7337
+ isRetryable: options.isRetryable ?? DEFAULTS.isRetryable,
7338
+ };
7339
+ }
7340
+ /**
7341
+ * Calculate exponential backoff delay with optional jitter.
7342
+ *
7343
+ * @param attempt - 1-indexed retry attempt number.
7344
+ * @param config - Resolved retry configuration.
7345
+ * @returns Delay in milliseconds.
7346
+ */
7347
+ function calculateDelay(attempt, config) {
7348
+ let delay = config.baseDelayMs * Math.pow(2, attempt - 1);
7349
+ delay = Math.min(delay, config.maxDelayMs);
7350
+ if (config.jitter) {
7351
+ const jitterFactor = 0.75 + Math.random() * 0.5; // NOSONAR - not security-sensitive
7352
+ delay = Math.round(delay * jitterFactor);
7353
+ }
7354
+ return delay;
7355
+ }
7356
+ /**
7357
+ * Retry an async function with exponential backoff and jitter.
7358
+ *
7359
+ * This is a lightweight standalone utility with no `@core/runtime`
7360
+ * dependency, suitable for use in adapters and providers that do not
7361
+ * participate in the middleware pipeline.
7362
+ *
7363
+ * @typeParam T - The resolved value type.
7364
+ * @param fn - The async function to execute (and potentially retry).
7365
+ * @param options - Retry configuration.
7366
+ * @returns The resolved value of `fn`.
7367
+ * @throws The last error when all retry attempts are exhausted, or
7368
+ * immediately when `isRetryable` returns `false`.
7369
+ *
7370
+ * @example
7371
+ * ```typescript
7372
+ * import { retryAsync } from '@core/utils'
7373
+ *
7374
+ * const receipt = await retryAsync(
7375
+ * () => adapter.waitForTransaction(txHash, { confirmations: 1 }, chain),
7376
+ * { maxRetries: 3, isRetryable: (err) => isTransientRpcError(err) },
7377
+ * )
7378
+ * ```
7379
+ */
7380
+ async function retryAsync(fn, options) {
7381
+ const config = resolveOptions(options);
7382
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
7383
+ try {
7384
+ return await fn();
7385
+ }
7386
+ catch (error) {
7387
+ const pastDeadline = config.deadlineMs !== undefined && Date.now() >= config.deadlineMs;
7388
+ const isLastAttempt = attempt >= config.maxRetries;
7389
+ if (isLastAttempt || pastDeadline || !config.isRetryable(error)) {
7390
+ throw error;
7391
+ }
7392
+ const delayMs = calculateDelay(attempt + 1, config);
7393
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
7394
+ }
7395
+ }
7396
+ /* istanbul ignore next: unreachable safety throw for TypeScript */
7397
+ throw new Error('retryAsync: unreachable');
7398
+ }
7399
+
6399
7400
  /**
6400
7401
  * Transfer speed options for cross-chain operations.
6401
7402
  *
@@ -8045,10 +9046,13 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
8045
9046
  }
8046
9047
  const txHash = await request.execute();
8047
9048
  step.txHash = txHash;
8048
- const transaction = await adapter.waitForTransaction(txHash, {
8049
- confirmations,
8050
- timeout,
8051
- }, chain);
9049
+ const retryOptions = {
9050
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
9051
+ };
9052
+ if (timeout !== undefined) {
9053
+ retryOptions.deadlineMs = Date.now() + timeout;
9054
+ }
9055
+ const transaction = await retryAsync(async () => adapter.waitForTransaction(txHash, { confirmations, timeout }, chain), retryOptions);
8052
9056
  step.state = transaction.blockNumber ? 'success' : 'error';
8053
9057
  step.data = transaction;
8054
9058
  // Generate explorer URL for the step
@@ -9663,7 +10667,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
9663
10667
  return step;
9664
10668
  }
9665
10669
  try {
9666
- const transaction = await adapter.waitForTransaction(receipt.txHash, { confirmations: 1 }, chain);
10670
+ const transaction = await retryAsync(async () => adapter.waitForTransaction(receipt.txHash, { confirmations: 1 }, chain), {
10671
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, {
10672
+ chain: chain.name,
10673
+ txHash: receipt.txHash,
10674
+ })),
10675
+ });
9667
10676
  step.state = transaction.blockNumber === undefined ? 'error' : 'success';
9668
10677
  step.data = transaction;
9669
10678
  if (transaction.blockNumber === undefined) {
@@ -9679,7 +10688,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
9679
10688
  return step;
9680
10689
  }
9681
10690
 
9682
- var version = "1.6.0";
10691
+ var version = "1.6.2";
9683
10692
  var pkg = {
9684
10693
  version: version};
9685
10694
 
@@ -10795,7 +11804,10 @@ async function waitForPendingTransaction(pendingStep, adapter, chain) {
10795
11804
  message: `Cannot wait for pending ${pendingStep.name}: no transaction hash available`,
10796
11805
  });
10797
11806
  }
10798
- const txReceipt = await adapter.waitForTransaction(pendingStep.txHash, undefined, chain);
11807
+ const txHash = pendingStep.txHash;
11808
+ const txReceipt = await retryAsync(async () => adapter.waitForTransaction(txHash, undefined, chain), {
11809
+ isRetryable: (err) => isRetryableError$1(parseBlockchainError(err, { chain: chain.name, txHash })),
11810
+ });
10799
11811
  // Check if transaction was confirmed on-chain
10800
11812
  if (!txReceipt.blockNumber) {
10801
11813
  return {