@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.mjs CHANGED
@@ -4741,6 +4741,15 @@ let activeRedactor = defaultMessageRedactor;
4741
4741
  recoverability: 'RETRYABLE'
4742
4742
  }
4743
4743
  ],
4744
+ // DEPOSIT_INTENT_INVALID: the Veda deposit intent is missing, expired, or
4745
+ // not signed by the depositing wallet.
4746
+ [
4747
+ 380418,
4748
+ {
4749
+ errorDef: EarnError.SIGNATURE_REJECTED,
4750
+ recoverability: 'FATAL'
4751
+ }
4752
+ ],
4744
4753
  // Bridge (380_5XX)
4745
4754
  [
4746
4755
  380500,
@@ -6349,14 +6358,14 @@ var BorrowChain;
6349
6358
  symbol: 'USDC',
6350
6359
  // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
6351
6360
  // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
6352
- // See: https://docs.arc.network/arc/references/contract-addresses
6361
+ // See: https://docs.arc.io/arc/references/contract-addresses
6353
6362
  decimals: 18
6354
6363
  },
6355
6364
  chainId: 5042002,
6356
6365
  isTestnet: true,
6357
6366
  explorerUrl: 'https://testnet.arcscan.app/tx/{hash}',
6358
6367
  rpcEndpoints: [
6359
- 'https://rpc.testnet.arc.network/'
6368
+ 'https://rpc.testnet.arc.io'
6360
6369
  ],
6361
6370
  eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
6362
6371
  usdcAddress: '0x3600000000000000000000000000000000000000',
@@ -14674,7 +14683,7 @@ function resolveOptions(options) {
14674
14683
  }
14675
14684
 
14676
14685
  var name$5 = "@circle-fin/bridge-kit";
14677
- var version$6 = "1.15.3";
14686
+ var version$6 = "1.16.0";
14678
14687
  var pkg$6 = {
14679
14688
  name: name$5,
14680
14689
  version: version$6};
@@ -16396,6 +16405,53 @@ const EURC_MAINNET_TOKEN_ID = '0x6ca9e29fa53becc29becaf4a90b9ca7a995ad4d2234880d
16396
16405
  }
16397
16406
  };
16398
16407
 
16408
+ /**
16409
+ * Map a CCTPx bytes32 token id back to its known display symbol.
16410
+ *
16411
+ * The inverse of the provider's internal symbol -> id resolution: a bridge
16412
+ * resolves a symbol such as `'cirBTC'` to its canonical bytes32 id and reports
16413
+ * that id on `result.token`, dropping the symbol. This helper recovers the
16414
+ * symbol for display. It is scoped to {@link KNOWN_TOKEN_SYMBOLS} — the same
16415
+ * known set the forward resolution covers — and is keyed by network, because a
16416
+ * symbol names a separate registration on each network, whose ids are
16417
+ * unrelated.
16418
+ *
16419
+ * The lookup is best-effort by design: an arbitrary registry token, a
16420
+ * non-canonical bridge of a known symbol, or any unrecognized id has no known
16421
+ * symbol and yields `undefined`. The id is matched case-insensitively (bytes32
16422
+ * ids have no checksum casing).
16423
+ *
16424
+ * @param id - The bytes32 token id to decode (e.g. a `result.token` value).
16425
+ * @param network - The network the id belongs to (`'testnet'` or `'mainnet'`);
16426
+ * derive it from the source chain, e.g. `chain.isTestnet ? 'testnet' :
16427
+ * 'mainnet'`.
16428
+ * @returns The matching {@link KnownTokenSymbol}, or `undefined` when `id` is
16429
+ * not one of the known canonical ids for `network`.
16430
+ * @throws A `KitError` (`INPUT_VALIDATION_FAILED`, FATAL) when `id` is not a
16431
+ * string or `network` is not a recognized network.
16432
+ *
16433
+ * @example
16434
+ * ```typescript
16435
+ * import { resolveToKnownTokenSymbol } from '@circle-fin/provider-cctpx'
16436
+ *
16437
+ * const result = await provider.bridge(params) // params.token === 'cirBTC'
16438
+ * const network = result.source.chain.isTestnet ? 'testnet' : 'mainnet'
16439
+ *
16440
+ * resolveToKnownTokenSymbol(result.token, network) // 'cirBTC'
16441
+ * resolveToKnownTokenSymbol('0xdeadbeef…', network) // undefined (not known)
16442
+ * ```
16443
+ */ function resolveToKnownTokenSymbol(id, network) {
16444
+ if (typeof id !== 'string') {
16445
+ throw createValidationFailedError$1('id', id, 'must be a string');
16446
+ }
16447
+ if (network !== 'testnet' && network !== 'mainnet') {
16448
+ throw createValidationFailedError$1('network', network, "must be 'testnet' or 'mainnet'");
16449
+ }
16450
+ const row = KNOWN_TOKEN_IDS_BY_NETWORK[network];
16451
+ const target = id.toLowerCase();
16452
+ return KNOWN_TOKEN_SYMBOLS.find((symbol)=>row[symbol].toLowerCase() === target);
16453
+ }
16454
+
16399
16455
  /**
16400
16456
  * Default clock implementation.
16401
16457
  *
@@ -23619,6 +23675,24 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
23619
23675
  }
23620
23676
  };
23621
23677
 
23678
+ /**
23679
+ * Check whether a forwarded burn to the given destination asks Circle's
23680
+ * Forwarding Service to set up the recipient's token account before minting.
23681
+ *
23682
+ * Must return `true` exactly for the destinations where
23683
+ * {@link getForwarderHookData} emits the `createAta` frame, so the forwarding
23684
+ * fee quote includes the setup rent whenever the burn requests it.
23685
+ *
23686
+ * @internal
23687
+ * @param chainType - The destination blockchain type.
23688
+ * @returns `true` for Solana destinations, `false` otherwise.
23689
+ *
23690
+ * @example
23691
+ * ```typescript
23692
+ * requiresRecipientSetup('solana') // true
23693
+ * requiresRecipientSetup('evm') // false
23694
+ * ```
23695
+ */ const requiresRecipientSetup = (chainType)=>chainType === 'solana';
23622
23696
  /**
23623
23697
  * Build the forwarding `hookData` for a forwarded (Orbit-relayed) CCTP v2 burn,
23624
23698
  * tailored to the destination chain.
@@ -23969,10 +24043,12 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
23969
24043
  * @param sourceDomain - The source domain
23970
24044
  * @param destinationDomain - The destination domain
23971
24045
  * @param isTestnet - Whether the request is for a testnet chain
24046
+ * @param includeRecipientSetup - Whether the quote includes recipient account setup rent
23972
24047
  * @returns The complete API URL with forward=true query parameter
23973
- */ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet) {
24048
+ */ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet, includeRecipientSetup) {
23974
24049
  const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
23975
- return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true`;
24050
+ const recipientSetupQuery = includeRecipientSetup ? '&includeRecipientSetup=true' : '';
24051
+ return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true${recipientSetupQuery}`;
23976
24052
  }
23977
24053
  /**
23978
24054
  * Fetches the forwarding fee for a USDC transfer operation with retry and timeout handling.
@@ -23989,6 +24065,10 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
23989
24065
  *
23990
24066
  * The high fee tier is always used to provide buffer for destination gas fluctuations.
23991
24067
  *
24068
+ * Pass `includeRecipientSetup: true` when the burn asks the Forwarding Service to create
24069
+ * the recipient's token account (Solana destinations). The quote then includes the
24070
+ * account rent, which the quote without this flag leaves out.
24071
+ *
23992
24072
  * Implements timeout and retry logic to handle network issues and temporary API failures.
23993
24073
  * Each HTTP request is given a maximum of 2 seconds before it is aborted.
23994
24074
  * We retry up to 10 times, waiting 200ms between attempts.
@@ -23997,6 +24077,8 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
23997
24077
  * @param destinationDomain - The destination domain
23998
24078
  * @param isTestnet - Whether the request is for a testnet chain
23999
24079
  * @param finalityThreshold - The finality threshold to select the fee tier (1000 for FAST, 2000 for SLOW)
24080
+ * @param includeRecipientSetup - Whether to include recipient account setup rent in the
24081
+ * quote. Defaults to `false`.
24000
24082
  * @returns The high-tier forwarding fee in USDC minor units (6 decimals)
24001
24083
  * @throws Error if the input domains are invalid, the API request fails, returns invalid data, or network errors occur
24002
24084
  *
@@ -24008,8 +24090,11 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
24008
24090
  *
24009
24091
  * // SLOW transfer - use 2000ms finality threshold
24010
24092
  * const slowForwardingFee = await fetchForwardingFee(0, 6, false, 2000)
24093
+ *
24094
+ * // Ethereum -> Solana: include the recipient token account rent
24095
+ * const solanaForwardingFee = await fetchForwardingFee(0, 5, false, 1000, true)
24011
24096
  * ```
24012
- */ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, finalityThreshold) {
24097
+ */ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, finalityThreshold, includeRecipientSetup = false) {
24013
24098
  // Validate source domain
24014
24099
  if (!Number.isInteger(sourceDomain) || sourceDomain < 0) {
24015
24100
  throw new KitError({
@@ -24037,7 +24122,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
24037
24122
  });
24038
24123
  }
24039
24124
  // Fetch the fee tiers from the API with forward=true
24040
- const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet);
24125
+ const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet, includeRecipientSetup);
24041
24126
  const response = await pollApiGet(url, isForwardingFeeResponse, DEFAULT_CONFIG$4);
24042
24127
  // Validate that we have at least one fee tier
24043
24128
  if (response.length === 0) {
@@ -26373,7 +26458,7 @@ const mockAttestationMessage = {
26373
26458
  return step;
26374
26459
  }
26375
26460
 
26376
- var version$5 = "1.14.2";
26461
+ var version$5 = "1.14.3";
26377
26462
  var pkg$5 = {
26378
26463
  version: version$5};
26379
26464
 
@@ -27292,9 +27377,34 @@ function assertCCTPV2Config(config) {
27292
27377
  tokenAddress: source.chain.usdcAddress,
27293
27378
  operationContext: sourceOperationContext
27294
27379
  });
27380
+ await this.assertAmountCoversRecipientSetupFee(params);
27295
27381
  return bridge$1(params, this);
27296
27382
  }
27297
27383
  /**
27384
+ * Reject a forwarded transfer to Solana whose amount does not exceed the fee.
27385
+ *
27386
+ * CCTP v2 reverts a burn whose `maxFee` is not less than its amount, and the Solana
27387
+ * forwarding fee includes the recipient token account rent. Checking before the
27388
+ * approval avoids sending a transaction that can never be followed by a burn.
27389
+ * Skipped when the caller provides `config.maxFee`.
27390
+ *
27391
+ * @param params - The validated CCTP v2 bridge parameters.
27392
+ * @returns A promise that resolves when the amount covers the fee.
27393
+ * @throws {KitError} INPUT_INVALID_AMOUNT (FATAL) when the fee is greater than or equal
27394
+ * to the amount.
27395
+ */ async assertAmountCoversRecipientSetupFee(params) {
27396
+ const { destination, amount, config } = params;
27397
+ if (destination.useForwarder !== true || config.maxFee !== undefined || !requiresRecipientSetup(destination.chain.type)) {
27398
+ return;
27399
+ }
27400
+ const { providerFee, forwarderFee } = await this.getMaxFee(params);
27401
+ const maxFee = providerFee + forwarderFee;
27402
+ if (maxFee >= BigInt(amount)) {
27403
+ // Amounts and fees are in USDC minor units (6 decimals)
27404
+ 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`);
27405
+ }
27406
+ }
27407
+ /**
27298
27408
  * Retry a failed or incomplete CCTP v2 bridge operation.
27299
27409
  *
27300
27410
  * Analyzes the bridge result to determine where to continue and executes
@@ -28011,6 +28121,9 @@ function assertCCTPV2Config(config) {
28011
28121
  * | FAST | true | No | maxFee = calculatedBurnFee + forwardingFee |
28012
28122
  * | FAST | true | Yes | maxFee = config.maxFee |
28013
28123
  *
28124
+ * For forwarded burns to Solana, the forwarding fee includes the rent for creating the
28125
+ * recipient's token account. A caller-provided `maxFee` must cover it too.
28126
+ *
28014
28127
  * @param params - The bridge parameters object containing:
28015
28128
  * - `source`: The source wallet context with chain definition and adapter
28016
28129
  * - `destination`: The destination wallet context with chain definition and adapter
@@ -28049,6 +28162,7 @@ function assertCCTPV2Config(config) {
28049
28162
  }
28050
28163
  // Get the min finality threshold based on the transfer speed
28051
28164
  const minFinalityThreshold = CCTPv2MinFinalityThreshold[transferSpeed];
28165
+ const includeRecipientSetup = requiresRecipientSetup(destination.chain.type);
28052
28166
  // SLOW transfer without forwarder: no fee
28053
28167
  if (transferSpeed === TransferSpeed.SLOW && !useForwarder) {
28054
28168
  return {
@@ -28058,7 +28172,7 @@ function assertCCTPV2Config(config) {
28058
28172
  }
28059
28173
  // SLOW transfer with forwarder: only forwarding fee
28060
28174
  if (transferSpeed === TransferSpeed.SLOW && useForwarder) {
28061
- const forwarderFee = await fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold);
28175
+ const forwarderFee = await fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold, includeRecipientSetup);
28062
28176
  return {
28063
28177
  providerFee: 0n,
28064
28178
  forwarderFee
@@ -28068,7 +28182,7 @@ function assertCCTPV2Config(config) {
28068
28182
  // Fetch fees in parallel when forwarder is enabled
28069
28183
  const [scaledBps, forwarderFee] = await Promise.all([
28070
28184
  fetchUsdcFastBurnFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet),
28071
- useForwarder ? fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold) : Promise.resolve(0n)
28185
+ useForwarder ? fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold, includeRecipientSetup) : Promise.resolve(0n)
28072
28186
  ]);
28073
28187
  // Calculate fee proportional to the transfer amount
28074
28188
  const amountInMinorUnits = BigInt(amount);
@@ -28618,6 +28732,21 @@ function assertCCTPV2Config(config) {
28618
28732
  'bridge_forward'
28619
28733
  ]
28620
28734
  ];
28735
+ /**
28736
+ * Provider step names whose `txHash` is the source-chain transaction that
28737
+ * moves funds into the bridge protocol.
28738
+ *
28739
+ * @remarks
28740
+ * The success event carries this hash so the backend can join it to the
28741
+ * on-chain CCTP / CCTPx event for the same transfer. `burn` is the CCTP v2
28742
+ * step (including batched and source-fee flows); `transfer` is the CCTPx
28743
+ * `CrossChainTokenService` step.
28744
+ *
28745
+ * @internal
28746
+ */ const BRIDGE_SOURCE_TX_STEP_NAMES = [
28747
+ 'burn',
28748
+ 'transfer'
28749
+ ];
28621
28750
 
28622
28751
  /**
28623
28752
  * Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
@@ -29713,6 +29842,85 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29713
29842
  }
29714
29843
  return undefined;
29715
29844
  }
29845
+ /**
29846
+ * Pick the source-chain transaction hash from a bridge result.
29847
+ *
29848
+ * Walk the steps from the end and return the hash of the most recent
29849
+ * successful source step (`burn` for CCTP v2, `transfer` for CCTPx). Walking
29850
+ * backwards selects the attempt that landed when a retry appended a new
29851
+ * source step after a failed one.
29852
+ *
29853
+ * @param steps - All steps from the bridge result.
29854
+ * @returns The source transaction hash, or `undefined` if no source step recorded one.
29855
+ *
29856
+ * @internal
29857
+ */ function pickSourceTxHash(steps) {
29858
+ for(let i = steps.length - 1; i >= 0; i--){
29859
+ const step = steps[i];
29860
+ if (step?.txHash != null && step.state === 'success' && BRIDGE_SOURCE_TX_STEP_NAMES.includes(step.name)) {
29861
+ return step.txHash;
29862
+ }
29863
+ }
29864
+ return undefined;
29865
+ }
29866
+ /**
29867
+ * Resolve the token value reported in telemetry.
29868
+ *
29869
+ * CCTPx reports `result.token` as a bytes32 token id, while the backend's
29870
+ * canonical tables key tokens by symbol. Map the canonical id of a known CCTPx
29871
+ * token back to its symbol and pass any other value (USDC, or a registry token
29872
+ * without a known symbol) through unchanged. Registry symbols are not used
29873
+ * because they are not unique across CCTPx tokens. Never throws.
29874
+ *
29875
+ * @param token - The token from a bridge result.
29876
+ * @param sourceChain - The result's source chain; selects testnet or mainnet ids.
29877
+ * @returns The known symbol, or `token` unchanged.
29878
+ *
29879
+ * @internal
29880
+ */ function toTelemetryToken(token, sourceChain) {
29881
+ try {
29882
+ const network = sourceChain.isTestnet ? 'testnet' : 'mainnet';
29883
+ return resolveToKnownTokenSymbol(token, network) ?? token;
29884
+ } catch {
29885
+ return token;
29886
+ }
29887
+ }
29888
+ /**
29889
+ * Emit the `bridge_bridge` success event for a completed transfer.
29890
+ *
29891
+ * No-ops unless `result.state` is `'success'`. Failed results are already
29892
+ * reported by error telemetry. Never throws: a malformed result (for example
29893
+ * from a custom provider) is reported as a telemetry drop warning and the
29894
+ * caller's result is returned unchanged.
29895
+ *
29896
+ * @param result - The human-readable bridge result returned to the caller.
29897
+ * @param config - Analytics telemetry identity, gated by `disableAnalytics`.
29898
+ *
29899
+ * @internal
29900
+ */ function emitBridgeSuccessTelemetry(result, config) {
29901
+ if (config.disabled || result.state !== 'success') {
29902
+ return;
29903
+ }
29904
+ try {
29905
+ const txHash = pickSourceTxHash(result.steps);
29906
+ // `JSON.stringify` turns a non-finite number into `null`, which the logs
29907
+ // endpoint's strict schema rejects along with the whole event.
29908
+ const amountIn = Number.parseFloat(result.amount);
29909
+ emitSuccessTelemetry(BRIDGE_EVENT_TYPES.BRIDGE, config, {
29910
+ sourceChain: result.source.chain.chain,
29911
+ destinationChain: result.destination.chain.chain,
29912
+ tokenIn: toTelemetryToken(result.token, result.source.chain),
29913
+ ...Number.isFinite(amountIn) && {
29914
+ amountIn
29915
+ },
29916
+ ...txHash != null && {
29917
+ txHash
29918
+ }
29919
+ });
29920
+ } catch (telemetryError) {
29921
+ warnTelemetryDrop(BRIDGE_EVENT_TYPES.BRIDGE, telemetryError);
29922
+ }
29923
+ }
29716
29924
  /**
29717
29925
  * BridgeKit caller component for retry and estimate operations.
29718
29926
  */ const BRIDGE_KIT_CALLER = {
@@ -29843,6 +30051,10 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29843
30051
  /** Whether error telemetry is disabled. */ disableErrorReporting;
29844
30052
  /** Per-kit telemetry identity for shared helpers. */ telemetryConfig;
29845
30053
  /**
30054
+ * Per-kit telemetry identity for success events. Gated by `disableAnalytics`
30055
+ * independently of `disableErrorReporting`.
30056
+ */ analyticsTelemetryConfig;
30057
+ /**
29846
30058
  * Create a new BridgeKit instance.
29847
30059
  *
29848
30060
  * @param config - The configuration containing the CCTPv2 provider
@@ -29869,6 +30081,11 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29869
30081
  sdkVersion: pkg$6.version,
29870
30082
  disabled: this.disableErrorReporting
29871
30083
  };
30084
+ this.analyticsTelemetryConfig = {
30085
+ sdkName: SDK_NAME$4,
30086
+ sdkVersion: pkg$6.version,
30087
+ disabled: config.disableAnalytics === true
30088
+ };
29872
30089
  for (const provider of this.providers){
29873
30090
  provider.registerDispatcher(this.actionDispatcher);
29874
30091
  }
@@ -29882,7 +30099,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29882
30099
  this.actionDispatcher.off(actionOrWildCard, handler);
29883
30100
  }
29884
30101
  async bridge(params) {
29885
- return withErrorTelemetry(async ()=>{
30102
+ const bridgeResult = await withErrorTelemetry(async ()=>{
29886
30103
  // First validate the parameters
29887
30104
  assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
29888
30105
  // Then resolve chain definitions (includes adapter chain support validation)
@@ -29919,7 +30136,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29919
30136
  emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.BRIDGE, this.telemetryConfig, {
29920
30137
  sourceChain: resolveChainName(params.from.chain),
29921
30138
  destinationChain: resolveChainName(params.to.chain),
29922
- tokenIn: params.token,
30139
+ tokenIn: toTelemetryToken(result.token, result.source.chain),
29923
30140
  ...txHash != null && {
29924
30141
  txHash
29925
30142
  }
@@ -29931,6 +30148,8 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
29931
30148
  destinationChain: resolveChainName(params.to.chain),
29932
30149
  tokenIn: params.token
29933
30150
  });
30151
+ emitBridgeSuccessTelemetry(bridgeResult, this.analyticsTelemetryConfig);
30152
+ return bridgeResult;
29934
30153
  }
29935
30154
  /**
29936
30155
  * Retry a failed or incomplete cross-chain USDC bridge operation.
@@ -30001,7 +30220,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30001
30220
  * )
30002
30221
  * ```
30003
30222
  */ async retry(result, context, invocationMeta) {
30004
- return withErrorTelemetry(async ()=>{
30223
+ const retriedResult = await withErrorTelemetry(async ()=>{
30005
30224
  // Cast the providers list to the wide view here. The tuple
30006
30225
  // type on `this.providers` retains concrete provider types
30007
30226
  // (CCTPv2 narrows `TToken` to `'USDC'`); calling `.retry`
@@ -30029,7 +30248,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30029
30248
  emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
30030
30249
  sourceChain: result.source.chain.chain,
30031
30250
  destinationChain: result.destination.chain.chain,
30032
- tokenIn: result.token,
30251
+ tokenIn: toTelemetryToken(result.token, result.source.chain),
30033
30252
  ...txHash != null && {
30034
30253
  txHash
30035
30254
  }
@@ -30043,8 +30262,16 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30043
30262
  }, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
30044
30263
  sourceChain: result.source.chain.chain,
30045
30264
  destinationChain: result.destination.chain.chain,
30046
- tokenIn: result.token
30265
+ tokenIn: toTelemetryToken(result.token, result.source.chain)
30047
30266
  });
30267
+ // A retry that completes the transfer is the transfer's only success
30268
+ // event, so it reports the same `bridge_bridge` type as `bridge()`. An
30269
+ // input that already succeeded was reported by the call that completed it
30270
+ // (CCTP v2 returns such a result unchanged), so it is not reported again.
30271
+ if (result.state !== 'success') {
30272
+ emitBridgeSuccessTelemetry(retriedResult, this.analyticsTelemetryConfig);
30273
+ }
30274
+ return retriedResult;
30048
30275
  }
30049
30276
  async estimate(params) {
30050
30277
  return withErrorTelemetry(async ()=>{
@@ -30593,7 +30820,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
30593
30820
  registerKit(`${pkg$6.name}/${pkg$6.version}`);
30594
30821
 
30595
30822
  var name$4 = "@circle-fin/swap-kit";
30596
- var version$4 = "1.8.0";
30823
+ var version$4 = "1.8.1";
30597
30824
  var pkg$4 = {
30598
30825
  name: name$4,
30599
30826
  version: version$4};
@@ -46827,6 +47054,9 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
46827
47054
  ...context.disableErrorReporting != null && {
46828
47055
  disableErrorReporting: context.disableErrorReporting
46829
47056
  },
47057
+ ...context.disableAnalytics != null && {
47058
+ disableAnalytics: context.disableAnalytics
47059
+ },
46830
47060
  ...context.headers != null && {
46831
47061
  headers: context.headers
46832
47062
  }
@@ -46947,7 +47177,7 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
46947
47177
  };
46948
47178
 
46949
47179
  var name$3 = "@circle-fin/earn-kit";
46950
- var version$3 = "1.9.0";
47180
+ var version$3 = "1.10.0";
46951
47181
  var pkg$3 = {
46952
47182
  name: name$3,
46953
47183
  version: version$3};
@@ -47054,6 +47284,61 @@ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
47054
47284
  *
47055
47285
  * @internal
47056
47286
  */ const EARN_BRIDGE_MAX_VALIDITY_SECONDS = 86_400n;
47287
+ /**
47288
+ * Static fields of the EIP-712 domain for the `DepositIntent` typed-data
47289
+ * intent. `chainId` is excluded here — it must come from the deposit's
47290
+ * actual chain (see {@link buildDepositIntentTypedData}), since a mismatched
47291
+ * `chainId` in the domain separator would make every intent signature
47292
+ * invalid.
47293
+ *
47294
+ * `verifyingContract` is intentionally omitted — the intent is verified
47295
+ * off-chain by the Earn Service, not by a deployed contract.
47296
+ *
47297
+ * @internal
47298
+ */ const DEPOSIT_INTENT_DOMAIN = {
47299
+ name: 'Circle EarnKit',
47300
+ version: '1'
47301
+ };
47302
+ /**
47303
+ * EIP-712 type definition for the `DepositIntent` struct.
47304
+ *
47305
+ * @internal
47306
+ */ const DEPOSIT_INTENT_TYPES = {
47307
+ DepositIntent: [
47308
+ {
47309
+ name: 'wallet',
47310
+ type: 'address'
47311
+ },
47312
+ {
47313
+ name: 'vault',
47314
+ type: 'address'
47315
+ },
47316
+ {
47317
+ name: 'amount',
47318
+ type: 'uint256'
47319
+ },
47320
+ {
47321
+ name: 'deadline',
47322
+ type: 'uint256'
47323
+ }
47324
+ ]
47325
+ };
47326
+ /**
47327
+ * Lifetime of a `DepositIntent` signature, in seconds.
47328
+ *
47329
+ * The Earn Service rejects a deadline more than 300 seconds ahead of its own
47330
+ * clock. 240 seconds leaves room for clock skew between the client and the
47331
+ * service while still covering the deposit request that follows the
47332
+ * signature.
47333
+ *
47334
+ * @internal
47335
+ */ const DEPOSIT_INTENT_TTL_SECONDS = 240;
47336
+ /**
47337
+ * Protocol value the Earn Service reports for Veda vaults. A deposit into a
47338
+ * Veda vault needs a signed `DepositIntent`.
47339
+ *
47340
+ * @internal
47341
+ */ const VEDA_PROTOCOL = 'veda';
47057
47342
  /**
47058
47343
  * Display symbol for vault share tokens.
47059
47344
  *
@@ -49929,7 +50214,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
49929
50214
  }
49930
50215
 
49931
50216
  var name$2 = "@circle-fin/provider-earn-service";
49932
- var version$2 = "1.7.2";
50217
+ var version$2 = "1.8.0";
49933
50218
  var pkg$2 = {
49934
50219
  name: name$2,
49935
50220
  version: version$2};
@@ -50281,8 +50566,9 @@ function toPositionInfo(data) {
50281
50566
  * Build signed deposit instructions via the Earn Service API.
50282
50567
  *
50283
50568
  * Call `POST /v1/earnKit/deposit` with the vault address, amount,
50284
- * wallet address, and chain. Return EIP-712 signed execution
50285
- * parameters for on-chain submission.
50569
+ * wallet address, and chain, plus the signed `DepositIntent` when one is
50570
+ * given. Return EIP-712 signed execution parameters for on-chain
50571
+ * submission.
50286
50572
  *
50287
50573
  * @param params - Deposit parameters
50288
50574
  * @returns Signed execution parameters for the deposit
@@ -50296,7 +50582,8 @@ function toPositionInfo(data) {
50296
50582
  vaultAddress: params.vaultAddress,
50297
50583
  amount: params.amount,
50298
50584
  address: params.address,
50299
- chain: params.chain
50585
+ chain: params.chain,
50586
+ ...params.intent
50300
50587
  };
50301
50588
  try {
50302
50589
  const response = await pollApiPost(url.toString(), requestBody, isDepositResponse, pollingConfig);
@@ -50308,6 +50595,232 @@ function toPositionInfo(data) {
50308
50595
  }
50309
50596
  }
50310
50597
 
50598
+ /**
50599
+ * Build the version-1 EarnKit `DepositIntent` EIP-712 typed data.
50600
+ *
50601
+ * The signed struct binds the depositing `wallet`, the target `vault`, the
50602
+ * deposit `amount` in the asset's smallest unit, and a signature `deadline`.
50603
+ * The Earn Service rebuilds the same struct from the deposit request and
50604
+ * rejects the deposit unless `wallet` signed it. The domain intentionally
50605
+ * omits `verifyingContract` because the service verifies the signature
50606
+ * off-chain.
50607
+ *
50608
+ * `domain.chainId` comes from the deposit's chain rather than a constant: the
50609
+ * domain separator must match what the service rebuilds, and a wrong
50610
+ * `chainId` would make every intent signature invalid.
50611
+ *
50612
+ * This function returns the fully-typed {@link DepositIntentTypedData} with
50613
+ * no assertion. {@link resolveDepositIntent} casts it to the adapter's
50614
+ * generic `EIP712Domain` shape only where it calls `adapter.signTypedData`,
50615
+ * since that shared interface always requires `verifyingContract`. The
50616
+ * adapter's runtime `eip712DomainSchema` treats `verifyingContract` as
50617
+ * optional, so the cast is safe at runtime. BorrowKit's
50618
+ * `buildWebhookRegistrationTypedData` works around the same gap.
50619
+ *
50620
+ * @param params - Wallet, vault, base-unit amount, deadline, and chain id.
50621
+ * @returns EIP-712 typed data ready to be cast for `adapter.signTypedData`.
50622
+ *
50623
+ * @example
50624
+ * ```typescript
50625
+ * const typedData = buildDepositIntentTypedData({
50626
+ * wallet: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
50627
+ * vault: '0x00000000000000000000000000000000000000a1',
50628
+ * amount: 100_000_000n,
50629
+ * deadline: 1_700_000_000n,
50630
+ * chainId: 5_042_002,
50631
+ * })
50632
+ * ```
50633
+ *
50634
+ * @internal
50635
+ */ function buildDepositIntentTypedData(params) {
50636
+ return {
50637
+ domain: {
50638
+ ...DEPOSIT_INTENT_DOMAIN,
50639
+ chainId: params.chainId
50640
+ },
50641
+ types: DEPOSIT_INTENT_TYPES,
50642
+ primaryType: 'DepositIntent',
50643
+ message: {
50644
+ wallet: params.wallet,
50645
+ vault: params.vault,
50646
+ amount: params.amount,
50647
+ deadline: params.deadline
50648
+ }
50649
+ };
50650
+ }
50651
+ /**
50652
+ * Earn Service error code for a missing, expired, or wrongly signed
50653
+ * `DepositIntent` (`DEPOSIT_INTENT_INVALID`).
50654
+ */ const DEPOSIT_INTENT_INVALID_CODE = 380418;
50655
+ /**
50656
+ * Sign a `DepositIntent` when the target vault needs one.
50657
+ *
50658
+ * A Veda vault mints shares straight to the depositing wallet, so the Earn
50659
+ * Service only builds a Veda deposit for a wallet that signed a
50660
+ * `DepositIntent` for the same vault and amount. The vault lookup exists only
50661
+ * to find out whether the vault is a Veda vault (and its asset decimals, to
50662
+ * sign the amount). Other vaults ignore the intent, so this returns no intent
50663
+ * for them and the wallet sees no extra signature prompt. A vault the service
50664
+ * does not return is also treated as not needing an intent; the deposit call
50665
+ * then reports why.
50666
+ *
50667
+ * A failed vault lookup does not stop the deposit: this returns the lookup
50668
+ * error with no intent, so a non-Veda deposit works as before. A Veda deposit
50669
+ * then gets `DEPOSIT_INTENT_INVALID` from the service, and
50670
+ * {@link resolveDepositError} reports the lookup error instead. The lookup
50671
+ * fails either by throwing or by returning the vault in its `errors` list (for
50672
+ * example `VAULT_REFRESH_BUSY`); both are kept the same way.
50673
+ *
50674
+ * @param params - Adapter, resolved chain, wallet, vault, human-readable
50675
+ * amount, and service config.
50676
+ * @returns The signed intent when the vault needs one, and the lookup error
50677
+ * when the vault lookup failed.
50678
+ * @throws {@link KitError} `InputError.VALIDATION_FAILED` when the vault needs
50679
+ * an intent but the chain is not EVM, the adapter cannot sign typed data, or
50680
+ * the amount has more decimal places than the vault asset.
50681
+ *
50682
+ * @example
50683
+ * ```typescript
50684
+ * const { intent } = await resolveDepositIntent({
50685
+ * adapter,
50686
+ * chain: ArcTestnet,
50687
+ * address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
50688
+ * vaultAddress: '0x00000000000000000000000000000000000000a1',
50689
+ * amount: '100',
50690
+ * config,
50691
+ * })
50692
+ * // intent -> { intentSignature: '0x…', intentDeadline: 1700000240 }
50693
+ * ```
50694
+ *
50695
+ * @internal
50696
+ */ async function resolveDepositIntent(params) {
50697
+ let lookup;
50698
+ try {
50699
+ lookup = await fetchVaults({
50700
+ // `resolveAdapterContext` already rejected chains the Earn Service does
50701
+ // not support, so the chain is an Earn chain here.
50702
+ vaults: [
50703
+ {
50704
+ chain: params.chain,
50705
+ vaultAddress: params.vaultAddress
50706
+ }
50707
+ ],
50708
+ config: params.config
50709
+ });
50710
+ } catch (lookupError) {
50711
+ return {
50712
+ lookupError
50713
+ };
50714
+ }
50715
+ const vaultError = lookup.errors.find((candidate)=>isSameAddress(candidate.vaultAddress, params.vaultAddress));
50716
+ if (vaultError !== undefined) {
50717
+ // Same handling as a thrown lookup error, mapped with the Earn Service
50718
+ // error table so it keeps its recoverability (VAULT_REFRESH_BUSY retries).
50719
+ return {
50720
+ lookupError: parseEarnApiError({
50721
+ responseBody: {
50722
+ code: vaultError.code,
50723
+ message: vaultError.message
50724
+ }
50725
+ }, {
50726
+ operation: 'getVaults'
50727
+ })
50728
+ };
50729
+ }
50730
+ const vault = lookup.vaults.find((candidate)=>isSameAddress(candidate.address, params.vaultAddress));
50731
+ if (vault?.protocol.toLowerCase() !== VEDA_PROTOCOL) {
50732
+ return {};
50733
+ }
50734
+ if (params.chain.type !== 'evm') {
50735
+ throw createValidationFailedError$1('from.chain', params.chain.chain, 'Veda vault deposits need an EVM chain to sign the deposit intent');
50736
+ }
50737
+ if (!canSignTypedData(params.adapter)) {
50738
+ throw createValidationFailedError$1('from.adapter', params.adapter, 'adapter must support signTypedData for Veda vault deposits');
50739
+ }
50740
+ const wallet = assertHexAddress('from.address', params.address, 'Depositor address must be a 0x-prefixed 20-byte hex address.');
50741
+ const assetDecimals = vault.liquidityProfile.totalDeposits.decimals;
50742
+ // Amount.parse truncates extra digits, but the service rejects them. Fail
50743
+ // before the wallet prompt instead of asking to sign a deposit that cannot
50744
+ // go through.
50745
+ assertDecimalPlaces('amount', params.amount, assetDecimals, `amount must have at most ${String(assetDecimals)} decimal places for the vault asset`);
50746
+ const deadline = Math.floor(Date.now() / 1000) + DEPOSIT_INTENT_TTL_SECONDS;
50747
+ const typedData = buildDepositIntentTypedData({
50748
+ wallet,
50749
+ vault: params.vaultAddress,
50750
+ amount: Amount.parse(params.amount, {
50751
+ decimals: assetDecimals
50752
+ }).raw,
50753
+ deadline: BigInt(deadline),
50754
+ chainId: params.chain.chainId
50755
+ });
50756
+ // `typedData.domain` intentionally omits `verifyingContract` (the Earn
50757
+ // Service verifies off-chain), but the shared `signTypedData` signature
50758
+ // requires the standard `EIP712Domain` shape. The adapter's runtime
50759
+ // `eip712DomainSchema` treats `verifyingContract` as optional, so this cast
50760
+ // is safe at runtime.
50761
+ const intentSignature = await params.adapter.signTypedData(typedData, {
50762
+ chain: params.chain,
50763
+ address: wallet
50764
+ });
50765
+ return {
50766
+ intent: {
50767
+ intentSignature,
50768
+ intentDeadline: deadline
50769
+ }
50770
+ };
50771
+ }
50772
+ /**
50773
+ * Pick the error to report for a failed deposit request.
50774
+ *
50775
+ * When the vault lookup failed, the SDK sent the deposit without an intent.
50776
+ * If the service then answers `DEPOSIT_INTENT_INVALID`, the vault is a Veda
50777
+ * vault and the real cause is the failed lookup, not a bad signature. Report
50778
+ * the lookup error, which keeps its own recoverability (often retryable),
50779
+ * instead of a fatal signature rejection.
50780
+ *
50781
+ * When the SDK did send an intent and the service still answers
50782
+ * `DEPOSIT_INTENT_INVALID`, the error stays a fatal signature rejection, but
50783
+ * its message adds that the intent may have expired: the deadline starts
50784
+ * before the wallet prompt, so a slow signer can submit an expired intent.
50785
+ * Calling `deposit()` again signs a new one. Every other error is reported as
50786
+ * is.
50787
+ *
50788
+ * @param resolution - The result of {@link resolveDepositIntent}.
50789
+ * @param error - The error from the deposit request.
50790
+ * @returns The error to throw.
50791
+ *
50792
+ * @example
50793
+ * ```typescript
50794
+ * try {
50795
+ * return await fetchDeposit({ ...params, intent: resolution.intent })
50796
+ * } catch (error) {
50797
+ * throw resolveDepositError(resolution, error)
50798
+ * }
50799
+ * ```
50800
+ *
50801
+ * @internal
50802
+ */ function resolveDepositError(resolution, error) {
50803
+ if (resolution.lookupError !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
50804
+ return resolution.lookupError;
50805
+ }
50806
+ if (resolution.intent !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
50807
+ return new KitError({
50808
+ code: error.code,
50809
+ name: error.name,
50810
+ type: error.type,
50811
+ recoverability: error.recoverability,
50812
+ 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.`,
50813
+ ...error.cause !== undefined && {
50814
+ cause: error.cause
50815
+ }
50816
+ });
50817
+ }
50818
+ return error;
50819
+ }
50820
+ function isEarnServiceCode(trace, code) {
50821
+ return typeof trace === 'object' && trace !== null && 'earnServiceCode' in trace && trace.earnServiceCode === code;
50822
+ }
50823
+
50311
50824
  /**
50312
50825
  * Build cross-chain deposit instructions via the bridge API.
50313
50826
  *
@@ -51546,13 +52059,31 @@ function finishElapsedWait(lastStatus, lastError) {
51546
52059
  const adapterContractAddress = requireAdapterContract(chain);
51547
52060
  const { adapter } = params.from;
51548
52061
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
51549
- const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
52062
+ const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>{
52063
+ // Veda vaults mint shares straight to `address`, so the service
52064
+ // needs proof that `address` asked for this deposit. Sign it here,
52065
+ // inside fetchParams, so a rejected prompt fails this phase.
52066
+ const resolution = await resolveDepositIntent({
52067
+ adapter,
52068
+ chain,
52069
+ address,
51550
52070
  vaultAddress,
51551
52071
  amount: params.amount,
51552
- address,
51553
- chain: apiChain,
51554
52072
  config
51555
- }), ()=>undefined);
52073
+ });
52074
+ try {
52075
+ return await fetchDeposit({
52076
+ vaultAddress,
52077
+ amount: params.amount,
52078
+ address,
52079
+ chain: apiChain,
52080
+ intent: resolution.intent,
52081
+ config
52082
+ });
52083
+ } catch (error) {
52084
+ throw resolveDepositError(resolution, error);
52085
+ }
52086
+ }, ()=>undefined);
51556
52087
  validateExecutionDeadline$1(executionParams);
51557
52088
  const transactionReportContext = {
51558
52089
  execId,
@@ -54559,7 +55090,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
54559
55090
  };
54560
55091
 
54561
55092
  var name$1 = "@circle-fin/borrow-kit";
54562
- var version$1 = "1.1.0";
55093
+ var version$1 = "1.1.1";
54563
55094
  var pkg$1 = {
54564
55095
  name: name$1,
54565
55096
  version: version$1};
@@ -67468,7 +67999,7 @@ async function deposit$2(context, params) {
67468
67999
  }
67469
68000
 
67470
68001
  var name = "@circle-fin/unified-balance-kit";
67471
- var version = "1.8.1";
68002
+ var version = "1.8.2";
67472
68003
  var pkg = {
67473
68004
  name: name,
67474
68005
  version: version};