@circle-fin/app-kit 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -4748,6 +4748,15 @@ let activeRedactor = defaultMessageRedactor;
4748
4748
  recoverability: 'RETRYABLE'
4749
4749
  }
4750
4750
  ],
4751
+ // DEPOSIT_INTENT_INVALID: the Veda deposit intent is missing, expired, or
4752
+ // not signed by the depositing wallet.
4753
+ [
4754
+ 380418,
4755
+ {
4756
+ errorDef: EarnError.SIGNATURE_REJECTED,
4757
+ recoverability: 'FATAL'
4758
+ }
4759
+ ],
4751
4760
  // Bridge (380_5XX)
4752
4761
  [
4753
4762
  380500,
@@ -6356,14 +6365,14 @@ exports.BorrowChain = void 0;
6356
6365
  symbol: 'USDC',
6357
6366
  // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
6358
6367
  // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
6359
- // See: https://docs.arc.network/arc/references/contract-addresses
6368
+ // See: https://docs.arc.io/arc/references/contract-addresses
6360
6369
  decimals: 18
6361
6370
  },
6362
6371
  chainId: 5042002,
6363
6372
  isTestnet: true,
6364
6373
  explorerUrl: 'https://testnet.arcscan.app/tx/{hash}',
6365
6374
  rpcEndpoints: [
6366
- 'https://rpc.testnet.arc.network/'
6375
+ 'https://rpc.testnet.arc.io'
6367
6376
  ],
6368
6377
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
6369
6378
  usdcAddress: '0x3600000000000000000000000000000000000000',
@@ -14681,7 +14690,7 @@ function resolveOptions(options) {
14681
14690
  }
14682
14691
 
14683
14692
  var name$5 = "@circle-fin/bridge-kit";
14684
- var version$6 = "1.15.3";
14693
+ var version$6 = "1.16.0";
14685
14694
  var pkg$6 = {
14686
14695
  name: name$5,
14687
14696
  version: version$6};
@@ -16403,6 +16412,53 @@ const EURC_MAINNET_TOKEN_ID = '0x6ca9e29fa53becc29becaf4a90b9ca7a995ad4d2234880d
16403
16412
  }
16404
16413
  };
16405
16414
 
16415
+ /**
16416
+ * Map a CCTPx bytes32 token id back to its known display symbol.
16417
+ *
16418
+ * The inverse of the provider's internal symbol -> id resolution: a bridge
16419
+ * resolves a symbol such as `'cirBTC'` to its canonical bytes32 id and reports
16420
+ * that id on `result.token`, dropping the symbol. This helper recovers the
16421
+ * symbol for display. It is scoped to {@link KNOWN_TOKEN_SYMBOLS} — the same
16422
+ * known set the forward resolution covers — and is keyed by network, because a
16423
+ * symbol names a separate registration on each network, whose ids are
16424
+ * unrelated.
16425
+ *
16426
+ * The lookup is best-effort by design: an arbitrary registry token, a
16427
+ * non-canonical bridge of a known symbol, or any unrecognized id has no known
16428
+ * symbol and yields `undefined`. The id is matched case-insensitively (bytes32
16429
+ * ids have no checksum casing).
16430
+ *
16431
+ * @param id - The bytes32 token id to decode (e.g. a `result.token` value).
16432
+ * @param network - The network the id belongs to (`'testnet'` or `'mainnet'`);
16433
+ * derive it from the source chain, e.g. `chain.isTestnet ? 'testnet' :
16434
+ * 'mainnet'`.
16435
+ * @returns The matching {@link KnownTokenSymbol}, or `undefined` when `id` is
16436
+ * not one of the known canonical ids for `network`.
16437
+ * @throws A `KitError` (`INPUT_VALIDATION_FAILED`, FATAL) when `id` is not a
16438
+ * string or `network` is not a recognized network.
16439
+ *
16440
+ * @example
16441
+ * ```typescript
16442
+ * import { resolveToKnownTokenSymbol } from '@circle-fin/provider-cctpx'
16443
+ *
16444
+ * const result = await provider.bridge(params) // params.token === 'cirBTC'
16445
+ * const network = result.source.chain.isTestnet ? 'testnet' : 'mainnet'
16446
+ *
16447
+ * resolveToKnownTokenSymbol(result.token, network) // 'cirBTC'
16448
+ * resolveToKnownTokenSymbol('0xdeadbeef…', network) // undefined (not known)
16449
+ * ```
16450
+ */ function resolveToKnownTokenSymbol(id, network) {
16451
+ if (typeof id !== 'string') {
16452
+ throw createValidationFailedError$1('id', id, 'must be a string');
16453
+ }
16454
+ if (network !== 'testnet' && network !== 'mainnet') {
16455
+ throw createValidationFailedError$1('network', network, "must be 'testnet' or 'mainnet'");
16456
+ }
16457
+ const row = KNOWN_TOKEN_IDS_BY_NETWORK[network];
16458
+ const target = id.toLowerCase();
16459
+ return KNOWN_TOKEN_SYMBOLS.find((symbol)=>row[symbol].toLowerCase() === target);
16460
+ }
16461
+
16406
16462
  /**
16407
16463
  * Default clock implementation.
16408
16464
  *
@@ -23626,6 +23682,24 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
23626
23682
  }
23627
23683
  };
23628
23684
 
23685
+ /**
23686
+ * Check whether a forwarded burn to the given destination asks Circle's
23687
+ * Forwarding Service to set up the recipient's token account before minting.
23688
+ *
23689
+ * Must return `true` exactly for the destinations where
23690
+ * {@link getForwarderHookData} emits the `createAta` frame, so the forwarding
23691
+ * fee quote includes the setup rent whenever the burn requests it.
23692
+ *
23693
+ * @internal
23694
+ * @param chainType - The destination blockchain type.
23695
+ * @returns `true` for Solana destinations, `false` otherwise.
23696
+ *
23697
+ * @example
23698
+ * ```typescript
23699
+ * requiresRecipientSetup('solana') // true
23700
+ * requiresRecipientSetup('evm') // false
23701
+ * ```
23702
+ */ const requiresRecipientSetup = (chainType)=>chainType === 'solana';
23629
23703
  /**
23630
23704
  * Build the forwarding `hookData` for a forwarded (Orbit-relayed) CCTP v2 burn,
23631
23705
  * tailored to the destination chain.
@@ -23976,10 +24050,12 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
23976
24050
  * @param sourceDomain - The source domain
23977
24051
  * @param destinationDomain - The destination domain
23978
24052
  * @param isTestnet - Whether the request is for a testnet chain
24053
+ * @param includeRecipientSetup - Whether the quote includes recipient account setup rent
23979
24054
  * @returns The complete API URL with forward=true query parameter
23980
- */ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet) {
24055
+ */ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet, includeRecipientSetup) {
23981
24056
  const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
23982
- return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true`;
24057
+ const recipientSetupQuery = includeRecipientSetup ? '&includeRecipientSetup=true' : '';
24058
+ return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true${recipientSetupQuery}`;
23983
24059
  }
23984
24060
  /**
23985
24061
  * Fetches the forwarding fee for a USDC transfer operation with retry and timeout handling.
@@ -23996,6 +24072,10 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
23996
24072
  *
23997
24073
  * The high fee tier is always used to provide buffer for destination gas fluctuations.
23998
24074
  *
24075
+ * Pass `includeRecipientSetup: true` when the burn asks the Forwarding Service to create
24076
+ * the recipient's token account (Solana destinations). The quote then includes the
24077
+ * account rent, which the quote without this flag leaves out.
24078
+ *
23999
24079
  * Implements timeout and retry logic to handle network issues and temporary API failures.
24000
24080
  * Each HTTP request is given a maximum of 2 seconds before it is aborted.
24001
24081
  * We retry up to 10 times, waiting 200ms between attempts.
@@ -24004,6 +24084,8 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
24004
24084
  * @param destinationDomain - The destination domain
24005
24085
  * @param isTestnet - Whether the request is for a testnet chain
24006
24086
  * @param finalityThreshold - The finality threshold to select the fee tier (1000 for FAST, 2000 for SLOW)
24087
+ * @param includeRecipientSetup - Whether to include recipient account setup rent in the
24088
+ * quote. Defaults to `false`.
24007
24089
  * @returns The high-tier forwarding fee in USDC minor units (6 decimals)
24008
24090
  * @throws Error if the input domains are invalid, the API request fails, returns invalid data, or network errors occur
24009
24091
  *
@@ -24015,8 +24097,11 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
24015
24097
  *
24016
24098
  * // SLOW transfer - use 2000ms finality threshold
24017
24099
  * const slowForwardingFee = await fetchForwardingFee(0, 6, false, 2000)
24100
+ *
24101
+ * // Ethereum -> Solana: include the recipient token account rent
24102
+ * const solanaForwardingFee = await fetchForwardingFee(0, 5, false, 1000, true)
24018
24103
  * ```
24019
- */ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, finalityThreshold) {
24104
+ */ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, finalityThreshold, includeRecipientSetup = false) {
24020
24105
  // Validate source domain
24021
24106
  if (!Number.isInteger(sourceDomain) || sourceDomain < 0) {
24022
24107
  throw new KitError({
@@ -24044,7 +24129,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
24044
24129
  });
24045
24130
  }
24046
24131
  // Fetch the fee tiers from the API with forward=true
24047
- const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet);
24132
+ const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet, includeRecipientSetup);
24048
24133
  const response = await pollApiGet(url, isForwardingFeeResponse, DEFAULT_CONFIG$4);
24049
24134
  // Validate that we have at least one fee tier
24050
24135
  if (response.length === 0) {
@@ -26380,7 +26465,7 @@ const mockAttestationMessage = {
26380
26465
  return step;
26381
26466
  }
26382
26467
 
26383
- var version$5 = "1.14.2";
26468
+ var version$5 = "1.14.3";
26384
26469
  var pkg$5 = {
26385
26470
  version: version$5};
26386
26471
 
@@ -27299,9 +27384,34 @@ function assertCCTPV2Config(config) {
27299
27384
  tokenAddress: source.chain.usdcAddress,
27300
27385
  operationContext: sourceOperationContext
27301
27386
  });
27387
+ await this.assertAmountCoversRecipientSetupFee(params);
27302
27388
  return bridge$1(params, this);
27303
27389
  }
27304
27390
  /**
27391
+ * Reject a forwarded transfer to Solana whose amount does not exceed the fee.
27392
+ *
27393
+ * CCTP v2 reverts a burn whose `maxFee` is not less than its amount, and the Solana
27394
+ * forwarding fee includes the recipient token account rent. Checking before the
27395
+ * approval avoids sending a transaction that can never be followed by a burn.
27396
+ * Skipped when the caller provides `config.maxFee`.
27397
+ *
27398
+ * @param params - The validated CCTP v2 bridge parameters.
27399
+ * @returns A promise that resolves when the amount covers the fee.
27400
+ * @throws {KitError} INPUT_INVALID_AMOUNT (FATAL) when the fee is greater than or equal
27401
+ * to the amount.
27402
+ */ async assertAmountCoversRecipientSetupFee(params) {
27403
+ const { destination, amount, config } = params;
27404
+ if (destination.useForwarder !== true || config.maxFee !== undefined || !requiresRecipientSetup(destination.chain.type)) {
27405
+ return;
27406
+ }
27407
+ const { providerFee, forwarderFee } = await this.getMaxFee(params);
27408
+ const maxFee = providerFee + forwarderFee;
27409
+ if (maxFee >= BigInt(amount)) {
27410
+ // Amounts and fees are in USDC minor units (6 decimals)
27411
+ throw createInvalidAmountError(formatUnits(amount, 6), `Amount must be greater than the ${formatUnits(String(maxFee), 6)} USDC fee for forwarded transfers to ${destination.chain.name}, which includes creating the recipient's token account`);
27412
+ }
27413
+ }
27414
+ /**
27305
27415
  * Retry a failed or incomplete CCTP v2 bridge operation.
27306
27416
  *
27307
27417
  * Analyzes the bridge result to determine where to continue and executes
@@ -28018,6 +28128,9 @@ function assertCCTPV2Config(config) {
28018
28128
  * | FAST | true | No | maxFee = calculatedBurnFee + forwardingFee |
28019
28129
  * | FAST | true | Yes | maxFee = config.maxFee |
28020
28130
  *
28131
+ * For forwarded burns to Solana, the forwarding fee includes the rent for creating the
28132
+ * recipient's token account. A caller-provided `maxFee` must cover it too.
28133
+ *
28021
28134
  * @param params - The bridge parameters object containing:
28022
28135
  * - `source`: The source wallet context with chain definition and adapter
28023
28136
  * - `destination`: The destination wallet context with chain definition and adapter
@@ -28056,6 +28169,7 @@ function assertCCTPV2Config(config) {
28056
28169
  }
28057
28170
  // Get the min finality threshold based on the transfer speed
28058
28171
  const minFinalityThreshold = CCTPv2MinFinalityThreshold[transferSpeed];
28172
+ const includeRecipientSetup = requiresRecipientSetup(destination.chain.type);
28059
28173
  // SLOW transfer without forwarder: no fee
28060
28174
  if (transferSpeed === exports.TransferSpeed.SLOW && !useForwarder) {
28061
28175
  return {
@@ -28065,7 +28179,7 @@ function assertCCTPV2Config(config) {
28065
28179
  }
28066
28180
  // SLOW transfer with forwarder: only forwarding fee
28067
28181
  if (transferSpeed === exports.TransferSpeed.SLOW && useForwarder) {
28068
- const forwarderFee = await fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold);
28182
+ const forwarderFee = await fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold, includeRecipientSetup);
28069
28183
  return {
28070
28184
  providerFee: 0n,
28071
28185
  forwarderFee
@@ -28075,7 +28189,7 @@ function assertCCTPV2Config(config) {
28075
28189
  // Fetch fees in parallel when forwarder is enabled
28076
28190
  const [scaledBps, forwarderFee] = await Promise.all([
28077
28191
  fetchUsdcFastBurnFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet),
28078
- useForwarder ? fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold) : Promise.resolve(0n)
28192
+ useForwarder ? fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold, includeRecipientSetup) : Promise.resolve(0n)
28079
28193
  ]);
28080
28194
  // Calculate fee proportional to the transfer amount
28081
28195
  const amountInMinorUnits = BigInt(amount);
@@ -28625,6 +28739,21 @@ function assertCCTPV2Config(config) {
28625
28739
  'bridge_forward'
28626
28740
  ]
28627
28741
  ];
28742
+ /**
28743
+ * Provider step names whose `txHash` is the source-chain transaction that
28744
+ * moves funds into the bridge protocol.
28745
+ *
28746
+ * @remarks
28747
+ * The success event carries this hash so the backend can join it to the
28748
+ * on-chain CCTP / CCTPx event for the same transfer. `burn` is the CCTP v2
28749
+ * step (including batched and source-fee flows); `transfer` is the CCTPx
28750
+ * `CrossChainTokenService` step.
28751
+ *
28752
+ * @internal
28753
+ */ const BRIDGE_SOURCE_TX_STEP_NAMES = [
28754
+ 'burn',
28755
+ 'transfer'
28756
+ ];
28628
28757
 
28629
28758
  /**
28630
28759
  * Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
@@ -29720,6 +29849,85 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29720
29849
  }
29721
29850
  return undefined;
29722
29851
  }
29852
+ /**
29853
+ * Pick the source-chain transaction hash from a bridge result.
29854
+ *
29855
+ * Walk the steps from the end and return the hash of the most recent
29856
+ * successful source step (`burn` for CCTP v2, `transfer` for CCTPx). Walking
29857
+ * backwards selects the attempt that landed when a retry appended a new
29858
+ * source step after a failed one.
29859
+ *
29860
+ * @param steps - All steps from the bridge result.
29861
+ * @returns The source transaction hash, or `undefined` if no source step recorded one.
29862
+ *
29863
+ * @internal
29864
+ */ function pickSourceTxHash(steps) {
29865
+ for(let i = steps.length - 1; i >= 0; i--){
29866
+ const step = steps[i];
29867
+ if (step?.txHash != null && step.state === 'success' && BRIDGE_SOURCE_TX_STEP_NAMES.includes(step.name)) {
29868
+ return step.txHash;
29869
+ }
29870
+ }
29871
+ return undefined;
29872
+ }
29873
+ /**
29874
+ * Resolve the token value reported in telemetry.
29875
+ *
29876
+ * CCTPx reports `result.token` as a bytes32 token id, while the backend's
29877
+ * canonical tables key tokens by symbol. Map the canonical id of a known CCTPx
29878
+ * token back to its symbol and pass any other value (USDC, or a registry token
29879
+ * without a known symbol) through unchanged. Registry symbols are not used
29880
+ * because they are not unique across CCTPx tokens. Never throws.
29881
+ *
29882
+ * @param token - The token from a bridge result.
29883
+ * @param sourceChain - The result's source chain; selects testnet or mainnet ids.
29884
+ * @returns The known symbol, or `token` unchanged.
29885
+ *
29886
+ * @internal
29887
+ */ function toTelemetryToken(token, sourceChain) {
29888
+ try {
29889
+ const network = sourceChain.isTestnet ? 'testnet' : 'mainnet';
29890
+ return resolveToKnownTokenSymbol(token, network) ?? token;
29891
+ } catch {
29892
+ return token;
29893
+ }
29894
+ }
29895
+ /**
29896
+ * Emit the `bridge_bridge` success event for a completed transfer.
29897
+ *
29898
+ * No-ops unless `result.state` is `'success'`. Failed results are already
29899
+ * reported by error telemetry. Never throws: a malformed result (for example
29900
+ * from a custom provider) is reported as a telemetry drop warning and the
29901
+ * caller's result is returned unchanged.
29902
+ *
29903
+ * @param result - The human-readable bridge result returned to the caller.
29904
+ * @param config - Analytics telemetry identity, gated by `disableAnalytics`.
29905
+ *
29906
+ * @internal
29907
+ */ function emitBridgeSuccessTelemetry(result, config) {
29908
+ if (config.disabled || result.state !== 'success') {
29909
+ return;
29910
+ }
29911
+ try {
29912
+ const txHash = pickSourceTxHash(result.steps);
29913
+ // `JSON.stringify` turns a non-finite number into `null`, which the logs
29914
+ // endpoint's strict schema rejects along with the whole event.
29915
+ const amountIn = Number.parseFloat(result.amount);
29916
+ emitSuccessTelemetry(BRIDGE_EVENT_TYPES.BRIDGE, config, {
29917
+ sourceChain: result.source.chain.chain,
29918
+ destinationChain: result.destination.chain.chain,
29919
+ tokenIn: toTelemetryToken(result.token, result.source.chain),
29920
+ ...Number.isFinite(amountIn) && {
29921
+ amountIn
29922
+ },
29923
+ ...txHash != null && {
29924
+ txHash
29925
+ }
29926
+ });
29927
+ } catch (telemetryError) {
29928
+ warnTelemetryDrop(BRIDGE_EVENT_TYPES.BRIDGE, telemetryError);
29929
+ }
29930
+ }
29723
29931
  /**
29724
29932
  * BridgeKit caller component for retry and estimate operations.
29725
29933
  */ const BRIDGE_KIT_CALLER = {
@@ -29850,6 +30058,10 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29850
30058
  /** Whether error telemetry is disabled. */ disableErrorReporting;
29851
30059
  /** Per-kit telemetry identity for shared helpers. */ telemetryConfig;
29852
30060
  /**
30061
+ * Per-kit telemetry identity for success events. Gated by `disableAnalytics`
30062
+ * independently of `disableErrorReporting`.
30063
+ */ analyticsTelemetryConfig;
30064
+ /**
29853
30065
  * Create a new BridgeKit instance.
29854
30066
  *
29855
30067
  * @param config - The configuration containing the CCTPv2 provider
@@ -29876,6 +30088,11 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29876
30088
  sdkVersion: pkg$6.version,
29877
30089
  disabled: this.disableErrorReporting
29878
30090
  };
30091
+ this.analyticsTelemetryConfig = {
30092
+ sdkName: SDK_NAME$4,
30093
+ sdkVersion: pkg$6.version,
30094
+ disabled: config.disableAnalytics === true
30095
+ };
29879
30096
  for (const provider of this.providers){
29880
30097
  provider.registerDispatcher(this.actionDispatcher);
29881
30098
  }
@@ -29889,7 +30106,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29889
30106
  this.actionDispatcher.off(actionOrWildCard, handler);
29890
30107
  }
29891
30108
  async bridge(params) {
29892
- return withErrorTelemetry(async ()=>{
30109
+ const bridgeResult = await withErrorTelemetry(async ()=>{
29893
30110
  // First validate the parameters
29894
30111
  assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
29895
30112
  // Then resolve chain definitions (includes adapter chain support validation)
@@ -29926,7 +30143,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29926
30143
  emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.BRIDGE, this.telemetryConfig, {
29927
30144
  sourceChain: resolveChainName(params.from.chain),
29928
30145
  destinationChain: resolveChainName(params.to.chain),
29929
- tokenIn: params.token,
30146
+ tokenIn: toTelemetryToken(result.token, result.source.chain),
29930
30147
  ...txHash != null && {
29931
30148
  txHash
29932
30149
  }
@@ -29938,6 +30155,8 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29938
30155
  destinationChain: resolveChainName(params.to.chain),
29939
30156
  tokenIn: params.token
29940
30157
  });
30158
+ emitBridgeSuccessTelemetry(bridgeResult, this.analyticsTelemetryConfig);
30159
+ return bridgeResult;
29941
30160
  }
29942
30161
  /**
29943
30162
  * Retry a failed or incomplete cross-chain USDC bridge operation.
@@ -30008,7 +30227,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30008
30227
  * )
30009
30228
  * ```
30010
30229
  */ async retry(result, context, invocationMeta) {
30011
- return withErrorTelemetry(async ()=>{
30230
+ const retriedResult = await withErrorTelemetry(async ()=>{
30012
30231
  // Cast the providers list to the wide view here. The tuple
30013
30232
  // type on `this.providers` retains concrete provider types
30014
30233
  // (CCTPv2 narrows `TToken` to `'USDC'`); calling `.retry`
@@ -30036,7 +30255,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30036
30255
  emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
30037
30256
  sourceChain: result.source.chain.chain,
30038
30257
  destinationChain: result.destination.chain.chain,
30039
- tokenIn: result.token,
30258
+ tokenIn: toTelemetryToken(result.token, result.source.chain),
30040
30259
  ...txHash != null && {
30041
30260
  txHash
30042
30261
  }
@@ -30050,8 +30269,16 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30050
30269
  }, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
30051
30270
  sourceChain: result.source.chain.chain,
30052
30271
  destinationChain: result.destination.chain.chain,
30053
- tokenIn: result.token
30272
+ tokenIn: toTelemetryToken(result.token, result.source.chain)
30054
30273
  });
30274
+ // A retry that completes the transfer is the transfer's only success
30275
+ // event, so it reports the same `bridge_bridge` type as `bridge()`. An
30276
+ // input that already succeeded was reported by the call that completed it
30277
+ // (CCTP v2 returns such a result unchanged), so it is not reported again.
30278
+ if (result.state !== 'success') {
30279
+ emitBridgeSuccessTelemetry(retriedResult, this.analyticsTelemetryConfig);
30280
+ }
30281
+ return retriedResult;
30055
30282
  }
30056
30283
  async estimate(params) {
30057
30284
  return withErrorTelemetry(async ()=>{
@@ -30600,7 +30827,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30600
30827
  registerKit(`${pkg$6.name}/${pkg$6.version}`);
30601
30828
 
30602
30829
  var name$4 = "@circle-fin/swap-kit";
30603
- var version$4 = "1.8.0";
30830
+ var version$4 = "1.8.1";
30604
30831
  var pkg$4 = {
30605
30832
  name: name$4,
30606
30833
  version: version$4};
@@ -46834,6 +47061,9 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
46834
47061
  ...context.disableErrorReporting != null && {
46835
47062
  disableErrorReporting: context.disableErrorReporting
46836
47063
  },
47064
+ ...context.disableAnalytics != null && {
47065
+ disableAnalytics: context.disableAnalytics
47066
+ },
46837
47067
  ...context.headers != null && {
46838
47068
  headers: context.headers
46839
47069
  }
@@ -46954,7 +47184,7 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
46954
47184
  };
46955
47185
 
46956
47186
  var name$3 = "@circle-fin/earn-kit";
46957
- var version$3 = "1.9.0";
47187
+ var version$3 = "1.10.0";
46958
47188
  var pkg$3 = {
46959
47189
  name: name$3,
46960
47190
  version: version$3};
@@ -47061,6 +47291,61 @@ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
47061
47291
  *
47062
47292
  * @internal
47063
47293
  */ const EARN_BRIDGE_MAX_VALIDITY_SECONDS = 86_400n;
47294
+ /**
47295
+ * Static fields of the EIP-712 domain for the `DepositIntent` typed-data
47296
+ * intent. `chainId` is excluded here — it must come from the deposit's
47297
+ * actual chain (see {@link buildDepositIntentTypedData}), since a mismatched
47298
+ * `chainId` in the domain separator would make every intent signature
47299
+ * invalid.
47300
+ *
47301
+ * `verifyingContract` is intentionally omitted — the intent is verified
47302
+ * off-chain by the Earn Service, not by a deployed contract.
47303
+ *
47304
+ * @internal
47305
+ */ const DEPOSIT_INTENT_DOMAIN = {
47306
+ name: 'Circle EarnKit',
47307
+ version: '1'
47308
+ };
47309
+ /**
47310
+ * EIP-712 type definition for the `DepositIntent` struct.
47311
+ *
47312
+ * @internal
47313
+ */ const DEPOSIT_INTENT_TYPES = {
47314
+ DepositIntent: [
47315
+ {
47316
+ name: 'wallet',
47317
+ type: 'address'
47318
+ },
47319
+ {
47320
+ name: 'vault',
47321
+ type: 'address'
47322
+ },
47323
+ {
47324
+ name: 'amount',
47325
+ type: 'uint256'
47326
+ },
47327
+ {
47328
+ name: 'deadline',
47329
+ type: 'uint256'
47330
+ }
47331
+ ]
47332
+ };
47333
+ /**
47334
+ * Lifetime of a `DepositIntent` signature, in seconds.
47335
+ *
47336
+ * The Earn Service rejects a deadline more than 300 seconds ahead of its own
47337
+ * clock. 240 seconds leaves room for clock skew between the client and the
47338
+ * service while still covering the deposit request that follows the
47339
+ * signature.
47340
+ *
47341
+ * @internal
47342
+ */ const DEPOSIT_INTENT_TTL_SECONDS = 240;
47343
+ /**
47344
+ * Protocol value the Earn Service reports for Veda vaults. A deposit into a
47345
+ * Veda vault needs a signed `DepositIntent`.
47346
+ *
47347
+ * @internal
47348
+ */ const VEDA_PROTOCOL = 'veda';
47064
47349
  /**
47065
47350
  * Display symbol for vault share tokens.
47066
47351
  *
@@ -49936,7 +50221,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
49936
50221
  }
49937
50222
 
49938
50223
  var name$2 = "@circle-fin/provider-earn-service";
49939
- var version$2 = "1.7.2";
50224
+ var version$2 = "1.8.0";
49940
50225
  var pkg$2 = {
49941
50226
  name: name$2,
49942
50227
  version: version$2};
@@ -50288,8 +50573,9 @@ function toPositionInfo(data) {
50288
50573
  * Build signed deposit instructions via the Earn Service API.
50289
50574
  *
50290
50575
  * Call `POST /v1/earnKit/deposit` with the vault address, amount,
50291
- * wallet address, and chain. Return EIP-712 signed execution
50292
- * parameters for on-chain submission.
50576
+ * wallet address, and chain, plus the signed `DepositIntent` when one is
50577
+ * given. Return EIP-712 signed execution parameters for on-chain
50578
+ * submission.
50293
50579
  *
50294
50580
  * @param params - Deposit parameters
50295
50581
  * @returns Signed execution parameters for the deposit
@@ -50303,7 +50589,8 @@ function toPositionInfo(data) {
50303
50589
  vaultAddress: params.vaultAddress,
50304
50590
  amount: params.amount,
50305
50591
  address: params.address,
50306
- chain: params.chain
50592
+ chain: params.chain,
50593
+ ...params.intent
50307
50594
  };
50308
50595
  try {
50309
50596
  const response = await pollApiPost(url.toString(), requestBody, isDepositResponse, pollingConfig);
@@ -50315,6 +50602,232 @@ function toPositionInfo(data) {
50315
50602
  }
50316
50603
  }
50317
50604
 
50605
+ /**
50606
+ * Build the version-1 EarnKit `DepositIntent` EIP-712 typed data.
50607
+ *
50608
+ * The signed struct binds the depositing `wallet`, the target `vault`, the
50609
+ * deposit `amount` in the asset's smallest unit, and a signature `deadline`.
50610
+ * The Earn Service rebuilds the same struct from the deposit request and
50611
+ * rejects the deposit unless `wallet` signed it. The domain intentionally
50612
+ * omits `verifyingContract` because the service verifies the signature
50613
+ * off-chain.
50614
+ *
50615
+ * `domain.chainId` comes from the deposit's chain rather than a constant: the
50616
+ * domain separator must match what the service rebuilds, and a wrong
50617
+ * `chainId` would make every intent signature invalid.
50618
+ *
50619
+ * This function returns the fully-typed {@link DepositIntentTypedData} with
50620
+ * no assertion. {@link resolveDepositIntent} casts it to the adapter's
50621
+ * generic `EIP712Domain` shape only where it calls `adapter.signTypedData`,
50622
+ * since that shared interface always requires `verifyingContract`. The
50623
+ * adapter's runtime `eip712DomainSchema` treats `verifyingContract` as
50624
+ * optional, so the cast is safe at runtime. BorrowKit's
50625
+ * `buildWebhookRegistrationTypedData` works around the same gap.
50626
+ *
50627
+ * @param params - Wallet, vault, base-unit amount, deadline, and chain id.
50628
+ * @returns EIP-712 typed data ready to be cast for `adapter.signTypedData`.
50629
+ *
50630
+ * @example
50631
+ * ```typescript
50632
+ * const typedData = buildDepositIntentTypedData({
50633
+ * wallet: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
50634
+ * vault: '0x00000000000000000000000000000000000000a1',
50635
+ * amount: 100_000_000n,
50636
+ * deadline: 1_700_000_000n,
50637
+ * chainId: 5_042_002,
50638
+ * })
50639
+ * ```
50640
+ *
50641
+ * @internal
50642
+ */ function buildDepositIntentTypedData(params) {
50643
+ return {
50644
+ domain: {
50645
+ ...DEPOSIT_INTENT_DOMAIN,
50646
+ chainId: params.chainId
50647
+ },
50648
+ types: DEPOSIT_INTENT_TYPES,
50649
+ primaryType: 'DepositIntent',
50650
+ message: {
50651
+ wallet: params.wallet,
50652
+ vault: params.vault,
50653
+ amount: params.amount,
50654
+ deadline: params.deadline
50655
+ }
50656
+ };
50657
+ }
50658
+ /**
50659
+ * Earn Service error code for a missing, expired, or wrongly signed
50660
+ * `DepositIntent` (`DEPOSIT_INTENT_INVALID`).
50661
+ */ const DEPOSIT_INTENT_INVALID_CODE = 380418;
50662
+ /**
50663
+ * Sign a `DepositIntent` when the target vault needs one.
50664
+ *
50665
+ * A Veda vault mints shares straight to the depositing wallet, so the Earn
50666
+ * Service only builds a Veda deposit for a wallet that signed a
50667
+ * `DepositIntent` for the same vault and amount. The vault lookup exists only
50668
+ * to find out whether the vault is a Veda vault (and its asset decimals, to
50669
+ * sign the amount). Other vaults ignore the intent, so this returns no intent
50670
+ * for them and the wallet sees no extra signature prompt. A vault the service
50671
+ * does not return is also treated as not needing an intent; the deposit call
50672
+ * then reports why.
50673
+ *
50674
+ * A failed vault lookup does not stop the deposit: this returns the lookup
50675
+ * error with no intent, so a non-Veda deposit works as before. A Veda deposit
50676
+ * then gets `DEPOSIT_INTENT_INVALID` from the service, and
50677
+ * {@link resolveDepositError} reports the lookup error instead. The lookup
50678
+ * fails either by throwing or by returning the vault in its `errors` list (for
50679
+ * example `VAULT_REFRESH_BUSY`); both are kept the same way.
50680
+ *
50681
+ * @param params - Adapter, resolved chain, wallet, vault, human-readable
50682
+ * amount, and service config.
50683
+ * @returns The signed intent when the vault needs one, and the lookup error
50684
+ * when the vault lookup failed.
50685
+ * @throws {@link KitError} `InputError.VALIDATION_FAILED` when the vault needs
50686
+ * an intent but the chain is not EVM, the adapter cannot sign typed data, or
50687
+ * the amount has more decimal places than the vault asset.
50688
+ *
50689
+ * @example
50690
+ * ```typescript
50691
+ * const { intent } = await resolveDepositIntent({
50692
+ * adapter,
50693
+ * chain: ArcTestnet,
50694
+ * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
50695
+ * vaultAddress: '0x00000000000000000000000000000000000000a1',
50696
+ * amount: '100',
50697
+ * config,
50698
+ * })
50699
+ * // intent -> { intentSignature: '0x…', intentDeadline: 1700000240 }
50700
+ * ```
50701
+ *
50702
+ * @internal
50703
+ */ async function resolveDepositIntent(params) {
50704
+ let lookup;
50705
+ try {
50706
+ lookup = await fetchVaults({
50707
+ // `resolveAdapterContext` already rejected chains the Earn Service does
50708
+ // not support, so the chain is an Earn chain here.
50709
+ vaults: [
50710
+ {
50711
+ chain: params.chain,
50712
+ vaultAddress: params.vaultAddress
50713
+ }
50714
+ ],
50715
+ config: params.config
50716
+ });
50717
+ } catch (lookupError) {
50718
+ return {
50719
+ lookupError
50720
+ };
50721
+ }
50722
+ const vaultError = lookup.errors.find((candidate)=>isSameAddress(candidate.vaultAddress, params.vaultAddress));
50723
+ if (vaultError !== undefined) {
50724
+ // Same handling as a thrown lookup error, mapped with the Earn Service
50725
+ // error table so it keeps its recoverability (VAULT_REFRESH_BUSY retries).
50726
+ return {
50727
+ lookupError: parseEarnApiError({
50728
+ responseBody: {
50729
+ code: vaultError.code,
50730
+ message: vaultError.message
50731
+ }
50732
+ }, {
50733
+ operation: 'getVaults'
50734
+ })
50735
+ };
50736
+ }
50737
+ const vault = lookup.vaults.find((candidate)=>isSameAddress(candidate.address, params.vaultAddress));
50738
+ if (vault?.protocol.toLowerCase() !== VEDA_PROTOCOL) {
50739
+ return {};
50740
+ }
50741
+ if (params.chain.type !== 'evm') {
50742
+ throw createValidationFailedError$1('from.chain', params.chain.chain, 'Veda vault deposits need an EVM chain to sign the deposit intent');
50743
+ }
50744
+ if (!canSignTypedData(params.adapter)) {
50745
+ throw createValidationFailedError$1('from.adapter', params.adapter, 'adapter must support signTypedData for Veda vault deposits');
50746
+ }
50747
+ const wallet = assertHexAddress('from.address', params.address, 'Depositor address must be a 0x-prefixed 20-byte hex address.');
50748
+ const assetDecimals = vault.liquidityProfile.totalDeposits.decimals;
50749
+ // Amount.parse truncates extra digits, but the service rejects them. Fail
50750
+ // before the wallet prompt instead of asking to sign a deposit that cannot
50751
+ // go through.
50752
+ assertDecimalPlaces('amount', params.amount, assetDecimals, `amount must have at most ${String(assetDecimals)} decimal places for the vault asset`);
50753
+ const deadline = Math.floor(Date.now() / 1000) + DEPOSIT_INTENT_TTL_SECONDS;
50754
+ const typedData = buildDepositIntentTypedData({
50755
+ wallet,
50756
+ vault: params.vaultAddress,
50757
+ amount: Amount.parse(params.amount, {
50758
+ decimals: assetDecimals
50759
+ }).raw,
50760
+ deadline: BigInt(deadline),
50761
+ chainId: params.chain.chainId
50762
+ });
50763
+ // `typedData.domain` intentionally omits `verifyingContract` (the Earn
50764
+ // Service verifies off-chain), but the shared `signTypedData` signature
50765
+ // requires the standard `EIP712Domain` shape. The adapter's runtime
50766
+ // `eip712DomainSchema` treats `verifyingContract` as optional, so this cast
50767
+ // is safe at runtime.
50768
+ const intentSignature = await params.adapter.signTypedData(typedData, {
50769
+ chain: params.chain,
50770
+ address: wallet
50771
+ });
50772
+ return {
50773
+ intent: {
50774
+ intentSignature,
50775
+ intentDeadline: deadline
50776
+ }
50777
+ };
50778
+ }
50779
+ /**
50780
+ * Pick the error to report for a failed deposit request.
50781
+ *
50782
+ * When the vault lookup failed, the SDK sent the deposit without an intent.
50783
+ * If the service then answers `DEPOSIT_INTENT_INVALID`, the vault is a Veda
50784
+ * vault and the real cause is the failed lookup, not a bad signature. Report
50785
+ * the lookup error, which keeps its own recoverability (often retryable),
50786
+ * instead of a fatal signature rejection.
50787
+ *
50788
+ * When the SDK did send an intent and the service still answers
50789
+ * `DEPOSIT_INTENT_INVALID`, the error stays a fatal signature rejection, but
50790
+ * its message adds that the intent may have expired: the deadline starts
50791
+ * before the wallet prompt, so a slow signer can submit an expired intent.
50792
+ * Calling `deposit()` again signs a new one. Every other error is reported as
50793
+ * is.
50794
+ *
50795
+ * @param resolution - The result of {@link resolveDepositIntent}.
50796
+ * @param error - The error from the deposit request.
50797
+ * @returns The error to throw.
50798
+ *
50799
+ * @example
50800
+ * ```typescript
50801
+ * try {
50802
+ * return await fetchDeposit({ ...params, intent: resolution.intent })
50803
+ * } catch (error) {
50804
+ * throw resolveDepositError(resolution, error)
50805
+ * }
50806
+ * ```
50807
+ *
50808
+ * @internal
50809
+ */ function resolveDepositError(resolution, error) {
50810
+ if (resolution.lookupError !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
50811
+ return resolution.lookupError;
50812
+ }
50813
+ if (resolution.intent !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
50814
+ return new KitError({
50815
+ code: error.code,
50816
+ name: error.name,
50817
+ type: error.type,
50818
+ recoverability: error.recoverability,
50819
+ message: `${error.message} The deposit intent may have expired before the deposit was submitted (it is valid for ${String(DEPOSIT_INTENT_TTL_SECONDS)} seconds from when the SDK asks the wallet to sign). Call deposit() again to sign a new intent.`,
50820
+ ...error.cause !== undefined && {
50821
+ cause: error.cause
50822
+ }
50823
+ });
50824
+ }
50825
+ return error;
50826
+ }
50827
+ function isEarnServiceCode(trace, code) {
50828
+ return typeof trace === 'object' && trace !== null && 'earnServiceCode' in trace && trace.earnServiceCode === code;
50829
+ }
50830
+
50318
50831
  /**
50319
50832
  * Build cross-chain deposit instructions via the bridge API.
50320
50833
  *
@@ -51553,13 +52066,31 @@ function finishElapsedWait(lastStatus, lastError) {
51553
52066
  const adapterContractAddress = requireAdapterContract(chain);
51554
52067
  const { adapter } = params.from;
51555
52068
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
51556
- const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
52069
+ const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>{
52070
+ // Veda vaults mint shares straight to `address`, so the service
52071
+ // needs proof that `address` asked for this deposit. Sign it here,
52072
+ // inside fetchParams, so a rejected prompt fails this phase.
52073
+ const resolution = await resolveDepositIntent({
52074
+ adapter,
52075
+ chain,
52076
+ address,
51557
52077
  vaultAddress,
51558
52078
  amount: params.amount,
51559
- address,
51560
- chain: apiChain,
51561
52079
  config
51562
- }), ()=>undefined);
52080
+ });
52081
+ try {
52082
+ return await fetchDeposit({
52083
+ vaultAddress,
52084
+ amount: params.amount,
52085
+ address,
52086
+ chain: apiChain,
52087
+ intent: resolution.intent,
52088
+ config
52089
+ });
52090
+ } catch (error) {
52091
+ throw resolveDepositError(resolution, error);
52092
+ }
52093
+ }, ()=>undefined);
51563
52094
  validateExecutionDeadline$1(executionParams);
51564
52095
  const transactionReportContext = {
51565
52096
  execId,
@@ -54566,7 +55097,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
54566
55097
  };
54567
55098
 
54568
55099
  var name$1 = "@circle-fin/borrow-kit";
54569
- var version$1 = "1.1.0";
55100
+ var version$1 = "1.1.1";
54570
55101
  var pkg$1 = {
54571
55102
  name: name$1,
54572
55103
  version: version$1};
@@ -67475,7 +68006,7 @@ async function deposit$2(context, params) {
67475
68006
  }
67476
68007
 
67477
68008
  var name = "@circle-fin/unified-balance-kit";
67478
- var version = "1.8.1";
68009
+ var version = "1.8.2";
67479
68010
  var pkg = {
67480
68011
  name: name,
67481
68012
  version: version};