@circle-fin/app-kit 1.4.2 → 1.5.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.
package/index.mjs CHANGED
@@ -3398,6 +3398,8 @@ var Blockchain;
3398
3398
  Blockchain["Hedera_Testnet"] = "Hedera_Testnet";
3399
3399
  Blockchain["HyperEVM"] = "HyperEVM";
3400
3400
  Blockchain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
3401
+ Blockchain["Injective"] = "Injective";
3402
+ Blockchain["Injective_Testnet"] = "Injective_Testnet";
3401
3403
  Blockchain["Ink"] = "Ink";
3402
3404
  Blockchain["Ink_Testnet"] = "Ink_Testnet";
3403
3405
  Blockchain["Linea"] = "Linea";
@@ -3617,6 +3619,7 @@ var BridgeChain;
3617
3619
  BridgeChain["Edge"] = "Edge";
3618
3620
  BridgeChain["Ethereum"] = "Ethereum";
3619
3621
  BridgeChain["HyperEVM"] = "HyperEVM";
3622
+ BridgeChain["Injective"] = "Injective";
3620
3623
  BridgeChain["Ink"] = "Ink";
3621
3624
  BridgeChain["Linea"] = "Linea";
3622
3625
  BridgeChain["Monad"] = "Monad";
@@ -3640,6 +3643,7 @@ var BridgeChain;
3640
3643
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
3641
3644
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
3642
3645
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
3646
+ BridgeChain["Injective_Testnet"] = "Injective_Testnet";
3643
3647
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
3644
3648
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
3645
3649
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
@@ -5084,6 +5088,98 @@ const HyperEVMTestnet = defineChain({
5084
5088
  },
5085
5089
  });
5086
5090
 
5091
+ /**
5092
+ * Injective Mainnet chain definition
5093
+ * @remarks
5094
+ * This represents the official production network for the Injective blockchain.
5095
+ * Injective is a high-performance, interoperable Layer-1 blockchain built for
5096
+ * finance, with an EVM execution layer on top of a Cosmos SDK base and
5097
+ * sub-second block finality.
5098
+ */
5099
+ const Injective = defineChain({
5100
+ type: 'evm',
5101
+ chain: Blockchain.Injective,
5102
+ name: 'Injective',
5103
+ title: 'Injective Mainnet',
5104
+ nativeCurrency: {
5105
+ name: 'Injective',
5106
+ symbol: 'INJ',
5107
+ decimals: 18,
5108
+ },
5109
+ chainId: 1776,
5110
+ isTestnet: false,
5111
+ explorerUrl: 'https://injscan.com/transaction/{hash}',
5112
+ rpcEndpoints: ['https://sentry.evm-rpc.injective.network'],
5113
+ eurcAddress: null,
5114
+ usdcAddress: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
5115
+ usdtAddress: null,
5116
+ cctp: {
5117
+ domain: 29,
5118
+ contracts: {
5119
+ v2: {
5120
+ type: 'split',
5121
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5122
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5123
+ confirmations: 1,
5124
+ fastConfirmations: 1,
5125
+ },
5126
+ },
5127
+ forwarderSupported: {
5128
+ source: false,
5129
+ destination: false,
5130
+ },
5131
+ },
5132
+ kitContracts: {
5133
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5134
+ },
5135
+ });
5136
+
5137
+ /**
5138
+ * Injective Testnet chain definition
5139
+ * @remarks
5140
+ * This represents the official test network for the Injective blockchain.
5141
+ * Injective is a high-performance, interoperable Layer-1 blockchain built for
5142
+ * finance, with an EVM execution layer on top of a Cosmos SDK base and
5143
+ * sub-second block finality.
5144
+ */
5145
+ const InjectiveTestnet = defineChain({
5146
+ type: 'evm',
5147
+ chain: Blockchain.Injective_Testnet,
5148
+ name: 'Injective Testnet',
5149
+ title: 'Injective Testnet',
5150
+ nativeCurrency: {
5151
+ name: 'Injective',
5152
+ symbol: 'INJ',
5153
+ decimals: 18,
5154
+ },
5155
+ chainId: 1439,
5156
+ isTestnet: true,
5157
+ explorerUrl: 'https://testnet.explorer.injective.network/transaction/{hash}',
5158
+ rpcEndpoints: ['https://k8s.testnet.json-rpc.injective.network'],
5159
+ eurcAddress: null,
5160
+ usdcAddress: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
5161
+ usdtAddress: null,
5162
+ cctp: {
5163
+ domain: 29,
5164
+ contracts: {
5165
+ v2: {
5166
+ type: 'split',
5167
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5168
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5169
+ confirmations: 1,
5170
+ fastConfirmations: 1,
5171
+ },
5172
+ },
5173
+ forwarderSupported: {
5174
+ source: false,
5175
+ destination: false,
5176
+ },
5177
+ },
5178
+ kitContracts: {
5179
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
5180
+ },
5181
+ });
5182
+
5087
5183
  /**
5088
5184
  * Ink Mainnet chain definition
5089
5185
  * @remarks
@@ -6926,6 +7022,8 @@ var Chains = /*#__PURE__*/Object.freeze({
6926
7022
  HederaTestnet: HederaTestnet,
6927
7023
  HyperEVM: HyperEVM,
6928
7024
  HyperEVMTestnet: HyperEVMTestnet,
7025
+ Injective: Injective,
7026
+ InjectiveTestnet: InjectiveTestnet,
6929
7027
  Ink: Ink,
6930
7028
  InkTestnet: InkTestnet,
6931
7029
  Linea: Linea,
@@ -7547,6 +7645,44 @@ function resolveChainIdentifier(chainIdentifier) {
7547
7645
  throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
7548
7646
  }
7549
7647
 
7648
+ /**
7649
+ * Resolve a chain identifier to a plain chain-name string.
7650
+ *
7651
+ * Accept a string literal (`'Ethereum'`), a `ChainDefinition`-like
7652
+ * object (`{ chain: 'Ethereum' }`), or `null`/`undefined` and return
7653
+ * the chain name as a string. Return `undefined` when the value
7654
+ * cannot be resolved.
7655
+ *
7656
+ * @remarks
7657
+ * Unlike `resolveChainIdentifier` (which returns a full `ChainDefinition`
7658
+ * and throws on invalid input), this helper is intentionally lenient and
7659
+ * never throws — it is safe to call in error-handling and telemetry paths.
7660
+ *
7661
+ * @param value - A string, chain-definition object, or nullish value.
7662
+ * @returns The chain name string, or `undefined`.
7663
+ *
7664
+ * @example
7665
+ * ```typescript
7666
+ * import { resolveChainName } from '@core/chains'
7667
+ *
7668
+ * resolveChainName('Ethereum') // 'Ethereum'
7669
+ * resolveChainName({ chain: 'Ethereum' }) // 'Ethereum'
7670
+ * resolveChainName(undefined) // undefined
7671
+ * ```
7672
+ */
7673
+ function resolveChainName(value) {
7674
+ if (value == null)
7675
+ return undefined;
7676
+ if (typeof value === 'string')
7677
+ return value;
7678
+ if (typeof value === 'object' &&
7679
+ 'chain' in value &&
7680
+ typeof value.chain === 'string') {
7681
+ return value.chain;
7682
+ }
7683
+ return undefined;
7684
+ }
7685
+
7550
7686
  /**
7551
7687
  * @packageDocumentation
7552
7688
  * @module SwapTokenUtils
@@ -9203,6 +9339,7 @@ const USDC = {
9203
9339
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9204
9340
  [Blockchain.Hedera]: '0.0.456858',
9205
9341
  [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
9342
+ [Blockchain.Injective]: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
9206
9343
  [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
9207
9344
  [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
9208
9345
  [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
@@ -9235,6 +9372,7 @@ const USDC = {
9235
9372
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9236
9373
  [Blockchain.Hedera_Testnet]: '0.0.429274',
9237
9374
  [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
9375
+ [Blockchain.Injective_Testnet]: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
9238
9376
  [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
9239
9377
  [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
9240
9378
  [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
@@ -10336,8 +10474,325 @@ async function retryAsync(fn, options) {
10336
10474
  throw new Error('retryAsync: unreachable');
10337
10475
  }
10338
10476
 
10477
+ /**
10478
+ * Default telemetry endpoint.
10479
+ *
10480
+ * Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
10481
+ * (e.g. for staging or local development).
10482
+ *
10483
+ * @internal
10484
+ */
10485
+ const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
10486
+ /**
10487
+ * Resolve the telemetry endpoint URL.
10488
+ *
10489
+ * @internal
10490
+ */
10491
+ function getLogsUrl() {
10492
+ if (isNodeEnvironment() &&
10493
+ typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' &&
10494
+ process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
10495
+ return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
10496
+ }
10497
+ return DEFAULT_LOGS_URL;
10498
+ }
10499
+ /**
10500
+ * Send a telemetry event to the proxy service.
10501
+ *
10502
+ * @remarks
10503
+ * Fire-and-forget: the returned promise is intentionally not awaited
10504
+ * by the caller. A fetch failure (network error, non-2xx, timeout)
10505
+ * is silently swallowed so telemetry never blocks or fails user
10506
+ * operations.
10507
+ *
10508
+ * @param payload - The structured log payload matching the server schema.
10509
+ *
10510
+ * @example
10511
+ * ```typescript
10512
+ * import { emitAnalyticsLog } from '@core/utils'
10513
+ *
10514
+ * // Fire-and-forget — do not await
10515
+ * void emitAnalyticsLog(payload)
10516
+ * ```
10517
+ */
10518
+ async function emitAnalyticsLog(payload) {
10519
+ try {
10520
+ const isNode = isNodeEnvironment();
10521
+ const userAgent = getUserAgent();
10522
+ await fetch(getLogsUrl(), {
10523
+ method: 'POST',
10524
+ headers: {
10525
+ 'Content-Type': 'application/json',
10526
+ // Browser restricts setting User-Agent; use X-User-Agent instead.
10527
+ ...(isNode
10528
+ ? { 'User-Agent': userAgent }
10529
+ : { 'X-User-Agent': userAgent }),
10530
+ },
10531
+ body: JSON.stringify(payload),
10532
+ signal: AbortSignal.timeout(5_000),
10533
+ });
10534
+ }
10535
+ catch {
10536
+ // Silently swallow — telemetry must never break user operations.
10537
+ }
10538
+ }
10539
+
10540
+ /**
10541
+ * Build the `clientContext` object for telemetry payloads.
10542
+ *
10543
+ * @remarks
10544
+ * Use the exported `getRuntime()` and `isNodeEnvironment()` from
10545
+ * `@core/utils` to detect the runtime environment. The returned
10546
+ * string is parsed into the structured `ClientContext` fields
10547
+ * expected by the server schema.
10548
+ *
10549
+ * @returns A {@link ClientContext} with platform, OS, and runtime name
10550
+ * populated from the current environment.
10551
+ *
10552
+ * @example
10553
+ * ```typescript
10554
+ * import { buildClientContext } from '@core/utils'
10555
+ *
10556
+ * const ctx = buildClientContext()
10557
+ * // Node: { platform: 'node', os: 'darwin', runtimeName: null }
10558
+ * // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
10559
+ * ```
10560
+ */
10561
+ function buildClientContext() {
10562
+ const runtime = getRuntime();
10563
+ if (runtime.startsWith('browser/')) {
10564
+ return {
10565
+ platform: 'browser',
10566
+ os: null,
10567
+ runtimeName: runtime.slice('browser/'.length).toLowerCase(),
10568
+ };
10569
+ }
10570
+ if (runtime.startsWith('node/')) {
10571
+ return {
10572
+ platform: 'node',
10573
+ os: isNodeEnvironment() ? process.platform : null,
10574
+ runtimeName: null,
10575
+ };
10576
+ }
10577
+ return {
10578
+ platform: 'node',
10579
+ os: null,
10580
+ runtimeName: null,
10581
+ };
10582
+ }
10583
+
10584
+ /**
10585
+ * Extract structured error details from an unknown error value.
10586
+ *
10587
+ * @remarks
10588
+ * Handle three cases:
10589
+ * - `KitError` — extract `code` and `name`.
10590
+ * - `Error` — extract `name`.
10591
+ * - Anything else — return empty details.
10592
+ *
10593
+ * Only structured, bounded fields (`errorCode`, `errorType`) are
10594
+ * included. Free-text fields (`message`, `stack`) are intentionally
10595
+ * omitted to avoid leaking secrets or PII through vendor telemetry.
10596
+ *
10597
+ * @param error - The thrown value to extract details from.
10598
+ * @returns A {@link ErrorDetails} object suitable for telemetry payloads.
10599
+ *
10600
+ * @example
10601
+ * ```typescript
10602
+ * import { extractErrorDetails } from '@core/utils'
10603
+ *
10604
+ * try {
10605
+ * await riskyOperation()
10606
+ * } catch (error) {
10607
+ * const details = extractErrorDetails(error)
10608
+ * // { errorCode: '1001', errorType: 'INPUT_NETWORK_MISMATCH' }
10609
+ * }
10610
+ * ```
10611
+ */
10612
+ function extractErrorDetails(error) {
10613
+ if (error instanceof KitError) {
10614
+ return {
10615
+ errorCode: String(error.code),
10616
+ errorType: error.name,
10617
+ };
10618
+ }
10619
+ if (error instanceof Error) {
10620
+ return {
10621
+ errorType: error.name,
10622
+ };
10623
+ }
10624
+ return {};
10625
+ }
10626
+
10627
+ /**
10628
+ * Register event handlers on an action dispatcher that emit analytics
10629
+ * telemetry for the configured events.
10630
+ *
10631
+ * @remarks
10632
+ * Subscribe to each action name in `actionEventMap`, call `mapEventToPayload`
10633
+ * for each event, and fire-and-forget `emitAnalyticsLog` if a non-null
10634
+ * payload is returned. All errors are silently swallowed so telemetry
10635
+ * never blocks or fails user operations.
10636
+ *
10637
+ * @param dispatcher - The `Actionable` instance from the kit.
10638
+ * @param sdkName - SDK package name (e.g. `'unified-balance-kit'`).
10639
+ * @param sdkVersion - SDK version string (e.g. `'1.0.0'`).
10640
+ * @param actionEventMap - Map of action names to subscribe to.
10641
+ * @param mapEventToPayload - Function that maps an action name and payload
10642
+ * to a {@link ClientLogPayload}, or `null` to skip logging.
10643
+ *
10644
+ * @example
10645
+ * ```typescript
10646
+ * import { registerTelemetryHandler } from '@core/utils'
10647
+ *
10648
+ * registerTelemetryHandler(
10649
+ * dispatcher,
10650
+ * 'unified-balance-kit',
10651
+ * '1.0.0',
10652
+ * VERB_EVENT_MAP,
10653
+ * mapSucceededEventToLog,
10654
+ * )
10655
+ * ```
10656
+ */
10657
+ function registerTelemetryHandler$1(dispatcher, sdkName, sdkVersion, actionEventMap, mapEventToPayload) {
10658
+ for (const actionName of Object.keys(actionEventMap)) {
10659
+ dispatcher.on(actionName, (payload) => {
10660
+ try {
10661
+ const log = mapEventToPayload(actionName, payload, sdkName, sdkVersion);
10662
+ if (log) {
10663
+ // Fire-and-forget — intentionally not awaited.
10664
+ void emitAnalyticsLog(log);
10665
+ }
10666
+ }
10667
+ catch {
10668
+ // Silently swallow — telemetry must never break user operations.
10669
+ }
10670
+ });
10671
+ }
10672
+ }
10673
+
10674
+ /**
10675
+ * Strip the `@circle-fin/` scope from a kit package name to produce the
10676
+ * short SDK name used in telemetry payloads.
10677
+ *
10678
+ * @param pkgName - The full npm package name (e.g. `@circle-fin/bridge-kit`).
10679
+ * @returns The unscoped kit name (e.g. `bridge-kit`).
10680
+ *
10681
+ * @example
10682
+ * ```typescript
10683
+ * import pkg from '../../package.json'
10684
+ * import { resolveKitSdkName } from '@core/utils'
10685
+ *
10686
+ * const SDK_NAME = resolveKitSdkName(pkg.name) // 'bridge-kit'
10687
+ * ```
10688
+ */
10689
+ function resolveKitSdkName(pkgName) {
10690
+ return pkgName.replace('@circle-fin/', '');
10691
+ }
10692
+
10693
+ /**
10694
+ * Build a telemetry payload from common fields.
10695
+ *
10696
+ * @internal
10697
+ */
10698
+ function buildPayload$1(config, eventType, errorDetails, context) {
10699
+ return {
10700
+ sdkName: config.sdkName,
10701
+ sdkVersion: config.sdkVersion,
10702
+ eventType,
10703
+ timestamp: new Date().toISOString(),
10704
+ errorDetails,
10705
+ clientContext: buildClientContext(),
10706
+ ...(context?.sourceChain != null && {
10707
+ sourceChain: context.sourceChain,
10708
+ }),
10709
+ ...(context?.destinationChain != null && {
10710
+ destinationChain: context.destinationChain,
10711
+ }),
10712
+ ...(context?.tokenIn != null && { tokenIn: context.tokenIn }),
10713
+ ...(context?.tokenOut != null && { tokenOut: context.tokenOut }),
10714
+ };
10715
+ }
10716
+ /**
10717
+ * Wrap an async operation with error telemetry.
10718
+ *
10719
+ * Execute `fn` and, if it throws, emit an error telemetry payload
10720
+ * before re-throwing. No-ops when `config.disabled` is `true`.
10721
+ *
10722
+ * @param fn - The async operation to execute.
10723
+ * @param eventType - The telemetry event type for this operation.
10724
+ * @param config - Per-kit SDK identity and disabled flag.
10725
+ * @param context - Optional chain/token context.
10726
+ * @returns The result of the operation.
10727
+ * @throws Re-throws any error after emitting telemetry.
10728
+ *
10729
+ * @example
10730
+ * ```typescript
10731
+ * import { withErrorTelemetry } from '@core/utils'
10732
+ *
10733
+ * const result = await withErrorTelemetry(
10734
+ * () => provider.bridge(params),
10735
+ * 'bridge_bridge',
10736
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
10737
+ * { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
10738
+ * )
10739
+ * ```
10740
+ */
10741
+ async function withErrorTelemetry(fn, eventType, config, context) {
10742
+ try {
10743
+ return await fn();
10744
+ }
10745
+ catch (error) {
10746
+ if (!config.disabled) {
10747
+ void emitAnalyticsLog(buildPayload$1(config, eventType, extractErrorDetails(error), context));
10748
+ }
10749
+ throw error;
10750
+ }
10751
+ }
10752
+ /**
10753
+ * Emit error telemetry for a result-reported step error.
10754
+ *
10755
+ * Handle the case where a provider reports a step failure inside a
10756
+ * result object (e.g. `BridgeResult.state === 'error'`) rather than
10757
+ * throwing. The caller extracts the failed step as a simple
10758
+ * {@link FailedStepInfo} — this function handles sanitization,
10759
+ * step-to-event mapping, and emission.
10760
+ *
10761
+ * No-ops when `config.disabled` is `true`.
10762
+ *
10763
+ * @param failedStep - The failed step info, or undefined if none found.
10764
+ * @param stepEventMap - Ordered mapping from step names to event types.
10765
+ * @param fallbackEventType - Event type when step is not in the map.
10766
+ * @param config - Per-kit SDK identity and disabled flag.
10767
+ * @param context - Optional chain/token context.
10768
+ *
10769
+ * @example
10770
+ * ```typescript
10771
+ * import { emitResultStepErrorTelemetry } from '@core/utils'
10772
+ *
10773
+ * const failedStep = result.steps.find((s) => s.state === 'error')
10774
+ * emitResultStepErrorTelemetry(
10775
+ * failedStep,
10776
+ * BRIDGE_STEP_EVENT_MAP,
10777
+ * 'bridge_bridge',
10778
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
10779
+ * { sourceChain: 'Ethereum', tokenIn: 'USDC' },
10780
+ * )
10781
+ * ```
10782
+ */
10783
+ function emitResultStepErrorTelemetry(failedStep, stepEventMap, fallbackEventType, config, context) {
10784
+ if (config.disabled) {
10785
+ return;
10786
+ }
10787
+ const stepEntry = stepEventMap.find(([name]) => name === failedStep?.name);
10788
+ const errorDetails = {
10789
+ ...(failedStep?.name != null && { errorType: failedStep.name }),
10790
+ };
10791
+ void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
10792
+ }
10793
+
10339
10794
  var name$2 = "@circle-fin/bridge-kit";
10340
- var version$3 = "1.9.0";
10795
+ var version$3 = "1.10.0";
10341
10796
  var pkg$3 = {
10342
10797
  name: name$2,
10343
10798
  version: version$3};
@@ -13302,18 +13757,16 @@ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet) => {
13302
13757
  };
13303
13758
  /**
13304
13759
  * Fetches attestation data from the IRIS API with retry and timeout handling.
13305
- * Since fetching starts after a confirmed burn transaction, we attempt up to 10 retries.
13306
- * Implements a conservative delay between requests to respect the API rate
13307
- * limit of 35 requests per second. To avoid rate limiting when multiple processes are
13308
- * running concurrently, we enforce a 200ms delay between retries.
13309
13760
  *
13310
- * Each HTTP request is given a maximum of 2 seconds before it is aborted.
13311
- * We retry up to 10 times, waiting 200ms between attempts.
13761
+ * Polls the IRIS API until a complete attestation is available. The default
13762
+ * window is sized for slow source chains where finality may take many
13763
+ * confirmations.
13312
13764
  *
13313
- * The total maximum time this function might take (worst case) is:
13314
- * - Perattempt timeout: 2 000 ms
13315
- * - Retry delays: 9 × 200 ms = 1 800 ms
13316
- * - Total max time: 2 000 ms + 1 800 ms = 3 800 ms
13765
+ * Defaults (see `DEFAULT_CONFIG`):
13766
+ * - Per-attempt timeout: 2 000 ms (each HTTP request aborts after 2 s)
13767
+ * - Retry delay: 2 000 ms between attempts
13768
+ * - Max retries: 600 (30 × 20)
13769
+ * - Total worst-case polling window: 600 × (2 000 ms + 2 000 ms) ≈ 40 minutes
13317
13770
  *
13318
13771
  * @param sourceDomainId - The CCTP domain ID.
13319
13772
  * @param transactionHash - The transaction hash to fetch attestation for.
@@ -15290,7 +15743,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCo
15290
15743
  return step;
15291
15744
  }
15292
15745
 
15293
- var version$2 = "1.7.0";
15746
+ var version$2 = "1.8.1";
15294
15747
  var pkg$2 = {
15295
15748
  version: version$2};
15296
15749
 
@@ -16956,6 +17409,35 @@ const formatBridgeResult = (result, formatDirection) => {
16956
17409
  };
16957
17410
  };
16958
17411
 
17412
+ /**
17413
+ * Telemetry event type identifiers for bridge-kit operations.
17414
+ *
17415
+ * @internal
17416
+ */
17417
+ const BRIDGE_EVENT_TYPES = {
17418
+ BRIDGE: 'bridge_bridge',
17419
+ RETRY: 'bridge_retry',
17420
+ ESTIMATE: 'bridge_estimate',
17421
+ };
17422
+ /**
17423
+ * Ordered mapping from provider step event names to telemetry event types.
17424
+ *
17425
+ * @remarks
17426
+ * The order matches the CCTP v2 bridge execution sequence. During
17427
+ * `bridge()`, completed step events are counted so the failing step
17428
+ * can be identified by its index.
17429
+ *
17430
+ * @internal
17431
+ */
17432
+ const BRIDGE_STEP_EVENT_MAP = [
17433
+ ['approve', 'bridge_approve'],
17434
+ ['burn', 'bridge_burn'],
17435
+ ['fetchAttestation', 'bridge_fetch_attestation'],
17436
+ ['mint', 'bridge_mint'],
17437
+ ];
17438
+
17439
+ /** SDK name used in telemetry payloads. */
17440
+ const SDK_NAME$2 = resolveKitSdkName(pkg$3.name);
16959
17441
  /**
16960
17442
  * BridgeKit caller component for retry and estimate operations.
16961
17443
  */
@@ -17039,6 +17521,10 @@ class BridgeKit {
17039
17521
  * A custom fee policy for the kit.
17040
17522
  */
17041
17523
  customFeePolicy;
17524
+ /** Whether error telemetry is disabled. */
17525
+ disableErrorReporting;
17526
+ /** Per-kit telemetry identity for shared helpers. */
17527
+ telemetryConfig;
17042
17528
  /**
17043
17529
  * Create a new BridgeKit instance.
17044
17530
  *
@@ -17056,6 +17542,12 @@ class BridgeKit {
17056
17542
  const defaultProviders = getDefaultProviders$2();
17057
17543
  this.providers = [...defaultProviders, ...(config.providers ?? [])];
17058
17544
  this.actionDispatcher = new Actionable();
17545
+ this.disableErrorReporting = config.disableErrorReporting === true;
17546
+ this.telemetryConfig = {
17547
+ sdkName: SDK_NAME$2,
17548
+ sdkVersion: pkg$3.version,
17549
+ disabled: this.disableErrorReporting,
17550
+ };
17059
17551
  for (const provider of this.providers) {
17060
17552
  provider.registerDispatcher(this.actionDispatcher);
17061
17553
  }
@@ -17129,19 +17621,36 @@ class BridgeKit {
17129
17621
  * ```
17130
17622
  */
17131
17623
  async bridge(params) {
17132
- // First validate the parameters
17133
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17134
- // Then resolve chain definitions (includes adapter chain support validation)
17135
- const resolvedParams = await resolveBridgeParams(params);
17136
- // Validate network compatibility
17137
- this.validateNetworkCompatibility(resolvedParams);
17138
- // Merge the custom fee config into the resolved params
17139
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17140
- // Find a provider that supports this route
17141
- const provider = this.findProviderForRoute(finalResolvedParams);
17142
- // Execute the transfer using the provider
17143
- // Format the bridge result into human-readable string values for the user
17144
- return formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
17624
+ return withErrorTelemetry(async () => {
17625
+ // First validate the parameters
17626
+ assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17627
+ // Then resolve chain definitions (includes adapter chain support validation)
17628
+ const resolvedParams = await resolveBridgeParams(params);
17629
+ // Validate network compatibility
17630
+ this.validateNetworkCompatibility(resolvedParams);
17631
+ // Merge the custom fee config into the resolved params
17632
+ const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17633
+ // Find a provider that supports this route
17634
+ const provider = this.findProviderForRoute(finalResolvedParams);
17635
+ // Execute the transfer using the provider
17636
+ // Format the bridge result into human-readable string values for the user
17637
+ const result = formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
17638
+ // Emit error telemetry when the provider returns an error state
17639
+ // (provider records step failures in the result instead of throwing).
17640
+ if (result.state === 'error') {
17641
+ const failedStep = result.steps.find((s) => s.state === 'error');
17642
+ emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.BRIDGE, this.telemetryConfig, {
17643
+ sourceChain: resolveChainName(params.from.chain),
17644
+ destinationChain: resolveChainName(params.to.chain),
17645
+ tokenIn: params.token,
17646
+ });
17647
+ }
17648
+ return result;
17649
+ }, BRIDGE_EVENT_TYPES.BRIDGE, this.telemetryConfig, {
17650
+ sourceChain: resolveChainName(params.from.chain),
17651
+ destinationChain: resolveChainName(params.to.chain),
17652
+ tokenIn: params.token,
17653
+ });
17145
17654
  }
17146
17655
  /**
17147
17656
  * Retry a failed or incomplete cross-chain USDC bridge operation.
@@ -17213,17 +17722,33 @@ class BridgeKit {
17213
17722
  * ```
17214
17723
  */
17215
17724
  async retry(result, context, invocationMeta) {
17216
- const provider = this.providers.find((p) => p.name === result.provider);
17217
- if (!provider) {
17218
- throw new Error(`Provider ${result.provider} not found`);
17219
- }
17220
- // Merge BridgeKit caller into invocation metadata for retry operation
17221
- const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
17222
- // Format the bridge result into bigint string values for internal use
17223
- const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
17224
- // Execute the retry using the provider
17225
- // Format the bridge result into human-readable string values for the user
17226
- return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
17725
+ return withErrorTelemetry(async () => {
17726
+ const provider = this.providers.find((p) => p.name === result.provider);
17727
+ if (!provider) {
17728
+ throw new Error(`Provider ${result.provider} not found`);
17729
+ }
17730
+ // Merge BridgeKit caller into invocation metadata for retry operation
17731
+ const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
17732
+ // Format the bridge result into bigint string values for internal use
17733
+ const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
17734
+ // Execute the retry using the provider
17735
+ // Format the bridge result into human-readable string values for the user
17736
+ const retryResult = formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
17737
+ // Emit error telemetry when the provider returns an error state.
17738
+ if (retryResult.state === 'error') {
17739
+ const failedStep = retryResult.steps.find((s) => s.state === 'error');
17740
+ emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
17741
+ sourceChain: result.source.chain.chain,
17742
+ destinationChain: result.destination.chain.chain,
17743
+ tokenIn: result.token,
17744
+ });
17745
+ }
17746
+ return retryResult;
17747
+ }, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
17748
+ sourceChain: result.source.chain.chain,
17749
+ destinationChain: result.destination.chain.chain,
17750
+ tokenIn: result.token,
17751
+ });
17227
17752
  }
17228
17753
  /**
17229
17754
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -17262,18 +17787,24 @@ class BridgeKit {
17262
17787
  * ```
17263
17788
  */
17264
17789
  async estimate(params) {
17265
- // First validate the parameters
17266
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17267
- // Then resolve chain definitions (includes adapter chain support validation)
17268
- const resolvedParams = await resolveBridgeParams(params);
17269
- // Validate network compatibility
17270
- this.validateNetworkCompatibility(resolvedParams);
17271
- // Merge the custom fee config into the resolved params
17272
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17273
- // Find a provider that supports this route
17274
- const provider = this.findProviderForRoute(finalResolvedParams);
17275
- // Estimate the transfer using the provider and format amounts to human-readable strings
17276
- return formatBridgeResult(await provider.estimate(finalResolvedParams), 'to-human-readable');
17790
+ return withErrorTelemetry(async () => {
17791
+ // First validate the parameters
17792
+ assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17793
+ // Then resolve chain definitions (includes adapter chain support validation)
17794
+ const resolvedParams = await resolveBridgeParams(params);
17795
+ // Validate network compatibility
17796
+ this.validateNetworkCompatibility(resolvedParams);
17797
+ // Merge the custom fee config into the resolved params
17798
+ const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17799
+ // Find a provider that supports this route
17800
+ const provider = this.findProviderForRoute(finalResolvedParams);
17801
+ // Estimate the transfer using the provider and format amounts to human-readable strings
17802
+ return formatBridgeResult(await provider.estimate(finalResolvedParams), 'to-human-readable');
17803
+ }, BRIDGE_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
17804
+ sourceChain: resolveChainName(params.from.chain),
17805
+ destinationChain: resolveChainName(params.to.chain),
17806
+ tokenIn: params.token,
17807
+ });
17277
17808
  }
17278
17809
  /**
17279
17810
  * Get all chains supported by any provider in the kit, with optional filtering.
@@ -17590,7 +18121,11 @@ const createBridgeKit = (context) => {
17590
18121
  const getFee = context.getFee?.bind(context);
17591
18122
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
17592
18123
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
17593
- const kit = new BridgeKit();
18124
+ const kit = new BridgeKit({
18125
+ ...(context.disableErrorReporting != null && {
18126
+ disableErrorReporting: context.disableErrorReporting,
18127
+ }),
18128
+ });
17594
18129
  if (hasBoth) {
17595
18130
  kit.setCustomFeePolicy({
17596
18131
  calculateFee: async (params) => {
@@ -17607,7 +18142,7 @@ const createBridgeKit = (context) => {
17607
18142
  };
17608
18143
 
17609
18144
  var name$1 = "@circle-fin/swap-kit";
17610
- var version$1 = "1.1.1";
18145
+ var version$1 = "1.2.1";
17611
18146
  var pkg$1 = {
17612
18147
  name: name$1,
17613
18148
  version: version$1};
@@ -21487,6 +22022,119 @@ function evmSigningData(burnIntent) {
21487
22022
  };
21488
22023
  }
21489
22024
 
22025
+ /**
22026
+ * EIP-7702 delegation indicator. A 7702-delegated EOA's bytecode is
22027
+ * `0xef0100` followed by the 20-byte delegate address (23 bytes total).
22028
+ * The underlying secp256k1 key still produces `ecrecover`-verifiable
22029
+ * signatures, so for Gateway's purposes a 7702-delegated address is
22030
+ * an EOA, not an SCA.
22031
+ *
22032
+ * Spec: https://eips.ethereum.org/EIPS/eip-7702
22033
+ */
22034
+ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
22035
+ /**
22036
+ * Assert that `address` on `chain` can sign Gateway burn intents.
22037
+ *
22038
+ * Gateway verifies burn-intent signatures with plain `ecrecover` (see
22039
+ * `evm-gateway-contracts/src/lib/EIP712Domain.sol`). Smart-contract
22040
+ * accounts (SCAs) produce signatures over wrapped hashes (ERC-1271 /
22041
+ * ERC-6492 / ERC-6900 replay-safe hashes) that Gateway cannot verify.
22042
+ * Additionally, the Circle Wallets backend rejects SCA typed-data signing
22043
+ * against Gateway's chainId-less domain with an opaque
22044
+ * `invalid integer value <nil>/<nil> for type uint256` error.
22045
+ *
22046
+ * EIP-7702-delegated EOAs are exempt: they expose non-empty bytecode
22047
+ * (`0xef0100<delegate>`) but the underlying secp256k1 key still produces
22048
+ * `ecrecover`-verifiable signatures, so Gateway accepts them.
22049
+ *
22050
+ * When the signer is a true SCA, raises an `INPUT_UNSUPPORTED_ACTION`
22051
+ * error directing the caller to register an EOA delegate against the
22052
+ * SCA and then submit the spend with the delegate EOA as the signer
22053
+ * and the SCA as the source account. See the unified-balance / Gateway
22054
+ * docs for the exact API.
22055
+ *
22056
+ * If bytecode cannot be read (RPC failure, etc.) the pre-check is
22057
+ * skipped and downstream signing surfaces its own error — a warning is
22058
+ * logged so the skip is diagnosable.
22059
+ *
22060
+ * @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
22061
+ * @param address - Signer address to validate.
22062
+ * @param chain - EVM chain where the signer lives.
22063
+ * @throws {KitError} INPUT_UNSUPPORTED_ACTION when `address` is an SCA.
22064
+ *
22065
+ * @example
22066
+ * ```typescript
22067
+ * import { assertSignerIsEoa } from '@core/adapter-evm'
22068
+ * import { Ethereum } from '@core/chains'
22069
+ *
22070
+ * await assertSignerIsEoa(adapter, '0xabc...', Ethereum)
22071
+ * ```
22072
+ */
22073
+ async function assertSignerIsEoa(adapter, address, chain) {
22074
+ let code;
22075
+ try {
22076
+ code = await adapter.readBytecode(address, chain);
22077
+ }
22078
+ catch (err) {
22079
+ console.warn(`[gateway] assertSignerIsEoa skipped (readBytecode failed for ` +
22080
+ `${address} on ${chain.name}): ` +
22081
+ (err instanceof Error ? err.message : String(err)));
22082
+ return;
22083
+ }
22084
+ if (code === undefined ||
22085
+ code === '0x' ||
22086
+ code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
22087
+ return;
22088
+ }
22089
+ throw new KitError({
22090
+ ...InputError.UNSUPPORTED_ACTION,
22091
+ recoverability: 'FATAL',
22092
+ message: `Gateway burn-intent signing requires an EOA signer (Gateway ` +
22093
+ `verifies signatures with ecrecover and does not support ERC-1271). ` +
22094
+ `The signer ${address} on ${chain.name} has on-chain bytecode, ` +
22095
+ `indicating it is a smart-contract account (SCA). Register an EOA ` +
22096
+ `delegate against the SCA, then submit the spend with the delegate ` +
22097
+ `EOA as the signer and the SCA as the source account. See DEVX-2774.`,
22098
+ cause: {
22099
+ trace: {
22100
+ operation: 'signEvmIntentGroup.assertSignerIsEoa',
22101
+ address,
22102
+ chain: chain.name,
22103
+ bytecodeBytes: (code.length - 2) / 2,
22104
+ bytecodePrefix: code.slice(0, 12),
22105
+ },
22106
+ },
22107
+ });
22108
+ }
22109
+
22110
+ /**
22111
+ * Duck-type test for an EVM adapter that exposes `readBytecode`.
22112
+ *
22113
+ * Used as a structural guard at package boundaries (e.g. the Circle Wallets
22114
+ * hybrid adapter calling into a chain adapter, or the Gateway sign path
22115
+ * receiving an arbitrary adapter implementation) where `instanceof EvmAdapter`
22116
+ * is unreliable because each consumer bundles its own copy of the base class.
22117
+ *
22118
+ * @param value - The value to test.
22119
+ * @returns `true` when `value` is an object exposing a callable
22120
+ * `readBytecode` method, narrowed to {@link EvmAdapterLike}.
22121
+ *
22122
+ * @example
22123
+ * ```typescript
22124
+ * import { isEvmAdapterLike } from '@core/adapter-evm'
22125
+ *
22126
+ * if (isEvmAdapterLike(adapter)) {
22127
+ * const code = await adapter.readBytecode('0xabc...', chain)
22128
+ * }
22129
+ * ```
22130
+ */
22131
+ function isEvmAdapterLike(value) {
22132
+ return (typeof value === 'object' &&
22133
+ value !== null &&
22134
+ 'readBytecode' in value &&
22135
+ typeof value.readBytecode === 'function');
22136
+ }
22137
+
21490
22138
  /**
21491
22139
  * Sign an EVM adapter group: batches all intents and produces a single
21492
22140
  * EIP-712 ECDSA signature.
@@ -21494,6 +22142,12 @@ function evmSigningData(burnIntent) {
21494
22142
  * For a single-intent group, `primaryType` is `'BurnIntent'`.
21495
22143
  * For multi-intent groups, `primaryType` is `'BurnIntentSet'`.
21496
22144
  *
22145
+ * Before signing, asserts that the signer address is an EOA. Gateway
22146
+ * verifies burn-intent signatures with plain `ecrecover` (no ERC-1271
22147
+ * fallback), so signatures produced by smart-contract accounts (SCAs)
22148
+ * cannot be verified. When an SCA is detected, a clear error is raised
22149
+ * directing the caller to the delegate workflow (DEVX-2774).
22150
+ *
21497
22151
  * @param group - The adapter group containing the adapter, chain, and
21498
22152
  * burn intents to sign.
21499
22153
  * @returns A signed set with the intents and the ECDSA signature.
@@ -21514,6 +22168,22 @@ function evmSigningData(burnIntent) {
21514
22168
  async function signEvmIntentGroup(group) {
21515
22169
  const { adapter, intents: groupIntents, chain, address } = group;
21516
22170
  const operationContext = address === undefined ? { chain } : { chain, address };
22171
+ // Gateway verifies burn-intent signatures with plain ecrecover. An SCA
22172
+ // signer silently produces a signature over a wrapped hash that Gateway
22173
+ // cannot verify, and Circle Wallets' KMS rejects the typed data up front
22174
+ // with an opaque `<nil>/<nil>` error. Short-circuit with a clear message
22175
+ // when we can detect bytecode at the signer address. See DEVX-2774.
22176
+ //
22177
+ // Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
22178
+ // each consumer package bundles its own copy of the base class and the
22179
+ // `instanceof` identity check fails across package boundaries.
22180
+ //
22181
+ // Empty string is defended against because assertSignerIsEoa would
22182
+ // otherwise call eth_getCode('') on the RPC.
22183
+ const hasResolvedSigner = typeof address === 'string' && address.length > 0;
22184
+ if (hasResolvedSigner && chain.type === 'evm' && isEvmAdapterLike(adapter)) {
22185
+ await assertSignerIsEoa(adapter, address, chain);
22186
+ }
21517
22187
  const firstIntent = groupIntents[0];
21518
22188
  const typedData = groupIntents.length === 1 && firstIntent
21519
22189
  ? evmSigningData(firstIntent)
@@ -21783,9 +22453,10 @@ function getSolanaFeeRecipient() {
21783
22453
  return CIRCLE_FEE_RECIPIENT_SOLANA;
21784
22454
  }
21785
22455
 
21786
- Buffer.from('gateway_minter');
21787
- Buffer.from('gateway_minter_custody');
21788
- Buffer.from('used_transfer_spec_hash');
22456
+ const enc = new TextEncoder();
22457
+ enc.encode('gateway_minter');
22458
+ enc.encode('gateway_minter_custody');
22459
+ enc.encode('used_transfer_spec_hash');
21789
22460
 
21790
22461
  async function executeAndWait$1(adapter, request, chain) {
21791
22462
  if (request.type === 'noop') {
@@ -28068,6 +28739,18 @@ function createSwapKitContext(config = {}) {
28068
28739
  return context;
28069
28740
  }
28070
28741
 
28742
+ /**
28743
+ * Telemetry event type identifiers for swap-kit operations.
28744
+ *
28745
+ * @internal
28746
+ */
28747
+ const SWAP_EVENT_TYPES = {
28748
+ SWAP: 'swap_swap',
28749
+ ESTIMATE: 'swap_estimate',
28750
+ };
28751
+
28752
+ /** SDK name used in telemetry payloads. */
28753
+ const SDK_NAME$1 = resolveKitSdkName(pkg$1.name);
28071
28754
  /**
28072
28755
  * A high-level class-based interface for single-chain token swap operations.
28073
28756
  *
@@ -28139,6 +28822,10 @@ function createSwapKitContext(config = {}) {
28139
28822
  */
28140
28823
  class SwapKit {
28141
28824
  context;
28825
+ /** Whether error telemetry is disabled. */
28826
+ disableErrorReporting;
28827
+ /** Per-kit telemetry identity for shared helpers. */
28828
+ telemetryConfig;
28142
28829
  /**
28143
28830
  * Create a new SwapKit instance.
28144
28831
  *
@@ -28187,6 +28874,12 @@ class SwapKit {
28187
28874
  */
28188
28875
  constructor(config = {}) {
28189
28876
  this.context = createSwapKitContext(config);
28877
+ this.disableErrorReporting = config.disableErrorReporting === true;
28878
+ this.telemetryConfig = {
28879
+ sdkName: SDK_NAME$1,
28880
+ sdkVersion: pkg$1.version,
28881
+ disabled: this.disableErrorReporting,
28882
+ };
28190
28883
  }
28191
28884
  /**
28192
28885
  * Estimate the output amount and fees for a swap operation.
@@ -28228,7 +28921,11 @@ class SwapKit {
28228
28921
  * ```
28229
28922
  */
28230
28923
  async estimate(params) {
28231
- return estimate(this.context, params);
28924
+ return withErrorTelemetry(async () => estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
28925
+ sourceChain: resolveChainName(params.from.chain),
28926
+ tokenIn: params.tokenIn,
28927
+ tokenOut: params.tokenOut,
28928
+ });
28232
28929
  }
28233
28930
  /**
28234
28931
  * Execute a token swap operation on a single chain.
@@ -28284,7 +28981,11 @@ class SwapKit {
28284
28981
  * ```
28285
28982
  */
28286
28983
  async swap(params) {
28287
- return swap$1(this.context, params);
28984
+ return withErrorTelemetry(async () => swap$1(this.context, params), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, {
28985
+ sourceChain: resolveChainName(params.from.chain),
28986
+ tokenIn: params.tokenIn,
28987
+ tokenOut: params.tokenOut,
28988
+ });
28288
28989
  }
28289
28990
  /**
28290
28991
  * Get all chains supported by the configured swap providers.
@@ -28477,7 +29178,11 @@ const createSwapKit = (context) => {
28477
29178
  const getFee = context.getFee?.bind(context);
28478
29179
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
28479
29180
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
28480
- const kit = new SwapKit();
29181
+ const kit = new SwapKit({
29182
+ ...(context.disableErrorReporting != null && {
29183
+ disableErrorReporting: context.disableErrorReporting,
29184
+ }),
29185
+ });
28481
29186
  if (hasBoth) {
28482
29187
  kit.setCustomFeePolicy({
28483
29188
  computeFee: async (params) => {
@@ -29426,121 +30131,11 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
29426
30131
  };
29427
30132
 
29428
30133
  var name = "@circle-fin/unified-balance-kit";
29429
- var version = "1.0.2";
30134
+ var version = "1.1.1";
29430
30135
  var pkg = {
29431
30136
  name: name,
29432
30137
  version: version};
29433
30138
 
29434
- /**
29435
- * Default telemetry endpoint.
29436
- *
29437
- * Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
29438
- * (e.g. for staging or local development).
29439
- *
29440
- * @internal
29441
- */
29442
- const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
29443
- /**
29444
- * Resolve the telemetry endpoint URL.
29445
- *
29446
- * @internal
29447
- */
29448
- function getLogsUrl() {
29449
- if (isNodeEnvironment() &&
29450
- typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' &&
29451
- process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
29452
- return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
29453
- }
29454
- return DEFAULT_LOGS_URL;
29455
- }
29456
- /**
29457
- * Send a telemetry event to the proxy service.
29458
- *
29459
- * @remarks
29460
- * Fire-and-forget: the returned promise is intentionally not awaited
29461
- * by the caller. A fetch failure (network error, non-2xx, timeout)
29462
- * is silently swallowed so telemetry never blocks or fails user
29463
- * operations.
29464
- *
29465
- * @param payload - The structured log payload matching the server schema.
29466
- *
29467
- * @example
29468
- * ```typescript
29469
- * import { emitAnalyticsLog } from './emitLog'
29470
- *
29471
- * // Fire-and-forget — do not await
29472
- * emitAnalyticsLog(payload).catch(() => {})
29473
- * ```
29474
- *
29475
- * @internal
29476
- */
29477
- async function emitAnalyticsLog(payload) {
29478
- try {
29479
- const isNode = isNodeEnvironment();
29480
- const userAgent = getUserAgent();
29481
- await fetch(getLogsUrl(), {
29482
- method: 'POST',
29483
- headers: {
29484
- 'Content-Type': 'application/json',
29485
- // Browser restricts setting User-Agent; use X-User-Agent instead.
29486
- ...(isNode
29487
- ? { 'User-Agent': userAgent }
29488
- : { 'X-User-Agent': userAgent }),
29489
- },
29490
- body: JSON.stringify(payload),
29491
- signal: AbortSignal.timeout(5_000),
29492
- });
29493
- }
29494
- catch {
29495
- // Silently swallow — telemetry must never break user operations.
29496
- }
29497
- }
29498
-
29499
- /**
29500
- * Build the `clientContext` object for telemetry payloads.
29501
- *
29502
- * @remarks
29503
- * Uses the exported `getRuntime()` and `isNodeEnvironment()` from
29504
- * `@core/utils` to detect the runtime environment (per Dominik's
29505
- * feedback to reuse existing infrastructure). The returned string
29506
- * is parsed into the structured `ClientContext` fields expected by
29507
- * the server schema.
29508
- *
29509
- * @returns A {@link ClientContext} with platform, OS, and runtime name
29510
- * populated from the current environment.
29511
- *
29512
- * @example
29513
- * ```typescript
29514
- * import { buildClientContext } from './clientContext'
29515
- *
29516
- * const ctx = buildClientContext()
29517
- * // Node: { platform: 'node', os: 'darwin', runtimeName: null }
29518
- * // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
29519
- * ```
29520
- */
29521
- function buildClientContext() {
29522
- const runtime = getRuntime();
29523
- if (runtime.startsWith('browser/')) {
29524
- return {
29525
- platform: 'browser',
29526
- os: null,
29527
- runtimeName: runtime.slice('browser/'.length).toLowerCase(),
29528
- };
29529
- }
29530
- if (runtime.startsWith('node/')) {
29531
- return {
29532
- platform: 'node',
29533
- os: isNodeEnvironment() ? process.platform : null,
29534
- runtimeName: null,
29535
- };
29536
- }
29537
- return {
29538
- platform: 'node',
29539
- os: null,
29540
- runtimeName: null,
29541
- };
29542
- }
29543
-
29544
30139
  /**
29545
30140
  * Event type identifiers accepted by the server-side logs endpoint.
29546
30141
  *
@@ -29553,6 +30148,11 @@ const EVENT_TYPES = {
29553
30148
  DEPOSIT_FOR: 'unified_balance_deposit_for',
29554
30149
  SPEND: 'unified_balance_spend',
29555
30150
  SPEND_FORWARDER: 'unified_balance_spend_forwarder',
30151
+ ESTIMATE_SPEND: 'unified_balance_estimate_spend',
30152
+ GET_BALANCES: 'unified_balance_get_balances',
30153
+ ADD_DELEGATE: 'unified_balance_add_delegate',
30154
+ REMOVE_DELEGATE: 'unified_balance_remove_delegate',
30155
+ GET_DELEGATE_STATUS: 'unified_balance_get_delegate_status',
29556
30156
  INITIATE_REMOVE_FUND: 'unified_balance_initiate_remove_fund',
29557
30157
  REMOVE_FUND: 'unified_balance_remove_fund',
29558
30158
  };
@@ -29590,33 +30190,29 @@ function extractSingleChainResult(data) {
29590
30190
  txHash: data.txHash,
29591
30191
  };
29592
30192
  }
29593
- /**
29594
- * Resolve a chain identifier that may be a string or a
29595
- * `ChainDefinition` object to a plain string.
29596
- *
29597
- * @internal
29598
- */
29599
- function resolveChainString(chain) {
29600
- if (typeof chain === 'string')
29601
- return chain;
29602
- return chain.chain;
29603
- }
29604
30193
  /**
29605
30194
  * Extract log-relevant fields from a spend result.
29606
30195
  *
30196
+ * @remarks
30197
+ * Collects all source chains from allocations and joins them with
30198
+ * commas so multi-chain spends are fully represented.
30199
+ *
29607
30200
  * @internal
29608
30201
  */
29609
30202
  function extractSpend(data) {
29610
- const rawChain = data.allocations?.[0]?.chain;
29611
- let firstSourceChain;
29612
- if (rawChain != null) {
29613
- firstSourceChain = resolveChainString(rawChain);
30203
+ const allocs = data.allocations ?? [];
30204
+ const chains = [];
30205
+ for (const alloc of allocs) {
30206
+ const name = resolveChainName(alloc.chain);
30207
+ if (name != null) {
30208
+ chains.push(name);
30209
+ }
29614
30210
  }
29615
- const hasAllocations = data.allocations?.[0] != null;
30211
+ const sourceChain = chains.length > 0 ? chains.join(',') : undefined;
29616
30212
  return {
29617
- ...(firstSourceChain != null && { sourceChain: firstSourceChain }),
30213
+ ...(sourceChain != null && { sourceChain }),
29618
30214
  destinationChain: data.destinationChain,
29619
- ...(hasAllocations && { tokenIn: 'USDC' }),
30215
+ ...(allocs.length > 0 && { tokenIn: 'USDC' }),
29620
30216
  txHash: data.txHash,
29621
30217
  };
29622
30218
  }
@@ -29715,10 +30311,9 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29715
30311
  * telemetry for successful verb operations.
29716
30312
  *
29717
30313
  * @remarks
29718
- * Follows Dominik's feedback to reuse the existing event bus as the
29719
- * SDK-side hook for telemetry. Registers a handler for each verb's
29720
- * `.succeeded` event. Non-verb and non-succeeded events are not
29721
- * subscribed to.
30314
+ * Registers a handler for each verb's `.succeeded` event using the
30315
+ * shared `registerTelemetryHandler` from `@core/utils`. Non-verb and
30316
+ * non-succeeded events are not subscribed to.
29722
30317
  *
29723
30318
  * The HTTP POST is fire-and-forget — telemetry never blocks or fails
29724
30319
  * user operations.
@@ -29738,20 +30333,7 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29738
30333
  * @internal
29739
30334
  */
29740
30335
  function registerTelemetryHandler(dispatcher, sdkName, sdkVersion) {
29741
- for (const actionName of Object.keys(VERB_EVENT_MAP)) {
29742
- dispatcher.on(actionName, (payload) => {
29743
- try {
29744
- const log = mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion);
29745
- if (log) {
29746
- // Fire-and-forget — intentionally not awaited.
29747
- void emitAnalyticsLog(log);
29748
- }
29749
- }
29750
- catch {
29751
- // Silently swallow — telemetry must never break user operations.
29752
- }
29753
- });
29754
- }
30336
+ registerTelemetryHandler$1(dispatcher, sdkName, sdkVersion, VERB_EVENT_MAP, mapSucceededEventToLog);
29755
30337
  }
29756
30338
 
29757
30339
  /**
@@ -32854,13 +33436,20 @@ async function resolveAllocationsAndIntents(params, destChain, recipientAddress,
32854
33436
  const sourcesArray = rawSources.filter((s) => s != null);
32855
33437
  const networkType = destChain.isTestnet ? 'testnet' : 'mainnet';
32856
33438
  const balanceResults = await Promise.all(sourcesArray.map(async (source) => {
32857
- const querySource = {
32858
- adapter: source.adapter,
32859
- };
32860
- if (source.sourceAccount)
32861
- querySource['account'] = source.sourceAccount;
32862
- if ('address' in source && source.address) {
32863
- querySource['address'] = source.address;
33439
+ // When sourceAccount is set (delegate flow), scope the balance
33440
+ // query to the Gateway depositor — not the signer. Using the
33441
+ // address-only path bypasses adapter address resolution, which
33442
+ // would otherwise return the signer's balance (developer-
33443
+ // controlled) or reject an explicit address (user-controlled).
33444
+ let querySource;
33445
+ if (source.sourceAccount) {
33446
+ querySource = { address: source.sourceAccount };
33447
+ }
33448
+ else {
33449
+ querySource = { adapter: source.adapter };
33450
+ if ('address' in source && source.address) {
33451
+ querySource['address'] = source.address;
33452
+ }
32864
33453
  }
32865
33454
  return getBalances$1({
32866
33455
  token: params.token,
@@ -35781,6 +36370,8 @@ function getSupportedChains(context, token, options) {
35781
36370
  return Object.values(Object.fromEntries(filtered.map((chain) => [chain.chain, chain])));
35782
36371
  }
35783
36372
 
36373
+ /** SDK name used in telemetry payloads. */
36374
+ const SDK_NAME = resolveKitSdkName(pkg.name);
35784
36375
  /**
35785
36376
  * A high-level class-based interface for cross-chain USDC deposits,
35786
36377
  * spending, balance queries, delegation management, and withdrawals.
@@ -35828,6 +36419,10 @@ class UnifiedBalanceKit {
35828
36419
  * The action dispatcher for the kit.
35829
36420
  */
35830
36421
  actionDispatcher;
36422
+ /** Whether error telemetry is disabled. */
36423
+ disableErrorReporting;
36424
+ /** Per-kit telemetry identity for shared helpers. */
36425
+ telemetryConfig;
35831
36426
  /**
35832
36427
  * Create a new UnifiedBalanceKit instance.
35833
36428
  *
@@ -35840,14 +36435,49 @@ class UnifiedBalanceKit {
35840
36435
  constructor(config = {}) {
35841
36436
  this.context = createUnifiedBalanceKitContext(config);
35842
36437
  this.actionDispatcher = new Actionable();
36438
+ this.disableErrorReporting = config.disableErrorReporting === true;
36439
+ this.telemetryConfig = {
36440
+ sdkName: SDK_NAME,
36441
+ sdkVersion: pkg.version,
36442
+ disabled: this.disableErrorReporting,
36443
+ };
35843
36444
  for (const provider of this.context.providers) {
35844
36445
  provider.registerDispatcher(this.actionDispatcher);
35845
36446
  }
35846
36447
  if (!config.disableAnalytics) {
35847
- const sdkName = pkg.name.replace('@circle-fin/', '');
35848
- registerTelemetryHandler(this.actionDispatcher, sdkName, pkg.version);
36448
+ registerTelemetryHandler(this.actionDispatcher, SDK_NAME, pkg.version);
35849
36449
  }
35850
36450
  }
36451
+ /**
36452
+ * Extract comma-separated source chain names from spend params.
36453
+ *
36454
+ * @internal
36455
+ */
36456
+ static extractSpendSourceChains(params) {
36457
+ if (params == null || typeof params !== 'object' || !('from' in params)) {
36458
+ return undefined;
36459
+ }
36460
+ const fromVal = params.from;
36461
+ const sources = Array.isArray(fromVal) ? fromVal : [fromVal];
36462
+ const chains = [];
36463
+ for (const src of sources) {
36464
+ const allocs = src?.allocations;
36465
+ let allocArr;
36466
+ if (allocs == null) {
36467
+ allocArr = [];
36468
+ }
36469
+ else {
36470
+ allocArr = Array.isArray(allocs) ? allocs : [allocs];
36471
+ }
36472
+ for (const alloc of allocArr) {
36473
+ const name = resolveChainName(alloc.chain);
36474
+ if (name != null) {
36475
+ chains.push(name);
36476
+ }
36477
+ }
36478
+ }
36479
+ return chains.length > 0 ? chains.join(',') : undefined;
36480
+ }
35851
36481
  // implementation just forwards to the bus
35852
36482
  on(actionOrWildcard, handler) {
35853
36483
  this.actionDispatcher.on(actionOrWildcard, handler);
@@ -35870,7 +36500,10 @@ class UnifiedBalanceKit {
35870
36500
  * @see UnifiedBalanceKit.depositFor to deposit into another account.
35871
36501
  */
35872
36502
  async deposit(params) {
35873
- return deposit(this.context, params);
36503
+ return withErrorTelemetry(async () => deposit(this.context, params), EVENT_TYPES.DEPOSIT, this.telemetryConfig, {
36504
+ sourceChain: resolveChainName(params.from?.chain),
36505
+ tokenIn: params.token ?? 'USDC',
36506
+ });
35874
36507
  }
35875
36508
  /**
35876
36509
  * Deposit USDC into another account (not the caller's).
@@ -35882,7 +36515,10 @@ class UnifiedBalanceKit {
35882
36515
  * @see UnifiedBalanceKit.deposit to deposit into your own account.
35883
36516
  */
35884
36517
  async depositFor(params) {
35885
- return depositFor(this.context, params);
36518
+ return withErrorTelemetry(async () => depositFor(this.context, params), EVENT_TYPES.DEPOSIT_FOR, this.telemetryConfig, {
36519
+ sourceChain: resolveChainName(params.from?.chain),
36520
+ tokenIn: params.token ?? 'USDC',
36521
+ });
35886
36522
  }
35887
36523
  /**
35888
36524
  * Spend (mint) USDC on a destination chain by pulling funds from one
@@ -35895,7 +36531,17 @@ class UnifiedBalanceKit {
35895
36531
  * @see UnifiedBalanceKit.estimateSpend to preview fees before spending.
35896
36532
  */
35897
36533
  async spend(params) {
35898
- return spend(this.context, params);
36534
+ const destChain = 'to' in params
36535
+ ? resolveChainName(params.to.chain)
36536
+ : undefined;
36537
+ const tokenIn = 'token' in params
36538
+ ? (params.token ?? 'USDC')
36539
+ : 'USDC';
36540
+ return withErrorTelemetry(async () => spend(this.context, params), EVENT_TYPES.SPEND, this.telemetryConfig, {
36541
+ sourceChain: UnifiedBalanceKit.extractSpendSourceChains(params),
36542
+ ...(destChain != null && { destinationChain: destChain }),
36543
+ tokenIn,
36544
+ });
35899
36545
  }
35900
36546
  /**
35901
36547
  * Estimate the fees for a spend operation without executing it.
@@ -35905,7 +36551,17 @@ class UnifiedBalanceKit {
35905
36551
  * @returns Promise resolving to the fee estimate.
35906
36552
  */
35907
36553
  async estimateSpend(params) {
35908
- return estimateSpend(this.context, params);
36554
+ const destChain = 'to' in params
36555
+ ? resolveChainName(params.to.chain)
36556
+ : undefined;
36557
+ const sourceChain = UnifiedBalanceKit.extractSpendSourceChains(params);
36558
+ return withErrorTelemetry(async () => estimateSpend(this.context, params), EVENT_TYPES.ESTIMATE_SPEND, this.telemetryConfig, {
36559
+ ...(sourceChain != null && { sourceChain }),
36560
+ ...(destChain != null && { destinationChain: destChain }),
36561
+ tokenIn: 'token' in params
36562
+ ? (params.token ?? 'USDC')
36563
+ : 'USDC',
36564
+ });
35909
36565
  }
35910
36566
  /**
35911
36567
  * Fetch aggregated and per-chain balances for one or more accounts.
@@ -35914,7 +36570,7 @@ class UnifiedBalanceKit {
35914
36570
  * @returns Promise resolving to the aggregated balance result.
35915
36571
  */
35916
36572
  async getBalances(params) {
35917
- return getBalances(this.context, params);
36573
+ return withErrorTelemetry(async () => getBalances(this.context, params), EVENT_TYPES.GET_BALANCES, this.telemetryConfig);
35918
36574
  }
35919
36575
  /**
35920
36576
  * Grant spending rights to another address on the owner's account.
@@ -35929,7 +36585,7 @@ class UnifiedBalanceKit {
35929
36585
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35930
36586
  */
35931
36587
  async addDelegate(params) {
35932
- return addDelegate(this.context, params);
36588
+ return withErrorTelemetry(async () => addDelegate(this.context, params), EVENT_TYPES.ADD_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35933
36589
  }
35934
36590
  /**
35935
36591
  * Revoke spending rights from a delegate on the owner's account.
@@ -35944,7 +36600,7 @@ class UnifiedBalanceKit {
35944
36600
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35945
36601
  */
35946
36602
  async removeDelegate(params) {
35947
- return removeDelegate(this.context, params);
36603
+ return withErrorTelemetry(async () => removeDelegate(this.context, params), EVENT_TYPES.REMOVE_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35948
36604
  }
35949
36605
  /**
35950
36606
  * Kick off a delayed fund removal from an account.
@@ -35956,7 +36612,10 @@ class UnifiedBalanceKit {
35956
36612
  * @see UnifiedBalanceKit.removeFund to complete the fund removal.
35957
36613
  */
35958
36614
  async initiateRemoveFund(params) {
35959
- return initiateRemoveFund(this.context, params);
36615
+ return withErrorTelemetry(async () => initiateRemoveFund(this.context, params), EVENT_TYPES.INITIATE_REMOVE_FUND, this.telemetryConfig, {
36616
+ sourceChain: resolveChainName(params.from?.chain),
36617
+ tokenIn: params.token ?? 'USDC',
36618
+ });
35960
36619
  }
35961
36620
  /**
35962
36621
  * Complete a fund removal once the activation period has passed.
@@ -35968,7 +36627,10 @@ class UnifiedBalanceKit {
35968
36627
  * @see UnifiedBalanceKit.initiateRemoveFund to start the process.
35969
36628
  */
35970
36629
  async removeFund(params) {
35971
- return removeFund(this.context, params);
36630
+ return withErrorTelemetry(async () => removeFund(this.context, params), EVENT_TYPES.REMOVE_FUND, this.telemetryConfig, {
36631
+ sourceChain: resolveChainName(params.from?.chain),
36632
+ tokenIn: params.token ?? 'USDC',
36633
+ });
35972
36634
  }
35973
36635
  /**
35974
36636
  * Check the finality-aware delegate status of an address.
@@ -35996,7 +36658,7 @@ class UnifiedBalanceKit {
35996
36658
  * ```
35997
36659
  */
35998
36660
  async getDelegateStatus(params) {
35999
- return getDelegateStatus(this.context, params);
36661
+ return withErrorTelemetry(async () => getDelegateStatus(this.context, params), EVENT_TYPES.GET_DELEGATE_STATUS, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
36000
36662
  }
36001
36663
  /**
36002
36664
  * Get all chains supported by the kit.
@@ -36486,8 +37148,18 @@ class AppKit {
36486
37148
  * ```
36487
37149
  */
36488
37150
  constructor(config = {}) {
36489
- this.context = createContext(config);
36490
- this.unifiedBalance = new AppKitUnifiedBalance(config.unifiedBalance);
37151
+ this.context = createContext({
37152
+ ...config,
37153
+ ...(config.disableErrorReporting != null && {
37154
+ disableErrorReporting: config.disableErrorReporting,
37155
+ }),
37156
+ });
37157
+ this.unifiedBalance = new AppKitUnifiedBalance({
37158
+ ...config.unifiedBalance,
37159
+ ...(config.disableErrorReporting != null && {
37160
+ disableErrorReporting: config.disableErrorReporting,
37161
+ }),
37162
+ });
36491
37163
  }
36492
37164
  /**
36493
37165
  * Execute a cross-chain USDC bridge transfer.