@circle-fin/app-kit 1.14.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +3 -3
  3. package/bridge.cjs +7464 -454
  4. package/bridge.d.cts +585 -61
  5. package/bridge.d.mts +585 -61
  6. package/bridge.d.ts +585 -61
  7. package/bridge.mjs +7464 -454
  8. package/chains.cjs +146 -5
  9. package/chains.d.cts +108 -2
  10. package/chains.d.mts +108 -2
  11. package/chains.d.ts +108 -2
  12. package/chains.mjs +146 -6
  13. package/context.d.cts +522 -57
  14. package/context.d.mts +522 -57
  15. package/context.d.ts +522 -57
  16. package/earn.cjs +478 -181
  17. package/earn.d.cts +522 -57
  18. package/earn.d.mts +522 -57
  19. package/earn.d.ts +522 -57
  20. package/earn.mjs +478 -181
  21. package/estimateBridge.cjs +7464 -457
  22. package/estimateBridge.d.cts +643 -82
  23. package/estimateBridge.d.mts +643 -82
  24. package/estimateBridge.d.ts +643 -82
  25. package/estimateBridge.mjs +7464 -457
  26. package/estimateSwap.cjs +528 -185
  27. package/estimateSwap.d.cts +522 -57
  28. package/estimateSwap.d.mts +522 -57
  29. package/estimateSwap.d.ts +522 -57
  30. package/estimateSwap.mjs +528 -185
  31. package/index.cjs +11548 -2936
  32. package/index.d.cts +5073 -1738
  33. package/index.d.mts +5073 -1738
  34. package/index.d.ts +5073 -1738
  35. package/index.mjs +11547 -2937
  36. package/package.json +17 -6
  37. package/server.cjs +10040 -0
  38. package/server.cjs.map +1 -0
  39. package/server.d.cts +2467 -0
  40. package/server.d.mts +2467 -0
  41. package/server.d.ts +2467 -0
  42. package/server.mjs +10028 -0
  43. package/server.mjs.map +1 -0
  44. package/swap.cjs +528 -185
  45. package/swap.d.cts +522 -57
  46. package/swap.d.mts +522 -57
  47. package/swap.d.ts +522 -57
  48. package/swap.mjs +528 -185
  49. package/unifiedBalance.cjs +742 -44
  50. package/unifiedBalance.d.cts +223 -1
  51. package/unifiedBalance.d.mts +223 -1
  52. package/unifiedBalance.d.ts +223 -1
  53. package/unifiedBalance.mjs +742 -44
@@ -708,6 +708,38 @@ class KitError extends Error {
708
708
  name: 'INPUT_AMOUNT_OUT_OF_RANGE',
709
709
  type: 'INPUT'
710
710
  },
711
+ /**
712
+ * Protocol fee is denominated in a token that is not supported for the
713
+ * route (only the native gas token and the route's supported fee tokens,
714
+ * such as USDC, are accepted).
715
+ */ UNSUPPORTED_FEE_TOKEN: {
716
+ code: 1014,
717
+ name: 'INPUT_UNSUPPORTED_FEE_TOKEN',
718
+ type: 'INPUT'
719
+ },
720
+ /** Retry / resume is not supported by this provider for the given result */ RETRY_NOT_SUPPORTED: {
721
+ code: 1015,
722
+ name: 'INPUT_RETRY_NOT_SUPPORTED',
723
+ type: 'INPUT'
724
+ },
725
+ /** Bridge-step analysis for retry is not supported by this provider */ STEP_ANALYSIS_NOT_SUPPORTED: {
726
+ code: 1016,
727
+ name: 'INPUT_STEP_ANALYSIS_NOT_SUPPORTED',
728
+ type: 'INPUT'
729
+ },
730
+ /**
731
+ * A kit operation name collides with a reserved event-subscription
732
+ * method (`on` / `off`) at kit construction.
733
+ */ RESERVED_OPERATION_NAME: {
734
+ code: 1000,
735
+ name: 'INPUT_RESERVED_OPERATION_NAME',
736
+ type: 'INPUT'
737
+ },
738
+ /** A kit was constructed with a missing / undefined operation factory */ MISSING_OPERATION: {
739
+ code: 1017,
740
+ name: 'INPUT_MISSING_OPERATION',
741
+ type: 'INPUT'
742
+ },
711
743
  /** General validation failure for complex validation rules */ VALIDATION_FAILED: {
712
744
  code: 1098,
713
745
  name: 'INPUT_VALIDATION_FAILED',
@@ -938,8 +970,376 @@ class KitError extends Error {
938
970
  code: 8002,
939
971
  name: 'SERVICE_UNKNOWN_ERROR',
940
972
  type: 'SERVICE'
973
+ }};
974
+
975
+ /**
976
+ * Log-safe error redaction for `KitError.cause.trace` fields.
977
+ *
978
+ * @packageDocumentation
979
+ */ // ============================================================================
980
+ // Message redactor (operator-configurable secret scrubbing)
981
+ // ============================================================================
982
+ /**
983
+ * A pluggable redactor for the `message` strings that flow onto
984
+ * `KitError.cause.trace`.
985
+ *
986
+ * @remarks
987
+ * Two equivalent shapes are accepted:
988
+ *
989
+ * 1. **`readonly RegExp[]`** — the common case. Each pattern is run
990
+ * against the message; matches are replaced with `[REDACTED]`.
991
+ * The operator owns the `g` flag — pass a global regex to scrub
992
+ * every occurrence, a non-global regex to scrub the first one.
993
+ *
994
+ * 2. **`(message: string) => string`** — escape hatch for callers
995
+ * who want full control over the substitution (different
996
+ * placeholders per pattern, length-preserving redaction for
997
+ * log-volume parity, format-preserving encryption, etc.).
998
+ *
999
+ * @public
1000
+ */ /**
1001
+ * Known RPC provider host suffixes scrubbed by the default redactor.
1002
+ *
1003
+ * @remarks
1004
+ * The default redactor (see {@link composeRedactor} and
1005
+ * {@link defaultMessageRedactor}) finds every `https?:` / `wss?:` URL
1006
+ * in an error message, parses each one with the platform `URL`
1007
+ * constructor, and replaces it with `[REDACTED]` if the parsed
1008
+ * hostname ends with one of these suffixes. Adding a new provider is
1009
+ * a one-line change and cannot be wrong: there is no regex shape to
1010
+ * audit, no anchor-vs-quantifier trade-off, no
1011
+ * {@link https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS | ReDoS}
1012
+ * surface — the worst case is over-redaction (an unrelated URL on
1013
+ * the same host suffix gets scrubbed) which is the safe failure
1014
+ * mode.
1015
+ *
1016
+ * Suffixes covered:
1017
+ *
1018
+ * | Provider | Suffix |
1019
+ * | --- | --- |
1020
+ * | Alchemy | `.g.alchemy.com` |
1021
+ * | Infura | `.infura.io` |
1022
+ * | QuickNode | `.quiknode.pro` |
1023
+ * | Ankr | `.ankr.com` |
1024
+ * | Chainstack | `.p2pify.com` |
1025
+ * | Tenderly | `.gateway.tenderly.co` |
1026
+ * | BlockPI | `.blockpi.network` |
1027
+ * | GetBlock | `.getblock.io` |
1028
+ * | Moralis | `.moralis-nodes.com` |
1029
+ * | Helius | `.helius-rpc.com`, `.helius.xyz` |
1030
+ * | Triton | `.rpcpool.com` |
1031
+ *
1032
+ * Compose this list with your own self-hosted RPC suffixes via
1033
+ * {@link composeRedactor}.
1034
+ *
1035
+ * @public
1036
+ */ const DEFAULT_RPC_HOST_SUFFIXES = Object.freeze([
1037
+ '.g.alchemy.com',
1038
+ '.infura.io',
1039
+ '.quiknode.pro',
1040
+ '.ankr.com',
1041
+ '.p2pify.com',
1042
+ '.gateway.tenderly.co',
1043
+ '.blockpi.network',
1044
+ '.getblock.io',
1045
+ '.moralis-nodes.com',
1046
+ '.helius-rpc.com',
1047
+ '.helius.xyz',
1048
+ '.rpcpool.com'
1049
+ ]);
1050
+ /**
1051
+ * Built-in non-URL redaction patterns applied alongside the
1052
+ * URL-host scrubber.
1053
+ *
1054
+ * @remarks
1055
+ * Two simple patterns:
1056
+ *
1057
+ * - **Generic credential-bearing query strings** — `?api-key=…`,
1058
+ * `?apikey=…`, `?api-token=…`, `?access-token=…`, `?auth-token=…`,
1059
+ * `?app-key=…`, plus bare `?key=…` / `?token=…` / `?secret=…`.
1060
+ * Catches the case where a self-hosted RPC bakes the credential
1061
+ * into a query string instead of the path. Single canonical-name
1062
+ * alternation; complexity is well under SonarQube S5856's
1063
+ * 20-complexity bound.
1064
+ * - **Bearer header tokens** — `Bearer <token>` with bounded body
1065
+ * length and a `(?<![A-Za-z])` anchor so it can't be matched as a
1066
+ * tail-fragment of a longer word.
1067
+ *
1068
+ * Provider-specific URL-host scrubbing lives in
1069
+ * {@link DEFAULT_RPC_HOST_SUFFIXES} (parsed via `URL`) rather than as
1070
+ * regexes here — the host-list approach is CodeQL-clean (no
1071
+ * `js/regex/missing-anchor` heuristic match), false-positive-free
1072
+ * (provider URLs cannot accidentally collide with calldata or tx
1073
+ * hashes), and trivial to extend (one string per provider).
1074
+ *
1075
+ * @public
1076
+ */ const DEFAULT_RPC_REDACTION_PATTERNS = Object.freeze([
1077
+ /[?&](?:(?:api|access|auth|app)[-_]?)?(?:key|token|secret)=[^&\s"'<>`]{1,256}/gi,
1078
+ /(?<![A-Za-z])Bearer\s+[A-Za-z0-9._~+/=-]{16,512}/g
1079
+ ]);
1080
+ /**
1081
+ * Generic URL extractor used by {@link composeRedactor}.
1082
+ *
1083
+ * @remarks
1084
+ * Matches any `http://`, `https://`, `ws://`, or `wss://` URL,
1085
+ * bounded to 2 048 characters (longest URL Chrome / Firefox / IE
1086
+ * accept in practice; well below any reasonable error-message
1087
+ * payload). The regex is intentionally generic — it does **not**
1088
+ * encode any provider host shape — so CodeQL's
1089
+ * `js/regex/missing-anchor` heuristic (which targets URL-validation
1090
+ * regexes that look for a specific host) does not fire. All
1091
+ * provider classification happens in JS via `URL` parsing + string
1092
+ * suffix matching, never in regex.
1093
+ */ const URL_EXTRACTOR = /(?<![A-Za-z0-9])(?:https?|wss?):\/\/[^\s"'<>`]{1,2048}/g;
1094
+ /**
1095
+ * Construct a redactor function from a host-suffix list and a
1096
+ * non-URL pattern list.
1097
+ *
1098
+ * @remarks
1099
+ * Each invocation:
1100
+ *
1101
+ * 1. Finds every URL in the message via {@link URL_EXTRACTOR}.
1102
+ * 2. Parses each candidate with the platform `URL` constructor (the
1103
+ * same one browsers and Node use for `fetch()` / `new Request()`),
1104
+ * discarding common trailing punctuation.
1105
+ * 3. Replaces the URL with `[REDACTED]` if its parsed hostname ends
1106
+ * with one of the configured suffixes.
1107
+ * 4. Applies every regex in `patterns` (Bearer headers, query-string
1108
+ * credentials, plus any operator-supplied additions).
1109
+ *
1110
+ * Both arrays default to the SDK's built-ins
1111
+ * ({@link DEFAULT_RPC_HOST_SUFFIXES} and
1112
+ * {@link DEFAULT_RPC_REDACTION_PATTERNS}); pass either to override.
1113
+ *
1114
+ * @param opts - Optional override for the default suffix list and/or
1115
+ * pattern list.
1116
+ * @returns A pure `(message: string) => string` function suitable for
1117
+ * passing to {@link setMessageRedactor}.
1118
+ *
1119
+ * @example
1120
+ * Add your self-hosted RPC URL to the host-suffix list (most common
1121
+ * extension shape):
1122
+ *
1123
+ * ```typescript
1124
+ * import {
1125
+ * composeRedactor,
1126
+ * setMessageRedactor,
1127
+ * DEFAULT_RPC_HOST_SUFFIXES,
1128
+ * } from '@core/errors'
1129
+ *
1130
+ * setMessageRedactor(
1131
+ * composeRedactor({
1132
+ * hostSuffixes: [...DEFAULT_RPC_HOST_SUFFIXES, '.rpc.my-corp.example'],
1133
+ * }),
1134
+ * )
1135
+ * ```
1136
+ *
1137
+ * @example
1138
+ * Add a custom non-URL pattern (e.g. a corporate header token):
1139
+ *
1140
+ * ```typescript
1141
+ * setMessageRedactor(
1142
+ * composeRedactor({
1143
+ * patterns: [
1144
+ * ...DEFAULT_RPC_REDACTION_PATTERNS,
1145
+ * /X-Corp-Token:\s+[A-Za-z0-9_-]{16,128}/g,
1146
+ * ],
1147
+ * }),
1148
+ * )
1149
+ * ```
1150
+ *
1151
+ * @public
1152
+ */ function composeRedactor(opts) {
1153
+ const hostSuffixes = opts?.hostSuffixes ?? DEFAULT_RPC_HOST_SUFFIXES;
1154
+ const patterns = opts?.patterns ?? DEFAULT_RPC_REDACTION_PATTERNS;
1155
+ return (message)=>{
1156
+ let result = message.replaceAll(URL_EXTRACTOR, (url)=>{
1157
+ // Common trailing punctuation that error-message templates put
1158
+ // immediately after a URL (`Status: 401.`, `(see ...)`, `[1]`).
1159
+ // Strip before parsing so the `URL` parser doesn't reject
1160
+ // `eth-mainnet.g.alchemy.com.` (or accept it with a trailing
1161
+ // dot in `hostname` and miss the suffix match). Implemented as
1162
+ // a length-bounded string loop instead of a `/[.,;)\]>]+$/`
1163
+ // regex so CodeQL `js/redos-on-uncontrolled-input` cannot
1164
+ // flag a polynomial-time backtracking shape on operator-
1165
+ // controlled error text.
1166
+ const TRIM_CHARS = '.,;)]>';
1167
+ let end = url.length;
1168
+ const cap = Math.max(end - 16, 0) // bound the loop, length-cap 16
1169
+ ;
1170
+ while(end > cap && TRIM_CHARS.includes(url.charAt(end - 1))){
1171
+ end--;
1172
+ }
1173
+ const trimmed = end === url.length ? url : url.slice(0, end);
1174
+ let parsed;
1175
+ try {
1176
+ parsed = new URL(trimmed);
1177
+ } catch {
1178
+ return url;
1179
+ }
1180
+ // HTTP-Basic userinfo (`user:pass@host`) is a credential by
1181
+ // definition — scrub it regardless of host, so enterprise /
1182
+ // self-hosted RPC URLs that don't match a known provider suffix
1183
+ // still get redacted.
1184
+ if (parsed.username !== '' || parsed.password !== '') {
1185
+ return '[REDACTED]';
1186
+ }
1187
+ const host = parsed.hostname.toLowerCase();
1188
+ return hostSuffixes.some((suffix)=>host.endsWith(suffix)) ? '[REDACTED]' : url;
1189
+ });
1190
+ for (const pattern of patterns){
1191
+ result = result.replaceAll(pattern, '[REDACTED]');
1192
+ }
1193
+ return result;
1194
+ };
1195
+ }
1196
+ /**
1197
+ * The SDK's default message redactor: scrubs URLs whose hostname
1198
+ * matches a {@link DEFAULT_RPC_HOST_SUFFIXES} entry, plus the
1199
+ * patterns in {@link DEFAULT_RPC_REDACTION_PATTERNS}.
1200
+ *
1201
+ * @remarks
1202
+ * Active at module load and re-installed by
1203
+ * {@link resetMessageRedactor}. Operators who want to layer
1204
+ * additional redaction on top of (rather than replace) the defaults
1205
+ * can call this directly inside a custom function form:
1206
+ *
1207
+ * ```typescript
1208
+ * setMessageRedactor((msg) =>
1209
+ * defaultMessageRedactor(msg).replace(/myExtraPattern/g, '[REDACTED]'),
1210
+ * )
1211
+ * ```
1212
+ *
1213
+ * For most extension cases prefer {@link composeRedactor}, which
1214
+ * gives the same result with structured options instead of a
1215
+ * post-hoc pipe.
1216
+ *
1217
+ * @public
1218
+ */ const defaultMessageRedactor = composeRedactor();
1219
+ let activeRedactor = defaultMessageRedactor;
1220
+ /**
1221
+ * Apply the active redactor to an arbitrary message string.
1222
+ *
1223
+ * @remarks
1224
+ * Adapters call this on every string they place on `cause.trace`
1225
+ * other than the redacted `rawError` itself — viem `shortMessage`,
1226
+ * ethers `error.info.requestUrl`, the provider's `reason` field,
1227
+ * etc. When no redactor is configured this is the identity function,
1228
+ * so the cost on the no-redactor path is one `if`.
1229
+ *
1230
+ * @param message - The string to scrub.
1231
+ * @returns The redacted string. The original is returned untouched
1232
+ * when no redactor is configured.
1233
+ *
1234
+ * @example
1235
+ * ```typescript
1236
+ * import { redactErrorMessage, setMessageRedactor } from '@core/errors'
1237
+ *
1238
+ * setMessageRedactor([/[A-Fa-f0-9]{32,64}/g])
1239
+ * redactErrorMessage('failed: 0xabc...64chars...')
1240
+ * // => 'failed: [REDACTED]'
1241
+ * ```
1242
+ *
1243
+ * @public
1244
+ */ function redactErrorMessage(message) {
1245
+ if (activeRedactor === undefined) return message;
1246
+ // `setMessageRedactor` eagerly compiles the array form into a
1247
+ // `composeRedactor`-wrapped function, so by the time we reach
1248
+ // here `activeRedactor` is always a function. The runtime
1249
+ // `typeof` guard exists for completeness — if a future
1250
+ // contributor introduces a setter path that bypasses the
1251
+ // compilation step, the guard keeps the dispatch correct.
1252
+ if (typeof activeRedactor === 'function') return activeRedactor(message);
1253
+ // Fallback (unreachable in normal use). See note above.
1254
+ return composeRedactor({
1255
+ patterns: activeRedactor,
1256
+ hostSuffixes: []
1257
+ })(message);
1258
+ }
1259
+ // ============================================================================
1260
+ // Object-graph redaction
1261
+ // ============================================================================
1262
+ /**
1263
+ * Redact an `unknown` error to a small, log-safe shape.
1264
+ *
1265
+ * @remarks
1266
+ * Wallet libraries (viem, ethers, web3-provider-engine, …) routinely
1267
+ * attach provider-injected fields to thrown errors: configured RPC URLs,
1268
+ * API keys, opaque internal state, the originating `XMLHttpRequest`,
1269
+ * and so on. Embedding the raw object directly inside `KitError.cause.trace`
1270
+ * means anyone who later JSON-serialises a `KitError` for telemetry leaks
1271
+ * those fields into logs.
1272
+ *
1273
+ * This helper extracts only the two fields that are useful for
1274
+ * post-mortem debugging — `name` and `message` — while never exposing the
1275
+ * underlying object graph. The `message` is run through the operator's
1276
+ * configured {@link setMessageRedactor | message redactor} (if any) so
1277
+ * that secrets the provider has interpolated into the message string
1278
+ * itself can be scrubbed. Non-`Error` throws (raw strings, plain
1279
+ * objects, primitives) are stringified with `String(error)` and then
1280
+ * run through the same redactor, so a secret-bearing string throw
1281
+ * (e.g. WalletConnect / older mobile wallets) is scrubbed too.
1282
+ *
1283
+ * **Two redaction layers, two responsibilities.**
1284
+ *
1285
+ * - **Object-graph stripping (this function, always on)** — drops
1286
+ * extra properties such as `request`, `requestUrl`, `info`, the
1287
+ * originating `XMLHttpRequest`, the configured `transport.url`,
1288
+ * etc. Without this, viem/ethers errors round-trip with the full
1289
+ * provider state graph attached.
1290
+ * - **Message-content scrubbing (configured via
1291
+ * {@link setMessageRedactor}, opt-in)** — handles the case where
1292
+ * the provider has already formatted secrets into `error.message`
1293
+ * itself. Notably, ethers v6's `makeError()` formats info keys
1294
+ * directly into the message:
1295
+ *
1296
+ * ```
1297
+ * missing response (requestUrl="https://mainnet.infura.io/v3/MY_KEY", code=SERVER_ERROR, ...)
1298
+ * ```
1299
+ *
1300
+ * Viem's `HttpRequestError` exhibits the same pattern via
1301
+ * `shortMessage`, which carries the configured URL with API path.
1302
+ * Without a redactor, the API key rides on `cause.trace.rawError.message`
1303
+ * verbatim. With one configured, every match is replaced with
1304
+ * `[REDACTED]` (or the operator's custom substitution).
1305
+ *
1306
+ * The split is intentional: there is no universal URL/secret
1307
+ * heuristic that won't false-positive on legitimate payloads
1308
+ * (mainnet addresses, contract revert reasons, raw call data), so
1309
+ * the SDK refuses to guess. Operators who know which patterns
1310
+ * appear in *their* RPC URLs configure the policy with one call
1311
+ * to {@link setMessageRedactor}.
1312
+ *
1313
+ * @param error - Anything thrown by a third-party wallet/provider library.
1314
+ * @returns A `{ name, message }` pair for `Error` instances (with
1315
+ * `message` run through the active redactor); the stringified value —
1316
+ * also run through the active redactor — otherwise. Always JSON-safe.
1317
+ *
1318
+ * @example
1319
+ * ```typescript
1320
+ * import { redactRawError, setMessageRedactor } from '@core/errors'
1321
+ *
1322
+ * setMessageRedactor([/infura\.io\/v3\/[a-z0-9]+/gi])
1323
+ * const err = new Error('failed: https://mainnet.infura.io/v3/abc123')
1324
+ * redactRawError(err)
1325
+ * // => { name: 'Error', message: 'failed: https://[REDACTED]' }
1326
+ * ```
1327
+ *
1328
+ * @public
1329
+ */ function redactRawError(error) {
1330
+ if (error instanceof Error) {
1331
+ return {
1332
+ name: error.name,
1333
+ message: redactErrorMessage(error.message)
1334
+ };
941
1335
  }
942
- };
1336
+ // Non-Error throws (raw strings from WalletConnect / older mobile
1337
+ // wallets, plain objects, primitives) are stringified and then run
1338
+ // through the active redactor. A raw string throw can carry a secret
1339
+ // RPC URL just like an `Error.message` does, so it must be scrubbed
1340
+ // rather than returned verbatim.
1341
+ return redactErrorMessage(String(error));
1342
+ }
943
1343
 
944
1344
  /**
945
1345
  * Creates error for network type mismatch between source and destination.
@@ -985,6 +1385,15 @@ class KitError extends Error {
985
1385
  *
986
1386
  * @param source - Source chain name
987
1387
  * @param destination - Destination chain name
1388
+ * @param providerErrors - Optional list of providers whose `supportsRoute`
1389
+ * call threw during the search. When non-empty, the message includes a
1390
+ * summary so consumers can distinguish "no provider supports this route"
1391
+ * from "a provider errored mid-search." Pass the errors raw: each one is
1392
+ * run through `redactRawError` here, so neither the message nor
1393
+ * `cause.trace.providerErrors` carries the provider's raw object graph.
1394
+ * @param token - Optional token the caller asked to bridge. When supplied it
1395
+ * is named in the message and the trace, so a symbol the routing layer does
1396
+ * not recognise reads as a token problem rather than a chain-pair one.
988
1397
  * @returns KitError with specific route details
989
1398
  *
990
1399
  * @example
@@ -992,22 +1401,51 @@ class KitError extends Error {
992
1401
  * import { createUnsupportedRouteError } from '@core/errors'
993
1402
  *
994
1403
  * throw createUnsupportedRouteError('Ethereum', 'Solana')
995
- * // Message: "Route from Ethereum to Solana is not supported"
996
- * ```
997
- */ function createUnsupportedRouteError(source, destination) {
1404
+ * // Message: "Route from Ethereum to Solana is not supported."
1405
+ *
1406
+ * throw createUnsupportedRouteError('Ethereum', 'Arc', [
1407
+ * { provider: 'cctpx', error: new Error('ECONNRESET') },
1408
+ * ])
1409
+ * // Message: "Route from Ethereum to Arc is not supported.
1410
+ * // Providers errored during route check: cctpx (ECONNRESET)."
1411
+ *
1412
+ * throw createUnsupportedRouteError('Ethereum', 'Arc', undefined, 'USCD')
1413
+ * // Message: "Route from Ethereum to Arc is not supported for token 'USCD'."
1414
+ * ```
1415
+ */ function createUnsupportedRouteError(source, destination, providerErrors, token) {
1416
+ const failures = [];
1417
+ // Redact once, here. `supportsRoute` may be async so a provider can do a
1418
+ // registry-backed network check, and a fetch rejection embeds the request
1419
+ // URL — with a credential in the worst case — in both its object graph and
1420
+ // its message. Both consumers below read from this redacted copy, so the
1421
+ // raw value never leaves this function. Redacting once is load-bearing:
1422
+ // `redactRawError` is not idempotent (it stringifies a non-`Error` input,
1423
+ // so a second pass collapses `{ name, message }` to `"[object Object]"`),
1424
+ // which is why callers pass raw errors in and let the factory do it.
1425
+ const redactedFailures = failures.map((failure)=>({
1426
+ provider: failure.provider,
1427
+ error: redactRawError(failure.error)
1428
+ }));
1429
+ const summary = redactedFailures.length === 0 ? '' : ` Providers errored during route check: ${redactedFailures.map((f)=>`${f.provider} (${describeProviderError(f.error)})`).join(', ')}.`;
1430
+ const tokenClause = '' ;
998
1431
  const errorDetails = {
999
1432
  ...InputError.UNSUPPORTED_ROUTE,
1000
1433
  recoverability: 'FATAL',
1001
- message: `Route from ${source} to ${destination} is not supported.`,
1434
+ message: `Route from ${source} to ${destination} is not supported` + `${tokenClause}.${summary}`,
1002
1435
  cause: {
1003
1436
  trace: {
1004
1437
  source,
1005
- destination
1438
+ destination,
1439
+ providerErrors: redactedFailures,
1440
+ ...{}
1006
1441
  }
1007
1442
  }
1008
1443
  };
1009
1444
  return new KitError(errorDetails);
1010
1445
  }
1446
+ /** Extract a human-readable summary from a redacted provider error. */ function describeProviderError(error) {
1447
+ return typeof error === 'string' ? error : error.message;
1448
+ }
1011
1449
  /**
1012
1450
  * Creates error for invalid amount format or precision.
1013
1451
  *
@@ -1196,7 +1634,7 @@ class KitError extends Error {
1196
1634
  * import { createValidationErrorFromZod } from '@core/errors'
1197
1635
  *
1198
1636
  * function validateBridgeParams(params: unknown): asserts params is BridgeParams {
1199
- * const result = bridgeParamsSchema.safeParse(params)
1637
+ * const result = usdcBridgeParamsSchema.safeParse(params)
1200
1638
  * if (!result.success) {
1201
1639
  * throw createValidationErrorFromZod(result.error, 'bridge parameters')
1202
1640
  * }
@@ -3042,9 +3480,10 @@ class KitError extends Error {
3042
3480
  Blockchain["Algorand_Testnet"] = "Algorand_Testnet";
3043
3481
  Blockchain["Aptos"] = "Aptos";
3044
3482
  Blockchain["Aptos_Testnet"] = "Aptos_Testnet";
3045
- Blockchain["Arc_Testnet"] = "Arc_Testnet";
3046
3483
  Blockchain["Arbitrum"] = "Arbitrum";
3047
3484
  Blockchain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
3485
+ Blockchain["Arc"] = "Arc";
3486
+ Blockchain["Arc_Testnet"] = "Arc_Testnet";
3048
3487
  Blockchain["Avalanche"] = "Avalanche";
3049
3488
  Blockchain["Avalanche_Fuji"] = "Avalanche_Fuji";
3050
3489
  Blockchain["Base"] = "Base";
@@ -3131,6 +3570,7 @@ var SwapChain;
3131
3570
  SwapChain["XDC"] = "XDC";
3132
3571
  SwapChain["HyperEVM"] = "HyperEVM";
3133
3572
  SwapChain["Monad"] = "Monad";
3573
+ SwapChain["Arc"] = "Arc";
3134
3574
  // Testnet chains with swap support
3135
3575
  SwapChain["Arc_Testnet"] = "Arc_Testnet";
3136
3576
  })(SwapChain || (SwapChain = {}));
@@ -3138,6 +3578,7 @@ var BridgeChain;
3138
3578
  (function(BridgeChain) {
3139
3579
  // Mainnet chains with CCTPv2 support
3140
3580
  BridgeChain["Arbitrum"] = "Arbitrum";
3581
+ BridgeChain["Arc"] = "Arc";
3141
3582
  BridgeChain["Avalanche"] = "Avalanche";
3142
3583
  BridgeChain["Base"] = "Base";
3143
3584
  BridgeChain["Codex"] = "Codex";
@@ -3193,6 +3634,7 @@ var BridgeChain;
3193
3634
  var UnifiedBalanceChain;
3194
3635
  (function(UnifiedBalanceChain) {
3195
3636
  // Mainnet chains with Gateway V1 support
3637
+ UnifiedBalanceChain["Arc"] = "Arc";
3196
3638
  UnifiedBalanceChain["Arbitrum"] = "Arbitrum";
3197
3639
  UnifiedBalanceChain["Avalanche"] = "Avalanche";
3198
3640
  UnifiedBalanceChain["Base"] = "Base";
@@ -3222,6 +3664,7 @@ var UnifiedBalanceChain;
3222
3664
  })(UnifiedBalanceChain || (UnifiedBalanceChain = {}));
3223
3665
  var EarnChain;
3224
3666
  (function(EarnChain) {
3667
+ EarnChain["Arc"] = "Arc";
3225
3668
  EarnChain["Arc_Testnet"] = "Arc_Testnet";
3226
3669
  })(EarnChain || (EarnChain = {}));
3227
3670
  /**
@@ -3238,8 +3681,11 @@ var EarnChain;
3238
3681
  * console.log(EARN_BRIDGE_SOURCE_BLOCKCHAINS.join(', '))
3239
3682
  * ```
3240
3683
  */ const EARN_BRIDGE_SOURCE_BLOCKCHAINS = [
3684
+ "Arbitrum",
3241
3685
  "Arbitrum_Sepolia",
3686
+ "Base",
3242
3687
  "Base_Sepolia",
3688
+ "Ethereum",
3243
3689
  "Ethereum_Sepolia"
3244
3690
  ];
3245
3691
  /**
@@ -3253,6 +3699,7 @@ var EarnChain;
3253
3699
  * console.log(EARN_BRIDGE_DESTINATION_BLOCKCHAINS.join(', '))
3254
3700
  * ```
3255
3701
  */ const EARN_BRIDGE_DESTINATION_BLOCKCHAINS = [
3702
+ "Arc",
3256
3703
  "Arc_Testnet"
3257
3704
  ];
3258
3705
 
@@ -3558,6 +4005,25 @@ var EarnChain;
3558
4005
  * EVM-compatible chains. Use this address when deploying or testing
3559
4006
  * cross-chain USDC transfers on test networks.
3560
4007
  */ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d';
4008
+ /**
4009
+ * The CrossChainTokenService (CCTPx) service address for EVM testnet networks.
4010
+ *
4011
+ * This service coordinates CCTPx cross-chain token operations on testnet
4012
+ * environments and is shared by every EVM test network CCTPx supports. Read the
4013
+ * chain definitions via `isCCTPXSupported` rather than trusting a list here,
4014
+ * which goes stale as deployments land.
4015
+ */ const CCTPX_SERVICE_ADDRESS_EVM_TESTNET = '0x63753E722bd2C2A5DF6EE19C5106662208B81077';
4016
+ /**
4017
+ * CrossChainTokenService (CCTS) proxy address for CCTPx on EVM **mainnet**.
4018
+ *
4019
+ * @remarks
4020
+ * A distinct deployment from {@link CCTPX_SERVICE_ADDRESS_EVM_TESTNET}, shared
4021
+ * by every EVM mainnet chain that CCTPx supports. A chain becomes
4022
+ * CCTPx-eligible only once its definition carries this as
4023
+ * `cctpx.serviceAddress`, and a route additionally needs both of its endpoints
4024
+ * eligible — so adding a chain here is the deliberate, reviewable step that
4025
+ * opens mainnet CCTPx for it.
4026
+ */ const CCTPX_SERVICE_ADDRESS_EVM_MAINNET = '0x431871229103b780868f8C6BB820cd16ECf942BC';
3561
4027
  /**
3562
4028
  * The bridge contract address for EVM mainnet networks.
3563
4029
  *
@@ -3676,6 +4142,76 @@ var EarnChain;
3676
4142
  * Used by the spend fee-split logic to route Circle's share of custom fees.
3677
4143
  */ const CIRCLE_FEE_RECIPIENT_SOLANA = 'HGMjULXsaMcbXgRhRECkSG3LyiKJgqiNAnLv1zZd8Kpn';
3678
4144
 
4145
+ /**
4146
+ * Arc Mainnet chain definition
4147
+ * @remarks
4148
+ * This represents the official production network for the Arc blockchain,
4149
+ * Circle's EVM-compatible Layer-1 designed for stablecoin finance
4150
+ * and asset tokenization. Arc uses USDC as the native gas token and
4151
+ * features the Malachite Byzantine Fault Tolerant (BFT) consensus
4152
+ * engine for sub-second finality.
4153
+ */ const Arc = defineChain({
4154
+ type: 'evm',
4155
+ chain: Blockchain.Arc,
4156
+ name: 'Arc',
4157
+ title: 'Arc Mainnet',
4158
+ nativeCurrency: {
4159
+ name: 'USDC',
4160
+ symbol: 'USDC',
4161
+ // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
4162
+ // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
4163
+ // See: https://docs.arc.network/arc/references/contract-addresses
4164
+ decimals: 18
4165
+ },
4166
+ chainId: 5042,
4167
+ isTestnet: false,
4168
+ explorerUrl: 'https://explorer.arc.io/tx/{hash}',
4169
+ rpcEndpoints: [
4170
+ 'https://rpc.mainnet.arc.io/'
4171
+ ],
4172
+ eurcAddress: '0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c1',
4173
+ usdcAddress: '0x3600000000000000000000000000000000000000',
4174
+ usdtAddress: null,
4175
+ cctp: {
4176
+ domain: 26,
4177
+ contracts: {
4178
+ v2: {
4179
+ type: 'split',
4180
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
4181
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
4182
+ confirmations: 1,
4183
+ fastConfirmations: 1
4184
+ }
4185
+ },
4186
+ forwarderSupported: {
4187
+ source: false,
4188
+ destination: true
4189
+ }
4190
+ },
4191
+ cctpx: {
4192
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4193
+ },
4194
+ kitContracts: {
4195
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4196
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET
4197
+ },
4198
+ gateway: {
4199
+ domain: 26,
4200
+ contracts: {
4201
+ v1: {
4202
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
4203
+ minter: GATEWAY_MINTER_EVM_MAINNET,
4204
+ depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
4205
+ genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
4206
+ }
4207
+ },
4208
+ forwarderSupported: {
4209
+ source: true,
4210
+ destination: true
4211
+ }
4212
+ }
4213
+ });
4214
+
3679
4215
  /**
3680
4216
  * Arc Testnet chain definition
3681
4217
  * @remarks
@@ -3723,6 +4259,9 @@ var EarnChain;
3723
4259
  destination: true
3724
4260
  }
3725
4261
  },
4262
+ cctpx: {
4263
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4264
+ },
3726
4265
  kitContracts: {
3727
4266
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
3728
4267
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -3790,6 +4329,9 @@ var EarnChain;
3790
4329
  destination: true
3791
4330
  }
3792
4331
  },
4332
+ cctpx: {
4333
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4334
+ },
3793
4335
  kitContracts: {
3794
4336
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3795
4337
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -3855,6 +4397,9 @@ var EarnChain;
3855
4397
  destination: true
3856
4398
  }
3857
4399
  },
4400
+ cctpx: {
4401
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4402
+ },
3858
4403
  kitContracts: {
3859
4404
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
3860
4405
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -3920,6 +4465,9 @@ var EarnChain;
3920
4465
  destination: true
3921
4466
  }
3922
4467
  },
4468
+ cctpx: {
4469
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4470
+ },
3923
4471
  kitContracts: {
3924
4472
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3925
4473
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -3987,6 +4535,9 @@ var EarnChain;
3987
4535
  rpcEndpoints: [
3988
4536
  'https://api.avax-test.network/ext/bc/C/rpc'
3989
4537
  ],
4538
+ cctpx: {
4539
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4540
+ },
3990
4541
  kitContracts: {
3991
4542
  bridge: BRIDGE_CONTRACT_EVM_TESTNET
3992
4543
  },
@@ -4054,6 +4605,9 @@ var EarnChain;
4054
4605
  destination: true
4055
4606
  }
4056
4607
  },
4608
+ cctpx: {
4609
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4610
+ },
4057
4611
  kitContracts: {
4058
4612
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4059
4613
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -4119,6 +4673,9 @@ var EarnChain;
4119
4673
  destination: true
4120
4674
  }
4121
4675
  },
4676
+ cctpx: {
4677
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4678
+ },
4122
4679
  kitContracts: {
4123
4680
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
4124
4681
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -4511,6 +5068,9 @@ var EarnChain;
4511
5068
  destination: true
4512
5069
  }
4513
5070
  },
5071
+ cctpx: {
5072
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
5073
+ },
4514
5074
  kitContracts: {
4515
5075
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4516
5076
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -4576,6 +5136,9 @@ var EarnChain;
4576
5136
  destination: true
4577
5137
  }
4578
5138
  },
5139
+ cctpx: {
5140
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
5141
+ },
4579
5142
  kitContracts: {
4580
5143
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
4581
5144
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -5407,6 +5970,9 @@ var EarnChain;
5407
5970
  destination: true
5408
5971
  }
5409
5972
  },
5973
+ cctpx: {
5974
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
5975
+ },
5410
5976
  kitContracts: {
5411
5977
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5412
5978
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -5472,6 +6038,9 @@ var EarnChain;
5472
6038
  destination: true
5473
6039
  }
5474
6040
  },
6041
+ cctpx: {
6042
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
6043
+ },
5475
6044
  kitContracts: {
5476
6045
  bridge: BRIDGE_CONTRACT_EVM_TESTNET
5477
6046
  },
@@ -5867,6 +6436,9 @@ var EarnChain;
5867
6436
  destination: true
5868
6437
  }
5869
6438
  },
6439
+ cctpx: {
6440
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
6441
+ },
5870
6442
  kitContracts: {
5871
6443
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5872
6444
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -5935,6 +6507,9 @@ var EarnChain;
5935
6507
  destination: true
5936
6508
  }
5937
6509
  },
6510
+ cctpx: {
6511
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
6512
+ },
5938
6513
  kitContracts: {
5939
6514
  bridge: BRIDGE_CONTRACT_EVM_TESTNET
5940
6515
  },
@@ -6943,6 +7518,7 @@ var Chains = {
6943
7518
  AptosTestnet: AptosTestnet,
6944
7519
  Arbitrum: Arbitrum,
6945
7520
  ArbitrumSepolia: ArbitrumSepolia,
7521
+ Arc: Arc,
6946
7522
  ArcTestnet: ArcTestnet,
6947
7523
  Avalanche: Avalanche,
6948
7524
  AvalancheFuji: AvalancheFuji,
@@ -7360,6 +7936,7 @@ var Chains = {
7360
7936
  usdcAddress: zod.z.string().nullable(),
7361
7937
  usdtAddress: zod.z.string().nullable(),
7362
7938
  cctp: zod.z.any().nullable(),
7939
+ cctpx: zod.z.any().optional(),
7363
7940
  kitContracts: zod.z.object({
7364
7941
  bridge: zod.z.string().optional(),
7365
7942
  adapter: zod.z.string().optional()
@@ -7565,14 +8142,15 @@ const EARN_BRIDGE_DESTINATION_CHAIN_VALUES = EARN_BRIDGE_DESTINATION_BLOCKCHAINS
7565
8142
  * Zod schema for validating the source chain of a cross-chain Earn deposit.
7566
8143
  *
7567
8144
  * Accept a supported source Blockchain value, a matching string literal, or a
7568
- * ChainDefinition for a supported source chain. Source chains are Ethereum
7569
- * Sepolia, Arbitrum Sepolia, and Base Sepolia.
8145
+ * ChainDefinition for a supported source chain. Source chains are Ethereum,
8146
+ * Arbitrum, and Base (mainnet and testnet).
7570
8147
  *
7571
8148
  * @example
7572
8149
  * ```typescript
7573
8150
  * import { earnBridgeSourceChainIdentifierSchema } from '@core/chains'
7574
8151
  *
7575
8152
  * // Valid
8153
+ * earnBridgeSourceChainIdentifierSchema.parse('Ethereum')
7576
8154
  * earnBridgeSourceChainIdentifierSchema.parse('Ethereum_Sepolia')
7577
8155
  *
7578
8156
  * // Invalid (throws ZodError)
@@ -7591,14 +8169,15 @@ const EARN_BRIDGE_DESTINATION_CHAIN_VALUES = EARN_BRIDGE_DESTINATION_BLOCKCHAINS
7591
8169
  * deposit.
7592
8170
  *
7593
8171
  * Accept a supported destination Blockchain value, a matching string literal,
7594
- * or a ChainDefinition for a supported destination chain. Currently only Arc
7595
- * Testnet is supported.
8172
+ * or a ChainDefinition for a supported destination chain. Arc (mainnet) and
8173
+ * Arc Testnet are supported.
7596
8174
  *
7597
8175
  * @example
7598
8176
  * ```typescript
7599
8177
  * import { earnBridgeDestinationChainIdentifierSchema } from '@core/chains'
7600
8178
  *
7601
8179
  * // Valid
8180
+ * earnBridgeDestinationChainIdentifierSchema.parse('Arc')
7602
8181
  * earnBridgeDestinationChainIdentifierSchema.parse('Arc_Testnet')
7603
8182
  *
7604
8183
  * // Invalid (throws ZodError)
@@ -9224,6 +9803,7 @@ function parseOrThrow(value, schema, context) {
9224
9803
  // Mainnets (alphabetically sorted)
9225
9804
  // =========================================================================
9226
9805
  [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
9806
+ [Blockchain.Arc]: '0x3600000000000000000000000000000000000000',
9227
9807
  [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
9228
9808
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
9229
9809
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
@@ -9338,6 +9918,7 @@ function parseOrThrow(value, schema, context) {
9338
9918
  // =========================================================================
9339
9919
  // Mainnets
9340
9920
  // =========================================================================
9921
+ [Blockchain.Arc]: '0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c1',
9341
9922
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
9342
9923
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
9343
9924
  [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
@@ -9581,6 +10162,7 @@ function parseOrThrow(value, schema, context) {
9581
10162
  // =========================================================================
9582
10163
  // Mainnets
9583
10164
  // =========================================================================
10165
+ [Blockchain.Arc]: '0x171A4217b86A807A64eB94757Db6849fb4bDbAA0',
9584
10166
  [Blockchain.Ethereum]: '0x72DFB2E44f59C5AD2bAFE84314E5b99a7cd5075E',
9585
10167
  // =========================================================================
9586
10168
  // Testnets
@@ -9673,13 +10255,28 @@ function parseOrThrow(value, schema, context) {
9673
10255
  }
9674
10256
 
9675
10257
  /**
9676
- * Check if a selector is a raw token selector (object form).
10258
+ * Type guard: narrows a {@link TokenSelector} to a {@link RawTokenSelector}.
9677
10259
  *
9678
10260
  * @param selector - The token selector to check.
9679
- * @returns True if the selector is a raw token selector.
10261
+ * @returns `true` if `selector` is a raw token selector (object with `locator`).
10262
+ *
10263
+ * @example
10264
+ * ```typescript
10265
+ * if (isRawSelector(selector)) {
10266
+ * console.log(selector.locator)
10267
+ * }
10268
+ * ```
9680
10269
  */ function isRawSelector(selector) {
9681
- return typeof selector === 'object' && 'locator' in selector;
10270
+ // `typeof null === 'object'` (a JS quirk) and `'locator' in null`
10271
+ // throws, so the explicit `null` check is required at this exported
10272
+ // guard for JS consumers passing nullish values. The lint rule sees
10273
+ // the parameter typed as `TokenSelector` (no null) and flags the
10274
+ // check as unnecessary — disabled because the check IS necessary at
10275
+ // runtime even though TypeScript can prove it dead statically.
10276
+ return typeof selector === 'object' && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
10277
+ selector !== null && 'locator' in selector;
9682
10278
  }
10279
+
9683
10280
  /**
9684
10281
  * Normalize a symbol to uppercase for case-insensitive lookup.
9685
10282
  *
@@ -9867,7 +10464,7 @@ function parseOrThrow(value, schema, context) {
9867
10464
  if (typeof selector === 'string') {
9868
10465
  return resolveSymbol(selector, chainId);
9869
10466
  }
9870
- throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
10467
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected a token symbol or a raw selector (with locator).`, selector, chainId);
9871
10468
  },
9872
10469
  resolveByAddress (address, chainId) {
9873
10470
  if (!address || typeof address !== 'string') {
@@ -11127,7 +11724,7 @@ function resolveOptions(options) {
11127
11724
  }
11128
11725
 
11129
11726
  var name = "@circle-fin/unified-balance-kit";
11130
- var version$1 = "1.6.0";
11727
+ var version$1 = "1.7.0";
11131
11728
  var pkg$1 = {
11132
11729
  name: name,
11133
11730
  version: version$1};
@@ -21142,10 +21739,26 @@ function toString(amount) {
21142
21739
  * - Executing the actual bridge operations
21143
21740
  * - Handling protocol-specific logic and error conditions
21144
21741
  *
21742
+ * @remarks
21743
+ * Token-type default rationale: `TToken` defaults to the literal
21744
+ * `'USDC'` so a USDC-only subclass needs no generic ceremony at every
21745
+ * call site. This matches the literal `'USDC'` default on
21746
+ * {@link BridgeParams} (consumed at the provider boundary, where the
21747
+ * provider knows exactly which token symbol it accepts). The default
21748
+ * matches the default on {@link BridgeResult} and {@link EstimateResult} for
21749
+ * source compatibility. Provider-agnostic kit internals widen those result
21750
+ * types explicitly.
21751
+ *
21752
+ * Quotes are not typed here. A provider that issues one narrows its own
21753
+ * `estimate` return to the shape it produces — see the CCTPx provider's
21754
+ * `QuoteEnvelope` — which keeps that shape in the provider package rather
21755
+ * than in this shared one. `bridge` takes a caller-supplied quote, which is
21756
+ * public input the provider validates before reading a field.
21757
+ *
21145
21758
  * @example
21146
21759
  * ```typescript
21147
21760
  * class CustomBridgingProvider extends BridgingProvider {
21148
- * async supportsRoute(source: Chain, destination: Chain, token: TokenType): Promise<boolean> {
21761
+ * supportsRoute(source: Chain, destination: Chain, token: TokenType): boolean {
21149
21762
  * // Implementation specific logic
21150
21763
  * return true
21151
21764
  * }
@@ -21194,6 +21807,36 @@ function toString(amount) {
21194
21807
  return this.supportedChains.filter((chain)=>chain.isTestnet === source.isTestnet && chain.chain !== source.chain);
21195
21808
  }
21196
21809
  /**
21810
+ * Return the decimal precision for the given token, or `undefined` when
21811
+ * the provider does not track it.
21812
+ *
21813
+ * Consumers that format a result amount resolve decimals through this
21814
+ * hook first, falling back to their own built-in token registry when it
21815
+ * returns `undefined`. The default implementation returns `undefined`,
21816
+ * so a provider whose tokens are already in the consumer's registry needs
21817
+ * no override; a provider that identifies tokens outside that registry
21818
+ * overrides this to supply their decimals.
21819
+ *
21820
+ * @param _token - The token to resolve decimals for (provider-defined);
21821
+ * unused by the default implementation, consumed by overrides.
21822
+ * @param _sourceChain - Source chain of the bridge. Providers whose tokens
21823
+ * live in per-network registries use it to scope the lookup to the relevant
21824
+ * network; unused by the default implementation. Required, so a lookup is
21825
+ * never asked to guess which network a token belongs to.
21826
+ * @returns A promise resolving to the token's decimals, or `undefined`
21827
+ * when the provider cannot resolve it.
21828
+ *
21829
+ * @example
21830
+ * ```typescript
21831
+ * const decimals = await provider.getTokenDecimals(token, source.chain)
21832
+ * if (decimals === undefined) {
21833
+ * // fall back to the consumer's own decimals lookup
21834
+ * }
21835
+ * ```
21836
+ */ /* eslint-disable @typescript-eslint/require-await, @typescript-eslint/no-unused-vars -- Async so overrides can resolve decimals via I/O; the default is a constant that ignores its arguments. */ async getTokenDecimals(_token, _sourceChain) {
21837
+ return undefined;
21838
+ }
21839
+ /* eslint-enable @typescript-eslint/require-await, @typescript-eslint/no-unused-vars */ /**
21197
21840
  * Register an event dispatcher for handling provider-specific actions and events.
21198
21841
  *
21199
21842
  *
@@ -21435,7 +22078,72 @@ var TransferSpeed;
21435
22078
  */ recipientAddress: zod.z.string().trim().min(1, 'Fee recipient must be a non-empty string').optional()
21436
22079
  }).strict();
21437
22080
  /**
21438
- * Schema for validating bridge parameters.
22081
+ * Factory for the bridge-params Zod schema, parameterized by the token
22082
+ * validator. The base schema enforces all non-token fields (amount,
22083
+ * source, destination, config); callers supply the Zod schema for the
22084
+ * `token` field, allowing each provider to validate the token type it
22085
+ * accepts.
22086
+ *
22087
+ * @param tokenSchema - Zod schema applied to the `token` field. Pass
22088
+ * `z.literal('USDC')` for USDC-only validation, or any
22089
+ * provider-specific schema (e.g. a `0x`-prefixed hex regex) for
22090
+ * providers that accept a different token type.
22091
+ * @param config - Required. `maxDecimals` caps the decimal precision
22092
+ * permitted in `amount` and `config.maxFee`. Pass the widest value
22093
+ * any of the provider's tokens require — `6` for USDC-only, `18`
22094
+ * for an ERC-20 family that includes wETH, etc. The schema is a
22095
+ * coarse syntactic gate; narrower per-token precision is enforced
22096
+ * by the provider downstream.
22097
+ * @returns A Zod object schema whose `token` field is validated by
22098
+ * `tokenSchema` and whose other fields (`amount`, `source`,
22099
+ * `destination`, `config`) are validated against the shared bridge
22100
+ * contract. Use `.safeParse` / `.parse` directly, or feed the schema
22101
+ * to {@link parseOrThrow} from `@core/utils` to surface validation
22102
+ * failures as `KitError` with the canonical
22103
+ * `INPUT_VALIDATION_FAILED` code.
22104
+ *
22105
+ * @example
22106
+ * ```typescript
22107
+ * import { createBridgeParamsSchema, z } from '@core/provider'
22108
+ *
22109
+ * const usdcSchema = createBridgeParamsSchema(z.literal('USDC'), { maxDecimals: 6 })
22110
+ * const hexSchema = createBridgeParamsSchema(
22111
+ * z.string().regex(/^0x[0-9a-fA-F]{64}$/),
22112
+ * { maxDecimals: 18 },
22113
+ * )
22114
+ * ```
22115
+ */ // The full inferred return type of `z.object({...})` here is
22116
+ // effectively impossible to spell out by hand without re-stating every
22117
+ // field's schema. TypeScript infers it from the body — callers receive
22118
+ // a precise Zod schema type via inference, and the explicit-return
22119
+ // rules are disabled for that reason.
22120
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types
22121
+ function createBridgeParamsSchema(tokenSchema, config) {
22122
+ const { maxDecimals } = config;
22123
+ return zod.z.object({
22124
+ amount: zod.z.string().min(1, 'Required').pipe(createDecimalStringValidator({
22125
+ allowZero: false,
22126
+ regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
22127
+ attributeName: 'amount',
22128
+ maxDecimals
22129
+ })(zod.z.string())),
22130
+ source: walletContextSchema,
22131
+ destination: bridgeDestinationSchema,
22132
+ token: tokenSchema,
22133
+ config: zod.z.object({
22134
+ transferSpeed: zod.z.nativeEnum(TransferSpeed).optional(),
22135
+ maxFee: zod.z.string().pipe(createDecimalStringValidator({
22136
+ allowZero: true,
22137
+ regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
22138
+ attributeName: 'maxFee',
22139
+ maxDecimals
22140
+ })(zod.z.string())).optional(),
22141
+ customFee: customFeeSchema.optional()
22142
+ })
22143
+ });
22144
+ }
22145
+ /**
22146
+ * Schema for validating bridge parameters with USDC as the token.
21439
22147
  * This ensures all required fields are present and properly typed.
21440
22148
  * A bridge must include:
21441
22149
  * - A valid amount (non-empty numeric string \> 0)
@@ -21443,11 +22151,15 @@ var TransferSpeed;
21443
22151
  * - USDC as the token
21444
22152
  * - Optional config with transfer speed and max fee settings
21445
22153
  *
22154
+ * Providers that accept a non-USDC token type should call
22155
+ * {@link createBridgeParamsSchema} with their own token schema rather
22156
+ * than reusing this constant.
22157
+ *
21446
22158
  * @throws KitError if validation fails
21447
22159
  *
21448
22160
  * @example
21449
22161
  * ```typescript
21450
- * import { bridgeParamsSchema } from '@core/provider'
22162
+ * import { usdcBridgeParamsSchema } from '@core/provider'
21451
22163
  *
21452
22164
  * const validBridge = {
21453
22165
  * amount: '100.50',
@@ -21472,29 +22184,11 @@ var TransferSpeed;
21472
22184
  * }
21473
22185
  * }
21474
22186
  *
21475
- * const result = bridgeParamsSchema.safeParse(validBridge)
22187
+ * const result = usdcBridgeParamsSchema.safeParse(validBridge)
21476
22188
  * console.log(result.success) // true
21477
22189
  * ```
21478
- */ const bridgeParamsSchema = zod.z.object({
21479
- amount: zod.z.string().min(1, 'Required').pipe(createDecimalStringValidator({
21480
- allowZero: false,
21481
- regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
21482
- attributeName: 'amount',
21483
- maxDecimals: 6
21484
- })(zod.z.string())),
21485
- source: walletContextSchema,
21486
- destination: bridgeDestinationSchema,
21487
- token: zod.z.literal('USDC'),
21488
- config: zod.z.object({
21489
- transferSpeed: zod.z.nativeEnum(TransferSpeed).optional(),
21490
- maxFee: zod.z.string().pipe(createDecimalStringValidator({
21491
- allowZero: true,
21492
- regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
21493
- attributeName: 'maxFee',
21494
- maxDecimals: 6
21495
- })(zod.z.string())).optional(),
21496
- customFee: customFeeSchema.optional()
21497
- })
22190
+ */ const usdcBridgeParamsSchema = createBridgeParamsSchema(zod.z.literal('USDC'), {
22191
+ maxDecimals: 6
21498
22192
  });
21499
22193
 
21500
22194
  // ---------------------------------------------------------------------------
@@ -22088,6 +22782,10 @@ const USDC_DECIMALS$1 = 6;
22088
22782
  *
22089
22783
  * @see {@link https://developers.circle.com/gateway/references/fees}
22090
22784
  */ const GAS_FEE_BY_CHAIN = new Map([
22785
+ [
22786
+ Blockchain.Arc,
22787
+ parseUnits('0.001', USDC_DECIMALS$1)
22788
+ ],
22091
22789
  [
22092
22790
  Blockchain.Arc_Testnet,
22093
22791
  parseUnits('0.001', USDC_DECIMALS$1)
@@ -27453,7 +28151,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
27453
28151
  * ```
27454
28152
  */ function assertCCTPv2BridgeParams(params) {
27455
28153
  // First validate basic bridge params
27456
- validateWithStateTracking(params, bridgeParamsSchema, 'CCTPv2 bridge parameters', assertCCTPv2BridgeParamsSymbol);
28154
+ validateWithStateTracking(params, usdcBridgeParamsSchema, 'CCTPv2 bridge parameters', assertCCTPv2BridgeParamsSymbol);
27457
28155
  // After validation, we know params is CCTPV2BridgeParams
27458
28156
  const bridgeParams = params;
27459
28157
  // Enforce that source and destination chains are either testnet or mainnet
@@ -28320,7 +29018,7 @@ const mockAttestationMessage = {
28320
29018
  return step;
28321
29019
  }
28322
29020
 
28323
- var version = "1.13.0";
29021
+ var version = "1.14.0";
28324
29022
  var pkg = {
28325
29023
  version: version};
28326
29024
 
@@ -29912,7 +30610,7 @@ function assertCCTPV2Config(config) {
29912
30610
  * @example
29913
30611
  * ```typescript
29914
30612
  * const provider = new CCTPV2BridgingProvider()
29915
- * const canTransfer = provider.supportsRoute(Ethereum, Base)
30613
+ * const canTransfer = provider.supportsRoute(Ethereum, Base, 'USDC')
29916
30614
  *
29917
30615
  * if (canTransfer) {
29918
30616
  * console.log('CCTP v2 transfer is supported between these chains')