@circle-fin/adapter-ethers-v6 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -85,6 +85,10 @@ const ERROR_TYPES = {
85
85
  RPC: 'RPC',
86
86
  /** Internet connectivity, DNS resolution, connection issues */
87
87
  NETWORK: 'NETWORK',
88
+ /** API throttling, request frequency limits errors */
89
+ RATE_LIMIT: 'RATE_LIMIT',
90
+ /** Service errors */
91
+ SERVICE: 'SERVICE',
88
92
  /** Catch-all for unrecognized errors (code 0) */
89
93
  UNKNOWN: 'UNKNOWN',
90
94
  };
@@ -108,6 +112,8 @@ const ERROR_CODE_RANGES = [
108
112
  { min: 3000, max: 3999, type: 'NETWORK' },
109
113
  { min: 4000, max: 4999, type: 'RPC' },
110
114
  { min: 5000, max: 5999, type: 'ONCHAIN' },
115
+ { min: 7000, max: 7999, type: 'RATE_LIMIT' },
116
+ { min: 8000, max: 8999, type: 'SERVICE' },
111
117
  { min: 9000, max: 9999, type: 'BALANCE' },
112
118
  ];
113
119
  /** Special code for UNKNOWN errors */
@@ -155,6 +161,8 @@ const errorDetailsSchema = zod.z.object({
155
161
  * - 3000-3999: NETWORK errors - Connectivity issues
156
162
  * - 4000-4999: RPC errors - Provider issues, gas estimation
157
163
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures
164
+ * - 7000-7999: RATE_LIMIT errors - API throttling
165
+ * - 8000-8999: SERVICE errors - Internal service errors
158
166
  * - 9000-9999: BALANCE errors - Insufficient funds
159
167
  */
160
168
  code: zod.z
@@ -245,11 +253,11 @@ function validateErrorDetails(details) {
245
253
  const MAX_MESSAGE_LENGTH = 950;
246
254
 
247
255
  /**
248
- * Structured error class for Stablecoin Kit operations.
256
+ * Structured error class for App Kit operations.
249
257
  *
250
258
  * This class extends the native Error class while implementing the ErrorDetails
251
259
  * interface, providing a consistent error format for programmatic handling
252
- * across the Stablecoin Kits ecosystem. All properties are immutable to ensure
260
+ * across the App Kits ecosystem. All properties are immutable to ensure
253
261
  * error objects cannot be modified after creation.
254
262
  *
255
263
  * @example
@@ -259,6 +267,7 @@ const MAX_MESSAGE_LENGTH = 950;
259
267
  * const error = new KitError({
260
268
  * code: 1001,
261
269
  * name: 'INPUT_NETWORK_MISMATCH',
270
+ * type: 'INPUT',
262
271
  * recoverability: 'FATAL',
263
272
  * message: 'Cannot bridge between mainnet and testnet'
264
273
  * })
@@ -276,7 +285,8 @@ const MAX_MESSAGE_LENGTH = 950;
276
285
  * // Error with cause information
277
286
  * const error = new KitError({
278
287
  * code: 1002,
279
- * name: 'INVALID_AMOUNT',
288
+ * name: 'INPUT_INVALID_AMOUNT',
289
+ * type: 'INPUT',
280
290
  * recoverability: 'FATAL',
281
291
  * message: 'Amount must be greater than zero',
282
292
  * cause: {
@@ -360,6 +370,8 @@ class KitError extends Error {
360
370
  * - 3000-3999: NETWORK errors - Internet connectivity, DNS, connection issues
361
371
  * - 4000-4999: RPC errors - Blockchain provider issues, gas estimation, nonce errors
362
372
  * - 5000-5999: ONCHAIN errors - Transaction/simulation failures, gas exhaustion, reverts
373
+ * - 7000-7999: RATE_LIMIT errors - API throttling, request frequency limits
374
+ * - 8000-8999: SERVICE errors - Internal service errors, server failures
363
375
  * - 9000-9999: BALANCE errors - Insufficient funds, token balance, allowance
364
376
  */
365
377
  /**
@@ -390,19 +402,30 @@ class KitError extends Error {
390
402
  * ```
391
403
  */
392
404
  const InputError = {
405
+ /** Invalid amount format or value (negative, zero, or malformed) */
406
+ INVALID_AMOUNT: {
407
+ code: 1002,
408
+ name: 'INPUT_INVALID_AMOUNT',
409
+ type: 'INPUT',
410
+ },
393
411
  /** Invalid or unsupported chain identifier */
394
412
  INVALID_CHAIN: {
395
413
  code: 1005,
396
414
  name: 'INPUT_INVALID_CHAIN',
397
415
  type: 'INPUT',
398
416
  },
417
+ /** Unsupported token for chain */
418
+ UNSUPPORTED_TOKEN: {
419
+ code: 1006,
420
+ name: 'INPUT_UNSUPPORTED_TOKEN',
421
+ type: 'INPUT',
422
+ },
399
423
  /** General validation failure for complex validation rules */
400
424
  VALIDATION_FAILED: {
401
425
  code: 1098,
402
426
  name: 'INPUT_VALIDATION_FAILED',
403
427
  type: 'INPUT',
404
- },
405
- };
428
+ }};
406
429
  /**
407
430
  * Standardized error definitions for BALANCE type errors.
408
431
  *
@@ -470,7 +493,20 @@ const OnchainError = {
470
493
  code: 5003,
471
494
  name: 'ONCHAIN_OUT_OF_GAS',
472
495
  type: 'ONCHAIN',
473
- }};
496
+ },
497
+ /** Transaction size exceeds blockchain limit */
498
+ TRANSACTION_TOO_LARGE: {
499
+ code: 5005,
500
+ name: 'ONCHAIN_TRANSACTION_TOO_LARGE',
501
+ type: 'ONCHAIN',
502
+ },
503
+ /** Unknown blockchain error that cannot be categorized */
504
+ UNKNOWN_BLOCKCHAIN_ERROR: {
505
+ code: 5099,
506
+ name: 'ONCHAIN_UNKNOWN_BLOCKCHAIN_ERROR',
507
+ type: 'ONCHAIN',
508
+ },
509
+ };
474
510
  /**
475
511
  * Standardized error definitions for RPC type errors.
476
512
  *
@@ -522,6 +558,66 @@ const NetworkError = {
522
558
  type: 'NETWORK',
523
559
  }};
524
560
 
561
+ /**
562
+ * Creates error for unsupported token on chain.
563
+ *
564
+ * This error is thrown when a token is not supported on the specified chain.
565
+ *
566
+ * @param token - The token symbol (e.g., 'USDC', 'USDT')
567
+ * @param chain - The chain name where the token is unsupported
568
+ * @returns KitError with specific token support details
569
+ *
570
+ * @example
571
+ * ```typescript
572
+ * import { createUnsupportedTokenError } from '@core/errors'
573
+ *
574
+ * throw createUnsupportedTokenError('USDC', 'UnknownChain')
575
+ * // Message: "USDC is not supported on UnknownChain"
576
+ * ```
577
+ */
578
+ function createUnsupportedTokenError(token, chain) {
579
+ const errorDetails = {
580
+ ...InputError.UNSUPPORTED_TOKEN,
581
+ recoverability: 'FATAL',
582
+ message: `${token} is not supported on ${chain}.`,
583
+ cause: {
584
+ trace: { token, chain },
585
+ },
586
+ };
587
+ return new KitError(errorDetails);
588
+ }
589
+ /**
590
+ * Creates error for invalid amount format or precision.
591
+ *
592
+ * This error is thrown when the provided amount doesn't meet validation
593
+ * requirements such as precision, range, or format.
594
+ *
595
+ * @param amount - The invalid amount string
596
+ * @param reason - Specific reason why amount is invalid
597
+ * @returns KitError with amount details and validation rule
598
+ *
599
+ * @example
600
+ * ```typescript
601
+ * import { createInvalidAmountError } from '@core/errors'
602
+ *
603
+ * throw createInvalidAmountError('0.000001', 'Amount must be at least 0.01 USDC')
604
+ * // Message: "Invalid amount '0.000001': Amount must be at least 0.01 USDC"
605
+ *
606
+ * throw createInvalidAmountError('1,000.50', 'Amount must be a numeric string with dot (.) as decimal separator, with no thousand separators or comma decimals')
607
+ * // Message: "Invalid amount '1,000.50': Amount must be a numeric string with dot (.) as decimal separator, with no thousand separators or comma decimals."
608
+ * ```
609
+ */
610
+ function createInvalidAmountError(amount, reason) {
611
+ const errorDetails = {
612
+ ...InputError.INVALID_AMOUNT,
613
+ recoverability: 'FATAL',
614
+ message: `Invalid amount '${amount}': ${reason}.`,
615
+ cause: {
616
+ trace: { amount, reason },
617
+ },
618
+ };
619
+ return new KitError(errorDetails);
620
+ }
525
621
  /**
526
622
  * Creates error for invalid chain configuration.
527
623
  *
@@ -544,7 +640,7 @@ function createInvalidChainError(chain, reason) {
544
640
  const errorDetails = {
545
641
  ...InputError.INVALID_CHAIN,
546
642
  recoverability: 'FATAL',
547
- message: `Invalid chain '${chain}': ${reason}`,
643
+ message: `Invalid chain '${chain}': ${reason}.`,
548
644
  cause: {
549
645
  trace: { chain, reason },
550
646
  },
@@ -816,6 +912,8 @@ function createSimulationFailedError(chain, reason, trace) {
816
912
  * @param chain - The blockchain network where the transaction reverted
817
913
  * @param reason - The reason for the revert (e.g., revert message)
818
914
  * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
915
+ * @param txHash - The transaction hash if the transaction was submitted (optional)
916
+ * @param explorerUrl - The block explorer URL for the transaction (optional)
819
917
  * @returns KitError with transaction revert details
820
918
  *
821
919
  * @example
@@ -828,15 +926,17 @@ function createSimulationFailedError(chain, reason, trace) {
828
926
  *
829
927
  * @example
830
928
  * ```typescript
831
- * // With trace context for debugging
832
- * throw createTransactionRevertedError('Base', 'Slippage exceeded', {
833
- * rawError: error,
834
- * txHash: '0xabc...',
835
- * blockNumber: '12345',
836
- * })
929
+ * // With trace context and transaction details for debugging
930
+ * throw createTransactionRevertedError(
931
+ * 'Base',
932
+ * 'Slippage exceeded',
933
+ * { rawError: error },
934
+ * '0x123...',
935
+ * 'https://basescan.org/tx/0x123...'
936
+ * )
837
937
  * ```
838
938
  */
839
- function createTransactionRevertedError(chain, reason, trace) {
939
+ function createTransactionRevertedError(chain, reason, trace, txHash, explorerUrl) {
840
940
  return new KitError({
841
941
  ...OnchainError.TRANSACTION_REVERTED,
842
942
  recoverability: 'FATAL',
@@ -846,6 +946,8 @@ function createTransactionRevertedError(chain, reason, trace) {
846
946
  ...trace,
847
947
  chain,
848
948
  reason,
949
+ ...(txHash !== undefined && txHash !== '' && { txHash }),
950
+ ...(explorerUrl !== undefined && explorerUrl !== '' && { explorerUrl }),
849
951
  },
850
952
  },
851
953
  });
@@ -858,6 +960,8 @@ function createTransactionRevertedError(chain, reason, trace) {
858
960
  *
859
961
  * @param chain - The blockchain network where the transaction ran out of gas
860
962
  * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
963
+ * @param txHash - The transaction hash if the transaction was submitted (optional)
964
+ * @param explorerUrl - The block explorer URL for the transaction (optional)
861
965
  * @returns KitError with out of gas details
862
966
  *
863
967
  * @example
@@ -872,13 +976,15 @@ function createTransactionRevertedError(chain, reason, trace) {
872
976
  * ```typescript
873
977
  * // With trace context for debugging
874
978
  * throw createOutOfGasError('Polygon', {
875
- * rawError: error,
876
979
  * gasUsed: '50000',
877
980
  * gasLimit: '45000',
878
- * })
981
+ * },
982
+ * '0xabc...',
983
+ * 'https://polygonscan.com/tx/0xabc...'
984
+ * )
879
985
  * ```
880
986
  */
881
- function createOutOfGasError(chain, trace) {
987
+ function createOutOfGasError(chain, trace, txHash, explorerUrl) {
882
988
  return new KitError({
883
989
  ...OnchainError.OUT_OF_GAS,
884
990
  recoverability: 'FATAL',
@@ -887,20 +993,119 @@ function createOutOfGasError(chain, trace) {
887
993
  trace: {
888
994
  ...trace,
889
995
  chain,
996
+ ...(txHash !== undefined && txHash !== '' && { txHash }),
997
+ ...(explorerUrl !== undefined && explorerUrl !== '' && { explorerUrl }),
998
+ },
999
+ },
1000
+ });
1001
+ }
1002
+ /**
1003
+ * Creates error for transaction size exceeding blockchain limits.
1004
+ *
1005
+ * This error is thrown when a transaction's serialized size exceeds
1006
+ * the blockchain's maximum transaction size limit. The error is FATAL
1007
+ * as it requires reducing the transaction size (e.g., fewer instructions,
1008
+ * smaller data payloads, or splitting into multiple transactions).
1009
+ *
1010
+ * Common on Solana where transactions have strict size limits (e.g., max 1232 bytes).
1011
+ *
1012
+ * @param chain - The blockchain network where the size limit was exceeded
1013
+ * @param rawError - The original error from the underlying system (optional)
1014
+ * @returns KitError with transaction size exceeded details
1015
+ *
1016
+ * @example
1017
+ * ```typescript
1018
+ * import { createTransactionTooLargeError } from '@core/errors'
1019
+ *
1020
+ * throw createTransactionTooLargeError('Solana')
1021
+ * // Message: "Transaction size exceeds limit on Solana"
1022
+ * ```
1023
+ *
1024
+ * @example
1025
+ * ```typescript
1026
+ * import { createTransactionTooLargeError } from '@core/errors'
1027
+ *
1028
+ * // With raw error containing size details
1029
+ * throw createTransactionTooLargeError(
1030
+ * 'Solana',
1031
+ * new Error('transaction too large: max: encoded/raw 1644/1232')
1032
+ * )
1033
+ * // Error includes size information in cause.trace
1034
+ * ```
1035
+ */
1036
+ function createTransactionTooLargeError(chain, rawError) {
1037
+ return new KitError({
1038
+ ...OnchainError.TRANSACTION_TOO_LARGE,
1039
+ recoverability: 'FATAL',
1040
+ message: `Transaction size exceeds limit on ${chain}`,
1041
+ cause: {
1042
+ trace: {
1043
+ chain,
1044
+ rawError,
1045
+ },
1046
+ },
1047
+ });
1048
+ }
1049
+ /**
1050
+ * Creates error for unknown blockchain errors that cannot be categorized.
1051
+ *
1052
+ * This error is used as a fallback when a blockchain error doesn't match
1053
+ * any known patterns (balance, simulation, gas, network, RPC, etc.).
1054
+ * The error is FATAL as we cannot determine if it's retriable without
1055
+ * understanding the underlying cause.
1056
+ *
1057
+ * The original error message and context are preserved in the trace for
1058
+ * debugging purposes.
1059
+ *
1060
+ * @param chain - The blockchain network where the error occurred
1061
+ * @param originalMessage - The original error message from the blockchain
1062
+ * @param rawError - The original error object from the underlying system (optional)
1063
+ * @returns KitError with unknown blockchain error details
1064
+ *
1065
+ * @example
1066
+ * ```typescript
1067
+ * import { createUnknownBlockchainError } from '@core/errors'
1068
+ *
1069
+ * throw createUnknownBlockchainError('Ethereum', 'Unknown protocol error')
1070
+ * // Message: "Unknown blockchain error on Ethereum: Unknown protocol error"
1071
+ * ```
1072
+ *
1073
+ * @example
1074
+ * ```typescript
1075
+ * import { createUnknownBlockchainError } from '@core/errors'
1076
+ *
1077
+ * // With raw error for debugging
1078
+ * const rawError = new Error('Unexpected blockchain state')
1079
+ * throw createUnknownBlockchainError('Solana', rawError.message, rawError)
1080
+ * // Error preserves full context in cause.trace
1081
+ * ```
1082
+ */
1083
+ function createUnknownBlockchainError(chain, originalMessage, rawError) {
1084
+ const errorMessage = originalMessage
1085
+ ? 'Unknown blockchain error on ' + chain + ': ' + originalMessage
1086
+ : 'Unknown blockchain error on ' + chain;
1087
+ return new KitError({
1088
+ ...OnchainError.UNKNOWN_BLOCKCHAIN_ERROR,
1089
+ recoverability: 'FATAL',
1090
+ message: errorMessage,
1091
+ cause: {
1092
+ trace: {
1093
+ chain,
1094
+ rawError,
890
1095
  },
891
1096
  },
892
1097
  });
893
1098
  }
894
1099
 
895
1100
  /**
896
- * Creates error for RPC endpoint failures.
1101
+ * Create error for RPC endpoint failures.
897
1102
  *
898
- * This error is thrown when an RPC provider endpoint fails, returns an error,
1103
+ * Throw when an RPC provider endpoint fails, returns an error,
899
1104
  * or is unavailable. The error is RETRYABLE as RPC issues are often temporary.
900
1105
  *
901
- * @param chain - The blockchain network where the RPC error occurred
902
- * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
903
- * @returns KitError with RPC endpoint error details
1106
+ * @param chain - The blockchain network where the RPC error occurred.
1107
+ * @param trace - Optional trace context (can include rawError and debugging data).
1108
+ * @returns KitError with RPC endpoint error details.
904
1109
  *
905
1110
  * @example
906
1111
  * ```typescript
@@ -912,7 +1117,6 @@ function createOutOfGasError(chain, trace) {
912
1117
  *
913
1118
  * @example
914
1119
  * ```typescript
915
- * // With trace context for debugging
916
1120
  * throw createRpcEndpointError('Ethereum', {
917
1121
  * rawError: error,
918
1122
  * endpoint: 'https://mainnet.infura.io/v3/...',
@@ -935,15 +1139,15 @@ function createRpcEndpointError(chain, trace) {
935
1139
  }
936
1140
 
937
1141
  /**
938
- * Creates error for network connection failures.
1142
+ * Create error for network connection failures.
939
1143
  *
940
- * This error is thrown when network connectivity issues prevent reaching
1144
+ * Throw when network connectivity issues prevent reaching
941
1145
  * the blockchain network. The error is RETRYABLE as network issues are
942
1146
  * often temporary.
943
1147
  *
944
- * @param chain - The blockchain network where the connection failed
945
- * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
946
- * @returns KitError with network connection error details
1148
+ * @param chain - The blockchain network where the connection failed.
1149
+ * @param trace - Optional trace context (can include rawError and debugging data).
1150
+ * @returns KitError with network connection error details.
947
1151
  *
948
1152
  * @example
949
1153
  * ```typescript
@@ -955,7 +1159,6 @@ function createRpcEndpointError(chain, trace) {
955
1159
  *
956
1160
  * @example
957
1161
  * ```typescript
958
- * // With trace context for debugging
959
1162
  * throw createNetworkConnectionError('Ethereum', {
960
1163
  * rawError: error,
961
1164
  * endpoint: 'https://eth-mainnet.g.alchemy.com/v2/...',
@@ -977,6 +1180,25 @@ function createNetworkConnectionError(chain, trace) {
977
1180
  });
978
1181
  }
979
1182
 
1183
+ /**
1184
+ * Normalizes optional transaction details for error factories.
1185
+ *
1186
+ * Converts empty strings to undefined to ensure clean error traces.
1187
+ *
1188
+ * @param txHash - The transaction hash.
1189
+ * @param explorerUrl - The explorer URL.
1190
+ * @returns Normalized values or undefined.
1191
+ */
1192
+ function normalizeTransactionDetails(txHash, explorerUrl) {
1193
+ const normalized = {};
1194
+ if (txHash !== undefined && txHash !== '') {
1195
+ normalized.txHash = txHash;
1196
+ }
1197
+ if (explorerUrl !== undefined && explorerUrl !== '') {
1198
+ normalized.explorerUrl = explorerUrl;
1199
+ }
1200
+ return normalized;
1201
+ }
980
1202
  /**
981
1203
  * Parses raw blockchain errors into structured KitError instances.
982
1204
  *
@@ -984,12 +1206,17 @@ function createNetworkConnectionError(chain, trace) {
984
1206
  * types and converts them into standardized KitError format. It handles
985
1207
  * errors from viem, ethers, Solana web3.js, and other blockchain libraries.
986
1208
  *
987
- * The parser recognizes 5 main error patterns:
1209
+ * The parser recognizes 6 main error patterns:
988
1210
  * 1. Insufficient balance errors
989
1211
  * 2. Simulation/execution reverted errors
990
1212
  * 3. Gas-related errors
991
1213
  * 4. Network connectivity errors
992
1214
  * 5. RPC provider errors
1215
+ * 6. Transaction size limit errors
1216
+ *
1217
+ * When errors don't match known patterns, the parser uses operation context
1218
+ * (e.g., 'simulation', 'estimateGas') to categorize them appropriately.
1219
+ * Unrecognized errors without context are categorized as UNKNOWN_BLOCKCHAIN_ERROR.
993
1220
  *
994
1221
  * @param error - The raw error from the blockchain library
995
1222
  * @param context - Context information including chain and optional token
@@ -1019,6 +1246,25 @@ function createNetworkConnectionError(chain, trace) {
1019
1246
  * throw parseBlockchainError(error, { chain: 'Solana' })
1020
1247
  * }
1021
1248
  * ```
1249
+ *
1250
+ * @example
1251
+ * ```typescript
1252
+ * // With transaction hash and explorer URL
1253
+ * import { buildExplorerUrl } from '@core/utils'
1254
+ * import { Ethereum } from '@core/chains'
1255
+ *
1256
+ * try {
1257
+ * const txHash = await walletClient.sendTransaction(...)
1258
+ * await waitForTransaction(txHash)
1259
+ * } catch (error) {
1260
+ * const explorerUrl = buildExplorerUrl(Ethereum, txHash)
1261
+ * throw parseBlockchainError(error, {
1262
+ * chain: 'Ethereum',
1263
+ * txHash,
1264
+ * explorerUrl
1265
+ * })
1266
+ * }
1267
+ * ```
1022
1268
  */
1023
1269
  function parseBlockchainError(error, context) {
1024
1270
  const msg = extractMessage(error);
@@ -1033,7 +1279,7 @@ function parseBlockchainError(error, context) {
1033
1279
  // Pattern 2: Simulation and execution reverts
1034
1280
  // Matches contract revert errors and simulation failures
1035
1281
  if (/execution reverted|simulation failed|transaction reverted|transaction failed/i.test(msg)) {
1036
- const reason = extractRevertReason(msg) ?? 'Transaction reverted';
1282
+ const reason = extractRevertReason(msg, error) ?? 'Transaction reverted';
1037
1283
  // Distinguish between simulation failures and transaction reverts
1038
1284
  // "simulation failed" or "eth_call" indicates pre-flight simulation
1039
1285
  // "transaction failed" or context.operation === 'transaction' indicates post-execution
@@ -1043,9 +1289,11 @@ function parseBlockchainError(error, context) {
1043
1289
  });
1044
1290
  }
1045
1291
  // Transaction execution failures or reverts
1292
+ // Include txHash and explorerUrl if available (transaction was submitted)
1293
+ const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
1046
1294
  return createTransactionRevertedError(context.chain, reason, {
1047
1295
  rawError: error,
1048
- });
1296
+ }, txHash, explorerUrl);
1049
1297
  }
1050
1298
  // Pattern 3: Gas-related errors
1051
1299
  // Matches gas estimation failures and gas exhaustion
@@ -1057,7 +1305,8 @@ function parseBlockchainError(error, context) {
1057
1305
  // Gas exhaustion errors
1058
1306
  // Use specific patterns without wildcards to avoid ReDoS
1059
1307
  if (/out of gas|gas limit exceeded|exceeds block gas limit/i.test(msg)) {
1060
- return createOutOfGasError(context.chain, { rawError: error });
1308
+ const { txHash, explorerUrl } = normalizeTransactionDetails(context.txHash, context.explorerUrl);
1309
+ return createOutOfGasError(context.chain, { rawError: error }, txHash, explorerUrl);
1061
1310
  }
1062
1311
  // Insufficient funds for gas
1063
1312
  if (/insufficient funds for gas/i.test(msg)) {
@@ -1073,15 +1322,38 @@ function parseBlockchainError(error, context) {
1073
1322
  if (/rpc|invalid response|rate limit|too many requests/i.test(msg)) {
1074
1323
  return createRpcEndpointError(context.chain, { rawError: error });
1075
1324
  }
1325
+ // Pattern 6: Transaction size limit errors
1326
+ // Matches transaction size errors from various blockchains:
1327
+ // - Solana: "transaction too large: max: encoded/raw 1644/1232"
1328
+ // - Generic: "transaction exceeds size limit", "max transaction size"
1329
+ // Split into two checks to reduce regex complexity for SonarQube
1330
+ 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);
1331
+ if (isSizeError) {
1332
+ return createTransactionTooLargeError(context.chain, error);
1333
+ }
1076
1334
  // Fallback based on operation context
1077
1335
  // Gas-related operations are RPC calls
1078
1336
  if (context.operation === 'estimateGas' ||
1079
1337
  context.operation === 'getGasPrice') {
1080
1338
  return createRpcEndpointError(context.chain, { rawError: error });
1081
1339
  }
1082
- // Fallback for unrecognized errors
1083
- // Defaults to simulation failed as transaction execution is the most common failure point
1084
- return createSimulationFailedError(context.chain, msg.length > 0 ? msg : 'Unknown error', { rawError: error });
1340
+ // Simulation operations that don't match standard patterns should still be
1341
+ // categorized as simulation failures. This handles cases where blockchain
1342
+ // libraries throw generic errors (e.g., "fail", "error") during staticCall
1343
+ // operations without descriptive messages. The operation context is authoritative
1344
+ // about what was being attempted, even when error messages are unclear.
1345
+ //
1346
+ // Example: ethers.js staticCall rejection with message "fail"
1347
+ // - Message doesn't match /execution reverted|simulation failed/
1348
+ // - But context.operation === 'simulation' tells us it was a simulation
1349
+ // - Result: SIMULATION_FAILED (accurate) vs UNKNOWN_BLOCKCHAIN_ERROR (misleading)
1350
+ if (context.operation === 'simulation') {
1351
+ return createSimulationFailedError(context.chain, msg.length > 0 ? msg : 'Simulation failed', { rawError: error });
1352
+ }
1353
+ // Final fallback for truly unrecognized errors
1354
+ // Only errors that don't match any pattern AND lack operation context
1355
+ // are categorized as unknown.
1356
+ return createUnknownBlockchainError(context.chain, msg.length > 0 ? msg : 'Unknown error', { rawError: error });
1085
1357
  }
1086
1358
  /**
1087
1359
  * Type guard to check if error has Solana-Kit structure with logs.
@@ -1140,12 +1412,76 @@ function extractMessage(error) {
1140
1412
  return String(error);
1141
1413
  }
1142
1414
  /**
1143
- * Extracts the revert reason from an error message.
1415
+ * Mapping of custom error selectors to human-readable error names.
1416
+ *
1417
+ * These selectors correspond to custom errors from the contracts.
1418
+ * When a transaction reverts with a custom error, the 4-byte selector
1419
+ * (first 4 bytes of keccak256 of the error signature) is included in the error message.
1420
+ *
1421
+ */
1422
+ const CUSTOM_ERROR_SELECTORS = {
1423
+ '0xd93c0665': 'This contract is currently paused', // EnforcedPause()
1424
+ '0x3ee5aeb5': 'Security check failed due to a reentrancy attempt', // ReentrancyGuardReentrantCall()
1425
+ '0x8baa579f': 'The provided signature is invalid or could not be verified', // InvalidSignature()
1426
+ '0x1ab7da6b': 'This request has expired', // DeadlineExpired()
1427
+ '0x64eee62e': 'No instructions provided', // EmptyInstructions()
1428
+ '0x3c46992e': 'Too many execution steps were provided', // TooManyInstructions()
1429
+ '0xe066e60e': 'Exceeds maximum token inputs limit', // TooManyTokenInputs()
1430
+ '0x5566df5c': 'The beneficiary address is invalid', // InvalidBeneficiary()
1431
+ '0xf41d7bc6': 'Execution ID already used', // ExecIdUsed()
1432
+ '0x948726e2': 'The account balance does not meet the required minimum', // InvalidBalanceState()
1433
+ '0x01aa0452': 'The permit type provided is not supported', // UnsupportedPermitType()
1434
+ '0x13be252b': 'Token approval failed and the allowance is insufficient', // InsufficientAllowance()
1435
+ '0x5274afe7': 'Token transfer or approval failed', // SafeERC20FailedOperation(address)
1436
+ '0x00bc1dcb': 'One of the execution targets is invalid', // InvalidInstruction(uint256)
1437
+ '0x5d693841': 'Cannot approve a zero address or a native token', // InvalidApprovalConfig(uint256)
1438
+ '0x3ec5cfe1': 'Cannot validate output for a zero address token', // InvalidOutputConfig(uint256)
1439
+ '0x492fd70e': 'Instruction call failed without revert data', // ExecutionFailed(uint256)
1440
+ '0xba235e1a': 'The received token amount is lower than the minimum required', // InsufficientOutput(uint256,address,uint256,uint256)
1441
+ '0x9147d463': 'Final balance is less than the initial balance', // InsufficientFinalBalance(address,uint256,uint256)
1442
+ '0xf4b3b1bc': 'Native token transfer to the beneficiary failed', // NativeTransferFailed()
1443
+ };
1444
+ /**
1445
+ * Attempts to extract an error selector from an error object or message.
1446
+ *
1447
+ * Checks multiple sources in priority order:
1448
+ * 1. `error.data` field
1449
+ * 2. Message pattern: "custom error 0x8baa579f"
1450
+ *
1451
+ * @param msg - The error message
1452
+ * @param error - The error object (optional)
1453
+ * @returns The 4 bytes selector , or undefined if not found
1454
+ */
1455
+ function extractErrorSelector(msg, error) {
1456
+ // Check error.data field
1457
+ if (error !== null && typeof error === 'object' && 'data' in error) {
1458
+ if (typeof error.data === 'string' && /^0x[0-9a-f]{8}$/i.test(error.data)) {
1459
+ return error.data.toLowerCase();
1460
+ }
1461
+ }
1462
+ // Check for "custom error 0x8baa579f" in the message
1463
+ const match = /custom error (0x[0-9a-f]{8})\b/i.exec(msg);
1464
+ const selector = match?.[1];
1465
+ if (selector !== undefined && selector.length > 0) {
1466
+ return selector.toLowerCase();
1467
+ }
1468
+ return undefined;
1469
+ }
1470
+ /**
1471
+ * Extracts the revert reason from an error message and error object.
1144
1472
  *
1145
1473
  * Attempts to parse out the meaningful reason from execution revert errors,
1146
1474
  * removing common prefixes like "execution reverted:" or "reverted:".
1475
+ * If the error contains a custom error selector, it will be resolved to its
1476
+ * human-readable description with the selector included for developer reference.
1477
+ *
1478
+ * Supports multiple error formats:
1479
+ * - Custom error with selector in `error.data` field (e.g., `{ data: "0x8baa579f", ... }`)
1480
+ * - Message with selector pattern: `"Execution reverted with reason: custom error 0x8baa579f"`
1481
+ * - Standard revert: `"execution reverted: Insufficient funds"`
1147
1482
  *
1148
1483
  * @param msg - The error message to extract from
1484
+ * @param error - The full error object (optional, used to extract selector from `data` field)
1149
1485
  * @returns The extracted revert reason, or null if not found
1150
1486
  *
1151
1487
  * @example
@@ -1163,10 +1499,48 @@ function extractMessage(error) {
1163
1499
  * )
1164
1500
  * // Returns: 'Insufficient allowance'
1165
1501
  * ```
1502
+ *
1503
+ * @example
1504
+ * ```typescript
1505
+ * // Custom error with selector in message
1506
+ * const reason = extractRevertReason(
1507
+ * 'Execution reverted with reason: custom error 0x8baa579f'
1508
+ * )
1509
+ * // Returns: 'The provided signature is invalid or could not be verified (0x8baa579f)'
1510
+ * ```
1511
+ *
1512
+ * @example
1513
+ * ```typescript
1514
+ * // Error with data field
1515
+ * const reason = extractRevertReason(
1516
+ * 'execution reverted (unknown custom error)',
1517
+ * { data: '0x8baa579f' }
1518
+ * )
1519
+ * // Returns: 'The provided signature is invalid or could not be verified (0x8baa579f)'
1520
+ * ```
1521
+ *
1522
+ * @example
1523
+ * ```typescript
1524
+ * // Unrecognized selector preserved for debugging
1525
+ * const reason = extractRevertReason(
1526
+ * 'execution reverted (unknown custom error)',
1527
+ * { data: '0xdeadbeef' }
1528
+ * )
1529
+ * // Returns: 'Transaction reverted with error (0xdeadbeef)'
1530
+ * ```
1166
1531
  */
1167
- function extractRevertReason(msg) {
1532
+ function extractRevertReason(msg, error) {
1533
+ // Try to extract and decode custom error selector
1534
+ const selector = extractErrorSelector(msg, error);
1535
+ if (selector !== undefined) {
1536
+ const errorMessage = CUSTOM_ERROR_SELECTORS[selector];
1537
+ if (errorMessage !== undefined) {
1538
+ return `${errorMessage} (${selector})`;
1539
+ }
1540
+ }
1168
1541
  // Try to extract reason after "execution reverted:" or "reason:"
1169
1542
  // Use [^\n.]+ instead of .+? to avoid ReDoS vulnerability
1543
+ // Fall back to standard revert reason extraction
1170
1544
  const patterns = [
1171
1545
  /(?:execution reverted|reverted):\s*([^\n.]+)/i,
1172
1546
  /reason:\s*([^\n.]+)/i,
@@ -1179,9 +1553,49 @@ function extractRevertReason(msg) {
1179
1553
  return extractedReason.trim();
1180
1554
  }
1181
1555
  }
1556
+ // Preserve unrecognized selector so it appears in error output for debugging
1557
+ if (selector !== undefined) {
1558
+ return `Transaction reverted with error (${selector})`;
1559
+ }
1182
1560
  return null;
1183
1561
  }
1184
1562
 
1563
+ /**
1564
+ * Safely extracts error message from any error type.
1565
+ *
1566
+ * This utility handles different error types gracefully, extracting
1567
+ * meaningful messages from Error instances, string errors, or providing
1568
+ * a fallback for unknown error types. Never throws.
1569
+ *
1570
+ * @param error - Unknown error to extract message from
1571
+ * @returns Error message string, or fallback message
1572
+ *
1573
+ * @example
1574
+ * ```typescript
1575
+ * import { getErrorMessage } from '@core/errors'
1576
+ *
1577
+ * try {
1578
+ * await riskyOperation()
1579
+ * } catch (error) {
1580
+ * const message = getErrorMessage(error)
1581
+ * console.log('Error occurred:', message)
1582
+ * // Works with Error, KitError, string, or any other type
1583
+ * }
1584
+ * ```
1585
+ */
1586
+ function getErrorMessage(error) {
1587
+ if (error instanceof Error) {
1588
+ return error.message;
1589
+ }
1590
+ if (typeof error === 'string') {
1591
+ return error;
1592
+ }
1593
+ if (typeof error === 'object' && error !== null && 'message' in error) {
1594
+ return String(error.message);
1595
+ }
1596
+ return 'An unknown error occurred';
1597
+ }
1598
+
1185
1599
  // -----------------------------------------------------------------------------
1186
1600
  // Blockchain Enum
1187
1601
  // -----------------------------------------------------------------------------
@@ -1257,6 +1671,122 @@ exports.Blockchain = void 0;
1257
1671
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
1258
1672
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
1259
1673
  })(exports.Blockchain || (exports.Blockchain = {}));
1674
+ /**
1675
+ * Enum representing the subset of {@link Blockchain} that supports swap operations.
1676
+ *
1677
+ * This enum provides compile-time type safety for swap chain selection,
1678
+ * ensuring only supported chains are available in IDE autocomplete
1679
+ * when building swap parameters.
1680
+ *
1681
+ * @remarks
1682
+ * Unlike the full {@link Blockchain} enum, SwapChain only includes networks
1683
+ * where the swap functionality is actively supported by the library.
1684
+ * Using this enum prevents runtime errors from attempting unsupported
1685
+ * cross-chain swaps.
1686
+ *
1687
+ * Currently supports:
1688
+ * - Ethereum mainnet
1689
+ * - Base mainnet
1690
+ * - Polygon mainnet
1691
+ * - Solana mainnet
1692
+ *
1693
+ * @example
1694
+ * ```typescript
1695
+ * import { SwapChain, swap, createSwapKitContext } from '@circle-fin/swap-kit'
1696
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
1697
+ *
1698
+ * const context = createSwapKitContext()
1699
+ * const adapter = createViemAdapterFromPrivateKey({
1700
+ * privateKey: process.env.PRIVATE_KEY
1701
+ * })
1702
+ *
1703
+ * // ✅ Autocomplete shows only swap-supported chains
1704
+ * const result = await swap(context, {
1705
+ * from: {
1706
+ * adapter,
1707
+ * chain: SwapChain.Ethereum // Autocomplete: Ethereum, Base, Polygon, Solana
1708
+ * },
1709
+ * tokenIn: 'USDC',
1710
+ * tokenOut: 'USDT',
1711
+ * amount: '100.0'
1712
+ * })
1713
+ * ```
1714
+ *
1715
+ * @example
1716
+ * ```typescript
1717
+ * // String literals also work (constrained to SwapChain values)
1718
+ * const result = await swap(context, {
1719
+ * from: {
1720
+ * adapter,
1721
+ * chain: 'Ethereum' // ✅ Only SwapChain strings allowed
1722
+ * },
1723
+ * tokenIn: 'USDC',
1724
+ * tokenOut: 'NATIVE',
1725
+ * amount: '50.0'
1726
+ * })
1727
+ *
1728
+ * // ❌ TypeScript error - Sui not in SwapChain enum
1729
+ * const invalidResult = await swap(context, {
1730
+ * from: {
1731
+ * adapter,
1732
+ * chain: 'Sui' // Compile-time error!
1733
+ * },
1734
+ * tokenIn: 'USDC',
1735
+ * tokenOut: 'USDT',
1736
+ * amount: '100.0'
1737
+ * })
1738
+ * ```
1739
+ */
1740
+ /**
1741
+ * Enum representing chains that support same-chain swaps through the Swap Kit.
1742
+ *
1743
+ * Unlike the full {@link Blockchain} enum, SwapChain includes only mainnet
1744
+ * networks where adapter contracts are deployed (CCTPv2 support).
1745
+ *
1746
+ * Dynamic validation via {@link isSwapSupportedChain} ensures chains
1747
+ * automatically work when adapter contracts and supported tokens are deployed.
1748
+ *
1749
+ * @example
1750
+ * ```typescript
1751
+ * import { SwapChain } from '@core/chains'
1752
+ * import { swap } from '@circle-fin/swap-kit'
1753
+ *
1754
+ * const result = await swap(context, {
1755
+ * from: {
1756
+ * adapter,
1757
+ * chain: SwapChain.Arbitrum // Now supported!
1758
+ * },
1759
+ * tokenIn: 'USDC',
1760
+ * tokenOut: 'WETH',
1761
+ * amount: '100.0'
1762
+ * })
1763
+ * ```
1764
+ *
1765
+ * @see {@link isSwapSupportedChain} for runtime validation
1766
+ * @see {@link getSwapSupportedChains} for all supported chains
1767
+ */
1768
+ var SwapChain;
1769
+ (function (SwapChain) {
1770
+ // Original 4 chains
1771
+ SwapChain["Ethereum"] = "Ethereum";
1772
+ SwapChain["Base"] = "Base";
1773
+ SwapChain["Polygon"] = "Polygon";
1774
+ SwapChain["Solana"] = "Solana";
1775
+ // Additional supported chains
1776
+ SwapChain["Arbitrum"] = "Arbitrum";
1777
+ SwapChain["Optimism"] = "Optimism";
1778
+ SwapChain["Avalanche"] = "Avalanche";
1779
+ SwapChain["Linea"] = "Linea";
1780
+ SwapChain["Ink"] = "Ink";
1781
+ SwapChain["World_Chain"] = "World_Chain";
1782
+ SwapChain["Unichain"] = "Unichain";
1783
+ SwapChain["Plume"] = "Plume";
1784
+ SwapChain["Sei"] = "Sei";
1785
+ SwapChain["Sonic"] = "Sonic";
1786
+ SwapChain["XDC"] = "XDC";
1787
+ SwapChain["HyperEVM"] = "HyperEVM";
1788
+ SwapChain["Monad"] = "Monad";
1789
+ })(SwapChain || (SwapChain = {}));
1260
1790
  // -----------------------------------------------------------------------------
1261
1791
  // Bridge Chain Enum (CCTPv2 Supported Chains)
1262
1792
  // -----------------------------------------------------------------------------
@@ -1408,6 +1938,7 @@ const Algorand = defineChain({
1408
1938
  rpcEndpoints: ['https://mainnet-api.algonode.cloud'],
1409
1939
  eurcAddress: null,
1410
1940
  usdcAddress: '31566704',
1941
+ usdtAddress: null,
1411
1942
  cctp: null,
1412
1943
  });
1413
1944
 
@@ -1431,6 +1962,7 @@ const AlgorandTestnet = defineChain({
1431
1962
  rpcEndpoints: ['https://testnet-api.algonode.cloud'],
1432
1963
  eurcAddress: null,
1433
1964
  usdcAddress: '10458941',
1965
+ usdtAddress: null,
1434
1966
  cctp: null,
1435
1967
  });
1436
1968
 
@@ -1454,6 +1986,7 @@ const Aptos = defineChain({
1454
1986
  rpcEndpoints: ['https://fullnode.mainnet.aptoslabs.com/v1'],
1455
1987
  eurcAddress: null,
1456
1988
  usdcAddress: '0xbae207659db88bea0cbead6da0ed00aac12edcdda169e591cd41c94180b46f3b',
1989
+ usdtAddress: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
1457
1990
  cctp: {
1458
1991
  domain: 9,
1459
1992
  contracts: {
@@ -1491,6 +2024,7 @@ const AptosTestnet = defineChain({
1491
2024
  rpcEndpoints: ['https://fullnode.testnet.aptoslabs.com/v1'],
1492
2025
  eurcAddress: null,
1493
2026
  usdcAddress: '0x69091fbab5f7d635ee7ac5098cf0c1efbe31d68fec0f2cd565e8d168daf52832',
2027
+ usdtAddress: null,
1494
2028
  cctp: {
1495
2029
  domain: 9,
1496
2030
  contracts: {
@@ -1508,6 +2042,121 @@ const AptosTestnet = defineChain({
1508
2042
  },
1509
2043
  });
1510
2044
 
2045
+ /**
2046
+ * Complete swap token registry - single source of truth for all swap-supported tokens.
2047
+ *
2048
+ * @remarks
2049
+ * All packages should import from this registry for swap operations.
2050
+ * Adding a new swap token requires updating only this registry.
2051
+ *
2052
+ * The NATIVE token is handled separately as it resolves dynamically based on chain.
2053
+ *
2054
+ * @example
2055
+ * ```typescript
2056
+ * import { SWAP_TOKEN_REGISTRY } from '@core/chains'
2057
+ *
2058
+ * // Get token decimals
2059
+ * const decimals = SWAP_TOKEN_REGISTRY.USDC.decimals // 6
2060
+ *
2061
+ * // Check if token is stablecoin
2062
+ * const isStable = SWAP_TOKEN_REGISTRY.DAI.category === 'stablecoin' // true
2063
+ * ```
2064
+ */
2065
+ const SWAP_TOKEN_REGISTRY = {
2066
+ // ============================================================================
2067
+ // Stablecoins (6 decimals)
2068
+ // ============================================================================
2069
+ USDC: {
2070
+ symbol: 'USDC',
2071
+ decimals: 6,
2072
+ category: 'stablecoin',
2073
+ description: 'USD Coin',
2074
+ },
2075
+ EURC: {
2076
+ symbol: 'EURC',
2077
+ decimals: 6,
2078
+ category: 'stablecoin',
2079
+ description: 'Euro Coin',
2080
+ },
2081
+ USDT: {
2082
+ symbol: 'USDT',
2083
+ decimals: 6,
2084
+ category: 'stablecoin',
2085
+ description: 'Tether USD',
2086
+ },
2087
+ PYUSD: {
2088
+ symbol: 'PYUSD',
2089
+ decimals: 6,
2090
+ category: 'stablecoin',
2091
+ description: 'PayPal USD',
2092
+ },
2093
+ // ============================================================================
2094
+ // Stablecoins (18 decimals)
2095
+ // ============================================================================
2096
+ DAI: {
2097
+ symbol: 'DAI',
2098
+ decimals: 18,
2099
+ category: 'stablecoin',
2100
+ description: 'MakerDAO stablecoin',
2101
+ },
2102
+ USDE: {
2103
+ symbol: 'USDE',
2104
+ decimals: 18,
2105
+ category: 'stablecoin',
2106
+ description: 'Ethena USD (synthetic dollar)',
2107
+ },
2108
+ // ============================================================================
2109
+ // Wrapped Tokens
2110
+ // ============================================================================
2111
+ WBTC: {
2112
+ symbol: 'WBTC',
2113
+ decimals: 8,
2114
+ category: 'wrapped',
2115
+ description: 'Wrapped Bitcoin',
2116
+ },
2117
+ WETH: {
2118
+ symbol: 'WETH',
2119
+ decimals: 18,
2120
+ category: 'wrapped',
2121
+ description: 'Wrapped Ethereum',
2122
+ },
2123
+ WSOL: {
2124
+ symbol: 'WSOL',
2125
+ decimals: 9,
2126
+ category: 'wrapped',
2127
+ description: 'Wrapped Solana',
2128
+ },
2129
+ WAVAX: {
2130
+ symbol: 'WAVAX',
2131
+ decimals: 18,
2132
+ category: 'wrapped',
2133
+ description: 'Wrapped Avalanche',
2134
+ },
2135
+ WPOL: {
2136
+ symbol: 'WPOL',
2137
+ decimals: 18,
2138
+ category: 'wrapped',
2139
+ description: 'Wrapped Polygon',
2140
+ },
2141
+ };
2142
+ /**
2143
+ * Special NATIVE token constant for swap operations.
2144
+ *
2145
+ * @remarks
2146
+ * NATIVE is handled separately from SWAP_TOKEN_REGISTRY because it resolves
2147
+ * dynamically based on the chain (ETH on Ethereum, SOL on Solana, etc.).
2148
+ * Its decimals are chain-specific.
2149
+ */
2150
+ const NATIVE_TOKEN = 'NATIVE';
2151
+ /**
2152
+ * Array of all supported swap token symbols including NATIVE.
2153
+ * Useful for iteration, validation, and filtering.
2154
+ */
2155
+ [
2156
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
2157
+ NATIVE_TOKEN,
2158
+ ];
2159
+
1511
2160
  /**
1512
2161
  * The bridge contract address for EVM testnet networks.
1513
2162
  *
@@ -1524,6 +2173,13 @@ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d'
1524
2173
  * USDC transfers on live networks.
1525
2174
  */
1526
2175
  const BRIDGE_CONTRACT_EVM_MAINNET = '0xB3FA262d0fB521cc93bE83d87b322b8A23DAf3F0';
2176
+ /**
2177
+ * The adapter contract address for EVM mainnet networks.
2178
+ *
2179
+ * This contract serves as an adapter for integrating with various protocols
2180
+ * on EVM-compatible chains. Use this address for mainnet adapter integrations.
2181
+ */
2182
+ const ADAPTER_CONTRACT_EVM_MAINNET = '0x7FB8c7260b63934d8da38aF902f87ae6e284a845';
1527
2183
 
1528
2184
  /**
1529
2185
  * Arc Testnet chain definition
@@ -1553,6 +2209,7 @@ const ArcTestnet = defineChain({
1553
2209
  rpcEndpoints: ['https://rpc.testnet.arc.network/'],
1554
2210
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
1555
2211
  usdcAddress: '0x3600000000000000000000000000000000000000',
2212
+ usdtAddress: null,
1556
2213
  cctp: {
1557
2214
  domain: 26,
1558
2215
  contracts: {
@@ -1595,6 +2252,7 @@ const Arbitrum = defineChain({
1595
2252
  rpcEndpoints: ['https://arb1.arbitrum.io/rpc'],
1596
2253
  eurcAddress: null,
1597
2254
  usdcAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831',
2255
+ usdtAddress: null,
1598
2256
  cctp: {
1599
2257
  domain: 3,
1600
2258
  contracts: {
@@ -1619,6 +2277,7 @@ const Arbitrum = defineChain({
1619
2277
  },
1620
2278
  kitContracts: {
1621
2279
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2280
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1622
2281
  },
1623
2282
  });
1624
2283
 
@@ -1643,6 +2302,7 @@ const ArbitrumSepolia = defineChain({
1643
2302
  rpcEndpoints: ['https://sepolia-rollup.arbitrum.io/rpc'],
1644
2303
  eurcAddress: null,
1645
2304
  usdcAddress: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
2305
+ usdtAddress: null,
1646
2306
  cctp: {
1647
2307
  domain: 3,
1648
2308
  contracts: {
@@ -1691,6 +2351,7 @@ const Avalanche = defineChain({
1691
2351
  rpcEndpoints: ['https://api.avax.network/ext/bc/C/rpc'],
1692
2352
  eurcAddress: '0xc891eb4cbdeff6e073e859e987815ed1505c2acd',
1693
2353
  usdcAddress: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
2354
+ usdtAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
1694
2355
  cctp: {
1695
2356
  domain: 1,
1696
2357
  contracts: {
@@ -1715,6 +2376,7 @@ const Avalanche = defineChain({
1715
2376
  },
1716
2377
  kitContracts: {
1717
2378
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2379
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1718
2380
  },
1719
2381
  });
1720
2382
 
@@ -1738,6 +2400,7 @@ const AvalancheFuji = defineChain({
1738
2400
  explorerUrl: 'https://subnets-test.avax.network/c-chain/tx/{hash}',
1739
2401
  eurcAddress: '0x5e44db7996c682e92a960b65ac713a54ad815c6b',
1740
2402
  usdcAddress: '0x5425890298aed601595a70ab815c96711a31bc65',
2403
+ usdtAddress: null,
1741
2404
  cctp: {
1742
2405
  domain: 1,
1743
2406
  contracts: {
@@ -1787,6 +2450,7 @@ const Base = defineChain({
1787
2450
  rpcEndpoints: ['https://mainnet.base.org', 'https://base.publicnode.com'],
1788
2451
  eurcAddress: '0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42',
1789
2452
  usdcAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
2453
+ usdtAddress: null,
1790
2454
  cctp: {
1791
2455
  domain: 6,
1792
2456
  contracts: {
@@ -1811,6 +2475,7 @@ const Base = defineChain({
1811
2475
  },
1812
2476
  kitContracts: {
1813
2477
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2478
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
1814
2479
  },
1815
2480
  });
1816
2481
 
@@ -1835,6 +2500,7 @@ const BaseSepolia = defineChain({
1835
2500
  rpcEndpoints: ['https://sepolia.base.org'],
1836
2501
  eurcAddress: '0x808456652fdb597867f38412077A9182bf77359F',
1837
2502
  usdcAddress: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
2503
+ usdtAddress: null,
1838
2504
  cctp: {
1839
2505
  domain: 6,
1840
2506
  contracts: {
@@ -1883,6 +2549,7 @@ const Celo = defineChain({
1883
2549
  rpcEndpoints: ['https://forno.celo.org'],
1884
2550
  eurcAddress: null,
1885
2551
  usdcAddress: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
2552
+ usdtAddress: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
1886
2553
  cctp: null,
1887
2554
  });
1888
2555
 
@@ -1907,6 +2574,7 @@ const CeloAlfajoresTestnet = defineChain({
1907
2574
  rpcEndpoints: ['https://alfajores-forno.celo-testnet.org'],
1908
2575
  eurcAddress: null,
1909
2576
  usdcAddress: '0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B',
2577
+ usdtAddress: null,
1910
2578
  cctp: null,
1911
2579
  });
1912
2580
 
@@ -1931,6 +2599,7 @@ const Codex = defineChain({
1931
2599
  rpcEndpoints: ['https://rpc.codex.xyz'],
1932
2600
  eurcAddress: null,
1933
2601
  usdcAddress: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
2602
+ usdtAddress: null,
1934
2603
  cctp: {
1935
2604
  domain: 12,
1936
2605
  contracts: {
@@ -1973,6 +2642,7 @@ const CodexTestnet = defineChain({
1973
2642
  rpcEndpoints: ['https://rpc.codex-stg.xyz'],
1974
2643
  eurcAddress: null,
1975
2644
  usdcAddress: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
2645
+ usdtAddress: null,
1976
2646
  cctp: {
1977
2647
  domain: 12,
1978
2648
  contracts: {
@@ -2015,6 +2685,7 @@ const Ethereum = defineChain({
2015
2685
  rpcEndpoints: ['https://eth.merkle.io', 'https://ethereum.publicnode.com'],
2016
2686
  eurcAddress: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
2017
2687
  usdcAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
2688
+ usdtAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7',
2018
2689
  cctp: {
2019
2690
  domain: 0,
2020
2691
  contracts: {
@@ -2039,6 +2710,7 @@ const Ethereum = defineChain({
2039
2710
  },
2040
2711
  kitContracts: {
2041
2712
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2713
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2042
2714
  },
2043
2715
  });
2044
2716
 
@@ -2063,6 +2735,7 @@ const EthereumSepolia = defineChain({
2063
2735
  rpcEndpoints: ['https://sepolia.drpc.org'],
2064
2736
  eurcAddress: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
2065
2737
  usdcAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
2738
+ usdtAddress: null,
2066
2739
  cctp: {
2067
2740
  domain: 0,
2068
2741
  contracts: {
@@ -2110,6 +2783,7 @@ const Hedera = defineChain({
2110
2783
  rpcEndpoints: ['https://mainnet.hashio.io/api'],
2111
2784
  eurcAddress: null,
2112
2785
  usdcAddress: '0.0.456858',
2786
+ usdtAddress: null,
2113
2787
  cctp: null,
2114
2788
  });
2115
2789
 
@@ -2133,6 +2807,7 @@ const HederaTestnet = defineChain({
2133
2807
  rpcEndpoints: ['https://testnet.hashio.io/api'],
2134
2808
  eurcAddress: null,
2135
2809
  usdcAddress: '0.0.429274',
2810
+ usdtAddress: null,
2136
2811
  cctp: null,
2137
2812
  });
2138
2813
 
@@ -2159,6 +2834,7 @@ const HyperEVM = defineChain({
2159
2834
  rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
2160
2835
  eurcAddress: null,
2161
2836
  usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
2837
+ usdtAddress: null,
2162
2838
  cctp: {
2163
2839
  domain: 19,
2164
2840
  contracts: {
@@ -2177,6 +2853,7 @@ const HyperEVM = defineChain({
2177
2853
  },
2178
2854
  kitContracts: {
2179
2855
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2856
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2180
2857
  },
2181
2858
  });
2182
2859
 
@@ -2202,6 +2879,7 @@ const HyperEVMTestnet = defineChain({
2202
2879
  rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
2203
2880
  eurcAddress: null,
2204
2881
  usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
2882
+ usdtAddress: null,
2205
2883
  cctp: {
2206
2884
  domain: 19,
2207
2885
  contracts: {
@@ -2249,6 +2927,7 @@ const Ink = defineChain({
2249
2927
  ],
2250
2928
  eurcAddress: null,
2251
2929
  usdcAddress: '0x2D270e6886d130D724215A266106e6832161EAEd',
2930
+ usdtAddress: null,
2252
2931
  cctp: {
2253
2932
  domain: 21,
2254
2933
  contracts: {
@@ -2267,6 +2946,7 @@ const Ink = defineChain({
2267
2946
  },
2268
2947
  kitContracts: {
2269
2948
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
2949
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2270
2950
  },
2271
2951
  });
2272
2952
 
@@ -2295,6 +2975,7 @@ const InkTestnet = defineChain({
2295
2975
  ],
2296
2976
  eurcAddress: null,
2297
2977
  usdcAddress: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
2978
+ usdtAddress: null,
2298
2979
  cctp: {
2299
2980
  domain: 21,
2300
2981
  contracts: {
@@ -2337,6 +3018,7 @@ const Linea = defineChain({
2337
3018
  rpcEndpoints: ['https://rpc.linea.build'],
2338
3019
  eurcAddress: null,
2339
3020
  usdcAddress: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
3021
+ usdtAddress: null,
2340
3022
  cctp: {
2341
3023
  domain: 11,
2342
3024
  contracts: {
@@ -2355,6 +3037,7 @@ const Linea = defineChain({
2355
3037
  },
2356
3038
  kitContracts: {
2357
3039
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3040
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2358
3041
  },
2359
3042
  });
2360
3043
 
@@ -2379,6 +3062,7 @@ const LineaSepolia = defineChain({
2379
3062
  rpcEndpoints: ['https://rpc.sepolia.linea.build'],
2380
3063
  eurcAddress: null,
2381
3064
  usdcAddress: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
3065
+ usdtAddress: null,
2382
3066
  cctp: {
2383
3067
  domain: 11,
2384
3068
  contracts: {
@@ -2423,6 +3107,7 @@ const Monad = defineChain({
2423
3107
  rpcEndpoints: ['https://rpc.monad.xyz'],
2424
3108
  eurcAddress: null,
2425
3109
  usdcAddress: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
3110
+ usdtAddress: null,
2426
3111
  cctp: {
2427
3112
  domain: 15,
2428
3113
  contracts: {
@@ -2441,6 +3126,7 @@ const Monad = defineChain({
2441
3126
  },
2442
3127
  kitContracts: {
2443
3128
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3129
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2444
3130
  },
2445
3131
  });
2446
3132
 
@@ -2467,6 +3153,7 @@ const MonadTestnet = defineChain({
2467
3153
  rpcEndpoints: ['https://testnet-rpc.monad.xyz'],
2468
3154
  eurcAddress: null,
2469
3155
  usdcAddress: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
3156
+ usdtAddress: null,
2470
3157
  cctp: {
2471
3158
  domain: 15,
2472
3159
  contracts: {
@@ -2508,6 +3195,7 @@ const NEAR = defineChain({
2508
3195
  rpcEndpoints: ['https://eth-rpc.mainnet.near.org'],
2509
3196
  eurcAddress: null,
2510
3197
  usdcAddress: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
3198
+ usdtAddress: 'usdt.tether-token.near',
2511
3199
  cctp: null,
2512
3200
  });
2513
3201
 
@@ -2531,6 +3219,7 @@ const NEARTestnet = defineChain({
2531
3219
  rpcEndpoints: ['https://eth-rpc.testnet.near.org'],
2532
3220
  eurcAddress: null,
2533
3221
  usdcAddress: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
3222
+ usdtAddress: null,
2534
3223
  cctp: null,
2535
3224
  });
2536
3225
 
@@ -2554,6 +3243,7 @@ const Noble = defineChain({
2554
3243
  rpcEndpoints: ['https://noble-rpc.polkachu.com'],
2555
3244
  eurcAddress: null,
2556
3245
  usdcAddress: 'uusdc',
3246
+ usdtAddress: null,
2557
3247
  cctp: {
2558
3248
  domain: 4,
2559
3249
  contracts: {
@@ -2590,6 +3280,7 @@ const NobleTestnet = defineChain({
2590
3280
  rpcEndpoints: ['https://noble-testnet-rpc.polkachu.com'],
2591
3281
  eurcAddress: null,
2592
3282
  usdcAddress: 'uusdc',
3283
+ usdtAddress: null,
2593
3284
  cctp: {
2594
3285
  domain: 4,
2595
3286
  contracts: {
@@ -2627,6 +3318,7 @@ const Optimism = defineChain({
2627
3318
  rpcEndpoints: ['https://mainnet.optimism.io'],
2628
3319
  eurcAddress: null,
2629
3320
  usdcAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
3321
+ usdtAddress: null,
2630
3322
  cctp: {
2631
3323
  domain: 2,
2632
3324
  contracts: {
@@ -2651,6 +3343,7 @@ const Optimism = defineChain({
2651
3343
  },
2652
3344
  kitContracts: {
2653
3345
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3346
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2654
3347
  },
2655
3348
  });
2656
3349
 
@@ -2675,6 +3368,7 @@ const OptimismSepolia = defineChain({
2675
3368
  rpcEndpoints: ['https://sepolia.optimism.io'],
2676
3369
  eurcAddress: null,
2677
3370
  usdcAddress: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
3371
+ usdtAddress: null,
2678
3372
  cctp: {
2679
3373
  domain: 2,
2680
3374
  contracts: {
@@ -2725,6 +3419,7 @@ const Plume = defineChain({
2725
3419
  rpcEndpoints: ['https://rpc.plume.org'],
2726
3420
  eurcAddress: null,
2727
3421
  usdcAddress: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
3422
+ usdtAddress: null,
2728
3423
  cctp: {
2729
3424
  domain: 22,
2730
3425
  contracts: {
@@ -2743,6 +3438,7 @@ const Plume = defineChain({
2743
3438
  },
2744
3439
  kitContracts: {
2745
3440
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3441
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2746
3442
  },
2747
3443
  });
2748
3444
 
@@ -2768,6 +3464,7 @@ const PlumeTestnet = defineChain({
2768
3464
  rpcEndpoints: ['https://testnet-rpc.plume.org'],
2769
3465
  eurcAddress: null,
2770
3466
  usdcAddress: '0xcB5f30e335672893c7eb944B374c196392C19D18',
3467
+ usdtAddress: null,
2771
3468
  cctp: {
2772
3469
  domain: 22,
2773
3470
  contracts: {
@@ -2809,6 +3506,7 @@ const PolkadotAssetHub = defineChain({
2809
3506
  rpcEndpoints: ['https://asset-hub-polkadot-rpc.n.dwellir.com'],
2810
3507
  eurcAddress: null,
2811
3508
  usdcAddress: '1337',
3509
+ usdtAddress: '1984',
2812
3510
  cctp: null,
2813
3511
  });
2814
3512
 
@@ -2832,6 +3530,7 @@ const PolkadotWestmint = defineChain({
2832
3530
  rpcEndpoints: ['https://westmint-rpc.polkadot.io'],
2833
3531
  eurcAddress: null,
2834
3532
  usdcAddress: 'Asset ID 31337',
3533
+ usdtAddress: null,
2835
3534
  cctp: null,
2836
3535
  });
2837
3536
 
@@ -2853,9 +3552,10 @@ const Polygon = defineChain({
2853
3552
  chainId: 137,
2854
3553
  isTestnet: false,
2855
3554
  explorerUrl: 'https://polygonscan.com/tx/{hash}',
2856
- rpcEndpoints: ['https://polygon-rpc.com', 'https://polygon.publicnode.com'],
3555
+ rpcEndpoints: ['https://polygon.publicnode.com', 'https://polygon.drpc.org'],
2857
3556
  eurcAddress: null,
2858
3557
  usdcAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
3558
+ usdtAddress: null,
2859
3559
  cctp: {
2860
3560
  domain: 7,
2861
3561
  contracts: {
@@ -2880,6 +3580,7 @@ const Polygon = defineChain({
2880
3580
  },
2881
3581
  kitContracts: {
2882
3582
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3583
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2883
3584
  },
2884
3585
  });
2885
3586
 
@@ -2904,6 +3605,7 @@ const PolygonAmoy = defineChain({
2904
3605
  rpcEndpoints: ['https://rpc-amoy.polygon.technology'],
2905
3606
  eurcAddress: null,
2906
3607
  usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
3608
+ usdtAddress: null,
2907
3609
  cctp: {
2908
3610
  domain: 7,
2909
3611
  contracts: {
@@ -2954,6 +3656,7 @@ const Sei = defineChain({
2954
3656
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
2955
3657
  eurcAddress: null,
2956
3658
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
3659
+ usdtAddress: null,
2957
3660
  cctp: {
2958
3661
  domain: 16,
2959
3662
  contracts: {
@@ -2972,6 +3675,7 @@ const Sei = defineChain({
2972
3675
  },
2973
3676
  kitContracts: {
2974
3677
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3678
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
2975
3679
  },
2976
3680
  });
2977
3681
 
@@ -2997,6 +3701,7 @@ const SeiTestnet = defineChain({
2997
3701
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
2998
3702
  eurcAddress: null,
2999
3703
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
3704
+ usdtAddress: null,
3000
3705
  cctp: {
3001
3706
  domain: 16,
3002
3707
  contracts: {
@@ -3039,6 +3744,7 @@ const Sonic = defineChain({
3039
3744
  rpcEndpoints: ['https://rpc.soniclabs.com'],
3040
3745
  eurcAddress: null,
3041
3746
  usdcAddress: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
3747
+ usdtAddress: null,
3042
3748
  cctp: {
3043
3749
  domain: 13,
3044
3750
  contracts: {
@@ -3057,6 +3763,7 @@ const Sonic = defineChain({
3057
3763
  },
3058
3764
  kitContracts: {
3059
3765
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3766
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
3060
3767
  },
3061
3768
  });
3062
3769
 
@@ -3081,6 +3788,7 @@ const SonicTestnet = defineChain({
3081
3788
  rpcEndpoints: ['https://rpc.testnet.soniclabs.com'],
3082
3789
  eurcAddress: null,
3083
3790
  usdcAddress: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
3791
+ usdtAddress: null,
3084
3792
  cctp: {
3085
3793
  domain: 13,
3086
3794
  contracts: {
@@ -3122,6 +3830,7 @@ const Solana = defineChain({
3122
3830
  rpcEndpoints: ['https://api.mainnet-beta.solana.com'],
3123
3831
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
3124
3832
  usdcAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
3833
+ usdtAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
3125
3834
  cctp: {
3126
3835
  domain: 5,
3127
3836
  contracts: {
@@ -3168,6 +3877,7 @@ const SolanaDevnet = defineChain({
3168
3877
  explorerUrl: 'https://solscan.io/tx/{hash}?cluster=devnet',
3169
3878
  eurcAddress: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
3170
3879
  usdcAddress: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
3880
+ usdtAddress: null,
3171
3881
  cctp: {
3172
3882
  domain: 5,
3173
3883
  contracts: {
@@ -3216,6 +3926,7 @@ const Stellar = defineChain({
3216
3926
  rpcEndpoints: ['https://horizon.stellar.org'],
3217
3927
  eurcAddress: 'EURC-GDHU6WRG4IEQXM5NZ4BMPKOXHW76MZM4Y2IEMFDVXBSDP6SJY4ITNPP2',
3218
3928
  usdcAddress: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
3929
+ usdtAddress: null,
3219
3930
  cctp: null,
3220
3931
  });
3221
3932
 
@@ -3239,6 +3950,7 @@ const StellarTestnet = defineChain({
3239
3950
  rpcEndpoints: ['https://horizon-testnet.stellar.org'],
3240
3951
  eurcAddress: 'EURC-GB3Q6QDZYTHWT7E5PVS3W7FUT5GVAFC5KSZFFLPU25GO7VTC3NM2ZTVO',
3241
3952
  usdcAddress: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
3953
+ usdtAddress: null,
3242
3954
  cctp: null,
3243
3955
  });
3244
3956
 
@@ -3262,6 +3974,7 @@ const Sui = defineChain({
3262
3974
  rpcEndpoints: ['https://fullnode.mainnet.sui.io'],
3263
3975
  eurcAddress: null,
3264
3976
  usdcAddress: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
3977
+ usdtAddress: null,
3265
3978
  cctp: {
3266
3979
  domain: 8,
3267
3980
  contracts: {
@@ -3299,6 +4012,7 @@ const SuiTestnet = defineChain({
3299
4012
  rpcEndpoints: ['https://fullnode.testnet.sui.io'],
3300
4013
  eurcAddress: null,
3301
4014
  usdcAddress: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
4015
+ usdtAddress: null,
3302
4016
  cctp: {
3303
4017
  domain: 8,
3304
4018
  contracts: {
@@ -3337,6 +4051,7 @@ const Unichain = defineChain({
3337
4051
  rpcEndpoints: ['https://mainnet.unichain.org'],
3338
4052
  eurcAddress: null,
3339
4053
  usdcAddress: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
4054
+ usdtAddress: null,
3340
4055
  cctp: {
3341
4056
  domain: 10,
3342
4057
  contracts: {
@@ -3361,6 +4076,7 @@ const Unichain = defineChain({
3361
4076
  },
3362
4077
  kitContracts: {
3363
4078
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4079
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
3364
4080
  },
3365
4081
  });
3366
4082
 
@@ -3385,6 +4101,7 @@ const UnichainSepolia = defineChain({
3385
4101
  rpcEndpoints: ['https://sepolia.unichain.org'],
3386
4102
  eurcAddress: null,
3387
4103
  usdcAddress: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
4104
+ usdtAddress: null,
3388
4105
  cctp: {
3389
4106
  domain: 10,
3390
4107
  contracts: {
@@ -3433,6 +4150,7 @@ const WorldChain = defineChain({
3433
4150
  rpcEndpoints: ['https://worldchain-mainnet.g.alchemy.com/public'],
3434
4151
  eurcAddress: null,
3435
4152
  usdcAddress: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
4153
+ usdtAddress: null,
3436
4154
  cctp: {
3437
4155
  domain: 14,
3438
4156
  contracts: {
@@ -3451,6 +4169,7 @@ const WorldChain = defineChain({
3451
4169
  },
3452
4170
  kitContracts: {
3453
4171
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4172
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
3454
4173
  },
3455
4174
  });
3456
4175
 
@@ -3478,6 +4197,7 @@ const WorldChainSepolia = defineChain({
3478
4197
  ],
3479
4198
  eurcAddress: null,
3480
4199
  usdcAddress: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
4200
+ usdtAddress: null,
3481
4201
  cctp: {
3482
4202
  domain: 14,
3483
4203
  contracts: {
@@ -3522,6 +4242,7 @@ const XDC = defineChain({
3522
4242
  rpcEndpoints: ['https://erpc.xdcrpc.com', 'https://erpc.xinfin.network'],
3523
4243
  eurcAddress: null,
3524
4244
  usdcAddress: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
4245
+ usdtAddress: null,
3525
4246
  cctp: {
3526
4247
  domain: 18,
3527
4248
  contracts: {
@@ -3540,6 +4261,7 @@ const XDC = defineChain({
3540
4261
  },
3541
4262
  kitContracts: {
3542
4263
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4264
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET,
3543
4265
  },
3544
4266
  });
3545
4267
 
@@ -3564,6 +4286,7 @@ const XDCApothem = defineChain({
3564
4286
  rpcEndpoints: ['https://erpc.apothem.network'],
3565
4287
  eurcAddress: null,
3566
4288
  usdcAddress: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
4289
+ usdtAddress: null,
3567
4290
  cctp: {
3568
4291
  domain: 18,
3569
4292
  contracts: {
@@ -3606,6 +4329,7 @@ const ZKSyncEra = defineChain({
3606
4329
  rpcEndpoints: ['https://mainnet.era.zksync.io'],
3607
4330
  eurcAddress: null,
3608
4331
  usdcAddress: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
4332
+ usdtAddress: null,
3609
4333
  cctp: null,
3610
4334
  });
3611
4335
 
@@ -3630,6 +4354,7 @@ const ZKSyncEraSepolia = defineChain({
3630
4354
  rpcEndpoints: ['https://sepolia.era.zksync.dev'],
3631
4355
  eurcAddress: null,
3632
4356
  usdcAddress: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
4357
+ usdtAddress: null,
3633
4358
  cctp: null,
3634
4359
  });
3635
4360
 
@@ -3775,10 +4500,12 @@ const baseChainDefinitionSchema = zod.z.object({
3775
4500
  rpcEndpoints: zod.z.array(zod.z.string()),
3776
4501
  eurcAddress: zod.z.string().nullable(),
3777
4502
  usdcAddress: zod.z.string().nullable(),
4503
+ usdtAddress: zod.z.string().nullable(),
3778
4504
  cctp: zod.z.any().nullable(), // We'll accept any CCTP config structure
3779
4505
  kitContracts: zod.z
3780
4506
  .object({
3781
4507
  bridge: zod.z.string().optional(),
4508
+ adapter: zod.z.string().optional(),
3782
4509
  })
3783
4510
  .optional(),
3784
4511
  });
@@ -3893,6 +4620,29 @@ zod.z.union([
3893
4620
  zod.z.nativeEnum(exports.Blockchain),
3894
4621
  chainDefinitionSchema$1,
3895
4622
  ]);
4623
+ /**
4624
+ * Zod schema for validating swap-specific chain identifiers.
4625
+ *
4626
+ * Validates chains based on:
4627
+ * - CCTPv2 support (adapter contract deployed)
4628
+ * - Mainnet only (no testnets)
4629
+ * - At least one supported token available
4630
+ *
4631
+ */
4632
+ zod.z.union([
4633
+ // String blockchain identifier (accepts SwapChain enum values)
4634
+ zod.z.string().refine((val) => val in SwapChain, (val) => ({
4635
+ message: `"${val}" is not a supported swap chain. ` +
4636
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
4637
+ })),
4638
+ // SwapChain enum
4639
+ zod.z.nativeEnum(SwapChain),
4640
+ // ChainDefinition object (checks if chain.chain is in SwapChain)
4641
+ chainDefinitionSchema$1.refine((chain) => chain.chain in SwapChain, (chain) => ({
4642
+ message: `"${chain.chain}" is not a supported swap chain. ` +
4643
+ `Supported chains: ${Object.values(SwapChain).join(', ')}`,
4644
+ })),
4645
+ ]);
3896
4646
  /**
3897
4647
  * Zod schema for validating bridge chain identifiers.
3898
4648
  *
@@ -3932,12 +4682,44 @@ zod.z.union([
3932
4682
  })),
3933
4683
  ]);
3934
4684
 
4685
+ /**
4686
+ * @packageDocumentation
4687
+ * @module SwapTokenSchemas
4688
+ *
4689
+ * Zod validation schemas for supported swap tokens.
4690
+ */
4691
+ // Internal enum used after input normalization.
4692
+ const swapTokenEnumSchema = zod.z.enum([
4693
+ ...Object.keys(SWAP_TOKEN_REGISTRY),
4694
+ NATIVE_TOKEN,
4695
+ ]);
4696
+ /**
4697
+ * Zod schema for validating supported swap token symbols.
4698
+ *
4699
+ * Accepts any token symbol from the SWAP_TOKEN_REGISTRY plus NATIVE.
4700
+ * Input matching is case-insensitive and normalized to uppercase.
4701
+ *
4702
+ * @example
4703
+ * ```typescript
4704
+ * import { supportedSwapTokenSchema } from '@core/chains'
4705
+ *
4706
+ * const result = supportedSwapTokenSchema.safeParse('USDC')
4707
+ * if (result.success) {
4708
+ * console.log('Valid swap token:', result.data)
4709
+ * }
4710
+ * ```
4711
+ */
4712
+ zod.z
4713
+ .string()
4714
+ .transform((value) => value.toUpperCase())
4715
+ .pipe(swapTokenEnumSchema);
4716
+
3935
4717
  /**
3936
4718
  * Get all supported EVM chain definitions.
3937
4719
  *
3938
4720
  * This function searches through all available blockchain definitions and returns
3939
4721
  * only those that are EVM-compatible. It provides a comprehensive list of all
3940
- * EVM chains supported by the Stablecoin Kits ecosystem.
4722
+ * EVM chains supported by the App Kits ecosystem.
3941
4723
  *
3942
4724
  * @returns Array of all EVM chain definitions supported by the library
3943
4725
  *
@@ -4031,6 +4813,11 @@ const getChainByEnum = (blockchain) => {
4031
4813
  * ```
4032
4814
  */
4033
4815
  function resolveChainIdentifier(chainIdentifier) {
4816
+ // Handle null explicitly (typeof null === 'object' in JavaScript)
4817
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
4818
+ if (chainIdentifier === null) {
4819
+ throw new Error(`Invalid chain identifier type: null. Expected ChainDefinition object, Blockchain enum, or string literal.`);
4820
+ }
4034
4821
  // If it's already a ChainDefinition object, return it unchanged
4035
4822
  if (typeof chainIdentifier === 'object') {
4036
4823
  return chainIdentifier;
@@ -4393,11 +5180,11 @@ async function resolveOperationContext(adapter, ctx) {
4393
5180
  /**
4394
5181
  * Abstract class defining the standard interface for an adapter that interacts with a specific blockchain.
4395
5182
  *
4396
- * A `Adapter` is responsible for encapsulating chain-specific logic necessary to
5183
+ * An `Adapter` is responsible for encapsulating chain-specific logic necessary to
4397
5184
  * perform operations like sending transactions, querying balances, or interacting with smart contracts.
4398
5185
  * Implementations of this class will provide concrete logic for a particular blockchain protocol.
4399
5186
  *
4400
- * This abstraction allows the Stablecoin Kits to work with multiple blockchains in a uniform way.
5187
+ * This abstraction allows the App Kit to work with multiple blockchains in a uniform way.
4401
5188
  *
4402
5189
  * @typeParam TAdapterCapabilities - The adapter capabilities type for compile-time address validation.
4403
5190
  * When provided, enables strict typing of operation context based on the adapter's address control model.
@@ -4921,6 +5708,435 @@ const convertAddress = (address, targetFormat) => {
4921
5708
  throw new Error(`Unsupported address format: ${address}`);
4922
5709
  };
4923
5710
 
5711
+ /**
5712
+ * USDC token definition with addresses and metadata.
5713
+ *
5714
+ * @remarks
5715
+ * This is the built-in USDC definition used by the TokenRegistry.
5716
+ * Includes all known USDC addresses across supported chains.
5717
+ *
5718
+ * Keys use the `Blockchain` enum for type safety. Both enum values
5719
+ * and string literals are supported:
5720
+ * - `Blockchain.Ethereum` or `'Ethereum'`
5721
+ *
5722
+ * @example
5723
+ * ```typescript
5724
+ * import { USDC } from '@core/tokens'
5725
+ * import { Blockchain } from '@core/chains'
5726
+ *
5727
+ * console.log(USDC.symbol) // 'USDC'
5728
+ * console.log(USDC.decimals) // 6
5729
+ * console.log(USDC.locators[Blockchain.Ethereum])
5730
+ * // '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
5731
+ * ```
5732
+ */
5733
+ const USDC = {
5734
+ symbol: 'USDC',
5735
+ decimals: 6,
5736
+ locators: {
5737
+ // =========================================================================
5738
+ // Mainnets (alphabetically sorted)
5739
+ // =========================================================================
5740
+ [exports.Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
5741
+ [exports.Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
5742
+ [exports.Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
5743
+ [exports.Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
5744
+ [exports.Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
5745
+ [exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
5746
+ [exports.Blockchain.Hedera]: '0.0.456858',
5747
+ [exports.Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
5748
+ [exports.Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
5749
+ [exports.Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
5750
+ [exports.Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
5751
+ [exports.Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
5752
+ [exports.Blockchain.Noble]: 'uusdc',
5753
+ [exports.Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
5754
+ [exports.Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
5755
+ [exports.Blockchain.Polkadot_Asset_Hub]: '1337',
5756
+ [exports.Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
5757
+ [exports.Blockchain.Sei]: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
5758
+ [exports.Blockchain.Solana]: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
5759
+ [exports.Blockchain.Sonic]: '0x29219dd400f2Bf60E5a23d13Be72B486D4038894',
5760
+ [exports.Blockchain.Stellar]: 'USDC-GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN',
5761
+ [exports.Blockchain.Sui]: '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC',
5762
+ [exports.Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
5763
+ [exports.Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
5764
+ [exports.Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
5765
+ [exports.Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
5766
+ // =========================================================================
5767
+ // Testnets (alphabetically sorted)
5768
+ // =========================================================================
5769
+ [exports.Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
5770
+ [exports.Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
5771
+ [exports.Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
5772
+ [exports.Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
5773
+ [exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
5774
+ [exports.Blockchain.Hedera_Testnet]: '0.0.429274',
5775
+ [exports.Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
5776
+ [exports.Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
5777
+ [exports.Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
5778
+ [exports.Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
5779
+ [exports.Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
5780
+ [exports.Blockchain.Noble_Testnet]: 'uusdc',
5781
+ [exports.Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
5782
+ [exports.Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
5783
+ [exports.Blockchain.Polkadot_Westmint]: '31337',
5784
+ [exports.Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
5785
+ [exports.Blockchain.Sei_Testnet]: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
5786
+ [exports.Blockchain.Solana_Devnet]: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
5787
+ [exports.Blockchain.Sonic_Testnet]: '0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51',
5788
+ [exports.Blockchain.Stellar_Testnet]: 'USDC-GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5',
5789
+ [exports.Blockchain.Sui_Testnet]: '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC',
5790
+ [exports.Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
5791
+ [exports.Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
5792
+ [exports.Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
5793
+ [exports.Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853',
5794
+ },
5795
+ };
5796
+
5797
+ /**
5798
+ * USDT (Tether) token definition with addresses and metadata.
5799
+ *
5800
+ * @remarks
5801
+ * Built-in USDT definition for the TokenRegistry. Includes chain locators
5802
+ * for swap-supported chains.
5803
+ */
5804
+ const USDT = {
5805
+ symbol: 'USDT',
5806
+ decimals: 6,
5807
+ locators: {
5808
+ [exports.Blockchain.Aptos]: '0x357b0b74bc833e95a115ad22604854d6b0fca151cecd94111770e5d6ffc9dc2b',
5809
+ [exports.Blockchain.Arbitrum]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
5810
+ [exports.Blockchain.Avalanche]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7',
5811
+ [exports.Blockchain.Celo]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
5812
+ [exports.Blockchain.Ethereum]: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
5813
+ [exports.Blockchain.HyperEVM]: '0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb',
5814
+ [exports.Blockchain.Ink]: '0x0200C29006150606B650577BBE7B6248F58470c1',
5815
+ [exports.Blockchain.Linea]: '0xA219439258ca9da29E9Cc4cE5596924745e12B93',
5816
+ [exports.Blockchain.Monad]: '0xe7cd86e13AC4309349F30B3435a9d337750fC82D',
5817
+ [exports.Blockchain.NEAR]: 'usdt.tether-token.near',
5818
+ [exports.Blockchain.Optimism]: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
5819
+ [exports.Blockchain.Polkadot_Asset_Hub]: '1984',
5820
+ [exports.Blockchain.Polygon]: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F',
5821
+ [exports.Blockchain.Sei]: '0x9151434b16b9763660705744891fA906F660EcC5',
5822
+ [exports.Blockchain.Solana]: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
5823
+ [exports.Blockchain.Unichain]: '0x9151434b16b9763660705744891fA906F660EcC5',
5824
+ },
5825
+ };
5826
+
5827
+ /**
5828
+ * EURC (Euro Coin) token definition with addresses and metadata.
5829
+ *
5830
+ * @remarks
5831
+ * Built-in EURC definition for the TokenRegistry. Includes chain locators
5832
+ * for swap-supported chains.
5833
+ */
5834
+ const EURC = {
5835
+ symbol: 'EURC',
5836
+ decimals: 6,
5837
+ locators: {
5838
+ [exports.Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
5839
+ [exports.Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
5840
+ [exports.Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
5841
+ [exports.Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
5842
+ [exports.Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
5843
+ },
5844
+ };
5845
+
5846
+ /**
5847
+ * DAI (Maker DAO) stablecoin token definition with addresses and metadata.
5848
+ *
5849
+ * @remarks
5850
+ * Built-in DAI definition for the TokenRegistry. Includes chain locators
5851
+ * for swap-supported chains.
5852
+ */
5853
+ const DAI = {
5854
+ symbol: 'DAI',
5855
+ decimals: 18,
5856
+ chainDecimals: {
5857
+ [exports.Blockchain.Solana]: 8,
5858
+ },
5859
+ locators: {
5860
+ [exports.Blockchain.Arbitrum]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5861
+ [exports.Blockchain.Avalanche]: '0xd586E7F844cEa2F87f50152665BCbc2C279D8d70',
5862
+ [exports.Blockchain.Base]: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
5863
+ [exports.Blockchain.Ethereum]: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
5864
+ [exports.Blockchain.Linea]: '0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5',
5865
+ [exports.Blockchain.Optimism]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
5866
+ [exports.Blockchain.Polygon]: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063',
5867
+ [exports.Blockchain.Solana]: 'EjmyN6qEC1Tf1JxiG1ae7UTJhUxSwk1TCWNWqxWV4J6o',
5868
+ },
5869
+ };
5870
+
5871
+ /**
5872
+ * USDe (Ethena) synthetic dollar token definition with addresses and metadata.
5873
+ *
5874
+ * @remarks
5875
+ * Built-in USDE definition for the TokenRegistry. Includes chain locators
5876
+ * for swap-supported chains.
5877
+ */
5878
+ const USDE = {
5879
+ symbol: 'USDe',
5880
+ decimals: 18,
5881
+ chainDecimals: {
5882
+ [exports.Blockchain.Solana]: 9,
5883
+ },
5884
+ locators: {
5885
+ [exports.Blockchain.Arbitrum]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5886
+ [exports.Blockchain.Base]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5887
+ [exports.Blockchain.Ethereum]: '0x4c9EDD5852cd905f086C759E8383e09bff1E68B3',
5888
+ [exports.Blockchain.Linea]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5889
+ [exports.Blockchain.Optimism]: '0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34',
5890
+ [exports.Blockchain.Solana]: 'DEkqHyPN7GMRJ5cArtQFAWefqbZb33Hyf6s5iCwjEonT',
5891
+ },
5892
+ };
5893
+
5894
+ /**
5895
+ * PYUSD (PayPal USD) stablecoin token definition with addresses and metadata.
5896
+ *
5897
+ * @remarks
5898
+ * Built-in PYUSD definition for the TokenRegistry. Includes chain locators
5899
+ * for swap-supported chains.
5900
+ */
5901
+ const PYUSD = {
5902
+ symbol: 'PYUSD',
5903
+ decimals: 6,
5904
+ locators: {
5905
+ [exports.Blockchain.Arbitrum]: '0x46850aD61C2B7d64d08c9C754F45254596696984',
5906
+ [exports.Blockchain.Ethereum]: '0x6c3ea9036406852006290770BEdFcAbA0e23A0e8',
5907
+ [exports.Blockchain.Solana]: '2b1kV6DkPAnxd5ixfnxCpjxmKwqjjaYmCZfHsFu24GXo',
5908
+ },
5909
+ };
5910
+
5911
+ /**
5912
+ * WETH (Wrapped Ether) token definition with addresses and metadata.
5913
+ *
5914
+ * @remarks
5915
+ * Built-in WETH definition for the TokenRegistry. Includes chain locators
5916
+ * for swap-supported chains where WETH is available.
5917
+ */
5918
+ const WETH = {
5919
+ symbol: 'WETH',
5920
+ decimals: 18,
5921
+ chainDecimals: {
5922
+ [exports.Blockchain.Solana]: 9,
5923
+ },
5924
+ locators: {
5925
+ [exports.Blockchain.Arbitrum]: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
5926
+ [exports.Blockchain.Avalanche]: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB',
5927
+ [exports.Blockchain.Base]: '0x4200000000000000000000000000000000000006',
5928
+ [exports.Blockchain.Ethereum]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
5929
+ [exports.Blockchain.Optimism]: '0x4200000000000000000000000000000000000006',
5930
+ [exports.Blockchain.Polygon]: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
5931
+ [exports.Blockchain.Solana]: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs',
5932
+ },
5933
+ };
5934
+
5935
+ /**
5936
+ * WBTC (Wrapped Bitcoin) token definition with addresses and metadata.
5937
+ *
5938
+ * @remarks
5939
+ * Built-in WBTC definition for the TokenRegistry. Includes chain locators
5940
+ * for swap-supported chains where WBTC is available.
5941
+ */
5942
+ const WBTC = {
5943
+ symbol: 'WBTC',
5944
+ decimals: 8,
5945
+ locators: {
5946
+ [exports.Blockchain.Ethereum]: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
5947
+ [exports.Blockchain.Arbitrum]: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
5948
+ [exports.Blockchain.Avalanche]: '0x50b7545627a5162F82A992c33b87aDc75187B218',
5949
+ },
5950
+ };
5951
+
5952
+ /**
5953
+ * WSOL (Wrapped SOL) token definition with addresses and metadata.
5954
+ *
5955
+ * @remarks
5956
+ * Built-in WSOL definition for the TokenRegistry. Includes chain locators
5957
+ * for swap-supported chains where WSOL is available.
5958
+ */
5959
+ const WSOL = {
5960
+ symbol: 'WSOL',
5961
+ decimals: 9,
5962
+ locators: {
5963
+ [exports.Blockchain.Solana]: 'So11111111111111111111111111111111111111112',
5964
+ },
5965
+ };
5966
+
5967
+ /**
5968
+ * WAVAX (Wrapped AVAX) token definition with addresses and metadata.
5969
+ *
5970
+ * @remarks
5971
+ * Built-in WAVAX definition for the TokenRegistry. Includes chain locators
5972
+ * for swap-supported chains where WAVAX is available.
5973
+ */
5974
+ const WAVAX = {
5975
+ symbol: 'WAVAX',
5976
+ decimals: 18,
5977
+ locators: {
5978
+ [exports.Blockchain.Avalanche]: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7',
5979
+ },
5980
+ };
5981
+
5982
+ /**
5983
+ * WPOL (Wrapped POL) token definition with addresses and metadata.
5984
+ *
5985
+ * @remarks
5986
+ * Built-in WPOL definition for the TokenRegistry. Includes chain locators
5987
+ * for swap-supported chains where WPOL is available.
5988
+ */
5989
+ const WPOL = {
5990
+ symbol: 'WPOL',
5991
+ decimals: 18,
5992
+ locators: {
5993
+ [exports.Blockchain.Polygon]: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270',
5994
+ },
5995
+ };
5996
+
5997
+ /**
5998
+ * ETH (native Ether alias) token definition with metadata.
5999
+ *
6000
+ * @remarks
6001
+ * Built-in ETH definition for the TokenRegistry. Used as a symbol alias
6002
+ * for native ETH (e.g. in swap flows). Chain locators are TBD and will
6003
+ * be added when addresses are available. Use raw selector with
6004
+ * locator + decimals where native gas token is represented as a contract.
6005
+ */
6006
+ const ETH = {
6007
+ symbol: 'ETH',
6008
+ decimals: 18,
6009
+ locators: {},
6010
+ };
6011
+
6012
+ /**
6013
+ * POL (Polygon native token) token definition with addresses and metadata.
6014
+ *
6015
+ * @remarks
6016
+ * Built-in POL definition for the TokenRegistry. Includes chain locators
6017
+ * where POL is available as a bridged/wrapped asset.
6018
+ */
6019
+ const POL = {
6020
+ symbol: 'POL',
6021
+ decimals: 18,
6022
+ locators: {
6023
+ [exports.Blockchain.Ethereum]: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6',
6024
+ },
6025
+ };
6026
+
6027
+ /**
6028
+ * PLUME (Plume network token) token definition with addresses and metadata.
6029
+ *
6030
+ * @remarks
6031
+ * Built-in PLUME definition for the TokenRegistry. Includes chain locators
6032
+ * where PLUME is available.
6033
+ */
6034
+ const PLUME = {
6035
+ symbol: 'PLUME',
6036
+ decimals: 18,
6037
+ locators: {
6038
+ [exports.Blockchain.Ethereum]: '0x4C1746A800D224393fE2470C70A35717eD4eA5F1',
6039
+ },
6040
+ };
6041
+
6042
+ /**
6043
+ * MON token definition with Solana mint metadata.
6044
+ *
6045
+ * @remarks
6046
+ * Built-in MON definition for the TokenRegistry. Includes chain locators
6047
+ * for swap-supported chains.
6048
+ */
6049
+ const MON = {
6050
+ symbol: 'MON',
6051
+ decimals: 18,
6052
+ chainDecimals: {
6053
+ [exports.Blockchain.Solana]: 8,
6054
+ },
6055
+ locators: {
6056
+ [exports.Blockchain.Solana]: 'CrAr4RRJMBVwRsZtT62pEhfA9H5utymC2mVx8e7FreP2',
6057
+ },
6058
+ };
6059
+
6060
+ // Re-export for consumers
6061
+ /**
6062
+ * All default token definitions.
6063
+ *
6064
+ * @remarks
6065
+ * These tokens are automatically included in the TokenRegistry when created
6066
+ * without explicit defaults. Extensions can override these definitions.
6067
+ * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
6068
+ * WPOL, ETH, POL, PLUME, and MON.
6069
+ *
6070
+ * @example
6071
+ * ```typescript
6072
+ * import { createTokenRegistry } from '@core/tokens'
6073
+ *
6074
+ * // Registry uses these by default
6075
+ * const registry = createTokenRegistry()
6076
+ *
6077
+ * // Add custom tokens (built-ins are still included)
6078
+ * const customRegistry = createTokenRegistry({
6079
+ * tokens: [myCustomToken],
6080
+ * })
6081
+ * ```
6082
+ */
6083
+ const DEFAULT_TOKENS = [
6084
+ USDC,
6085
+ USDT,
6086
+ EURC,
6087
+ DAI,
6088
+ USDE,
6089
+ PYUSD,
6090
+ WETH,
6091
+ WBTC,
6092
+ WSOL,
6093
+ WAVAX,
6094
+ WPOL,
6095
+ ETH,
6096
+ POL,
6097
+ PLUME,
6098
+ MON,
6099
+ ];
6100
+ /**
6101
+ * Uppercased token symbols approved for swap fee collection.
6102
+ *
6103
+ * @remarks
6104
+ * Derived from {@link DEFAULT_TOKENS} so all consumers share a single
6105
+ * allowlist source and stay in sync when defaults change.
6106
+ */
6107
+ new Set(DEFAULT_TOKENS.map((t) => t.symbol.toUpperCase()));
6108
+
6109
+ /**
6110
+ * Define a schema for token registry interfaces.
6111
+ *
6112
+ * @remarks
6113
+ * Validate that a registry exposes the `resolve()` API used by adapters.
6114
+ *
6115
+ * @example
6116
+ * ```typescript
6117
+ * import { tokenRegistrySchema } from '@core/tokens'
6118
+ *
6119
+ * const registry = {
6120
+ * resolve: () => ({ locator: '0x0', decimals: 6 }),
6121
+ * }
6122
+ *
6123
+ * tokenRegistrySchema.parse(registry)
6124
+ * ```
6125
+ */
6126
+ zod.z.custom((value) => {
6127
+ if (value === null || typeof value !== 'object') {
6128
+ return false;
6129
+ }
6130
+ const record = value;
6131
+ return (typeof record['resolve'] === 'function' &&
6132
+ typeof record['get'] === 'function' &&
6133
+ typeof record['has'] === 'function' &&
6134
+ typeof record['symbols'] === 'function' &&
6135
+ typeof record['entries'] === 'function');
6136
+ }, {
6137
+ message: 'Invalid token registry',
6138
+ });
6139
+
4924
6140
  /**
4925
6141
  * Schema for validating hexadecimal strings with '0x' prefix.
4926
6142
  *
@@ -5108,9 +6324,27 @@ const adapterSchema = zod.z.object({
5108
6324
  * console.log('EVM address is valid')
5109
6325
  * ```
5110
6326
  */
5111
- function assertEvmAddress(address) {
5112
- validateOrThrow(address, evmAddressSchema, 'Invalid EVM address');
5113
- }
6327
+ function assertEvmAddress(address) {
6328
+ validateOrThrow(address, evmAddressSchema, 'Invalid EVM address');
6329
+ }
6330
+
6331
+ /**
6332
+ * Permit signature standards for gasless token approvals.
6333
+ *
6334
+ * Defines the permit types that can be used to approve token spending
6335
+ * without requiring a separate approval transaction.
6336
+ *
6337
+ * @remarks
6338
+ * - NONE: No permit, tokens must be pre-approved via separate transaction
6339
+ * - EIP2612: Standard ERC-20 permit (USDC, DAI v2, and most modern tokens)
6340
+ */
6341
+ var PermitType;
6342
+ (function (PermitType) {
6343
+ /** No permit required - tokens must be pre-approved */
6344
+ PermitType[PermitType["NONE"] = 0] = "NONE";
6345
+ /** EIP-2612 standard permit */
6346
+ PermitType[PermitType["EIP2612"] = 1] = "EIP2612";
6347
+ })(PermitType || (PermitType = {}));
5114
6348
 
5115
6349
  /**
5116
6350
  * Creates a no-op prepared chain request.
@@ -5219,6 +6453,12 @@ function getSupportedChains(ecosystem) {
5219
6453
  *
5220
6454
  * This ABI is used to interact with generic ERC20 tokens on EVM networks.
5221
6455
  *
6456
+ * @remarks
6457
+ * The `approve`, `transfer` and `transferFrom` functions have been modified to not expect a return value
6458
+ * (empty `outputs` array) to support non-standard implementations like USDT
6459
+ * that return `void` instead of `bool`. This is safe because the SDK never
6460
+ * uses the return value from these functions - only the transaction hash matters.
6461
+ *
5222
6462
  * @see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
5223
6463
  */
5224
6464
  const erc20Abi = [
@@ -5249,12 +6489,7 @@ const erc20Abi = [
5249
6489
  },
5250
6490
  ],
5251
6491
  name: 'approve',
5252
- outputs: [
5253
- {
5254
- name: '',
5255
- type: 'bool',
5256
- },
5257
- ],
6492
+ outputs: [],
5258
6493
  payable: false,
5259
6494
  stateMutability: 'nonpayable',
5260
6495
  type: 'function',
@@ -5290,12 +6525,7 @@ const erc20Abi = [
5290
6525
  },
5291
6526
  ],
5292
6527
  name: 'transferFrom',
5293
- outputs: [
5294
- {
5295
- name: '',
5296
- type: 'bool',
5297
- },
5298
- ],
6528
+ outputs: [],
5299
6529
  payable: false,
5300
6530
  stateMutability: 'nonpayable',
5301
6531
  type: 'function',
@@ -5360,12 +6590,7 @@ const erc20Abi = [
5360
6590
  },
5361
6591
  ],
5362
6592
  name: 'transfer',
5363
- outputs: [
5364
- {
5365
- name: '',
5366
- type: 'bool',
5367
- },
5368
- ],
6593
+ outputs: [],
5369
6594
  payable: false,
5370
6595
  stateMutability: 'nonpayable',
5371
6596
  type: 'function',
@@ -8588,6 +9813,78 @@ const bridgeContractAbi = [
8588
9813
  },
8589
9814
  ];
8590
9815
 
9816
+ /**
9817
+ * Adapter Contract ABI
9818
+ *
9819
+ * This ABI is used to interact with the Adapter smart contract that handles
9820
+ * gasless token approvals via permits and atomic swap execution.
9821
+ *
9822
+ * The execute() function is the primary entry point for swap operations.
9823
+ */
9824
+ const adapterContractAbi = [
9825
+ {
9826
+ type: 'function',
9827
+ name: 'execute',
9828
+ inputs: [
9829
+ {
9830
+ name: 'params',
9831
+ type: 'tuple',
9832
+ internalType: 'struct IAdapter.ExecutionParams',
9833
+ components: [
9834
+ {
9835
+ name: 'instructions',
9836
+ type: 'tuple[]',
9837
+ internalType: 'struct IAdapter.Instruction[]',
9838
+ components: [
9839
+ { name: 'target', type: 'address', internalType: 'address' },
9840
+ { name: 'data', type: 'bytes', internalType: 'bytes' },
9841
+ { name: 'value', type: 'uint256', internalType: 'uint256' },
9842
+ { name: 'tokenIn', type: 'address', internalType: 'address' },
9843
+ {
9844
+ name: 'amountToApprove',
9845
+ type: 'uint256',
9846
+ internalType: 'uint256',
9847
+ },
9848
+ { name: 'tokenOut', type: 'address', internalType: 'address' },
9849
+ { name: 'minTokenOut', type: 'uint256', internalType: 'uint256' },
9850
+ ],
9851
+ },
9852
+ {
9853
+ name: 'tokens',
9854
+ type: 'tuple[]',
9855
+ internalType: 'struct IAdapter.TokenRecipient[]',
9856
+ components: [
9857
+ { name: 'token', type: 'address', internalType: 'address' },
9858
+ { name: 'beneficiary', type: 'address', internalType: 'address' },
9859
+ ],
9860
+ },
9861
+ { name: 'execId', type: 'uint256', internalType: 'uint256' },
9862
+ { name: 'deadline', type: 'uint256', internalType: 'uint256' },
9863
+ { name: 'metadata', type: 'bytes', internalType: 'bytes' },
9864
+ ],
9865
+ },
9866
+ {
9867
+ name: 'tokenInputs',
9868
+ type: 'tuple[]',
9869
+ internalType: 'struct IAdapter.TokenInput[]',
9870
+ components: [
9871
+ {
9872
+ name: 'permitType',
9873
+ type: 'uint8',
9874
+ internalType: 'enum IAdapter.PermitType',
9875
+ },
9876
+ { name: 'token', type: 'address', internalType: 'address' },
9877
+ { name: 'amount', type: 'uint256', internalType: 'uint256' },
9878
+ { name: 'permitCalldata', type: 'bytes', internalType: 'bytes' },
9879
+ ],
9880
+ },
9881
+ { name: 'signature', type: 'bytes', internalType: 'bytes' },
9882
+ ],
9883
+ outputs: [],
9884
+ stateMutability: 'payable',
9885
+ },
9886
+ ];
9887
+
8591
9888
  /**
8592
9889
  * Core type definitions for EVM-compatible blockchain transaction execution
8593
9890
  * and gas estimation.
@@ -8597,6 +9894,66 @@ const bridgeContractAbi = [
8597
9894
  *
8598
9895
  * @module constants
8599
9896
  */
9897
+ /**
9898
+ * The zero address constant for EVM chains.
9899
+ *
9900
+ * @remarks
9901
+ * This 20-byte address (`0x00...00`) is commonly used to represent:
9902
+ * - Disabled token approvals in instructions (tokenIn = ZERO_ADDRESS)
9903
+ * - Disabled balance validations (tokenOut = ZERO_ADDRESS)
9904
+ * - Null/unset address values
9905
+ *
9906
+ * **Note**: For native currency representation in adapter contract instructions,
9907
+ * use {@link NATIVE_TOKEN_ADDRESS} (`0xEeee...`) instead.
9908
+ *
9909
+ * @example
9910
+ * ```typescript
9911
+ * import { ZERO_ADDRESS } from '@core/adapter-evm'
9912
+ *
9913
+ * // Check if instruction has no token output (validation disabled)
9914
+ * if (instruction.tokenOut === ZERO_ADDRESS) {
9915
+ * // No output validation needed
9916
+ * }
9917
+ * ```
9918
+ */
9919
+ /**
9920
+ * The native token address constant used by the Adapter Contract.
9921
+ *
9922
+ * @remarks
9923
+ * This address (`0xEeee...`) is the standard convention used by DEX aggregators
9924
+ * and the Adapter Contract to represent native currency (ETH, MATIC, AVAX, etc.)
9925
+ * in swap instructions.
9926
+ *
9927
+ * **Address Conventions in Adapter Contract:**
9928
+ * - Native Token: `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`
9929
+ * - No Token: `0x0000000000000000000000000000000000000000` (ZERO_ADDRESS)
9930
+ * - ERC20: Any other valid address
9931
+ *
9932
+ * **Native currency swaps:**
9933
+ * - When `tokenIn === NATIVE_TOKEN_ADDRESS`, value is sent via tx.value
9934
+ * - No approval or permit is required for native currency
9935
+ * - Used in instructions from stablecoin-service for ETH/MATIC/AVAX swaps
9936
+ *
9937
+ * @example
9938
+ * ```typescript
9939
+ * import { NATIVE_TOKEN_ADDRESS, ZERO_ADDRESS } from '@core/adapter-evm'
9940
+ *
9941
+ * // Check if token requires approval (exclude native and zero)
9942
+ * function requiresApproval(tokenAddress: string): boolean {
9943
+ * const normalized = tokenAddress.toLowerCase()
9944
+ * return normalized !== NATIVE_TOKEN_ADDRESS.toLowerCase() &&
9945
+ * normalized !== ZERO_ADDRESS.toLowerCase()
9946
+ * }
9947
+ *
9948
+ * // Example: ETH → USDC swap
9949
+ * const instruction = {
9950
+ * tokenIn: NATIVE_TOKEN_ADDRESS, // No approval needed
9951
+ * amount: 1000000000000000000n, // 1 ETH
9952
+ * value: 1000000000000000000n, // Sent via tx.value
9953
+ * }
9954
+ * ```
9955
+ */
9956
+ const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
8600
9957
  /**
8601
9958
  * A constant representing the zero hash value.
8602
9959
  *
@@ -9217,6 +10574,58 @@ const customBurnWithHook = async (params, adapter, context) => {
9217
10574
  return prepareCustomBurn(params, adapter, context);
9218
10575
  };
9219
10576
 
10577
+ /**
10578
+ * Prepare an EVM-compatible native token transfer transaction.
10579
+ *
10580
+ * Create a prepared chain request to move native tokens from the caller's wallet to a
10581
+ * recipient address on EVM chains.
10582
+ *
10583
+ * The function validates that the current chain is EVM-compatible, then builds a
10584
+ * transaction targeting the provided recipient address. The resulting prepared
10585
+ * request can be simulated or executed by the caller.
10586
+ *
10587
+ * @param params - The action payload for `native.transfer` containing:
10588
+ * - `to`: The recipient wallet address.
10589
+ * - `amount`: The native token amount (as a `bigint`).
10590
+ * - `chain`: The chain to transfer the native tokens on.
10591
+ * @param adapter - The EVM adapter responsible for chain context and transaction preparation.
10592
+ * @returns A promise that resolves to a prepared chain request for the `transfer` call.
10593
+ * @throws {KitError} If chain is not EVM (INPUT_INVALID_CHAIN) or amount is invalid (INPUT_INVALID_AMOUNT).
10594
+ *
10595
+ * @example
10596
+ * ```typescript
10597
+ * const prepared = await transfer(
10598
+ * {
10599
+ * to: '0x1111111111111111111111111111111111111111',
10600
+ * amount: 1_000_000n,
10601
+ * chain: 'Ethereum_Sepolia',
10602
+ * },
10603
+ * adapter
10604
+ * );
10605
+ * await prepared.execute();
10606
+ * ```
10607
+ */
10608
+ const transfer$3 = async (params, adapter, context) => {
10609
+ const chain = context.chain;
10610
+ if (chain.type !== 'evm') {
10611
+ throw createInvalidChainError(chain.name, `Expected EVM chain definition, but received chain type: ${chain.type}`);
10612
+ }
10613
+ const { to, amount } = params;
10614
+ // Validate the amount
10615
+ if (typeof amount !== 'bigint' || amount <= 0n) {
10616
+ throw createInvalidAmountError(String(amount), 'Transfer amount must be a positive bigint');
10617
+ }
10618
+ // Validate the to address
10619
+ assertEvmAddress(to);
10620
+ // Prepare the transfer transaction
10621
+ // Note: Type assertion needed - native transfers don't use contract ABI
10622
+ return adapter.prepare({
10623
+ type: 'evm',
10624
+ address: to,
10625
+ value: amount,
10626
+ }, context);
10627
+ };
10628
+
9220
10629
  /**
9221
10630
  * Prepares an EVM-compatible native token balance read (ETH, MATIC, etc.).
9222
10631
  *
@@ -9271,6 +10680,159 @@ const balanceOf$2 = async (params, adapter, context) => {
9271
10680
  };
9272
10681
  };
9273
10682
 
10683
+ /**
10684
+ * Type guard to check if params is ExecuteSwapEVMParams.
10685
+ *
10686
+ * Uses property-based narrowing: EVM swap params have `executeParams`, `tokenInputs`,
10687
+ * `inputAmount`, and `tokenInAddress`, while Solana swap params have `serializedTransaction`.
10688
+ *
10689
+ * @remarks
10690
+ * Checks both property existence and that values are not undefined to provide
10691
+ * robust runtime validation against malformed inputs.
10692
+ */
10693
+ const isEVMSwapParams = (params) => {
10694
+ return ('executeParams' in params &&
10695
+ params.executeParams !== undefined &&
10696
+ 'tokenInputs' in params &&
10697
+ params.tokenInputs !== undefined &&
10698
+ 'inputAmount' in params &&
10699
+ params.inputAmount !== undefined &&
10700
+ 'tokenInAddress' in params &&
10701
+ params.tokenInAddress !== undefined);
10702
+ };
10703
+ /**
10704
+ * Executes a swap transaction via the Adapter smart contract.
10705
+ *
10706
+ * This action prepares swap transactions that execute through the Adapter
10707
+ * Contract, which handles gasless token approvals via permits (EIP-2612,
10708
+ * Permit2, etc.) and executes multi-step swap instructions atomically.
10709
+ *
10710
+ * @remarks
10711
+ * **Adapter Contract Pattern**
10712
+ *
10713
+ * The Adapter Contract pattern enables single-transaction swaps with gasless
10714
+ * approvals. The flow is:
10715
+ * 1. Service provides `executeParams` (swap instructions) and `signature` (proxy authorization)
10716
+ * 2. SDK builds `tokenInputs` with permit signatures for token approvals
10717
+ * 3. SDK calls AdapterContract.execute(executeParams, tokenInputs, signature)
10718
+ * 4. Adapter pulls tokens via permits, executes swaps, validates outputs, sweeps residuals
10719
+ *
10720
+ * **Permit Support**
10721
+ *
10722
+ * Token approvals are handled via permits encoded in `tokenInputs`. The SDK
10723
+ * constructs `TokenInput` objects with `permitCalldata` containing the encoded
10724
+ * permit signature. The Adapter Contract executes these permits on-chain before
10725
+ * executing swap instructions, enabling gasless approvals in a single atomic transaction.
10726
+ *
10727
+ * Supported permit types: EIP-2612, Permit2, DAI-like, EIP-3009
10728
+ *
10729
+ * **Service Integration**
10730
+ *
10731
+ * The `executeParams` and `signature` come from the stablecoin-service `createSwap`
10732
+ * endpoint. These are EIP-712 signed by Circle's proxy service and must be passed
10733
+ * to the Adapter Contract exactly as received (no modification).
10734
+ *
10735
+ * @param params - Swap execution parameters:
10736
+ * - `executeParams`: Execution parameters from service (instructions, tokens, execId, deadline)
10737
+ * - `tokenInputs`: Token inputs with permit signatures (built by provider)
10738
+ * - `signature`: EIP-712 signature from service (proxy authorization)
10739
+ * - `inputAmount`: User's swap input amount from service response (source of truth)
10740
+ * @param adapter - EVM adapter for transaction preparation
10741
+ * @param context - Resolved operation context with chain and address information
10742
+ * @returns Prepared chain request ready for estimation or execution
10743
+ *
10744
+ * @throws KitError If chain is not EVM-compatible (INPUT_INVALID_CHAIN)
10745
+ * @throws KitError If executeParams is missing or invalid (INPUT_VALIDATION_FAILED)
10746
+ * @throws KitError If signature is invalid (INPUT_VALIDATION_FAILED)
10747
+ * @throws KitError If tokenInputs is not an array (INPUT_VALIDATION_FAILED)
10748
+ *
10749
+ * @example
10750
+ * ```typescript
10751
+ * import { createSwap } from '@core/service-client'
10752
+ * import { PermitType } from '@core/adapter'
10753
+ * import type { EvmAdapter } from '@core/adapter-evm'
10754
+ *
10755
+ * // 1. Get swap transaction from stablecoin-service
10756
+ * const swapResponse = await createSwap({
10757
+ * tokenInAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
10758
+ * tokenOutAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec8', // USDT
10759
+ * tokenInChain: 'Ethereum',
10760
+ * fromAddress: await adapter.getAddress(),
10761
+ * toAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
10762
+ * amount: '1000000', // 1 USDC in base units
10763
+ * apiKey: process.env.API_KEY,
10764
+ * })
10765
+ *
10766
+ * // 2. Build token inputs with permit signature (provider responsibility)
10767
+ * const tokenInputs: TokenInput[] = [{
10768
+ * permitType: PermitType.EIP2612,
10769
+ * token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
10770
+ * from: await adapter.getAddress(Ethereum),
10771
+ * amount: 1000000n,
10772
+ * permitCalldata: '0x...' // Encoded permit signature from provider
10773
+ * }]
10774
+ *
10775
+ * // 3. Execute swap via adapter action
10776
+ * const swapRequest = await adapter.prepareAction(
10777
+ * 'swap.execute',
10778
+ * {
10779
+ * executeParams: swapResponse.transaction.executeParams,
10780
+ * tokenInputs,
10781
+ * signature: swapResponse.transaction.signature,
10782
+ * inputAmount: BigInt(swapResponse.amount) // From service response
10783
+ * },
10784
+ * context
10785
+ * )
10786
+ *
10787
+ * // 4. Estimate gas
10788
+ * const gasEstimate = await swapRequest.estimate()
10789
+ * console.log('Gas required:', gasEstimate.gas)
10790
+ *
10791
+ * // 5. Execute transaction
10792
+ * const txHash = await swapRequest.execute()
10793
+ * console.log('Swap transaction:', txHash)
10794
+ * ```
10795
+ */
10796
+ const executeSwap = async (params, adapter, context) => {
10797
+ const { chain } = context;
10798
+ // Validate EVM chain first (this is an EVM-specific action)
10799
+ if (chain.type !== 'evm') {
10800
+ throw createInvalidChainError(chain.name, `Expected EVM chain, but received chain type: ${String(chain.type)}`);
10801
+ }
10802
+ const adapterContractAddress = chain.kitContracts?.adapter;
10803
+ if (adapterContractAddress === undefined) {
10804
+ throw createInvalidChainError(chain.name, `Swap action is not supported on this chain: ${chain.name}`);
10805
+ }
10806
+ // Narrow type using property-based guard - this function only handles EVM swaps
10807
+ if (!isEVMSwapParams(params)) {
10808
+ throw createValidationFailedError('params', 'Invalid or incomplete EVM swap parameters', 'This action handler only supports EVM swap parameters (must have executeParams and tokenInputs)');
10809
+ }
10810
+ // Validate executeParams exists
10811
+ if (typeof params.executeParams !== 'object') {
10812
+ throw createValidationFailedError('executeParams', params.executeParams, 'ExecuteParams is required from service response');
10813
+ }
10814
+ // Validate signature
10815
+ if (params.signature === '0x') {
10816
+ throw createValidationFailedError('signature', params.signature, 'Signature is required from service response');
10817
+ }
10818
+ // Validate tokenInputs array (can be empty if no permits needed)
10819
+ if (!Array.isArray(params.tokenInputs)) {
10820
+ throw createValidationFailedError('tokenInputs', params.tokenInputs, 'TokenInputs must be an array');
10821
+ }
10822
+ // Check if swapping native currency (ETH, MATIC, etc.) vs ERC20 tokens
10823
+ const isNativeSwap = params.tokenInAddress.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase();
10824
+ // Prepare transaction to Adapter Contract
10825
+ return adapter.prepare({
10826
+ type: 'evm',
10827
+ abi: adapterContractAbi,
10828
+ address: adapterContractAddress,
10829
+ functionName: 'execute',
10830
+ args: [params.executeParams, params.tokenInputs, params.signature],
10831
+ // Include value only for native currency swaps (ETH → USDC, etc.)
10832
+ ...(isNativeSwap && { value: params.inputAmount }),
10833
+ }, context);
10834
+ };
10835
+
9274
10836
  /**
9275
10837
  * Prepares an EVM-compatible `balanceOf` read for any ERC-20 token.
9276
10838
  *
@@ -9319,66 +10881,228 @@ const balanceOf$1 = async (params, adapter, context) => {
9319
10881
  args: [walletAddress],
9320
10882
  }, context);
9321
10883
  }
9322
- catch (error) {
9323
- const errorMessage = error instanceof Error ? error.message : String(error);
9324
- throw new Error(`Failed to get token balance for ${walletAddress} from ${tokenAddress}: ${errorMessage}`);
10884
+ catch (error) {
10885
+ const errorMessage = error instanceof Error ? error.message : String(error);
10886
+ throw new Error(`Failed to get token balance for ${walletAddress} from ${tokenAddress}: ${errorMessage}`);
10887
+ }
10888
+ };
10889
+
10890
+ /**
10891
+ * Prepares an EVM-compatible `allowance` read for any ERC-20 token.
10892
+ *
10893
+ * This function creates a prepared chain request for reading the allowance of a given wallet (owner)
10894
+ * and delegate (spender) address for a specified ERC-20 token on an EVM-based chain. It validates the chain type
10895
+ * and all addresses, then constructs a read-only contract call using the canonical ERC-20 ABI and the provided token address.
10896
+ * The resulting prepared request can be executed or simulated by the caller.
10897
+ *
10898
+ * @param params - The action payload containing:
10899
+ * - `tokenAddress`: The contract address of the ERC-20 token.
10900
+ * - `walletAddress`: The address of the token owner (if not provided, will use the adapter's address).
10901
+ * - `delegate`: The address to check the allowance for (spender).
10902
+ * @param adapter - The EVM adapter responsible for chain context and contract interaction.
10903
+ * @param context - The resolved operation context providing chain and address information.
10904
+ * @returns A promise that resolves to a prepared chain request for the `allowance` call.
10905
+ * The `execute` method returns the allowance as a string (in the token's smallest unit).
10906
+ * @throws Error if the current chain is not EVM-compatible, if address validation fails, or if the contract call fails.
10907
+ *
10908
+ * @example
10909
+ * ```typescript
10910
+ * const prepared = await allowance(
10911
+ * { tokenAddress: '0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', walletAddress: '0xabc...', delegate: '0xdef...' },
10912
+ * adapter,
10913
+ * context
10914
+ * );
10915
+ * const allowance = await prepared.execute();
10916
+ * console.log(allowance); // e.g., "1000000" (for 1 USDC allowance with 6 decimals)
10917
+ * ```
10918
+ */
10919
+ const allowance$1 = async (params, adapter, context) => {
10920
+ const chain = context.chain;
10921
+ if (chain.type !== 'evm') {
10922
+ throw new Error(`Expected EVM chain definition, but received chain type: ${chain.type}`);
10923
+ }
10924
+ const { tokenAddress, walletAddress: walletAddressParam, delegate } = params;
10925
+ // Use provided wallet address or fall back to context address
10926
+ const walletAddress = walletAddressParam ?? context.address;
10927
+ // validate the address
10928
+ assertEvmAddress(walletAddress);
10929
+ assertEvmAddress(tokenAddress);
10930
+ assertEvmAddress(delegate);
10931
+ try {
10932
+ return await adapter.prepare({
10933
+ type: 'evm',
10934
+ address: tokenAddress,
10935
+ abi: erc20Abi,
10936
+ functionName: 'allowance',
10937
+ args: [walletAddress, delegate],
10938
+ }, context);
10939
+ }
10940
+ catch (error) {
10941
+ const errorMessage = error instanceof Error ? error.message : String(error);
10942
+ throw new Error(`Failed to get token allowance for ${walletAddress} from ${tokenAddress}: ${errorMessage}`);
10943
+ }
10944
+ };
10945
+
10946
+ /**
10947
+ * Prepares an EVM-compatible `approve` transaction for any ERC-20 token.
10948
+ *
10949
+ * This function creates a prepared chain request for approving a delegate (spender)
10950
+ * to spend a specified amount of tokens on behalf of the caller. It validates the
10951
+ * chain type and all addresses, then constructs a transaction using the standard
10952
+ * ERC-20 ABI and the provided token address. The resulting prepared request can be
10953
+ * executed or simulated by the caller.
10954
+ *
10955
+ * @remarks
10956
+ * **Race Condition Consideration**
10957
+ *
10958
+ * The standard ERC-20 `approve()` function has a known theoretical race condition
10959
+ * where a malicious spender could front-run an approval change to spend both the
10960
+ * old and new allowance amounts. However, this is rare in practice and is the
10961
+ * accepted industry standard used by major protocols (Uniswap, 1inch, etc.).
10962
+ *
10963
+ * @param params - The action payload containing:
10964
+ * - `tokenAddress`: The contract address of the ERC-20 token.
10965
+ * - `delegate`: The address to grant the allowance to (spender).
10966
+ * - `amount`: The amount of tokens to approve (as a bigint, in the token's smallest unit).
10967
+ * @param adapter - The EVM adapter responsible for chain context and transaction preparation.
10968
+ * @param context - The resolved operation context providing chain and address information.
10969
+ * @returns A promise that resolves to a prepared chain request for the `approve` call.
10970
+ * The `execute` method returns the transaction hash.
10971
+ * @throws {KitError} If the current chain is not EVM-compatible (INPUT_INVALID_CHAIN).
10972
+ * @throws {KitError} if the amount is not a postive or zero bigint (INPUT_INVALID_AMOUNT).
10973
+ * @throws {ValidationError} If address validation fails for tokenAddress or delegate.
10974
+ *
10975
+ * @example Approve USDT for a swap contract
10976
+ * ```typescript
10977
+ * import { approve } from '@core/adapter-evm/actions/token'
10978
+ * import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
10979
+ * import { Ethereum } from '@core/chains'
10980
+ *
10981
+ * const adapter = createAdapterFromPrivateKey({ privateKey: '0x...' })
10982
+ * const context = {
10983
+ * chain: Ethereum,
10984
+ * address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
10985
+ * }
10986
+ *
10987
+ * // Approve 100 USDT (6 decimals) for a swap contract
10988
+ * const prepared = await approve(
10989
+ * {
10990
+ * tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
10991
+ * delegate: '0x1234567890123456789012345678901234567890', // Swap contract
10992
+ * amount: 100_000000n // 100 USDT
10993
+ * },
10994
+ * adapter,
10995
+ * context
10996
+ * )
10997
+ *
10998
+ * // Execute the approval
10999
+ * const txHash = await prepared.execute()
11000
+ * console.log('Approval transaction:', txHash)
11001
+ * ```
11002
+ *
11003
+ * @example Approve DAI for a lending protocol
11004
+ * ```typescript
11005
+ * // Approve 1000 DAI (18 decimals) for a lending protocol
11006
+ * const prepared = await approve(
11007
+ * {
11008
+ * tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
11009
+ * delegate: '0xLendingProtocolAddress',
11010
+ * amount: 1000_000000000000000000n // 1000 DAI
11011
+ * },
11012
+ * adapter,
11013
+ * context
11014
+ * )
11015
+ *
11016
+ * await prepared.execute()
11017
+ * ```
11018
+ *
11019
+ * @example Revoke approval by setting to zero
11020
+ * ```typescript
11021
+ * // Revoke approval by setting allowance to 0
11022
+ * const prepared = await approve(
11023
+ * {
11024
+ * tokenAddress: '0xTokenAddress',
11025
+ * delegate: '0xSpenderAddress',
11026
+ * amount: 0n // Revoke approval
11027
+ * },
11028
+ * adapter,
11029
+ * context
11030
+ * )
11031
+ *
11032
+ * await prepared.execute()
11033
+ * ```
11034
+ */
11035
+ const approve = async (params, adapter, context) => {
11036
+ const chain = context.chain;
11037
+ if (chain.type !== 'evm') {
11038
+ throw createInvalidChainError(chain.name, `Expected EVM chain definition, but received chain type: ${chain.type}`);
11039
+ }
11040
+ const { tokenAddress, delegate, amount } = params;
11041
+ // Validate addresses
11042
+ assertEvmAddress(tokenAddress);
11043
+ assertEvmAddress(delegate);
11044
+ // Validate amount (must be bigint and >= 0)
11045
+ if (typeof amount !== 'bigint' || amount < 0n) {
11046
+ throw createInvalidAmountError(String(amount), 'Approval amount must be a non-negative bigint');
9325
11047
  }
11048
+ // Prepare the approve transaction
11049
+ return adapter.prepare({
11050
+ type: 'evm',
11051
+ abi: erc20Abi,
11052
+ address: tokenAddress,
11053
+ functionName: 'approve',
11054
+ args: [delegate, amount],
11055
+ }, context);
9326
11056
  };
9327
11057
 
9328
11058
  /**
9329
- * Prepares an EVM-compatible `allowance` read for any ERC-20 token.
11059
+ * Prepare an EVM-compatible ERC-20 `transfer` transaction.
9330
11060
  *
9331
- * This function creates a prepared chain request for reading the allowance of a given wallet (owner)
9332
- * and delegate (spender) address for a specified ERC-20 token on an EVM-based chain. It validates the chain type
9333
- * and all addresses, then constructs a read-only contract call using the canonical ERC-20 ABI and the provided token address.
9334
- * The resulting prepared request can be executed or simulated by the caller.
11061
+ * Create a prepared chain request to move tokens from the caller's wallet to a
11062
+ * recipient address on EVM chains using the standard ERC-20 ABI.
9335
11063
  *
9336
- * @param params - The action payload containing:
9337
- * - `tokenAddress`: The contract address of the ERC-20 token.
9338
- * - `walletAddress`: The address of the token owner (if not provided, will use the adapter's address).
9339
- * - `delegate`: The address to check the allowance for (spender).
9340
- * @param adapter - The EVM adapter responsible for chain context and contract interaction.
9341
- * @param context - The resolved operation context providing chain and address information.
9342
- * @returns A promise that resolves to a prepared chain request for the `allowance` call.
9343
- * The `execute` method returns the allowance as a string (in the token's smallest unit).
9344
- * @throws Error if the current chain is not EVM-compatible, if address validation fails, or if the contract call fails.
11064
+ * The function validates that the current chain is EVM-compatible, then builds a
11065
+ * transaction targeting the provided token contract address. The resulting prepared
11066
+ * request can be simulated or executed by the caller.
11067
+ *
11068
+ * @param params - The action payload for `token.transfer` containing:
11069
+ * - `tokenAddress`: The ERC-20 token contract address.
11070
+ * - `to`: The recipient wallet address.
11071
+ * - `amount`: The token amount in the smallest unit (as a `bigint`).
11072
+ * @param adapter - The EVM adapter responsible for chain context and transaction preparation.
11073
+ * @returns A promise that resolves to a prepared chain request for the `transfer` call.
11074
+ * @throws {KitError} If chain is not EVM (INPUT_INVALID_CHAIN) or amount is invalid (INPUT_INVALID_AMOUNT).
9345
11075
  *
9346
11076
  * @example
9347
11077
  * ```typescript
9348
- * const prepared = await allowance(
9349
- * { tokenAddress: '0xA0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', walletAddress: '0xabc...', delegate: '0xdef...' },
9350
- * adapter,
9351
- * context
11078
+ * const prepared = await transfer(
11079
+ * {
11080
+ * tokenAddress: '0x0000000000000000000000000000000000000000',
11081
+ * to: '0x1111111111111111111111111111111111111111',
11082
+ * amount: 1_000_000n,
11083
+ * },
11084
+ * adapter
9352
11085
  * );
9353
- * const allowance = await prepared.execute();
9354
- * console.log(allowance); // e.g., "1000000" (for 1 USDC allowance with 6 decimals)
11086
+ * await prepared.execute();
9355
11087
  * ```
9356
11088
  */
9357
- const allowance$1 = async (params, adapter, context) => {
11089
+ const transfer$2 = async (params, adapter, context) => {
9358
11090
  const chain = context.chain;
9359
11091
  if (chain.type !== 'evm') {
9360
- throw new Error(`Expected EVM chain definition, but received chain type: ${chain.type}`);
9361
- }
9362
- const { tokenAddress, walletAddress: walletAddressParam, delegate } = params;
9363
- // Use provided wallet address or fall back to context address
9364
- const walletAddress = walletAddressParam ?? context.address;
9365
- // validate the address
9366
- assertEvmAddress(walletAddress);
9367
- assertEvmAddress(tokenAddress);
9368
- assertEvmAddress(delegate);
9369
- try {
9370
- return await adapter.prepare({
9371
- type: 'evm',
9372
- address: tokenAddress,
9373
- abi: erc20Abi,
9374
- functionName: 'allowance',
9375
- args: [walletAddress, delegate],
9376
- }, context);
11092
+ throw createInvalidChainError(chain.name, `Expected EVM chain definition, but received chain type: ${chain.type}`);
9377
11093
  }
9378
- catch (error) {
9379
- const errorMessage = error instanceof Error ? error.message : String(error);
9380
- throw new Error(`Failed to get token allowance for ${walletAddress} from ${tokenAddress}: ${errorMessage}`);
11094
+ // Validate the amount
11095
+ if (typeof params.amount !== 'bigint' || params.amount <= 0n) {
11096
+ throw createInvalidAmountError(String(params.amount), 'Transfer amount must be a positive bigint');
9381
11097
  }
11098
+ // Prepare the transfer transaction
11099
+ return adapter.prepare({
11100
+ type: 'evm',
11101
+ abi: erc20Abi,
11102
+ address: params.tokenAddress,
11103
+ functionName: 'transfer',
11104
+ args: [params.to, params.amount],
11105
+ }, context);
9382
11106
  };
9383
11107
 
9384
11108
  /**
@@ -9508,6 +11232,106 @@ const allowance = async (params, adapter, context) => {
9508
11232
  }, adapter, context);
9509
11233
  };
9510
11234
 
11235
+ /**
11236
+ * Prepares a USDC `transfer` transaction for EVM-compatible chains.
11237
+ *
11238
+ * This function validates that the current chain is EVM-compatible and that a canonical
11239
+ * USDC contract address is available for the chain. It then constructs a prepared chain
11240
+ * request to transfer USDC from the caller's wallet to a specified recipient address,
11241
+ * using the standard ERC-20 ABI and the chain's USDC contract address.
11242
+ *
11243
+ * The resulting prepared request can be simulated, estimated, or executed by the caller.
11244
+ *
11245
+ * @param params - The action payload for `usdc.transfer`:
11246
+ * - `to`: The recipient wallet address.
11247
+ * - `amount`: The USDC amount to transfer (in the smallest unit, as a `bigint`).
11248
+ * @param adapter - The EVM adapter providing chain context and contract interaction.
11249
+ * @returns A promise resolving to a prepared chain request for the USDC `transfer` call.
11250
+ * The returned request's `execute` method sends the transaction.
11251
+ * @throws {KitError} If chain is not EVM (INPUT_INVALID_CHAIN) or USDC is not supported (INPUT_UNSUPPORTED_TOKEN).
11252
+ *
11253
+ * @example
11254
+ * ```typescript
11255
+ * const prepared = await transfer(
11256
+ * {
11257
+ * to: '0x1111111111111111111111111111111111111111',
11258
+ * amount: 1_000_000n,
11259
+ * },
11260
+ * adapter
11261
+ * );
11262
+ * await prepared.execute();
11263
+ * ```
11264
+ */
11265
+ const transfer$1 = async (params, adapter, context) => {
11266
+ const chain = context.chain;
11267
+ if (chain.type !== 'evm') {
11268
+ throw createInvalidChainError(chain.name, `Expected EVM chain definition, but received chain type: ${chain.type}`);
11269
+ }
11270
+ const tokenAddress = chain.usdcAddress;
11271
+ // Check if USDC address exists before validating
11272
+ if (tokenAddress == null) {
11273
+ throw createUnsupportedTokenError('USDC', chain.name);
11274
+ }
11275
+ assertEvmAddress(tokenAddress);
11276
+ // Prepare the usdc transfer transaction using the base token transfer function
11277
+ return transfer$2({
11278
+ tokenAddress,
11279
+ to: params.to,
11280
+ amount: params.amount,
11281
+ }, adapter, context);
11282
+ };
11283
+
11284
+ /**
11285
+ * Prepares a USDT `transfer` transaction for EVM-compatible chains.
11286
+ *
11287
+ * This function validates that the current chain is EVM-compatible and that a canonical
11288
+ * USDT contract address is available for the chain. It then constructs a prepared chain
11289
+ * request to transfer USDT from the caller's wallet to a specified recipient address,
11290
+ * using the standard ERC-20 ABI and the chain's USDT contract address.
11291
+ *
11292
+ * The resulting prepared request can be simulated, estimated, or executed by the caller.
11293
+ *
11294
+ * @param params - The action payload for `usdt.transfer`:
11295
+ * - `to`: The recipient wallet address.
11296
+ * - `amount`: The USDT amount to transfer (in the smallest unit, as a `bigint`).
11297
+ * @param adapter - The EVM adapter providing chain context and contract interaction.
11298
+ * @param context - The resolved operation context containing chain definition.
11299
+ * @returns A promise resolving to a prepared chain request for the USDT `transfer` call.
11300
+ * The returned request's `execute` method sends the transaction.
11301
+ * @throws {KitError} If chain is not EVM (INPUT_INVALID_CHAIN) or USDT is not supported (INPUT_UNSUPPORTED_TOKEN).
11302
+ *
11303
+ * @example
11304
+ * ```typescript
11305
+ * const prepared = await transfer(
11306
+ * {
11307
+ * to: '0x1111111111111111111111111111111111111111',
11308
+ * amount: 1_000_000n,
11309
+ * },
11310
+ * adapter,
11311
+ * context
11312
+ * );
11313
+ * await prepared.execute();
11314
+ * ```
11315
+ */
11316
+ const transfer = async (params, adapter, context) => {
11317
+ const chain = context.chain;
11318
+ if (chain.type !== 'evm') {
11319
+ throw createInvalidChainError(chain.name, `Expected EVM chain, but received chain type: ${chain.type}`);
11320
+ }
11321
+ const tokenAddress = chain.usdtAddress;
11322
+ // Check if USDT address exists before validating
11323
+ if (tokenAddress == null) {
11324
+ throw createUnsupportedTokenError('USDT', chain.name);
11325
+ }
11326
+ assertEvmAddress(tokenAddress);
11327
+ // Prepare the usdt transfer transaction using the base token transfer function
11328
+ return transfer$2({
11329
+ tokenAddress,
11330
+ to: params.to,
11331
+ amount: params.amount,
11332
+ }, adapter, context);
11333
+ };
11334
+
9511
11335
  /**
9512
11336
  * Creates a collection of action handlers for EVM blockchain operations.
9513
11337
  *
@@ -9544,6 +11368,15 @@ const getHandlers = (adapter) => {
9544
11368
  'token.balanceOf': async (params, context) => {
9545
11369
  return balanceOf$1(params, adapter, context);
9546
11370
  },
11371
+ /**
11372
+ * Handler for token approve operations on EVM chains.
11373
+ *
11374
+ * Approves a delegate to spend tokens on behalf of the caller.
11375
+ * Uses the standard ERC-20 approve function.
11376
+ */
11377
+ 'token.approve': async (params, context) => {
11378
+ return approve(params, adapter, context);
11379
+ },
9547
11380
  /**
9548
11381
  * Handler for USDC allowance operations on EVM chains.
9549
11382
  *
@@ -9607,6 +11440,49 @@ const getHandlers = (adapter) => {
9607
11440
  'cctp.v2.customBurn': async (params, context) => {
9608
11441
  return customBurn(params, adapter, context);
9609
11442
  },
11443
+ /**
11444
+ * Handler for token transfer operations on EVM chains.
11445
+ *
11446
+ * Transfers the specified amount of the token from the caller's wallet to the specified recipient address.
11447
+ */
11448
+ 'token.transfer': async (params, context) => {
11449
+ return transfer$2(params, adapter, context);
11450
+ },
11451
+ /**
11452
+ * Handler for native token transfer operations on EVM chains.
11453
+ *
11454
+ * Transfers the specified amount of native tokens from the caller's wallet to the specified recipient address.
11455
+ */
11456
+ 'native.transfer': async (params, context) => {
11457
+ return transfer$3(params, adapter, context);
11458
+ },
11459
+ /**
11460
+ * Handler for USDC transfer operations on EVM chains.
11461
+ *
11462
+ * Transfers the specified amount of USDC from the caller's wallet to the specified recipient address.
11463
+ */
11464
+ 'usdc.transfer': async (params, context) => {
11465
+ return transfer$1(params, adapter, context);
11466
+ },
11467
+ /**
11468
+ * Handler for USDT transfer operations on EVM chains.
11469
+ *
11470
+ * Transfers the specified amount of USDT from the caller's wallet to the specified recipient address.
11471
+ */
11472
+ 'usdt.transfer': async (params, context) => {
11473
+ return transfer(params, adapter, context);
11474
+ },
11475
+ /**
11476
+ * Handler for swap execution.
11477
+ *
11478
+ * Executes pre-built swap transactions from the stablecoin-service API.
11479
+ * The service handles DEX aggregation and routing; this action handles
11480
+ * transaction preparation and execution. Token approvals are managed
11481
+ * separately by the provider layer.
11482
+ */
11483
+ 'swap.execute': async (params, context) => {
11484
+ return executeSwap(params, adapter, context);
11485
+ },
9610
11486
  /**
9611
11487
  * Handler for CCTP v2 custom burn with hook operations on EVM chains.
9612
11488
  *
@@ -10444,6 +12320,44 @@ const evmPreparedChainRequestParamsSchema = zod.z.object({
10444
12320
  invalid_type_error: 'Arguments must be an array',
10445
12321
  }),
10446
12322
  });
12323
+ /**
12324
+ * Zod schema for validating native token transfer parameters.
12325
+ *
12326
+ * This schema validates the parameters required for native token transfers
12327
+ * (e.g., ETH on Ethereum, MATIC on Polygon):
12328
+ * - Address must be a valid EVM address (42 characters starting with 0x)
12329
+ * - Value must be a positive bigint greater than 0
12330
+ *
12331
+ * @throws KitError if validation fails
12332
+ *
12333
+ * @example
12334
+ * ```typescript
12335
+ * import { nativeTransferParamsSchema } from '@core/adapter-evm/validation'
12336
+ *
12337
+ * const params = {
12338
+ * address: '0x1234567890123456789012345678901234567890',
12339
+ * value: BigInt(1000000)
12340
+ * }
12341
+ *
12342
+ * const result = nativeTransferParamsSchema.safeParse(params)
12343
+ * if (result.success) {
12344
+ * console.log('Native transfer params are valid')
12345
+ * } else {
12346
+ * console.error('Validation failed:', result.error)
12347
+ * }
12348
+ * ```
12349
+ */
12350
+ const nativeTransferParamsSchema = zod.z.object({
12351
+ address: evmAddressSchema,
12352
+ value: zod.z
12353
+ .bigint({
12354
+ required_error: 'Value is required for native transfers',
12355
+ invalid_type_error: 'Value must be a bigint',
12356
+ })
12357
+ .refine((val) => val > BigInt(0), {
12358
+ message: 'Value must be greater than 0',
12359
+ }),
12360
+ });
10447
12361
  /**
10448
12362
  * Zod schema for validating Ethereum transaction hashes.
10449
12363
  *
@@ -10588,6 +12502,44 @@ function assertEvmTransactionHash(txHash) {
10588
12502
  validate(txHash, evmTransactionHashSchema, 'Transaction hash');
10589
12503
  }
10590
12504
 
12505
+ const assertNativeTransferParamsSymbol = Symbol('assertNativeTransferParams');
12506
+ /**
12507
+ * Asserts that the provided parameters are valid for native token transfers.
12508
+ *
12509
+ * The validation includes:
12510
+ * - A valid EVM address (42 characters starting with 0x)
12511
+ * - A positive bigint value greater than 0
12512
+ *
12513
+ * @param params - The parameters to validate
12514
+ * @throws KitError with INPUT_VALIDATION_FAILED code if validation fails, with details about which properties failed
12515
+ *
12516
+ * @example
12517
+ * ```typescript
12518
+ * import { assertNativeTransferParams } from '@core/adapter-evm'
12519
+ *
12520
+ * // Valid native transfer
12521
+ * assertNativeTransferParams({
12522
+ * address: '0x1234567890123456789012345678901234567890',
12523
+ * value: BigInt(1000000)
12524
+ * })
12525
+ *
12526
+ * // This will throw KitError - invalid address
12527
+ * assertNativeTransferParams({
12528
+ * address: '0x123',
12529
+ * value: BigInt(1000000)
12530
+ * })
12531
+ *
12532
+ * // This will throw KitError - zero value
12533
+ * assertNativeTransferParams({
12534
+ * address: '0x1234567890123456789012345678901234567890',
12535
+ * value: BigInt(0)
12536
+ * })
12537
+ * ```
12538
+ */
12539
+ function assertNativeTransferParams(params) {
12540
+ validateWithStateTracking(params, nativeTransferParamsSchema, 'Native transfer params', assertNativeTransferParamsSymbol);
12541
+ }
12542
+
10591
12543
  /**
10592
12544
  * Asserts that the provided data matches the EIP-712 TypedData interface.
10593
12545
  *
@@ -11683,15 +13635,21 @@ class EthersAdapter extends EvmAdapter {
11683
13635
  /**
11684
13636
  * Simulates a contract function call using Ethers v6 `.staticCall`.
11685
13637
  */
11686
- async simulateFunctionCall(contract, functionName, args, chain) {
13638
+ async simulateFunctionCall(contract, functionName, args, chain, overrides) {
11687
13639
  // Retry on allowance errors to handle RPC state propagation delays
11688
13640
  // (e.g., approve tx confirmed but not yet visible in latest block for staticCall)
11689
- const MAX_SIMULATION_ATTEMPTS = 3;
13641
+ const MAX_SIMULATION_ATTEMPTS = 5;
11690
13642
  const SIMULATION_RETRY_DELAY_MS = 1000;
11691
13643
  for (let attempt = 1; attempt <= MAX_SIMULATION_ATTEMPTS; attempt++) {
11692
13644
  try {
11693
13645
  const func = contract.getFunction(functionName);
11694
- await func.staticCall(...args);
13646
+ // Pass overrides to staticCall so that value (ETH) is included in simulation
13647
+ if (overrides) {
13648
+ await func.staticCall(...args, overrides);
13649
+ }
13650
+ else {
13651
+ await func.staticCall(...args);
13652
+ }
11695
13653
  return;
11696
13654
  }
11697
13655
  catch (err) {
@@ -11786,6 +13744,24 @@ class EthersAdapter extends EvmAdapter {
11786
13744
  if (!txRequest) {
11787
13745
  throw new Error(`Failed to populate transaction for function '${functionName}'.`);
11788
13746
  }
13747
+ return this.sendTransactionWithNonceManagement(txRequest, fromAddress, chain, overrides);
13748
+ }
13749
+ /**
13750
+ * Common transaction sending logic with nonce management and retry logic.
13751
+ *
13752
+ * This method handles the complete transaction lifecycle:
13753
+ * - Nonce allocation and management via {@link ethersNonceManager}
13754
+ * - Automatic retry on nonce-related errors (up to 3 attempts)
13755
+ * - Graceful handling of user-provided nonces (no retry)
13756
+ * - Proper error handling and wrapping
13757
+ *
13758
+ * @param txRequest - Populated transaction request
13759
+ * @param fromAddress - The address sending the transaction
13760
+ * @param chain - The chain definition for context
13761
+ * @param overrides - Optional transaction overrides
13762
+ * @returns Transaction hash
13763
+ */
13764
+ async sendTransactionWithNonceManagement(txRequest, fromAddress, chain, overrides) {
11789
13765
  // Chain is passed as parameter to avoid race conditions in concurrent requests
11790
13766
  const provider = await this.getProvider(chain);
11791
13767
  if (!fromAddress) {
@@ -11942,9 +13918,18 @@ class EthersAdapter extends EvmAdapter {
11942
13918
  * ```
11943
13919
  */
11944
13920
  async prepare(params, ctx) {
11945
- // Runtime validation for JavaScript consumers who lack TypeScript's compile-time checks
11946
- assertEvmPreparedChainRequestParams(params);
11947
- const { abi, functionName, args } = params;
13921
+ const { address, abi, functionName, args, value } = params;
13922
+ // Detect native transfer early: no abi, no functionName, no args, but has value
13923
+ const isNativeTransfer = !abi && !functionName && !args && value !== undefined;
13924
+ // Validate parameters based on transfer type
13925
+ if (isNativeTransfer) {
13926
+ // For native transfers, validate address and value
13927
+ assertNativeTransferParams({ address, value });
13928
+ }
13929
+ else {
13930
+ // For contract calls, use full validation
13931
+ assertEvmPreparedChainRequestParams(params);
13932
+ }
11948
13933
  // First, resolve the target chain from the operation context
11949
13934
  const targetChain = resolveChainIdentifier(ctx.chain);
11950
13935
  if (targetChain.type !== 'evm') {
@@ -11967,6 +13952,10 @@ class EthersAdapter extends EvmAdapter {
11967
13952
  if (!resolvedContext) {
11968
13953
  throw new Error('OperationContext resolution failed. Ensure the adapter has capabilities configured.');
11969
13954
  }
13955
+ // Handle native transfers separately (after context resolution)
13956
+ if (isNativeTransfer) {
13957
+ return this.prepareNativeTransfer(address, value, targetChain, resolvedContext.address);
13958
+ }
11970
13959
  const signer = this.getSigner();
11971
13960
  const contract = this.createContractInstance(params, signer, resolvedContext.address, provider);
11972
13961
  return {
@@ -11977,18 +13966,33 @@ class EthersAdapter extends EvmAdapter {
11977
13966
  // gas estimation and fee data retrieval use the correct network.
11978
13967
  const estimationProvider = await this.getProvider(targetChain);
11979
13968
  const contractForEstimation = contract.connect(estimationProvider);
11980
- return this.estimateGasForFunction(contractForEstimation, functionName, args, targetChain, overrides, fallback);
13969
+ // Merge value from params with overrides, with overrides taking precedence
13970
+ // Include 'from' address since connecting to a Provider (not Signer) loses
13971
+ // the ability to infer the sender address, which would otherwise default to
13972
+ // the zero address and cause "transfer from the zero address" errors.
13973
+ const mergedOverrides = {
13974
+ from: resolvedContext.address,
13975
+ ...(value !== undefined && { value }),
13976
+ ...overrides,
13977
+ };
13978
+ return this.estimateGasForFunction(contractForEstimation, functionName, args, targetChain, mergedOverrides, fallback);
11981
13979
  },
11982
13980
  execute: async (overrides) => {
13981
+ // Merge value from params with overrides, with overrides taking precedence
13982
+ // Only add value if it's defined (exactOptionalPropertyTypes requires this pattern)
13983
+ const mergedOverrides = {
13984
+ ...(value !== undefined && { value }),
13985
+ ...overrides,
13986
+ };
11983
13987
  // Simulate the function call to catch errors before submission
11984
- await this.simulateFunctionCall(contract, functionName, args, targetChain);
13988
+ await this.simulateFunctionCall(contract, functionName, args, targetChain, mergedOverrides);
11985
13989
  await this.ensureChain(targetChain);
11986
13990
  // Reconnect the contract with the current signer, which is on the correct
11987
13991
  // chain after `ensureChain`, to ensure the transaction is populated and
11988
13992
  // sent correctly.
11989
13993
  const currentSigner = this.getSigner();
11990
13994
  const contractForExecution = contract.connect(currentSigner);
11991
- return this.executeTransaction(contractForExecution, functionName, args, resolvedContext.address, targetChain, overrides);
13995
+ return this.executeTransaction(contractForExecution, functionName, args, resolvedContext.address, targetChain, mergedOverrides);
11992
13996
  },
11993
13997
  };
11994
13998
  }
@@ -12026,11 +14030,11 @@ class EthersAdapter extends EvmAdapter {
12026
14030
  async getAddress(chain) {
12027
14031
  // Prevent calling getAddress on developer-controlled adapters
12028
14032
  if (this.capabilities?.addressContext === 'developer-controlled') {
12029
- throw new Error('Cannot call getAddress() on developer-controlled adapters. Address must be provided explicitly in the operation context.');
14033
+ throw createValidationFailedError('adapter', 'invalid-operation', 'Cannot call getAddress() on developer-controlled adapters. Address must be provided explicitly in the operation context.');
12030
14034
  }
12031
14035
  // Chain parameter should now be provided by resolveOperationContext
12032
14036
  if (!chain) {
12033
- throw new Error('Chain parameter is required for address resolution. This should be provided by the OperationContext pattern.');
14037
+ throw createValidationFailedError('chain', 'required', 'Chain parameter is required for address resolution. This should be provided by the OperationContext pattern.');
12034
14038
  }
12035
14039
  const signer = this.getSigner();
12036
14040
  const address = await signer.getAddress();
@@ -12229,15 +14233,15 @@ class EthersAdapter extends EvmAdapter {
12229
14233
  // Get chain from required OperationContext
12230
14234
  const resolvedContext = await resolveOperationContext(this, ctx);
12231
14235
  if (!resolvedContext) {
12232
- throw new Error('OperationContext resolution failed. Ensure the adapter has capabilities configured.');
14236
+ throw createValidationFailedError('context', 'resolution-failed', 'OperationContext resolution failed. Ensure the adapter has capabilities configured.');
12233
14237
  }
12234
14238
  const targetChain = resolvedContext.chain;
12235
14239
  if (targetChain.type !== 'evm') {
12236
- throw new Error(`Invalid chain type '${String(targetChain.type)}' for EthersAdapter. Expected 'evm' chain type.`);
14240
+ throw createInvalidChainError(targetChain.name, `Invalid chain type '${String(targetChain.type)}' for EthersAdapter. Expected 'evm' chain type.`);
12237
14241
  }
12238
14242
  const signer = this.getSigner();
12239
14243
  if (!signer) {
12240
- throw new Error('No signer is configured. Please provide a signer to sign typed data.');
14244
+ throw createValidationFailedError('signer', 'not-configured', 'No signer is configured. Please provide a signer to sign typed data.');
12241
14245
  }
12242
14246
  const types = { ...typedData.types };
12243
14247
  delete types['EIP712Domain'];
@@ -12294,6 +14298,93 @@ class EthersAdapter extends EvmAdapter {
12294
14298
  },
12295
14299
  };
12296
14300
  }
14301
+ /**
14302
+ * Prepares a native token transfer (ETH, MATIC, etc.) for gas estimation and execution.
14303
+ *
14304
+ * Native transfers are simple value transfers that don't require contract ABI or function calls.
14305
+ * This method reuses the existing transaction execution flow but skips contract-specific logic.
14306
+ *
14307
+ * @param address - The recipient address for the native token transfer
14308
+ * @param value - The amount of native tokens to send (in wei)
14309
+ * @param targetChain - The target chain definition
14310
+ * @param resolvedAddress - The resolved sender address from operation context
14311
+ * @returns Prepared chain request for native transfer
14312
+ * @throws Error when gas estimation fails without fallback
14313
+ * @throws Error when gas price retrieval fails
14314
+ * @throws Error when transaction execution fails
14315
+ *
14316
+ * @example
14317
+ * ```typescript
14318
+ * // Internal usage - called by prepare() when native transfer is detected
14319
+ * const prepared = this.prepareNativeTransfer(
14320
+ * '0x1234567890123456789012345678901234567890',
14321
+ * BigInt(1000000000000000000), // 1 ETH
14322
+ * Ethereum,
14323
+ * '0xsenderAddress'
14324
+ * );
14325
+ * const estimate = await prepared.estimate();
14326
+ * const txHash = await prepared.execute();
14327
+ * ```
14328
+ */
14329
+ prepareNativeTransfer(address, value, targetChain, resolvedAddress) {
14330
+ return {
14331
+ type: 'evm',
14332
+ estimate: async (overrides, fallback) => {
14333
+ await this.ensureChain(targetChain);
14334
+ const estimationProvider = await this.getProvider(targetChain);
14335
+ let gas;
14336
+ try {
14337
+ gas = await estimationProvider.estimateGas({
14338
+ to: address,
14339
+ value,
14340
+ from: resolvedAddress,
14341
+ ...overrides,
14342
+ });
14343
+ }
14344
+ catch (error) {
14345
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
14346
+ if (fallback &&
14347
+ errorMessage.toLowerCase().includes('execution reverted')) {
14348
+ return fallback;
14349
+ }
14350
+ // Wrap gas estimation errors with structured error format
14351
+ throw parseBlockchainError(error, {
14352
+ chain: targetChain.name,
14353
+ operation: 'estimateGas',
14354
+ });
14355
+ }
14356
+ let gasPrice;
14357
+ try {
14358
+ gasPrice = await this.fetchGasPrice(targetChain);
14359
+ }
14360
+ catch (error) {
14361
+ // Wrap gas price errors with structured error format
14362
+ throw parseBlockchainError(error, {
14363
+ chain: targetChain.name,
14364
+ operation: 'getGasPrice',
14365
+ });
14366
+ }
14367
+ return {
14368
+ gas,
14369
+ gasPrice,
14370
+ fee: (gas * gasPrice).toString(),
14371
+ };
14372
+ },
14373
+ execute: async (overrides) => {
14374
+ await this.ensureChain(targetChain);
14375
+ // Create transaction request for native transfer
14376
+ const txRequest = {
14377
+ to: address,
14378
+ value,
14379
+ ...overrides,
14380
+ };
14381
+ // Use common transaction sending logic with nonce management
14382
+ const hash = await this.sendTransactionWithNonceManagement(txRequest, resolvedAddress, targetChain, overrides);
14383
+ assertEvmTransactionHash(hash);
14384
+ return hash;
14385
+ },
14386
+ };
14387
+ }
12297
14388
  /**
12298
14389
  * Reads a contract function using Ethers v6.
12299
14390
  *
@@ -12309,6 +14400,64 @@ class EthersAdapter extends EvmAdapter {
12309
14400
  const contractFunction = contract.getFunction(functionName);
12310
14401
  return (await contractFunction.staticCall(...args));
12311
14402
  }
14403
+ /**
14404
+ * Get the decimal places for an ERC-20 token on an EVM chain.
14405
+ *
14406
+ * This method calls the `decimals()` function on the ERC-20 token contract
14407
+ * to fetch the number of decimal places. Ethers v6 returns decimals as bigint,
14408
+ * which is validated and converted to a number.
14409
+ *
14410
+ * @param tokenAddress - The ERC-20 token contract address
14411
+ * @param chain - The EVM chain definition where the token is deployed
14412
+ * @returns Promise resolving to the number of decimal places
14413
+ * @throws Error when the contract doesn't exist, doesn't support decimals(), or returns invalid data
14414
+ *
14415
+ * @remarks
14416
+ * - Ethers v6 returns decimals as bigint
14417
+ * - Validates that decimals are within 0-255 range (ERC-20 uint8 standard)
14418
+ * - Converts bigint to number for consistent API
14419
+ *
14420
+ * @example
14421
+ * ```typescript
14422
+ * import { EthersAdapter } from '@circle-fin/adapter-ethers-v6'
14423
+ * import { Ethereum } from '@core/chains'
14424
+ *
14425
+ * const adapter = new EthersAdapter({ signer })
14426
+ *
14427
+ * // Fetch decimals for DAI token
14428
+ * const decimals = await adapter.getTokenDecimals(
14429
+ * '0x6B175474E89094C44Da98b954EedeAC495271d0F',
14430
+ * Ethereum
14431
+ * )
14432
+ * console.log(decimals) // 18
14433
+ * ```
14434
+ */
14435
+ async getTokenDecimals(tokenAddress, chain) {
14436
+ try {
14437
+ // Ethers v6 returns bigint for uint8
14438
+ const decimalsResult = await this.readContract({
14439
+ address: tokenAddress,
14440
+ abi: erc20Abi,
14441
+ functionName: 'decimals',
14442
+ args: [],
14443
+ }, chain);
14444
+ // Validate decimals are within valid range (0-255)
14445
+ if (typeof decimalsResult !== 'bigint' ||
14446
+ decimalsResult < 0n ||
14447
+ decimalsResult > 255n) {
14448
+ throw createValidationFailedError('decimals', decimalsResult, 'Token decimals must be a bigint between 0 and 255');
14449
+ }
14450
+ // Convert bigint to number
14451
+ return Number(decimalsResult);
14452
+ }
14453
+ catch (error) {
14454
+ // If it's already a KitError from our validation, re-throw as-is
14455
+ if (error instanceof KitError) {
14456
+ throw error;
14457
+ }
14458
+ throw createValidationFailedError('tokenAddress', tokenAddress, `Failed to fetch decimals for token on ${chain.name}: ${getErrorMessage(error)}`);
14459
+ }
14460
+ }
12312
14461
  }
12313
14462
 
12314
14463
  /**