@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
@@ -701,6 +701,38 @@ class KitError extends Error {
701
701
  name: 'INPUT_AMOUNT_OUT_OF_RANGE',
702
702
  type: 'INPUT'
703
703
  },
704
+ /**
705
+ * Protocol fee is denominated in a token that is not supported for the
706
+ * route (only the native gas token and the route's supported fee tokens,
707
+ * such as USDC, are accepted).
708
+ */ UNSUPPORTED_FEE_TOKEN: {
709
+ code: 1014,
710
+ name: 'INPUT_UNSUPPORTED_FEE_TOKEN',
711
+ type: 'INPUT'
712
+ },
713
+ /** Retry / resume is not supported by this provider for the given result */ RETRY_NOT_SUPPORTED: {
714
+ code: 1015,
715
+ name: 'INPUT_RETRY_NOT_SUPPORTED',
716
+ type: 'INPUT'
717
+ },
718
+ /** Bridge-step analysis for retry is not supported by this provider */ STEP_ANALYSIS_NOT_SUPPORTED: {
719
+ code: 1016,
720
+ name: 'INPUT_STEP_ANALYSIS_NOT_SUPPORTED',
721
+ type: 'INPUT'
722
+ },
723
+ /**
724
+ * A kit operation name collides with a reserved event-subscription
725
+ * method (`on` / `off`) at kit construction.
726
+ */ RESERVED_OPERATION_NAME: {
727
+ code: 1000,
728
+ name: 'INPUT_RESERVED_OPERATION_NAME',
729
+ type: 'INPUT'
730
+ },
731
+ /** A kit was constructed with a missing / undefined operation factory */ MISSING_OPERATION: {
732
+ code: 1017,
733
+ name: 'INPUT_MISSING_OPERATION',
734
+ type: 'INPUT'
735
+ },
704
736
  /** General validation failure for complex validation rules */ VALIDATION_FAILED: {
705
737
  code: 1098,
706
738
  name: 'INPUT_VALIDATION_FAILED',
@@ -931,8 +963,376 @@ class KitError extends Error {
931
963
  code: 8002,
932
964
  name: 'SERVICE_UNKNOWN_ERROR',
933
965
  type: 'SERVICE'
966
+ }};
967
+
968
+ /**
969
+ * Log-safe error redaction for `KitError.cause.trace` fields.
970
+ *
971
+ * @packageDocumentation
972
+ */ // ============================================================================
973
+ // Message redactor (operator-configurable secret scrubbing)
974
+ // ============================================================================
975
+ /**
976
+ * A pluggable redactor for the `message` strings that flow onto
977
+ * `KitError.cause.trace`.
978
+ *
979
+ * @remarks
980
+ * Two equivalent shapes are accepted:
981
+ *
982
+ * 1. **`readonly RegExp[]`** — the common case. Each pattern is run
983
+ * against the message; matches are replaced with `[REDACTED]`.
984
+ * The operator owns the `g` flag — pass a global regex to scrub
985
+ * every occurrence, a non-global regex to scrub the first one.
986
+ *
987
+ * 2. **`(message: string) => string`** — escape hatch for callers
988
+ * who want full control over the substitution (different
989
+ * placeholders per pattern, length-preserving redaction for
990
+ * log-volume parity, format-preserving encryption, etc.).
991
+ *
992
+ * @public
993
+ */ /**
994
+ * Known RPC provider host suffixes scrubbed by the default redactor.
995
+ *
996
+ * @remarks
997
+ * The default redactor (see {@link composeRedactor} and
998
+ * {@link defaultMessageRedactor}) finds every `https?:` / `wss?:` URL
999
+ * in an error message, parses each one with the platform `URL`
1000
+ * constructor, and replaces it with `[REDACTED]` if the parsed
1001
+ * hostname ends with one of these suffixes. Adding a new provider is
1002
+ * a one-line change and cannot be wrong: there is no regex shape to
1003
+ * audit, no anchor-vs-quantifier trade-off, no
1004
+ * {@link https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS | ReDoS}
1005
+ * surface — the worst case is over-redaction (an unrelated URL on
1006
+ * the same host suffix gets scrubbed) which is the safe failure
1007
+ * mode.
1008
+ *
1009
+ * Suffixes covered:
1010
+ *
1011
+ * | Provider | Suffix |
1012
+ * | --- | --- |
1013
+ * | Alchemy | `.g.alchemy.com` |
1014
+ * | Infura | `.infura.io` |
1015
+ * | QuickNode | `.quiknode.pro` |
1016
+ * | Ankr | `.ankr.com` |
1017
+ * | Chainstack | `.p2pify.com` |
1018
+ * | Tenderly | `.gateway.tenderly.co` |
1019
+ * | BlockPI | `.blockpi.network` |
1020
+ * | GetBlock | `.getblock.io` |
1021
+ * | Moralis | `.moralis-nodes.com` |
1022
+ * | Helius | `.helius-rpc.com`, `.helius.xyz` |
1023
+ * | Triton | `.rpcpool.com` |
1024
+ *
1025
+ * Compose this list with your own self-hosted RPC suffixes via
1026
+ * {@link composeRedactor}.
1027
+ *
1028
+ * @public
1029
+ */ const DEFAULT_RPC_HOST_SUFFIXES = Object.freeze([
1030
+ '.g.alchemy.com',
1031
+ '.infura.io',
1032
+ '.quiknode.pro',
1033
+ '.ankr.com',
1034
+ '.p2pify.com',
1035
+ '.gateway.tenderly.co',
1036
+ '.blockpi.network',
1037
+ '.getblock.io',
1038
+ '.moralis-nodes.com',
1039
+ '.helius-rpc.com',
1040
+ '.helius.xyz',
1041
+ '.rpcpool.com'
1042
+ ]);
1043
+ /**
1044
+ * Built-in non-URL redaction patterns applied alongside the
1045
+ * URL-host scrubber.
1046
+ *
1047
+ * @remarks
1048
+ * Two simple patterns:
1049
+ *
1050
+ * - **Generic credential-bearing query strings** — `?api-key=…`,
1051
+ * `?apikey=…`, `?api-token=…`, `?access-token=…`, `?auth-token=…`,
1052
+ * `?app-key=…`, plus bare `?key=…` / `?token=…` / `?secret=…`.
1053
+ * Catches the case where a self-hosted RPC bakes the credential
1054
+ * into a query string instead of the path. Single canonical-name
1055
+ * alternation; complexity is well under SonarQube S5856's
1056
+ * 20-complexity bound.
1057
+ * - **Bearer header tokens** — `Bearer <token>` with bounded body
1058
+ * length and a `(?<![A-Za-z])` anchor so it can't be matched as a
1059
+ * tail-fragment of a longer word.
1060
+ *
1061
+ * Provider-specific URL-host scrubbing lives in
1062
+ * {@link DEFAULT_RPC_HOST_SUFFIXES} (parsed via `URL`) rather than as
1063
+ * regexes here — the host-list approach is CodeQL-clean (no
1064
+ * `js/regex/missing-anchor` heuristic match), false-positive-free
1065
+ * (provider URLs cannot accidentally collide with calldata or tx
1066
+ * hashes), and trivial to extend (one string per provider).
1067
+ *
1068
+ * @public
1069
+ */ const DEFAULT_RPC_REDACTION_PATTERNS = Object.freeze([
1070
+ /[?&](?:(?:api|access|auth|app)[-_]?)?(?:key|token|secret)=[^&\s"'<>`]{1,256}/gi,
1071
+ /(?<![A-Za-z])Bearer\s+[A-Za-z0-9._~+/=-]{16,512}/g
1072
+ ]);
1073
+ /**
1074
+ * Generic URL extractor used by {@link composeRedactor}.
1075
+ *
1076
+ * @remarks
1077
+ * Matches any `http://`, `https://`, `ws://`, or `wss://` URL,
1078
+ * bounded to 2 048 characters (longest URL Chrome / Firefox / IE
1079
+ * accept in practice; well below any reasonable error-message
1080
+ * payload). The regex is intentionally generic — it does **not**
1081
+ * encode any provider host shape — so CodeQL's
1082
+ * `js/regex/missing-anchor` heuristic (which targets URL-validation
1083
+ * regexes that look for a specific host) does not fire. All
1084
+ * provider classification happens in JS via `URL` parsing + string
1085
+ * suffix matching, never in regex.
1086
+ */ const URL_EXTRACTOR = /(?<![A-Za-z0-9])(?:https?|wss?):\/\/[^\s"'<>`]{1,2048}/g;
1087
+ /**
1088
+ * Construct a redactor function from a host-suffix list and a
1089
+ * non-URL pattern list.
1090
+ *
1091
+ * @remarks
1092
+ * Each invocation:
1093
+ *
1094
+ * 1. Finds every URL in the message via {@link URL_EXTRACTOR}.
1095
+ * 2. Parses each candidate with the platform `URL` constructor (the
1096
+ * same one browsers and Node use for `fetch()` / `new Request()`),
1097
+ * discarding common trailing punctuation.
1098
+ * 3. Replaces the URL with `[REDACTED]` if its parsed hostname ends
1099
+ * with one of the configured suffixes.
1100
+ * 4. Applies every regex in `patterns` (Bearer headers, query-string
1101
+ * credentials, plus any operator-supplied additions).
1102
+ *
1103
+ * Both arrays default to the SDK's built-ins
1104
+ * ({@link DEFAULT_RPC_HOST_SUFFIXES} and
1105
+ * {@link DEFAULT_RPC_REDACTION_PATTERNS}); pass either to override.
1106
+ *
1107
+ * @param opts - Optional override for the default suffix list and/or
1108
+ * pattern list.
1109
+ * @returns A pure `(message: string) => string` function suitable for
1110
+ * passing to {@link setMessageRedactor}.
1111
+ *
1112
+ * @example
1113
+ * Add your self-hosted RPC URL to the host-suffix list (most common
1114
+ * extension shape):
1115
+ *
1116
+ * ```typescript
1117
+ * import {
1118
+ * composeRedactor,
1119
+ * setMessageRedactor,
1120
+ * DEFAULT_RPC_HOST_SUFFIXES,
1121
+ * } from '@core/errors'
1122
+ *
1123
+ * setMessageRedactor(
1124
+ * composeRedactor({
1125
+ * hostSuffixes: [...DEFAULT_RPC_HOST_SUFFIXES, '.rpc.my-corp.example'],
1126
+ * }),
1127
+ * )
1128
+ * ```
1129
+ *
1130
+ * @example
1131
+ * Add a custom non-URL pattern (e.g. a corporate header token):
1132
+ *
1133
+ * ```typescript
1134
+ * setMessageRedactor(
1135
+ * composeRedactor({
1136
+ * patterns: [
1137
+ * ...DEFAULT_RPC_REDACTION_PATTERNS,
1138
+ * /X-Corp-Token:\s+[A-Za-z0-9_-]{16,128}/g,
1139
+ * ],
1140
+ * }),
1141
+ * )
1142
+ * ```
1143
+ *
1144
+ * @public
1145
+ */ function composeRedactor(opts) {
1146
+ const hostSuffixes = opts?.hostSuffixes ?? DEFAULT_RPC_HOST_SUFFIXES;
1147
+ const patterns = opts?.patterns ?? DEFAULT_RPC_REDACTION_PATTERNS;
1148
+ return (message)=>{
1149
+ let result = message.replaceAll(URL_EXTRACTOR, (url)=>{
1150
+ // Common trailing punctuation that error-message templates put
1151
+ // immediately after a URL (`Status: 401.`, `(see ...)`, `[1]`).
1152
+ // Strip before parsing so the `URL` parser doesn't reject
1153
+ // `eth-mainnet.g.alchemy.com.` (or accept it with a trailing
1154
+ // dot in `hostname` and miss the suffix match). Implemented as
1155
+ // a length-bounded string loop instead of a `/[.,;)\]>]+$/`
1156
+ // regex so CodeQL `js/redos-on-uncontrolled-input` cannot
1157
+ // flag a polynomial-time backtracking shape on operator-
1158
+ // controlled error text.
1159
+ const TRIM_CHARS = '.,;)]>';
1160
+ let end = url.length;
1161
+ const cap = Math.max(end - 16, 0) // bound the loop, length-cap 16
1162
+ ;
1163
+ while(end > cap && TRIM_CHARS.includes(url.charAt(end - 1))){
1164
+ end--;
1165
+ }
1166
+ const trimmed = end === url.length ? url : url.slice(0, end);
1167
+ let parsed;
1168
+ try {
1169
+ parsed = new URL(trimmed);
1170
+ } catch {
1171
+ return url;
1172
+ }
1173
+ // HTTP-Basic userinfo (`user:pass@host`) is a credential by
1174
+ // definition — scrub it regardless of host, so enterprise /
1175
+ // self-hosted RPC URLs that don't match a known provider suffix
1176
+ // still get redacted.
1177
+ if (parsed.username !== '' || parsed.password !== '') {
1178
+ return '[REDACTED]';
1179
+ }
1180
+ const host = parsed.hostname.toLowerCase();
1181
+ return hostSuffixes.some((suffix)=>host.endsWith(suffix)) ? '[REDACTED]' : url;
1182
+ });
1183
+ for (const pattern of patterns){
1184
+ result = result.replaceAll(pattern, '[REDACTED]');
1185
+ }
1186
+ return result;
1187
+ };
1188
+ }
1189
+ /**
1190
+ * The SDK's default message redactor: scrubs URLs whose hostname
1191
+ * matches a {@link DEFAULT_RPC_HOST_SUFFIXES} entry, plus the
1192
+ * patterns in {@link DEFAULT_RPC_REDACTION_PATTERNS}.
1193
+ *
1194
+ * @remarks
1195
+ * Active at module load and re-installed by
1196
+ * {@link resetMessageRedactor}. Operators who want to layer
1197
+ * additional redaction on top of (rather than replace) the defaults
1198
+ * can call this directly inside a custom function form:
1199
+ *
1200
+ * ```typescript
1201
+ * setMessageRedactor((msg) =>
1202
+ * defaultMessageRedactor(msg).replace(/myExtraPattern/g, '[REDACTED]'),
1203
+ * )
1204
+ * ```
1205
+ *
1206
+ * For most extension cases prefer {@link composeRedactor}, which
1207
+ * gives the same result with structured options instead of a
1208
+ * post-hoc pipe.
1209
+ *
1210
+ * @public
1211
+ */ const defaultMessageRedactor = composeRedactor();
1212
+ let activeRedactor = defaultMessageRedactor;
1213
+ /**
1214
+ * Apply the active redactor to an arbitrary message string.
1215
+ *
1216
+ * @remarks
1217
+ * Adapters call this on every string they place on `cause.trace`
1218
+ * other than the redacted `rawError` itself — viem `shortMessage`,
1219
+ * ethers `error.info.requestUrl`, the provider's `reason` field,
1220
+ * etc. When no redactor is configured this is the identity function,
1221
+ * so the cost on the no-redactor path is one `if`.
1222
+ *
1223
+ * @param message - The string to scrub.
1224
+ * @returns The redacted string. The original is returned untouched
1225
+ * when no redactor is configured.
1226
+ *
1227
+ * @example
1228
+ * ```typescript
1229
+ * import { redactErrorMessage, setMessageRedactor } from '@core/errors'
1230
+ *
1231
+ * setMessageRedactor([/[A-Fa-f0-9]{32,64}/g])
1232
+ * redactErrorMessage('failed: 0xabc...64chars...')
1233
+ * // => 'failed: [REDACTED]'
1234
+ * ```
1235
+ *
1236
+ * @public
1237
+ */ function redactErrorMessage(message) {
1238
+ if (activeRedactor === undefined) return message;
1239
+ // `setMessageRedactor` eagerly compiles the array form into a
1240
+ // `composeRedactor`-wrapped function, so by the time we reach
1241
+ // here `activeRedactor` is always a function. The runtime
1242
+ // `typeof` guard exists for completeness — if a future
1243
+ // contributor introduces a setter path that bypasses the
1244
+ // compilation step, the guard keeps the dispatch correct.
1245
+ if (typeof activeRedactor === 'function') return activeRedactor(message);
1246
+ // Fallback (unreachable in normal use). See note above.
1247
+ return composeRedactor({
1248
+ patterns: activeRedactor,
1249
+ hostSuffixes: []
1250
+ })(message);
1251
+ }
1252
+ // ============================================================================
1253
+ // Object-graph redaction
1254
+ // ============================================================================
1255
+ /**
1256
+ * Redact an `unknown` error to a small, log-safe shape.
1257
+ *
1258
+ * @remarks
1259
+ * Wallet libraries (viem, ethers, web3-provider-engine, …) routinely
1260
+ * attach provider-injected fields to thrown errors: configured RPC URLs,
1261
+ * API keys, opaque internal state, the originating `XMLHttpRequest`,
1262
+ * and so on. Embedding the raw object directly inside `KitError.cause.trace`
1263
+ * means anyone who later JSON-serialises a `KitError` for telemetry leaks
1264
+ * those fields into logs.
1265
+ *
1266
+ * This helper extracts only the two fields that are useful for
1267
+ * post-mortem debugging — `name` and `message` — while never exposing the
1268
+ * underlying object graph. The `message` is run through the operator's
1269
+ * configured {@link setMessageRedactor | message redactor} (if any) so
1270
+ * that secrets the provider has interpolated into the message string
1271
+ * itself can be scrubbed. Non-`Error` throws (raw strings, plain
1272
+ * objects, primitives) are stringified with `String(error)` and then
1273
+ * run through the same redactor, so a secret-bearing string throw
1274
+ * (e.g. WalletConnect / older mobile wallets) is scrubbed too.
1275
+ *
1276
+ * **Two redaction layers, two responsibilities.**
1277
+ *
1278
+ * - **Object-graph stripping (this function, always on)** — drops
1279
+ * extra properties such as `request`, `requestUrl`, `info`, the
1280
+ * originating `XMLHttpRequest`, the configured `transport.url`,
1281
+ * etc. Without this, viem/ethers errors round-trip with the full
1282
+ * provider state graph attached.
1283
+ * - **Message-content scrubbing (configured via
1284
+ * {@link setMessageRedactor}, opt-in)** — handles the case where
1285
+ * the provider has already formatted secrets into `error.message`
1286
+ * itself. Notably, ethers v6's `makeError()` formats info keys
1287
+ * directly into the message:
1288
+ *
1289
+ * ```
1290
+ * missing response (requestUrl="https://mainnet.infura.io/v3/MY_KEY", code=SERVER_ERROR, ...)
1291
+ * ```
1292
+ *
1293
+ * Viem's `HttpRequestError` exhibits the same pattern via
1294
+ * `shortMessage`, which carries the configured URL with API path.
1295
+ * Without a redactor, the API key rides on `cause.trace.rawError.message`
1296
+ * verbatim. With one configured, every match is replaced with
1297
+ * `[REDACTED]` (or the operator's custom substitution).
1298
+ *
1299
+ * The split is intentional: there is no universal URL/secret
1300
+ * heuristic that won't false-positive on legitimate payloads
1301
+ * (mainnet addresses, contract revert reasons, raw call data), so
1302
+ * the SDK refuses to guess. Operators who know which patterns
1303
+ * appear in *their* RPC URLs configure the policy with one call
1304
+ * to {@link setMessageRedactor}.
1305
+ *
1306
+ * @param error - Anything thrown by a third-party wallet/provider library.
1307
+ * @returns A `{ name, message }` pair for `Error` instances (with
1308
+ * `message` run through the active redactor); the stringified value —
1309
+ * also run through the active redactor — otherwise. Always JSON-safe.
1310
+ *
1311
+ * @example
1312
+ * ```typescript
1313
+ * import { redactRawError, setMessageRedactor } from '@core/errors'
1314
+ *
1315
+ * setMessageRedactor([/infura\.io\/v3\/[a-z0-9]+/gi])
1316
+ * const err = new Error('failed: https://mainnet.infura.io/v3/abc123')
1317
+ * redactRawError(err)
1318
+ * // => { name: 'Error', message: 'failed: https://[REDACTED]' }
1319
+ * ```
1320
+ *
1321
+ * @public
1322
+ */ function redactRawError(error) {
1323
+ if (error instanceof Error) {
1324
+ return {
1325
+ name: error.name,
1326
+ message: redactErrorMessage(error.message)
1327
+ };
934
1328
  }
935
- };
1329
+ // Non-Error throws (raw strings from WalletConnect / older mobile
1330
+ // wallets, plain objects, primitives) are stringified and then run
1331
+ // through the active redactor. A raw string throw can carry a secret
1332
+ // RPC URL just like an `Error.message` does, so it must be scrubbed
1333
+ // rather than returned verbatim.
1334
+ return redactErrorMessage(String(error));
1335
+ }
936
1336
 
937
1337
  /**
938
1338
  * Creates error for network type mismatch between source and destination.
@@ -978,6 +1378,15 @@ class KitError extends Error {
978
1378
  *
979
1379
  * @param source - Source chain name
980
1380
  * @param destination - Destination chain name
1381
+ * @param providerErrors - Optional list of providers whose `supportsRoute`
1382
+ * call threw during the search. When non-empty, the message includes a
1383
+ * summary so consumers can distinguish "no provider supports this route"
1384
+ * from "a provider errored mid-search." Pass the errors raw: each one is
1385
+ * run through `redactRawError` here, so neither the message nor
1386
+ * `cause.trace.providerErrors` carries the provider's raw object graph.
1387
+ * @param token - Optional token the caller asked to bridge. When supplied it
1388
+ * is named in the message and the trace, so a symbol the routing layer does
1389
+ * not recognise reads as a token problem rather than a chain-pair one.
981
1390
  * @returns KitError with specific route details
982
1391
  *
983
1392
  * @example
@@ -985,22 +1394,51 @@ class KitError extends Error {
985
1394
  * import { createUnsupportedRouteError } from '@core/errors'
986
1395
  *
987
1396
  * throw createUnsupportedRouteError('Ethereum', 'Solana')
988
- * // Message: "Route from Ethereum to Solana is not supported"
989
- * ```
990
- */ function createUnsupportedRouteError(source, destination) {
1397
+ * // Message: "Route from Ethereum to Solana is not supported."
1398
+ *
1399
+ * throw createUnsupportedRouteError('Ethereum', 'Arc', [
1400
+ * { provider: 'cctpx', error: new Error('ECONNRESET') },
1401
+ * ])
1402
+ * // Message: "Route from Ethereum to Arc is not supported.
1403
+ * // Providers errored during route check: cctpx (ECONNRESET)."
1404
+ *
1405
+ * throw createUnsupportedRouteError('Ethereum', 'Arc', undefined, 'USCD')
1406
+ * // Message: "Route from Ethereum to Arc is not supported for token 'USCD'."
1407
+ * ```
1408
+ */ function createUnsupportedRouteError(source, destination, providerErrors, token) {
1409
+ const failures = [];
1410
+ // Redact once, here. `supportsRoute` may be async so a provider can do a
1411
+ // registry-backed network check, and a fetch rejection embeds the request
1412
+ // URL — with a credential in the worst case — in both its object graph and
1413
+ // its message. Both consumers below read from this redacted copy, so the
1414
+ // raw value never leaves this function. Redacting once is load-bearing:
1415
+ // `redactRawError` is not idempotent (it stringifies a non-`Error` input,
1416
+ // so a second pass collapses `{ name, message }` to `"[object Object]"`),
1417
+ // which is why callers pass raw errors in and let the factory do it.
1418
+ const redactedFailures = failures.map((failure)=>({
1419
+ provider: failure.provider,
1420
+ error: redactRawError(failure.error)
1421
+ }));
1422
+ const summary = redactedFailures.length === 0 ? '' : ` Providers errored during route check: ${redactedFailures.map((f)=>`${f.provider} (${describeProviderError(f.error)})`).join(', ')}.`;
1423
+ const tokenClause = '' ;
991
1424
  const errorDetails = {
992
1425
  ...InputError.UNSUPPORTED_ROUTE,
993
1426
  recoverability: 'FATAL',
994
- message: `Route from ${source} to ${destination} is not supported.`,
1427
+ message: `Route from ${source} to ${destination} is not supported` + `${tokenClause}.${summary}`,
995
1428
  cause: {
996
1429
  trace: {
997
1430
  source,
998
- destination
1431
+ destination,
1432
+ providerErrors: redactedFailures,
1433
+ ...{}
999
1434
  }
1000
1435
  }
1001
1436
  };
1002
1437
  return new KitError(errorDetails);
1003
1438
  }
1439
+ /** Extract a human-readable summary from a redacted provider error. */ function describeProviderError(error) {
1440
+ return typeof error === 'string' ? error : error.message;
1441
+ }
1004
1442
  /**
1005
1443
  * Creates error for invalid amount format or precision.
1006
1444
  *
@@ -1189,7 +1627,7 @@ class KitError extends Error {
1189
1627
  * import { createValidationErrorFromZod } from '@core/errors'
1190
1628
  *
1191
1629
  * function validateBridgeParams(params: unknown): asserts params is BridgeParams {
1192
- * const result = bridgeParamsSchema.safeParse(params)
1630
+ * const result = usdcBridgeParamsSchema.safeParse(params)
1193
1631
  * if (!result.success) {
1194
1632
  * throw createValidationErrorFromZod(result.error, 'bridge parameters')
1195
1633
  * }
@@ -3035,9 +3473,10 @@ class KitError extends Error {
3035
3473
  Blockchain["Algorand_Testnet"] = "Algorand_Testnet";
3036
3474
  Blockchain["Aptos"] = "Aptos";
3037
3475
  Blockchain["Aptos_Testnet"] = "Aptos_Testnet";
3038
- Blockchain["Arc_Testnet"] = "Arc_Testnet";
3039
3476
  Blockchain["Arbitrum"] = "Arbitrum";
3040
3477
  Blockchain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
3478
+ Blockchain["Arc"] = "Arc";
3479
+ Blockchain["Arc_Testnet"] = "Arc_Testnet";
3041
3480
  Blockchain["Avalanche"] = "Avalanche";
3042
3481
  Blockchain["Avalanche_Fuji"] = "Avalanche_Fuji";
3043
3482
  Blockchain["Base"] = "Base";
@@ -3124,6 +3563,7 @@ var SwapChain;
3124
3563
  SwapChain["XDC"] = "XDC";
3125
3564
  SwapChain["HyperEVM"] = "HyperEVM";
3126
3565
  SwapChain["Monad"] = "Monad";
3566
+ SwapChain["Arc"] = "Arc";
3127
3567
  // Testnet chains with swap support
3128
3568
  SwapChain["Arc_Testnet"] = "Arc_Testnet";
3129
3569
  })(SwapChain || (SwapChain = {}));
@@ -3131,6 +3571,7 @@ var BridgeChain;
3131
3571
  (function(BridgeChain) {
3132
3572
  // Mainnet chains with CCTPv2 support
3133
3573
  BridgeChain["Arbitrum"] = "Arbitrum";
3574
+ BridgeChain["Arc"] = "Arc";
3134
3575
  BridgeChain["Avalanche"] = "Avalanche";
3135
3576
  BridgeChain["Base"] = "Base";
3136
3577
  BridgeChain["Codex"] = "Codex";
@@ -3186,6 +3627,7 @@ var BridgeChain;
3186
3627
  var UnifiedBalanceChain;
3187
3628
  (function(UnifiedBalanceChain) {
3188
3629
  // Mainnet chains with Gateway V1 support
3630
+ UnifiedBalanceChain["Arc"] = "Arc";
3189
3631
  UnifiedBalanceChain["Arbitrum"] = "Arbitrum";
3190
3632
  UnifiedBalanceChain["Avalanche"] = "Avalanche";
3191
3633
  UnifiedBalanceChain["Base"] = "Base";
@@ -3215,6 +3657,7 @@ var UnifiedBalanceChain;
3215
3657
  })(UnifiedBalanceChain || (UnifiedBalanceChain = {}));
3216
3658
  var EarnChain;
3217
3659
  (function(EarnChain) {
3660
+ EarnChain["Arc"] = "Arc";
3218
3661
  EarnChain["Arc_Testnet"] = "Arc_Testnet";
3219
3662
  })(EarnChain || (EarnChain = {}));
3220
3663
  /**
@@ -3231,8 +3674,11 @@ var EarnChain;
3231
3674
  * console.log(EARN_BRIDGE_SOURCE_BLOCKCHAINS.join(', '))
3232
3675
  * ```
3233
3676
  */ const EARN_BRIDGE_SOURCE_BLOCKCHAINS = [
3677
+ "Arbitrum",
3234
3678
  "Arbitrum_Sepolia",
3679
+ "Base",
3235
3680
  "Base_Sepolia",
3681
+ "Ethereum",
3236
3682
  "Ethereum_Sepolia"
3237
3683
  ];
3238
3684
  /**
@@ -3246,6 +3692,7 @@ var EarnChain;
3246
3692
  * console.log(EARN_BRIDGE_DESTINATION_BLOCKCHAINS.join(', '))
3247
3693
  * ```
3248
3694
  */ const EARN_BRIDGE_DESTINATION_BLOCKCHAINS = [
3695
+ "Arc",
3249
3696
  "Arc_Testnet"
3250
3697
  ];
3251
3698
 
@@ -3551,6 +3998,25 @@ var EarnChain;
3551
3998
  * EVM-compatible chains. Use this address when deploying or testing
3552
3999
  * cross-chain USDC transfers on test networks.
3553
4000
  */ const BRIDGE_CONTRACT_EVM_TESTNET = '0xC5567a5E3370d4DBfB0540025078e283e36A363d';
4001
+ /**
4002
+ * The CrossChainTokenService (CCTPx) service address for EVM testnet networks.
4003
+ *
4004
+ * This service coordinates CCTPx cross-chain token operations on testnet
4005
+ * environments and is shared by every EVM test network CCTPx supports. Read the
4006
+ * chain definitions via `isCCTPXSupported` rather than trusting a list here,
4007
+ * which goes stale as deployments land.
4008
+ */ const CCTPX_SERVICE_ADDRESS_EVM_TESTNET = '0x63753E722bd2C2A5DF6EE19C5106662208B81077';
4009
+ /**
4010
+ * CrossChainTokenService (CCTS) proxy address for CCTPx on EVM **mainnet**.
4011
+ *
4012
+ * @remarks
4013
+ * A distinct deployment from {@link CCTPX_SERVICE_ADDRESS_EVM_TESTNET}, shared
4014
+ * by every EVM mainnet chain that CCTPx supports. A chain becomes
4015
+ * CCTPx-eligible only once its definition carries this as
4016
+ * `cctpx.serviceAddress`, and a route additionally needs both of its endpoints
4017
+ * eligible — so adding a chain here is the deliberate, reviewable step that
4018
+ * opens mainnet CCTPx for it.
4019
+ */ const CCTPX_SERVICE_ADDRESS_EVM_MAINNET = '0x431871229103b780868f8C6BB820cd16ECf942BC';
3554
4020
  /**
3555
4021
  * The bridge contract address for EVM mainnet networks.
3556
4022
  *
@@ -3669,6 +4135,76 @@ var EarnChain;
3669
4135
  * Used by the spend fee-split logic to route Circle's share of custom fees.
3670
4136
  */ const CIRCLE_FEE_RECIPIENT_SOLANA = 'HGMjULXsaMcbXgRhRECkSG3LyiKJgqiNAnLv1zZd8Kpn';
3671
4137
 
4138
+ /**
4139
+ * Arc Mainnet chain definition
4140
+ * @remarks
4141
+ * This represents the official production network for the Arc blockchain,
4142
+ * Circle's EVM-compatible Layer-1 designed for stablecoin finance
4143
+ * and asset tokenization. Arc uses USDC as the native gas token and
4144
+ * features the Malachite Byzantine Fault Tolerant (BFT) consensus
4145
+ * engine for sub-second finality.
4146
+ */ const Arc = defineChain({
4147
+ type: 'evm',
4148
+ chain: Blockchain.Arc,
4149
+ name: 'Arc',
4150
+ title: 'Arc Mainnet',
4151
+ nativeCurrency: {
4152
+ name: 'USDC',
4153
+ symbol: 'USDC',
4154
+ // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
4155
+ // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
4156
+ // See: https://docs.arc.network/arc/references/contract-addresses
4157
+ decimals: 18
4158
+ },
4159
+ chainId: 5042,
4160
+ isTestnet: false,
4161
+ explorerUrl: 'https://explorer.arc.io/tx/{hash}',
4162
+ rpcEndpoints: [
4163
+ 'https://rpc.mainnet.arc.io/'
4164
+ ],
4165
+ eurcAddress: '0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c1',
4166
+ usdcAddress: '0x3600000000000000000000000000000000000000',
4167
+ usdtAddress: null,
4168
+ cctp: {
4169
+ domain: 26,
4170
+ contracts: {
4171
+ v2: {
4172
+ type: 'split',
4173
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
4174
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
4175
+ confirmations: 1,
4176
+ fastConfirmations: 1
4177
+ }
4178
+ },
4179
+ forwarderSupported: {
4180
+ source: false,
4181
+ destination: true
4182
+ }
4183
+ },
4184
+ cctpx: {
4185
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4186
+ },
4187
+ kitContracts: {
4188
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4189
+ adapter: ADAPTER_CONTRACT_EVM_MAINNET
4190
+ },
4191
+ gateway: {
4192
+ domain: 26,
4193
+ contracts: {
4194
+ v1: {
4195
+ wallet: GATEWAY_WALLET_EVM_MAINNET,
4196
+ minter: GATEWAY_MINTER_EVM_MAINNET,
4197
+ depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
4198
+ genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
4199
+ }
4200
+ },
4201
+ forwarderSupported: {
4202
+ source: true,
4203
+ destination: true
4204
+ }
4205
+ }
4206
+ });
4207
+
3672
4208
  /**
3673
4209
  * Arc Testnet chain definition
3674
4210
  * @remarks
@@ -3716,6 +4252,9 @@ var EarnChain;
3716
4252
  destination: true
3717
4253
  }
3718
4254
  },
4255
+ cctpx: {
4256
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4257
+ },
3719
4258
  kitContracts: {
3720
4259
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
3721
4260
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -3783,6 +4322,9 @@ var EarnChain;
3783
4322
  destination: true
3784
4323
  }
3785
4324
  },
4325
+ cctpx: {
4326
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4327
+ },
3786
4328
  kitContracts: {
3787
4329
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3788
4330
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -3848,6 +4390,9 @@ var EarnChain;
3848
4390
  destination: true
3849
4391
  }
3850
4392
  },
4393
+ cctpx: {
4394
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4395
+ },
3851
4396
  kitContracts: {
3852
4397
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
3853
4398
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -3913,6 +4458,9 @@ var EarnChain;
3913
4458
  destination: true
3914
4459
  }
3915
4460
  },
4461
+ cctpx: {
4462
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4463
+ },
3916
4464
  kitContracts: {
3917
4465
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
3918
4466
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -3980,6 +4528,9 @@ var EarnChain;
3980
4528
  rpcEndpoints: [
3981
4529
  'https://api.avax-test.network/ext/bc/C/rpc'
3982
4530
  ],
4531
+ cctpx: {
4532
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4533
+ },
3983
4534
  kitContracts: {
3984
4535
  bridge: BRIDGE_CONTRACT_EVM_TESTNET
3985
4536
  },
@@ -4047,6 +4598,9 @@ var EarnChain;
4047
4598
  destination: true
4048
4599
  }
4049
4600
  },
4601
+ cctpx: {
4602
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
4603
+ },
4050
4604
  kitContracts: {
4051
4605
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4052
4606
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -4112,6 +4666,9 @@ var EarnChain;
4112
4666
  destination: true
4113
4667
  }
4114
4668
  },
4669
+ cctpx: {
4670
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
4671
+ },
4115
4672
  kitContracts: {
4116
4673
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
4117
4674
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -4504,6 +5061,9 @@ var EarnChain;
4504
5061
  destination: true
4505
5062
  }
4506
5063
  },
5064
+ cctpx: {
5065
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
5066
+ },
4507
5067
  kitContracts: {
4508
5068
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
4509
5069
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -4569,6 +5129,9 @@ var EarnChain;
4569
5129
  destination: true
4570
5130
  }
4571
5131
  },
5132
+ cctpx: {
5133
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
5134
+ },
4572
5135
  kitContracts: {
4573
5136
  bridge: BRIDGE_CONTRACT_EVM_TESTNET,
4574
5137
  adapter: ADAPTER_CONTRACT_EVM_TESTNET
@@ -5400,6 +5963,9 @@ var EarnChain;
5400
5963
  destination: true
5401
5964
  }
5402
5965
  },
5966
+ cctpx: {
5967
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
5968
+ },
5403
5969
  kitContracts: {
5404
5970
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5405
5971
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -5465,6 +6031,9 @@ var EarnChain;
5465
6031
  destination: true
5466
6032
  }
5467
6033
  },
6034
+ cctpx: {
6035
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
6036
+ },
5468
6037
  kitContracts: {
5469
6038
  bridge: BRIDGE_CONTRACT_EVM_TESTNET
5470
6039
  },
@@ -5860,6 +6429,9 @@ var EarnChain;
5860
6429
  destination: true
5861
6430
  }
5862
6431
  },
6432
+ cctpx: {
6433
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_MAINNET
6434
+ },
5863
6435
  kitContracts: {
5864
6436
  bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5865
6437
  adapter: ADAPTER_CONTRACT_EVM_MAINNET
@@ -5928,6 +6500,9 @@ var EarnChain;
5928
6500
  destination: true
5929
6501
  }
5930
6502
  },
6503
+ cctpx: {
6504
+ serviceAddress: CCTPX_SERVICE_ADDRESS_EVM_TESTNET
6505
+ },
5931
6506
  kitContracts: {
5932
6507
  bridge: BRIDGE_CONTRACT_EVM_TESTNET
5933
6508
  },
@@ -6936,6 +7511,7 @@ var Chains = /*#__PURE__*/Object.freeze({
6936
7511
  AptosTestnet: AptosTestnet,
6937
7512
  Arbitrum: Arbitrum,
6938
7513
  ArbitrumSepolia: ArbitrumSepolia,
7514
+ Arc: Arc,
6939
7515
  ArcTestnet: ArcTestnet,
6940
7516
  Avalanche: Avalanche,
6941
7517
  AvalancheFuji: AvalancheFuji,
@@ -7353,6 +7929,7 @@ var Chains = /*#__PURE__*/Object.freeze({
7353
7929
  usdcAddress: z.string().nullable(),
7354
7930
  usdtAddress: z.string().nullable(),
7355
7931
  cctp: z.any().nullable(),
7932
+ cctpx: z.any().optional(),
7356
7933
  kitContracts: z.object({
7357
7934
  bridge: z.string().optional(),
7358
7935
  adapter: z.string().optional()
@@ -7558,14 +8135,15 @@ const EARN_BRIDGE_DESTINATION_CHAIN_VALUES = EARN_BRIDGE_DESTINATION_BLOCKCHAINS
7558
8135
  * Zod schema for validating the source chain of a cross-chain Earn deposit.
7559
8136
  *
7560
8137
  * Accept a supported source Blockchain value, a matching string literal, or a
7561
- * ChainDefinition for a supported source chain. Source chains are Ethereum
7562
- * Sepolia, Arbitrum Sepolia, and Base Sepolia.
8138
+ * ChainDefinition for a supported source chain. Source chains are Ethereum,
8139
+ * Arbitrum, and Base (mainnet and testnet).
7563
8140
  *
7564
8141
  * @example
7565
8142
  * ```typescript
7566
8143
  * import { earnBridgeSourceChainIdentifierSchema } from '@core/chains'
7567
8144
  *
7568
8145
  * // Valid
8146
+ * earnBridgeSourceChainIdentifierSchema.parse('Ethereum')
7569
8147
  * earnBridgeSourceChainIdentifierSchema.parse('Ethereum_Sepolia')
7570
8148
  *
7571
8149
  * // Invalid (throws ZodError)
@@ -7584,14 +8162,15 @@ const EARN_BRIDGE_DESTINATION_CHAIN_VALUES = EARN_BRIDGE_DESTINATION_BLOCKCHAINS
7584
8162
  * deposit.
7585
8163
  *
7586
8164
  * Accept a supported destination Blockchain value, a matching string literal,
7587
- * or a ChainDefinition for a supported destination chain. Currently only Arc
7588
- * Testnet is supported.
8165
+ * or a ChainDefinition for a supported destination chain. Arc (mainnet) and
8166
+ * Arc Testnet are supported.
7589
8167
  *
7590
8168
  * @example
7591
8169
  * ```typescript
7592
8170
  * import { earnBridgeDestinationChainIdentifierSchema } from '@core/chains'
7593
8171
  *
7594
8172
  * // Valid
8173
+ * earnBridgeDestinationChainIdentifierSchema.parse('Arc')
7595
8174
  * earnBridgeDestinationChainIdentifierSchema.parse('Arc_Testnet')
7596
8175
  *
7597
8176
  * // Invalid (throws ZodError)
@@ -9217,6 +9796,7 @@ function parseOrThrow(value, schema, context) {
9217
9796
  // Mainnets (alphabetically sorted)
9218
9797
  // =========================================================================
9219
9798
  [Blockchain.Arbitrum]: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
9799
+ [Blockchain.Arc]: '0x3600000000000000000000000000000000000000',
9220
9800
  [Blockchain.Avalanche]: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',
9221
9801
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
9222
9802
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
@@ -9331,6 +9911,7 @@ function parseOrThrow(value, schema, context) {
9331
9911
  // =========================================================================
9332
9912
  // Mainnets
9333
9913
  // =========================================================================
9914
+ [Blockchain.Arc]: '0xbEf5f6d51CB62b58e6A8f77868681825C6fe21c1',
9334
9915
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
9335
9916
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
9336
9917
  [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
@@ -9574,6 +10155,7 @@ function parseOrThrow(value, schema, context) {
9574
10155
  // =========================================================================
9575
10156
  // Mainnets
9576
10157
  // =========================================================================
10158
+ [Blockchain.Arc]: '0x171A4217b86A807A64eB94757Db6849fb4bDbAA0',
9577
10159
  [Blockchain.Ethereum]: '0x72DFB2E44f59C5AD2bAFE84314E5b99a7cd5075E',
9578
10160
  // =========================================================================
9579
10161
  // Testnets
@@ -9666,13 +10248,28 @@ function parseOrThrow(value, schema, context) {
9666
10248
  }
9667
10249
 
9668
10250
  /**
9669
- * Check if a selector is a raw token selector (object form).
10251
+ * Type guard: narrows a {@link TokenSelector} to a {@link RawTokenSelector}.
9670
10252
  *
9671
10253
  * @param selector - The token selector to check.
9672
- * @returns True if the selector is a raw token selector.
10254
+ * @returns `true` if `selector` is a raw token selector (object with `locator`).
10255
+ *
10256
+ * @example
10257
+ * ```typescript
10258
+ * if (isRawSelector(selector)) {
10259
+ * console.log(selector.locator)
10260
+ * }
10261
+ * ```
9673
10262
  */ function isRawSelector(selector) {
9674
- return typeof selector === 'object' && 'locator' in selector;
10263
+ // `typeof null === 'object'` (a JS quirk) and `'locator' in null`
10264
+ // throws, so the explicit `null` check is required at this exported
10265
+ // guard for JS consumers passing nullish values. The lint rule sees
10266
+ // the parameter typed as `TokenSelector` (no null) and flags the
10267
+ // check as unnecessary — disabled because the check IS necessary at
10268
+ // runtime even though TypeScript can prove it dead statically.
10269
+ return typeof selector === 'object' && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
10270
+ selector !== null && 'locator' in selector;
9675
10271
  }
10272
+
9676
10273
  /**
9677
10274
  * Normalize a symbol to uppercase for case-insensitive lookup.
9678
10275
  *
@@ -9860,7 +10457,7 @@ function parseOrThrow(value, schema, context) {
9860
10457
  if (typeof selector === 'string') {
9861
10458
  return resolveSymbol(selector, chainId);
9862
10459
  }
9863
- throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected string or object with locator.`, selector, chainId);
10460
+ throw createTokenResolutionError(`Invalid selector type: ${typeof selector}. Expected a token symbol or a raw selector (with locator).`, selector, chainId);
9864
10461
  },
9865
10462
  resolveByAddress (address, chainId) {
9866
10463
  if (!address || typeof address !== 'string') {
@@ -11120,7 +11717,7 @@ function resolveOptions(options) {
11120
11717
  }
11121
11718
 
11122
11719
  var name = "@circle-fin/unified-balance-kit";
11123
- var version$1 = "1.6.0";
11720
+ var version$1 = "1.7.0";
11124
11721
  var pkg$1 = {
11125
11722
  name: name,
11126
11723
  version: version$1};
@@ -21135,10 +21732,26 @@ function toString(amount) {
21135
21732
  * - Executing the actual bridge operations
21136
21733
  * - Handling protocol-specific logic and error conditions
21137
21734
  *
21735
+ * @remarks
21736
+ * Token-type default rationale: `TToken` defaults to the literal
21737
+ * `'USDC'` so a USDC-only subclass needs no generic ceremony at every
21738
+ * call site. This matches the literal `'USDC'` default on
21739
+ * {@link BridgeParams} (consumed at the provider boundary, where the
21740
+ * provider knows exactly which token symbol it accepts). The default
21741
+ * matches the default on {@link BridgeResult} and {@link EstimateResult} for
21742
+ * source compatibility. Provider-agnostic kit internals widen those result
21743
+ * types explicitly.
21744
+ *
21745
+ * Quotes are not typed here. A provider that issues one narrows its own
21746
+ * `estimate` return to the shape it produces — see the CCTPx provider's
21747
+ * `QuoteEnvelope` — which keeps that shape in the provider package rather
21748
+ * than in this shared one. `bridge` takes a caller-supplied quote, which is
21749
+ * public input the provider validates before reading a field.
21750
+ *
21138
21751
  * @example
21139
21752
  * ```typescript
21140
21753
  * class CustomBridgingProvider extends BridgingProvider {
21141
- * async supportsRoute(source: Chain, destination: Chain, token: TokenType): Promise<boolean> {
21754
+ * supportsRoute(source: Chain, destination: Chain, token: TokenType): boolean {
21142
21755
  * // Implementation specific logic
21143
21756
  * return true
21144
21757
  * }
@@ -21187,6 +21800,36 @@ function toString(amount) {
21187
21800
  return this.supportedChains.filter((chain)=>chain.isTestnet === source.isTestnet && chain.chain !== source.chain);
21188
21801
  }
21189
21802
  /**
21803
+ * Return the decimal precision for the given token, or `undefined` when
21804
+ * the provider does not track it.
21805
+ *
21806
+ * Consumers that format a result amount resolve decimals through this
21807
+ * hook first, falling back to their own built-in token registry when it
21808
+ * returns `undefined`. The default implementation returns `undefined`,
21809
+ * so a provider whose tokens are already in the consumer's registry needs
21810
+ * no override; a provider that identifies tokens outside that registry
21811
+ * overrides this to supply their decimals.
21812
+ *
21813
+ * @param _token - The token to resolve decimals for (provider-defined);
21814
+ * unused by the default implementation, consumed by overrides.
21815
+ * @param _sourceChain - Source chain of the bridge. Providers whose tokens
21816
+ * live in per-network registries use it to scope the lookup to the relevant
21817
+ * network; unused by the default implementation. Required, so a lookup is
21818
+ * never asked to guess which network a token belongs to.
21819
+ * @returns A promise resolving to the token's decimals, or `undefined`
21820
+ * when the provider cannot resolve it.
21821
+ *
21822
+ * @example
21823
+ * ```typescript
21824
+ * const decimals = await provider.getTokenDecimals(token, source.chain)
21825
+ * if (decimals === undefined) {
21826
+ * // fall back to the consumer's own decimals lookup
21827
+ * }
21828
+ * ```
21829
+ */ /* 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) {
21830
+ return undefined;
21831
+ }
21832
+ /* eslint-enable @typescript-eslint/require-await, @typescript-eslint/no-unused-vars */ /**
21190
21833
  * Register an event dispatcher for handling provider-specific actions and events.
21191
21834
  *
21192
21835
  *
@@ -21428,7 +22071,72 @@ var TransferSpeed;
21428
22071
  */ recipientAddress: z.string().trim().min(1, 'Fee recipient must be a non-empty string').optional()
21429
22072
  }).strict();
21430
22073
  /**
21431
- * Schema for validating bridge parameters.
22074
+ * Factory for the bridge-params Zod schema, parameterized by the token
22075
+ * validator. The base schema enforces all non-token fields (amount,
22076
+ * source, destination, config); callers supply the Zod schema for the
22077
+ * `token` field, allowing each provider to validate the token type it
22078
+ * accepts.
22079
+ *
22080
+ * @param tokenSchema - Zod schema applied to the `token` field. Pass
22081
+ * `z.literal('USDC')` for USDC-only validation, or any
22082
+ * provider-specific schema (e.g. a `0x`-prefixed hex regex) for
22083
+ * providers that accept a different token type.
22084
+ * @param config - Required. `maxDecimals` caps the decimal precision
22085
+ * permitted in `amount` and `config.maxFee`. Pass the widest value
22086
+ * any of the provider's tokens require — `6` for USDC-only, `18`
22087
+ * for an ERC-20 family that includes wETH, etc. The schema is a
22088
+ * coarse syntactic gate; narrower per-token precision is enforced
22089
+ * by the provider downstream.
22090
+ * @returns A Zod object schema whose `token` field is validated by
22091
+ * `tokenSchema` and whose other fields (`amount`, `source`,
22092
+ * `destination`, `config`) are validated against the shared bridge
22093
+ * contract. Use `.safeParse` / `.parse` directly, or feed the schema
22094
+ * to {@link parseOrThrow} from `@core/utils` to surface validation
22095
+ * failures as `KitError` with the canonical
22096
+ * `INPUT_VALIDATION_FAILED` code.
22097
+ *
22098
+ * @example
22099
+ * ```typescript
22100
+ * import { createBridgeParamsSchema, z } from '@core/provider'
22101
+ *
22102
+ * const usdcSchema = createBridgeParamsSchema(z.literal('USDC'), { maxDecimals: 6 })
22103
+ * const hexSchema = createBridgeParamsSchema(
22104
+ * z.string().regex(/^0x[0-9a-fA-F]{64}$/),
22105
+ * { maxDecimals: 18 },
22106
+ * )
22107
+ * ```
22108
+ */ // The full inferred return type of `z.object({...})` here is
22109
+ // effectively impossible to spell out by hand without re-stating every
22110
+ // field's schema. TypeScript infers it from the body — callers receive
22111
+ // a precise Zod schema type via inference, and the explicit-return
22112
+ // rules are disabled for that reason.
22113
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types
22114
+ function createBridgeParamsSchema(tokenSchema, config) {
22115
+ const { maxDecimals } = config;
22116
+ return z.object({
22117
+ amount: z.string().min(1, 'Required').pipe(createDecimalStringValidator({
22118
+ allowZero: false,
22119
+ regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
22120
+ attributeName: 'amount',
22121
+ maxDecimals
22122
+ })(z.string())),
22123
+ source: walletContextSchema,
22124
+ destination: bridgeDestinationSchema,
22125
+ token: tokenSchema,
22126
+ config: z.object({
22127
+ transferSpeed: z.nativeEnum(TransferSpeed).optional(),
22128
+ maxFee: z.string().pipe(createDecimalStringValidator({
22129
+ allowZero: true,
22130
+ regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
22131
+ attributeName: 'maxFee',
22132
+ maxDecimals
22133
+ })(z.string())).optional(),
22134
+ customFee: customFeeSchema.optional()
22135
+ })
22136
+ });
22137
+ }
22138
+ /**
22139
+ * Schema for validating bridge parameters with USDC as the token.
21432
22140
  * This ensures all required fields are present and properly typed.
21433
22141
  * A bridge must include:
21434
22142
  * - A valid amount (non-empty numeric string \> 0)
@@ -21436,11 +22144,15 @@ var TransferSpeed;
21436
22144
  * - USDC as the token
21437
22145
  * - Optional config with transfer speed and max fee settings
21438
22146
  *
22147
+ * Providers that accept a non-USDC token type should call
22148
+ * {@link createBridgeParamsSchema} with their own token schema rather
22149
+ * than reusing this constant.
22150
+ *
21439
22151
  * @throws KitError if validation fails
21440
22152
  *
21441
22153
  * @example
21442
22154
  * ```typescript
21443
- * import { bridgeParamsSchema } from '@core/provider'
22155
+ * import { usdcBridgeParamsSchema } from '@core/provider'
21444
22156
  *
21445
22157
  * const validBridge = {
21446
22158
  * amount: '100.50',
@@ -21465,29 +22177,11 @@ var TransferSpeed;
21465
22177
  * }
21466
22178
  * }
21467
22179
  *
21468
- * const result = bridgeParamsSchema.safeParse(validBridge)
22180
+ * const result = usdcBridgeParamsSchema.safeParse(validBridge)
21469
22181
  * console.log(result.success) // true
21470
22182
  * ```
21471
- */ const bridgeParamsSchema = z.object({
21472
- amount: z.string().min(1, 'Required').pipe(createDecimalStringValidator({
21473
- allowZero: false,
21474
- regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
21475
- attributeName: 'amount',
21476
- maxDecimals: 6
21477
- })(z.string())),
21478
- source: walletContextSchema,
21479
- destination: bridgeDestinationSchema,
21480
- token: z.literal('USDC'),
21481
- config: z.object({
21482
- transferSpeed: z.nativeEnum(TransferSpeed).optional(),
21483
- maxFee: z.string().pipe(createDecimalStringValidator({
21484
- allowZero: true,
21485
- regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
21486
- attributeName: 'maxFee',
21487
- maxDecimals: 6
21488
- })(z.string())).optional(),
21489
- customFee: customFeeSchema.optional()
21490
- })
22183
+ */ const usdcBridgeParamsSchema = createBridgeParamsSchema(z.literal('USDC'), {
22184
+ maxDecimals: 6
21491
22185
  });
21492
22186
 
21493
22187
  // ---------------------------------------------------------------------------
@@ -22081,6 +22775,10 @@ const USDC_DECIMALS$1 = 6;
22081
22775
  *
22082
22776
  * @see {@link https://developers.circle.com/gateway/references/fees}
22083
22777
  */ const GAS_FEE_BY_CHAIN = new Map([
22778
+ [
22779
+ Blockchain.Arc,
22780
+ parseUnits('0.001', USDC_DECIMALS$1)
22781
+ ],
22084
22782
  [
22085
22783
  Blockchain.Arc_Testnet,
22086
22784
  parseUnits('0.001', USDC_DECIMALS$1)
@@ -27446,7 +28144,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
27446
28144
  * ```
27447
28145
  */ function assertCCTPv2BridgeParams(params) {
27448
28146
  // First validate basic bridge params
27449
- validateWithStateTracking(params, bridgeParamsSchema, 'CCTPv2 bridge parameters', assertCCTPv2BridgeParamsSymbol);
28147
+ validateWithStateTracking(params, usdcBridgeParamsSchema, 'CCTPv2 bridge parameters', assertCCTPv2BridgeParamsSymbol);
27450
28148
  // After validation, we know params is CCTPV2BridgeParams
27451
28149
  const bridgeParams = params;
27452
28150
  // Enforce that source and destination chains are either testnet or mainnet
@@ -28313,7 +29011,7 @@ const mockAttestationMessage = {
28313
29011
  return step;
28314
29012
  }
28315
29013
 
28316
- var version = "1.13.0";
29014
+ var version = "1.14.0";
28317
29015
  var pkg = {
28318
29016
  version: version};
28319
29017
 
@@ -29905,7 +30603,7 @@ function assertCCTPV2Config(config) {
29905
30603
  * @example
29906
30604
  * ```typescript
29907
30605
  * const provider = new CCTPV2BridgingProvider()
29908
- * const canTransfer = provider.supportsRoute(Ethereum, Base)
30606
+ * const canTransfer = provider.supportsRoute(Ethereum, Base, 'USDC')
29909
30607
  *
29910
30608
  * if (canTransfer) {
29911
30609
  * console.log('CCTP v2 transfer is supported between these chains')