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