@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.cjs CHANGED
@@ -3405,6 +3405,8 @@ exports.Blockchain = void 0;
3405
3405
  Blockchain["Hedera_Testnet"] = "Hedera_Testnet";
3406
3406
  Blockchain["HyperEVM"] = "HyperEVM";
3407
3407
  Blockchain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
3408
+ Blockchain["Injective"] = "Injective";
3409
+ Blockchain["Injective_Testnet"] = "Injective_Testnet";
3408
3410
  Blockchain["Ink"] = "Ink";
3409
3411
  Blockchain["Ink_Testnet"] = "Ink_Testnet";
3410
3412
  Blockchain["Linea"] = "Linea";
@@ -3624,6 +3626,7 @@ exports.BridgeChain = void 0;
3624
3626
  BridgeChain["Edge"] = "Edge";
3625
3627
  BridgeChain["Ethereum"] = "Ethereum";
3626
3628
  BridgeChain["HyperEVM"] = "HyperEVM";
3629
+ BridgeChain["Injective"] = "Injective";
3627
3630
  BridgeChain["Ink"] = "Ink";
3628
3631
  BridgeChain["Linea"] = "Linea";
3629
3632
  BridgeChain["Monad"] = "Monad";
@@ -3647,6 +3650,7 @@ exports.BridgeChain = void 0;
3647
3650
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
3648
3651
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
3649
3652
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
3653
+ BridgeChain["Injective_Testnet"] = "Injective_Testnet";
3650
3654
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
3651
3655
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
3652
3656
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
@@ -5091,6 +5095,98 @@ const HyperEVMTestnet = defineChain({
5091
5095
  },
5092
5096
  });
5093
5097
 
5098
+ /**
5099
+ * Injective Mainnet chain definition
5100
+ * @remarks
5101
+ * This represents the official production network for the Injective blockchain.
5102
+ * Injective is a high-performance, interoperable Layer-1 blockchain built for
5103
+ * finance, with an EVM execution layer on top of a Cosmos SDK base and
5104
+ * sub-second block finality.
5105
+ */
5106
+ const Injective = defineChain({
5107
+ type: 'evm',
5108
+ chain: exports.Blockchain.Injective,
5109
+ name: 'Injective',
5110
+ title: 'Injective Mainnet',
5111
+ nativeCurrency: {
5112
+ name: 'Injective',
5113
+ symbol: 'INJ',
5114
+ decimals: 18,
5115
+ },
5116
+ chainId: 1776,
5117
+ isTestnet: false,
5118
+ explorerUrl: 'https://injscan.com/transaction/{hash}',
5119
+ rpcEndpoints: ['https://sentry.evm-rpc.injective.network'],
5120
+ eurcAddress: null,
5121
+ usdcAddress: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
5122
+ usdtAddress: null,
5123
+ cctp: {
5124
+ domain: 29,
5125
+ contracts: {
5126
+ v2: {
5127
+ type: 'split',
5128
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5129
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5130
+ confirmations: 1,
5131
+ fastConfirmations: 1,
5132
+ },
5133
+ },
5134
+ forwarderSupported: {
5135
+ source: false,
5136
+ destination: false,
5137
+ },
5138
+ },
5139
+ kitContracts: {
5140
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5141
+ },
5142
+ });
5143
+
5144
+ /**
5145
+ * Injective Testnet chain definition
5146
+ * @remarks
5147
+ * This represents the official test network for the Injective blockchain.
5148
+ * Injective is a high-performance, interoperable Layer-1 blockchain built for
5149
+ * finance, with an EVM execution layer on top of a Cosmos SDK base and
5150
+ * sub-second block finality.
5151
+ */
5152
+ const InjectiveTestnet = defineChain({
5153
+ type: 'evm',
5154
+ chain: exports.Blockchain.Injective_Testnet,
5155
+ name: 'Injective Testnet',
5156
+ title: 'Injective Testnet',
5157
+ nativeCurrency: {
5158
+ name: 'Injective',
5159
+ symbol: 'INJ',
5160
+ decimals: 18,
5161
+ },
5162
+ chainId: 1439,
5163
+ isTestnet: true,
5164
+ explorerUrl: 'https://testnet.explorer.injective.network/transaction/{hash}',
5165
+ rpcEndpoints: ['https://k8s.testnet.json-rpc.injective.network'],
5166
+ eurcAddress: null,
5167
+ usdcAddress: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
5168
+ usdtAddress: null,
5169
+ cctp: {
5170
+ domain: 29,
5171
+ contracts: {
5172
+ v2: {
5173
+ type: 'split',
5174
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5175
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5176
+ confirmations: 1,
5177
+ fastConfirmations: 1,
5178
+ },
5179
+ },
5180
+ forwarderSupported: {
5181
+ source: false,
5182
+ destination: false,
5183
+ },
5184
+ },
5185
+ kitContracts: {
5186
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
5187
+ },
5188
+ });
5189
+
5094
5190
  /**
5095
5191
  * Ink Mainnet chain definition
5096
5192
  * @remarks
@@ -6933,6 +7029,8 @@ var Chains = {
6933
7029
  HederaTestnet: HederaTestnet,
6934
7030
  HyperEVM: HyperEVM,
6935
7031
  HyperEVMTestnet: HyperEVMTestnet,
7032
+ Injective: Injective,
7033
+ InjectiveTestnet: InjectiveTestnet,
6936
7034
  Ink: Ink,
6937
7035
  InkTestnet: InkTestnet,
6938
7036
  Linea: Linea,
@@ -7554,6 +7652,44 @@ function resolveChainIdentifier(chainIdentifier) {
7554
7652
  throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
7555
7653
  }
7556
7654
 
7655
+ /**
7656
+ * Resolve a chain identifier to a plain chain-name string.
7657
+ *
7658
+ * Accept a string literal (`'Ethereum'`), a `ChainDefinition`-like
7659
+ * object (`{ chain: 'Ethereum' }`), or `null`/`undefined` and return
7660
+ * the chain name as a string. Return `undefined` when the value
7661
+ * cannot be resolved.
7662
+ *
7663
+ * @remarks
7664
+ * Unlike `resolveChainIdentifier` (which returns a full `ChainDefinition`
7665
+ * and throws on invalid input), this helper is intentionally lenient and
7666
+ * never throws — it is safe to call in error-handling and telemetry paths.
7667
+ *
7668
+ * @param value - A string, chain-definition object, or nullish value.
7669
+ * @returns The chain name string, or `undefined`.
7670
+ *
7671
+ * @example
7672
+ * ```typescript
7673
+ * import { resolveChainName } from '@core/chains'
7674
+ *
7675
+ * resolveChainName('Ethereum') // 'Ethereum'
7676
+ * resolveChainName({ chain: 'Ethereum' }) // 'Ethereum'
7677
+ * resolveChainName(undefined) // undefined
7678
+ * ```
7679
+ */
7680
+ function resolveChainName(value) {
7681
+ if (value == null)
7682
+ return undefined;
7683
+ if (typeof value === 'string')
7684
+ return value;
7685
+ if (typeof value === 'object' &&
7686
+ 'chain' in value &&
7687
+ typeof value.chain === 'string') {
7688
+ return value.chain;
7689
+ }
7690
+ return undefined;
7691
+ }
7692
+
7557
7693
  /**
7558
7694
  * @packageDocumentation
7559
7695
  * @module SwapTokenUtils
@@ -9210,6 +9346,7 @@ const USDC = {
9210
9346
  [exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9211
9347
  [exports.Blockchain.Hedera]: '0.0.456858',
9212
9348
  [exports.Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
9349
+ [exports.Blockchain.Injective]: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
9213
9350
  [exports.Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
9214
9351
  [exports.Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
9215
9352
  [exports.Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
@@ -9242,6 +9379,7 @@ const USDC = {
9242
9379
  [exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9243
9380
  [exports.Blockchain.Hedera_Testnet]: '0.0.429274',
9244
9381
  [exports.Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
9382
+ [exports.Blockchain.Injective_Testnet]: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
9245
9383
  [exports.Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
9246
9384
  [exports.Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
9247
9385
  [exports.Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
@@ -10343,8 +10481,325 @@ async function retryAsync(fn, options) {
10343
10481
  throw new Error('retryAsync: unreachable');
10344
10482
  }
10345
10483
 
10484
+ /**
10485
+ * Default telemetry endpoint.
10486
+ *
10487
+ * Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
10488
+ * (e.g. for staging or local development).
10489
+ *
10490
+ * @internal
10491
+ */
10492
+ const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
10493
+ /**
10494
+ * Resolve the telemetry endpoint URL.
10495
+ *
10496
+ * @internal
10497
+ */
10498
+ function getLogsUrl() {
10499
+ if (isNodeEnvironment() &&
10500
+ typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' &&
10501
+ process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
10502
+ return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
10503
+ }
10504
+ return DEFAULT_LOGS_URL;
10505
+ }
10506
+ /**
10507
+ * Send a telemetry event to the proxy service.
10508
+ *
10509
+ * @remarks
10510
+ * Fire-and-forget: the returned promise is intentionally not awaited
10511
+ * by the caller. A fetch failure (network error, non-2xx, timeout)
10512
+ * is silently swallowed so telemetry never blocks or fails user
10513
+ * operations.
10514
+ *
10515
+ * @param payload - The structured log payload matching the server schema.
10516
+ *
10517
+ * @example
10518
+ * ```typescript
10519
+ * import { emitAnalyticsLog } from '@core/utils'
10520
+ *
10521
+ * // Fire-and-forget — do not await
10522
+ * void emitAnalyticsLog(payload)
10523
+ * ```
10524
+ */
10525
+ async function emitAnalyticsLog(payload) {
10526
+ try {
10527
+ const isNode = isNodeEnvironment();
10528
+ const userAgent = getUserAgent();
10529
+ await fetch(getLogsUrl(), {
10530
+ method: 'POST',
10531
+ headers: {
10532
+ 'Content-Type': 'application/json',
10533
+ // Browser restricts setting User-Agent; use X-User-Agent instead.
10534
+ ...(isNode
10535
+ ? { 'User-Agent': userAgent }
10536
+ : { 'X-User-Agent': userAgent }),
10537
+ },
10538
+ body: JSON.stringify(payload),
10539
+ signal: AbortSignal.timeout(5_000),
10540
+ });
10541
+ }
10542
+ catch {
10543
+ // Silently swallow — telemetry must never break user operations.
10544
+ }
10545
+ }
10546
+
10547
+ /**
10548
+ * Build the `clientContext` object for telemetry payloads.
10549
+ *
10550
+ * @remarks
10551
+ * Use the exported `getRuntime()` and `isNodeEnvironment()` from
10552
+ * `@core/utils` to detect the runtime environment. The returned
10553
+ * string is parsed into the structured `ClientContext` fields
10554
+ * expected by the server schema.
10555
+ *
10556
+ * @returns A {@link ClientContext} with platform, OS, and runtime name
10557
+ * populated from the current environment.
10558
+ *
10559
+ * @example
10560
+ * ```typescript
10561
+ * import { buildClientContext } from '@core/utils'
10562
+ *
10563
+ * const ctx = buildClientContext()
10564
+ * // Node: { platform: 'node', os: 'darwin', runtimeName: null }
10565
+ * // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
10566
+ * ```
10567
+ */
10568
+ function buildClientContext() {
10569
+ const runtime = getRuntime();
10570
+ if (runtime.startsWith('browser/')) {
10571
+ return {
10572
+ platform: 'browser',
10573
+ os: null,
10574
+ runtimeName: runtime.slice('browser/'.length).toLowerCase(),
10575
+ };
10576
+ }
10577
+ if (runtime.startsWith('node/')) {
10578
+ return {
10579
+ platform: 'node',
10580
+ os: isNodeEnvironment() ? process.platform : null,
10581
+ runtimeName: null,
10582
+ };
10583
+ }
10584
+ return {
10585
+ platform: 'node',
10586
+ os: null,
10587
+ runtimeName: null,
10588
+ };
10589
+ }
10590
+
10591
+ /**
10592
+ * Extract structured error details from an unknown error value.
10593
+ *
10594
+ * @remarks
10595
+ * Handle three cases:
10596
+ * - `KitError` — extract `code` and `name`.
10597
+ * - `Error` — extract `name`.
10598
+ * - Anything else — return empty details.
10599
+ *
10600
+ * Only structured, bounded fields (`errorCode`, `errorType`) are
10601
+ * included. Free-text fields (`message`, `stack`) are intentionally
10602
+ * omitted to avoid leaking secrets or PII through vendor telemetry.
10603
+ *
10604
+ * @param error - The thrown value to extract details from.
10605
+ * @returns A {@link ErrorDetails} object suitable for telemetry payloads.
10606
+ *
10607
+ * @example
10608
+ * ```typescript
10609
+ * import { extractErrorDetails } from '@core/utils'
10610
+ *
10611
+ * try {
10612
+ * await riskyOperation()
10613
+ * } catch (error) {
10614
+ * const details = extractErrorDetails(error)
10615
+ * // { errorCode: '1001', errorType: 'INPUT_NETWORK_MISMATCH' }
10616
+ * }
10617
+ * ```
10618
+ */
10619
+ function extractErrorDetails(error) {
10620
+ if (error instanceof KitError) {
10621
+ return {
10622
+ errorCode: String(error.code),
10623
+ errorType: error.name,
10624
+ };
10625
+ }
10626
+ if (error instanceof Error) {
10627
+ return {
10628
+ errorType: error.name,
10629
+ };
10630
+ }
10631
+ return {};
10632
+ }
10633
+
10634
+ /**
10635
+ * Register event handlers on an action dispatcher that emit analytics
10636
+ * telemetry for the configured events.
10637
+ *
10638
+ * @remarks
10639
+ * Subscribe to each action name in `actionEventMap`, call `mapEventToPayload`
10640
+ * for each event, and fire-and-forget `emitAnalyticsLog` if a non-null
10641
+ * payload is returned. All errors are silently swallowed so telemetry
10642
+ * never blocks or fails user operations.
10643
+ *
10644
+ * @param dispatcher - The `Actionable` instance from the kit.
10645
+ * @param sdkName - SDK package name (e.g. `'unified-balance-kit'`).
10646
+ * @param sdkVersion - SDK version string (e.g. `'1.0.0'`).
10647
+ * @param actionEventMap - Map of action names to subscribe to.
10648
+ * @param mapEventToPayload - Function that maps an action name and payload
10649
+ * to a {@link ClientLogPayload}, or `null` to skip logging.
10650
+ *
10651
+ * @example
10652
+ * ```typescript
10653
+ * import { registerTelemetryHandler } from '@core/utils'
10654
+ *
10655
+ * registerTelemetryHandler(
10656
+ * dispatcher,
10657
+ * 'unified-balance-kit',
10658
+ * '1.0.0',
10659
+ * VERB_EVENT_MAP,
10660
+ * mapSucceededEventToLog,
10661
+ * )
10662
+ * ```
10663
+ */
10664
+ function registerTelemetryHandler$1(dispatcher, sdkName, sdkVersion, actionEventMap, mapEventToPayload) {
10665
+ for (const actionName of Object.keys(actionEventMap)) {
10666
+ dispatcher.on(actionName, (payload) => {
10667
+ try {
10668
+ const log = mapEventToPayload(actionName, payload, sdkName, sdkVersion);
10669
+ if (log) {
10670
+ // Fire-and-forget — intentionally not awaited.
10671
+ void emitAnalyticsLog(log);
10672
+ }
10673
+ }
10674
+ catch {
10675
+ // Silently swallow — telemetry must never break user operations.
10676
+ }
10677
+ });
10678
+ }
10679
+ }
10680
+
10681
+ /**
10682
+ * Strip the `@circle-fin/` scope from a kit package name to produce the
10683
+ * short SDK name used in telemetry payloads.
10684
+ *
10685
+ * @param pkgName - The full npm package name (e.g. `@circle-fin/bridge-kit`).
10686
+ * @returns The unscoped kit name (e.g. `bridge-kit`).
10687
+ *
10688
+ * @example
10689
+ * ```typescript
10690
+ * import pkg from '../../package.json'
10691
+ * import { resolveKitSdkName } from '@core/utils'
10692
+ *
10693
+ * const SDK_NAME = resolveKitSdkName(pkg.name) // 'bridge-kit'
10694
+ * ```
10695
+ */
10696
+ function resolveKitSdkName(pkgName) {
10697
+ return pkgName.replace('@circle-fin/', '');
10698
+ }
10699
+
10700
+ /**
10701
+ * Build a telemetry payload from common fields.
10702
+ *
10703
+ * @internal
10704
+ */
10705
+ function buildPayload$1(config, eventType, errorDetails, context) {
10706
+ return {
10707
+ sdkName: config.sdkName,
10708
+ sdkVersion: config.sdkVersion,
10709
+ eventType,
10710
+ timestamp: new Date().toISOString(),
10711
+ errorDetails,
10712
+ clientContext: buildClientContext(),
10713
+ ...(context?.sourceChain != null && {
10714
+ sourceChain: context.sourceChain,
10715
+ }),
10716
+ ...(context?.destinationChain != null && {
10717
+ destinationChain: context.destinationChain,
10718
+ }),
10719
+ ...(context?.tokenIn != null && { tokenIn: context.tokenIn }),
10720
+ ...(context?.tokenOut != null && { tokenOut: context.tokenOut }),
10721
+ };
10722
+ }
10723
+ /**
10724
+ * Wrap an async operation with error telemetry.
10725
+ *
10726
+ * Execute `fn` and, if it throws, emit an error telemetry payload
10727
+ * before re-throwing. No-ops when `config.disabled` is `true`.
10728
+ *
10729
+ * @param fn - The async operation to execute.
10730
+ * @param eventType - The telemetry event type for this operation.
10731
+ * @param config - Per-kit SDK identity and disabled flag.
10732
+ * @param context - Optional chain/token context.
10733
+ * @returns The result of the operation.
10734
+ * @throws Re-throws any error after emitting telemetry.
10735
+ *
10736
+ * @example
10737
+ * ```typescript
10738
+ * import { withErrorTelemetry } from '@core/utils'
10739
+ *
10740
+ * const result = await withErrorTelemetry(
10741
+ * () => provider.bridge(params),
10742
+ * 'bridge_bridge',
10743
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
10744
+ * { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
10745
+ * )
10746
+ * ```
10747
+ */
10748
+ async function withErrorTelemetry(fn, eventType, config, context) {
10749
+ try {
10750
+ return await fn();
10751
+ }
10752
+ catch (error) {
10753
+ if (!config.disabled) {
10754
+ void emitAnalyticsLog(buildPayload$1(config, eventType, extractErrorDetails(error), context));
10755
+ }
10756
+ throw error;
10757
+ }
10758
+ }
10759
+ /**
10760
+ * Emit error telemetry for a result-reported step error.
10761
+ *
10762
+ * Handle the case where a provider reports a step failure inside a
10763
+ * result object (e.g. `BridgeResult.state === 'error'`) rather than
10764
+ * throwing. The caller extracts the failed step as a simple
10765
+ * {@link FailedStepInfo} — this function handles sanitization,
10766
+ * step-to-event mapping, and emission.
10767
+ *
10768
+ * No-ops when `config.disabled` is `true`.
10769
+ *
10770
+ * @param failedStep - The failed step info, or undefined if none found.
10771
+ * @param stepEventMap - Ordered mapping from step names to event types.
10772
+ * @param fallbackEventType - Event type when step is not in the map.
10773
+ * @param config - Per-kit SDK identity and disabled flag.
10774
+ * @param context - Optional chain/token context.
10775
+ *
10776
+ * @example
10777
+ * ```typescript
10778
+ * import { emitResultStepErrorTelemetry } from '@core/utils'
10779
+ *
10780
+ * const failedStep = result.steps.find((s) => s.state === 'error')
10781
+ * emitResultStepErrorTelemetry(
10782
+ * failedStep,
10783
+ * BRIDGE_STEP_EVENT_MAP,
10784
+ * 'bridge_bridge',
10785
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
10786
+ * { sourceChain: 'Ethereum', tokenIn: 'USDC' },
10787
+ * )
10788
+ * ```
10789
+ */
10790
+ function emitResultStepErrorTelemetry(failedStep, stepEventMap, fallbackEventType, config, context) {
10791
+ if (config.disabled) {
10792
+ return;
10793
+ }
10794
+ const stepEntry = stepEventMap.find(([name]) => name === failedStep?.name);
10795
+ const errorDetails = {
10796
+ ...(failedStep?.name != null && { errorType: failedStep.name }),
10797
+ };
10798
+ void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
10799
+ }
10800
+
10346
10801
  var name$2 = "@circle-fin/bridge-kit";
10347
- var version$3 = "1.9.0";
10802
+ var version$3 = "1.10.0";
10348
10803
  var pkg$3 = {
10349
10804
  name: name$2,
10350
10805
  version: version$3};
@@ -13309,18 +13764,16 @@ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet) => {
13309
13764
  };
13310
13765
  /**
13311
13766
  * Fetches attestation data from the IRIS API with retry and timeout handling.
13312
- * Since fetching starts after a confirmed burn transaction, we attempt up to 10 retries.
13313
- * Implements a conservative delay between requests to respect the API rate
13314
- * limit of 35 requests per second. To avoid rate limiting when multiple processes are
13315
- * running concurrently, we enforce a 200ms delay between retries.
13316
13767
  *
13317
- * Each HTTP request is given a maximum of 2 seconds before it is aborted.
13318
- * We retry up to 10 times, waiting 200ms between attempts.
13768
+ * Polls the IRIS API until a complete attestation is available. The default
13769
+ * window is sized for slow source chains where finality may take many
13770
+ * confirmations.
13319
13771
  *
13320
- * The total maximum time this function might take (worst case) is:
13321
- * - Perattempt timeout: 2 000 ms
13322
- * - Retry delays: 9 × 200 ms = 1 800 ms
13323
- * - Total max time: 2 000 ms + 1 800 ms = 3 800 ms
13772
+ * Defaults (see `DEFAULT_CONFIG`):
13773
+ * - Per-attempt timeout: 2 000 ms (each HTTP request aborts after 2 s)
13774
+ * - Retry delay: 2 000 ms between attempts
13775
+ * - Max retries: 600 (30 × 20)
13776
+ * - Total worst-case polling window: 600 × (2 000 ms + 2 000 ms) ≈ 40 minutes
13324
13777
  *
13325
13778
  * @param sourceDomainId - The CCTP domain ID.
13326
13779
  * @param transactionHash - The transaction hash to fetch attestation for.
@@ -15297,7 +15750,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCo
15297
15750
  return step;
15298
15751
  }
15299
15752
 
15300
- var version$2 = "1.7.0";
15753
+ var version$2 = "1.8.1";
15301
15754
  var pkg$2 = {
15302
15755
  version: version$2};
15303
15756
 
@@ -16963,6 +17416,35 @@ const formatBridgeResult = (result, formatDirection) => {
16963
17416
  };
16964
17417
  };
16965
17418
 
17419
+ /**
17420
+ * Telemetry event type identifiers for bridge-kit operations.
17421
+ *
17422
+ * @internal
17423
+ */
17424
+ const BRIDGE_EVENT_TYPES = {
17425
+ BRIDGE: 'bridge_bridge',
17426
+ RETRY: 'bridge_retry',
17427
+ ESTIMATE: 'bridge_estimate',
17428
+ };
17429
+ /**
17430
+ * Ordered mapping from provider step event names to telemetry event types.
17431
+ *
17432
+ * @remarks
17433
+ * The order matches the CCTP v2 bridge execution sequence. During
17434
+ * `bridge()`, completed step events are counted so the failing step
17435
+ * can be identified by its index.
17436
+ *
17437
+ * @internal
17438
+ */
17439
+ const BRIDGE_STEP_EVENT_MAP = [
17440
+ ['approve', 'bridge_approve'],
17441
+ ['burn', 'bridge_burn'],
17442
+ ['fetchAttestation', 'bridge_fetch_attestation'],
17443
+ ['mint', 'bridge_mint'],
17444
+ ];
17445
+
17446
+ /** SDK name used in telemetry payloads. */
17447
+ const SDK_NAME$2 = resolveKitSdkName(pkg$3.name);
16966
17448
  /**
16967
17449
  * BridgeKit caller component for retry and estimate operations.
16968
17450
  */
@@ -17046,6 +17528,10 @@ class BridgeKit {
17046
17528
  * A custom fee policy for the kit.
17047
17529
  */
17048
17530
  customFeePolicy;
17531
+ /** Whether error telemetry is disabled. */
17532
+ disableErrorReporting;
17533
+ /** Per-kit telemetry identity for shared helpers. */
17534
+ telemetryConfig;
17049
17535
  /**
17050
17536
  * Create a new BridgeKit instance.
17051
17537
  *
@@ -17063,6 +17549,12 @@ class BridgeKit {
17063
17549
  const defaultProviders = getDefaultProviders$2();
17064
17550
  this.providers = [...defaultProviders, ...(config.providers ?? [])];
17065
17551
  this.actionDispatcher = new Actionable();
17552
+ this.disableErrorReporting = config.disableErrorReporting === true;
17553
+ this.telemetryConfig = {
17554
+ sdkName: SDK_NAME$2,
17555
+ sdkVersion: pkg$3.version,
17556
+ disabled: this.disableErrorReporting,
17557
+ };
17066
17558
  for (const provider of this.providers) {
17067
17559
  provider.registerDispatcher(this.actionDispatcher);
17068
17560
  }
@@ -17136,19 +17628,36 @@ class BridgeKit {
17136
17628
  * ```
17137
17629
  */
17138
17630
  async bridge(params) {
17139
- // First validate the parameters
17140
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17141
- // Then resolve chain definitions (includes adapter chain support validation)
17142
- const resolvedParams = await resolveBridgeParams(params);
17143
- // Validate network compatibility
17144
- this.validateNetworkCompatibility(resolvedParams);
17145
- // Merge the custom fee config into the resolved params
17146
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17147
- // Find a provider that supports this route
17148
- const provider = this.findProviderForRoute(finalResolvedParams);
17149
- // Execute the transfer using the provider
17150
- // Format the bridge result into human-readable string values for the user
17151
- return formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
17631
+ return withErrorTelemetry(async () => {
17632
+ // First validate the parameters
17633
+ assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17634
+ // Then resolve chain definitions (includes adapter chain support validation)
17635
+ const resolvedParams = await resolveBridgeParams(params);
17636
+ // Validate network compatibility
17637
+ this.validateNetworkCompatibility(resolvedParams);
17638
+ // Merge the custom fee config into the resolved params
17639
+ const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17640
+ // Find a provider that supports this route
17641
+ const provider = this.findProviderForRoute(finalResolvedParams);
17642
+ // Execute the transfer using the provider
17643
+ // Format the bridge result into human-readable string values for the user
17644
+ const result = formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
17645
+ // Emit error telemetry when the provider returns an error state
17646
+ // (provider records step failures in the result instead of throwing).
17647
+ if (result.state === 'error') {
17648
+ const failedStep = result.steps.find((s) => s.state === 'error');
17649
+ emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.BRIDGE, this.telemetryConfig, {
17650
+ sourceChain: resolveChainName(params.from.chain),
17651
+ destinationChain: resolveChainName(params.to.chain),
17652
+ tokenIn: params.token,
17653
+ });
17654
+ }
17655
+ return result;
17656
+ }, BRIDGE_EVENT_TYPES.BRIDGE, this.telemetryConfig, {
17657
+ sourceChain: resolveChainName(params.from.chain),
17658
+ destinationChain: resolveChainName(params.to.chain),
17659
+ tokenIn: params.token,
17660
+ });
17152
17661
  }
17153
17662
  /**
17154
17663
  * Retry a failed or incomplete cross-chain USDC bridge operation.
@@ -17220,17 +17729,33 @@ class BridgeKit {
17220
17729
  * ```
17221
17730
  */
17222
17731
  async retry(result, context, invocationMeta) {
17223
- const provider = this.providers.find((p) => p.name === result.provider);
17224
- if (!provider) {
17225
- throw new Error(`Provider ${result.provider} not found`);
17226
- }
17227
- // Merge BridgeKit caller into invocation metadata for retry operation
17228
- const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
17229
- // Format the bridge result into bigint string values for internal use
17230
- const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
17231
- // Execute the retry using the provider
17232
- // Format the bridge result into human-readable string values for the user
17233
- return formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
17732
+ return withErrorTelemetry(async () => {
17733
+ const provider = this.providers.find((p) => p.name === result.provider);
17734
+ if (!provider) {
17735
+ throw new Error(`Provider ${result.provider} not found`);
17736
+ }
17737
+ // Merge BridgeKit caller into invocation metadata for retry operation
17738
+ const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
17739
+ // Format the bridge result into bigint string values for internal use
17740
+ const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
17741
+ // Execute the retry using the provider
17742
+ // Format the bridge result into human-readable string values for the user
17743
+ const retryResult = formatBridgeResult(await provider.retry(formattedBridgeResultInternal, context, mergedMeta), 'to-human-readable');
17744
+ // Emit error telemetry when the provider returns an error state.
17745
+ if (retryResult.state === 'error') {
17746
+ const failedStep = retryResult.steps.find((s) => s.state === 'error');
17747
+ emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
17748
+ sourceChain: result.source.chain.chain,
17749
+ destinationChain: result.destination.chain.chain,
17750
+ tokenIn: result.token,
17751
+ });
17752
+ }
17753
+ return retryResult;
17754
+ }, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
17755
+ sourceChain: result.source.chain.chain,
17756
+ destinationChain: result.destination.chain.chain,
17757
+ tokenIn: result.token,
17758
+ });
17234
17759
  }
17235
17760
  /**
17236
17761
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -17269,18 +17794,24 @@ class BridgeKit {
17269
17794
  * ```
17270
17795
  */
17271
17796
  async estimate(params) {
17272
- // First validate the parameters
17273
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17274
- // Then resolve chain definitions (includes adapter chain support validation)
17275
- const resolvedParams = await resolveBridgeParams(params);
17276
- // Validate network compatibility
17277
- this.validateNetworkCompatibility(resolvedParams);
17278
- // Merge the custom fee config into the resolved params
17279
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17280
- // Find a provider that supports this route
17281
- const provider = this.findProviderForRoute(finalResolvedParams);
17282
- // Estimate the transfer using the provider and format amounts to human-readable strings
17283
- return formatBridgeResult(await provider.estimate(finalResolvedParams), 'to-human-readable');
17797
+ return withErrorTelemetry(async () => {
17798
+ // First validate the parameters
17799
+ assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
17800
+ // Then resolve chain definitions (includes adapter chain support validation)
17801
+ const resolvedParams = await resolveBridgeParams(params);
17802
+ // Validate network compatibility
17803
+ this.validateNetworkCompatibility(resolvedParams);
17804
+ // Merge the custom fee config into the resolved params
17805
+ const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
17806
+ // Find a provider that supports this route
17807
+ const provider = this.findProviderForRoute(finalResolvedParams);
17808
+ // Estimate the transfer using the provider and format amounts to human-readable strings
17809
+ return formatBridgeResult(await provider.estimate(finalResolvedParams), 'to-human-readable');
17810
+ }, BRIDGE_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
17811
+ sourceChain: resolveChainName(params.from.chain),
17812
+ destinationChain: resolveChainName(params.to.chain),
17813
+ tokenIn: params.token,
17814
+ });
17284
17815
  }
17285
17816
  /**
17286
17817
  * Get all chains supported by any provider in the kit, with optional filtering.
@@ -17597,7 +18128,11 @@ const createBridgeKit = (context) => {
17597
18128
  const getFee = context.getFee?.bind(context);
17598
18129
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
17599
18130
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
17600
- const kit = new BridgeKit();
18131
+ const kit = new BridgeKit({
18132
+ ...(context.disableErrorReporting != null && {
18133
+ disableErrorReporting: context.disableErrorReporting,
18134
+ }),
18135
+ });
17601
18136
  if (hasBoth) {
17602
18137
  kit.setCustomFeePolicy({
17603
18138
  calculateFee: async (params) => {
@@ -17614,7 +18149,7 @@ const createBridgeKit = (context) => {
17614
18149
  };
17615
18150
 
17616
18151
  var name$1 = "@circle-fin/swap-kit";
17617
- var version$1 = "1.1.1";
18152
+ var version$1 = "1.2.1";
17618
18153
  var pkg$1 = {
17619
18154
  name: name$1,
17620
18155
  version: version$1};
@@ -21494,6 +22029,119 @@ function evmSigningData(burnIntent) {
21494
22029
  };
21495
22030
  }
21496
22031
 
22032
+ /**
22033
+ * EIP-7702 delegation indicator. A 7702-delegated EOA's bytecode is
22034
+ * `0xef0100` followed by the 20-byte delegate address (23 bytes total).
22035
+ * The underlying secp256k1 key still produces `ecrecover`-verifiable
22036
+ * signatures, so for Gateway's purposes a 7702-delegated address is
22037
+ * an EOA, not an SCA.
22038
+ *
22039
+ * Spec: https://eips.ethereum.org/EIPS/eip-7702
22040
+ */
22041
+ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
22042
+ /**
22043
+ * Assert that `address` on `chain` can sign Gateway burn intents.
22044
+ *
22045
+ * Gateway verifies burn-intent signatures with plain `ecrecover` (see
22046
+ * `evm-gateway-contracts/src/lib/EIP712Domain.sol`). Smart-contract
22047
+ * accounts (SCAs) produce signatures over wrapped hashes (ERC-1271 /
22048
+ * ERC-6492 / ERC-6900 replay-safe hashes) that Gateway cannot verify.
22049
+ * Additionally, the Circle Wallets backend rejects SCA typed-data signing
22050
+ * against Gateway's chainId-less domain with an opaque
22051
+ * `invalid integer value <nil>/<nil> for type uint256` error.
22052
+ *
22053
+ * EIP-7702-delegated EOAs are exempt: they expose non-empty bytecode
22054
+ * (`0xef0100<delegate>`) but the underlying secp256k1 key still produces
22055
+ * `ecrecover`-verifiable signatures, so Gateway accepts them.
22056
+ *
22057
+ * When the signer is a true SCA, raises an `INPUT_UNSUPPORTED_ACTION`
22058
+ * error directing the caller to register an EOA delegate against the
22059
+ * SCA and then submit the spend with the delegate EOA as the signer
22060
+ * and the SCA as the source account. See the unified-balance / Gateway
22061
+ * docs for the exact API.
22062
+ *
22063
+ * If bytecode cannot be read (RPC failure, etc.) the pre-check is
22064
+ * skipped and downstream signing surfaces its own error — a warning is
22065
+ * logged so the skip is diagnosable.
22066
+ *
22067
+ * @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
22068
+ * @param address - Signer address to validate.
22069
+ * @param chain - EVM chain where the signer lives.
22070
+ * @throws {KitError} INPUT_UNSUPPORTED_ACTION when `address` is an SCA.
22071
+ *
22072
+ * @example
22073
+ * ```typescript
22074
+ * import { assertSignerIsEoa } from '@core/adapter-evm'
22075
+ * import { Ethereum } from '@core/chains'
22076
+ *
22077
+ * await assertSignerIsEoa(adapter, '0xabc...', Ethereum)
22078
+ * ```
22079
+ */
22080
+ async function assertSignerIsEoa(adapter, address, chain) {
22081
+ let code;
22082
+ try {
22083
+ code = await adapter.readBytecode(address, chain);
22084
+ }
22085
+ catch (err) {
22086
+ console.warn(`[gateway] assertSignerIsEoa skipped (readBytecode failed for ` +
22087
+ `${address} on ${chain.name}): ` +
22088
+ (err instanceof Error ? err.message : String(err)));
22089
+ return;
22090
+ }
22091
+ if (code === undefined ||
22092
+ code === '0x' ||
22093
+ code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
22094
+ return;
22095
+ }
22096
+ throw new KitError({
22097
+ ...InputError.UNSUPPORTED_ACTION,
22098
+ recoverability: 'FATAL',
22099
+ message: `Gateway burn-intent signing requires an EOA signer (Gateway ` +
22100
+ `verifies signatures with ecrecover and does not support ERC-1271). ` +
22101
+ `The signer ${address} on ${chain.name} has on-chain bytecode, ` +
22102
+ `indicating it is a smart-contract account (SCA). Register an EOA ` +
22103
+ `delegate against the SCA, then submit the spend with the delegate ` +
22104
+ `EOA as the signer and the SCA as the source account. See DEVX-2774.`,
22105
+ cause: {
22106
+ trace: {
22107
+ operation: 'signEvmIntentGroup.assertSignerIsEoa',
22108
+ address,
22109
+ chain: chain.name,
22110
+ bytecodeBytes: (code.length - 2) / 2,
22111
+ bytecodePrefix: code.slice(0, 12),
22112
+ },
22113
+ },
22114
+ });
22115
+ }
22116
+
22117
+ /**
22118
+ * Duck-type test for an EVM adapter that exposes `readBytecode`.
22119
+ *
22120
+ * Used as a structural guard at package boundaries (e.g. the Circle Wallets
22121
+ * hybrid adapter calling into a chain adapter, or the Gateway sign path
22122
+ * receiving an arbitrary adapter implementation) where `instanceof EvmAdapter`
22123
+ * is unreliable because each consumer bundles its own copy of the base class.
22124
+ *
22125
+ * @param value - The value to test.
22126
+ * @returns `true` when `value` is an object exposing a callable
22127
+ * `readBytecode` method, narrowed to {@link EvmAdapterLike}.
22128
+ *
22129
+ * @example
22130
+ * ```typescript
22131
+ * import { isEvmAdapterLike } from '@core/adapter-evm'
22132
+ *
22133
+ * if (isEvmAdapterLike(adapter)) {
22134
+ * const code = await adapter.readBytecode('0xabc...', chain)
22135
+ * }
22136
+ * ```
22137
+ */
22138
+ function isEvmAdapterLike(value) {
22139
+ return (typeof value === 'object' &&
22140
+ value !== null &&
22141
+ 'readBytecode' in value &&
22142
+ typeof value.readBytecode === 'function');
22143
+ }
22144
+
21497
22145
  /**
21498
22146
  * Sign an EVM adapter group: batches all intents and produces a single
21499
22147
  * EIP-712 ECDSA signature.
@@ -21501,6 +22149,12 @@ function evmSigningData(burnIntent) {
21501
22149
  * For a single-intent group, `primaryType` is `'BurnIntent'`.
21502
22150
  * For multi-intent groups, `primaryType` is `'BurnIntentSet'`.
21503
22151
  *
22152
+ * Before signing, asserts that the signer address is an EOA. Gateway
22153
+ * verifies burn-intent signatures with plain `ecrecover` (no ERC-1271
22154
+ * fallback), so signatures produced by smart-contract accounts (SCAs)
22155
+ * cannot be verified. When an SCA is detected, a clear error is raised
22156
+ * directing the caller to the delegate workflow (DEVX-2774).
22157
+ *
21504
22158
  * @param group - The adapter group containing the adapter, chain, and
21505
22159
  * burn intents to sign.
21506
22160
  * @returns A signed set with the intents and the ECDSA signature.
@@ -21521,6 +22175,22 @@ function evmSigningData(burnIntent) {
21521
22175
  async function signEvmIntentGroup(group) {
21522
22176
  const { adapter, intents: groupIntents, chain, address } = group;
21523
22177
  const operationContext = address === undefined ? { chain } : { chain, address };
22178
+ // Gateway verifies burn-intent signatures with plain ecrecover. An SCA
22179
+ // signer silently produces a signature over a wrapped hash that Gateway
22180
+ // cannot verify, and Circle Wallets' KMS rejects the typed data up front
22181
+ // with an opaque `<nil>/<nil>` error. Short-circuit with a clear message
22182
+ // when we can detect bytecode at the signer address. See DEVX-2774.
22183
+ //
22184
+ // Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
22185
+ // each consumer package bundles its own copy of the base class and the
22186
+ // `instanceof` identity check fails across package boundaries.
22187
+ //
22188
+ // Empty string is defended against because assertSignerIsEoa would
22189
+ // otherwise call eth_getCode('') on the RPC.
22190
+ const hasResolvedSigner = typeof address === 'string' && address.length > 0;
22191
+ if (hasResolvedSigner && chain.type === 'evm' && isEvmAdapterLike(adapter)) {
22192
+ await assertSignerIsEoa(adapter, address, chain);
22193
+ }
21524
22194
  const firstIntent = groupIntents[0];
21525
22195
  const typedData = groupIntents.length === 1 && firstIntent
21526
22196
  ? evmSigningData(firstIntent)
@@ -21790,9 +22460,10 @@ function getSolanaFeeRecipient() {
21790
22460
  return CIRCLE_FEE_RECIPIENT_SOLANA;
21791
22461
  }
21792
22462
 
21793
- Buffer.from('gateway_minter');
21794
- Buffer.from('gateway_minter_custody');
21795
- Buffer.from('used_transfer_spec_hash');
22463
+ const enc = new TextEncoder();
22464
+ enc.encode('gateway_minter');
22465
+ enc.encode('gateway_minter_custody');
22466
+ enc.encode('used_transfer_spec_hash');
21796
22467
 
21797
22468
  async function executeAndWait$1(adapter, request, chain) {
21798
22469
  if (request.type === 'noop') {
@@ -28075,6 +28746,18 @@ function createSwapKitContext(config = {}) {
28075
28746
  return context;
28076
28747
  }
28077
28748
 
28749
+ /**
28750
+ * Telemetry event type identifiers for swap-kit operations.
28751
+ *
28752
+ * @internal
28753
+ */
28754
+ const SWAP_EVENT_TYPES = {
28755
+ SWAP: 'swap_swap',
28756
+ ESTIMATE: 'swap_estimate',
28757
+ };
28758
+
28759
+ /** SDK name used in telemetry payloads. */
28760
+ const SDK_NAME$1 = resolveKitSdkName(pkg$1.name);
28078
28761
  /**
28079
28762
  * A high-level class-based interface for single-chain token swap operations.
28080
28763
  *
@@ -28146,6 +28829,10 @@ function createSwapKitContext(config = {}) {
28146
28829
  */
28147
28830
  class SwapKit {
28148
28831
  context;
28832
+ /** Whether error telemetry is disabled. */
28833
+ disableErrorReporting;
28834
+ /** Per-kit telemetry identity for shared helpers. */
28835
+ telemetryConfig;
28149
28836
  /**
28150
28837
  * Create a new SwapKit instance.
28151
28838
  *
@@ -28194,6 +28881,12 @@ class SwapKit {
28194
28881
  */
28195
28882
  constructor(config = {}) {
28196
28883
  this.context = createSwapKitContext(config);
28884
+ this.disableErrorReporting = config.disableErrorReporting === true;
28885
+ this.telemetryConfig = {
28886
+ sdkName: SDK_NAME$1,
28887
+ sdkVersion: pkg$1.version,
28888
+ disabled: this.disableErrorReporting,
28889
+ };
28197
28890
  }
28198
28891
  /**
28199
28892
  * Estimate the output amount and fees for a swap operation.
@@ -28235,7 +28928,11 @@ class SwapKit {
28235
28928
  * ```
28236
28929
  */
28237
28930
  async estimate(params) {
28238
- return estimate(this.context, params);
28931
+ return withErrorTelemetry(async () => estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
28932
+ sourceChain: resolveChainName(params.from.chain),
28933
+ tokenIn: params.tokenIn,
28934
+ tokenOut: params.tokenOut,
28935
+ });
28239
28936
  }
28240
28937
  /**
28241
28938
  * Execute a token swap operation on a single chain.
@@ -28291,7 +28988,11 @@ class SwapKit {
28291
28988
  * ```
28292
28989
  */
28293
28990
  async swap(params) {
28294
- return swap$1(this.context, params);
28991
+ return withErrorTelemetry(async () => swap$1(this.context, params), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, {
28992
+ sourceChain: resolveChainName(params.from.chain),
28993
+ tokenIn: params.tokenIn,
28994
+ tokenOut: params.tokenOut,
28995
+ });
28295
28996
  }
28296
28997
  /**
28297
28998
  * Get all chains supported by the configured swap providers.
@@ -28484,7 +29185,11 @@ const createSwapKit = (context) => {
28484
29185
  const getFee = context.getFee?.bind(context);
28485
29186
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
28486
29187
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
28487
- const kit = new SwapKit();
29188
+ const kit = new SwapKit({
29189
+ ...(context.disableErrorReporting != null && {
29190
+ disableErrorReporting: context.disableErrorReporting,
29191
+ }),
29192
+ });
28488
29193
  if (hasBoth) {
28489
29194
  kit.setCustomFeePolicy({
28490
29195
  computeFee: async (params) => {
@@ -29433,121 +30138,11 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
29433
30138
  };
29434
30139
 
29435
30140
  var name = "@circle-fin/unified-balance-kit";
29436
- var version = "1.0.2";
30141
+ var version = "1.1.1";
29437
30142
  var pkg = {
29438
30143
  name: name,
29439
30144
  version: version};
29440
30145
 
29441
- /**
29442
- * Default telemetry endpoint.
29443
- *
29444
- * Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
29445
- * (e.g. for staging or local development).
29446
- *
29447
- * @internal
29448
- */
29449
- const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
29450
- /**
29451
- * Resolve the telemetry endpoint URL.
29452
- *
29453
- * @internal
29454
- */
29455
- function getLogsUrl() {
29456
- if (isNodeEnvironment() &&
29457
- typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' &&
29458
- process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
29459
- return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
29460
- }
29461
- return DEFAULT_LOGS_URL;
29462
- }
29463
- /**
29464
- * Send a telemetry event to the proxy service.
29465
- *
29466
- * @remarks
29467
- * Fire-and-forget: the returned promise is intentionally not awaited
29468
- * by the caller. A fetch failure (network error, non-2xx, timeout)
29469
- * is silently swallowed so telemetry never blocks or fails user
29470
- * operations.
29471
- *
29472
- * @param payload - The structured log payload matching the server schema.
29473
- *
29474
- * @example
29475
- * ```typescript
29476
- * import { emitAnalyticsLog } from './emitLog'
29477
- *
29478
- * // Fire-and-forget — do not await
29479
- * emitAnalyticsLog(payload).catch(() => {})
29480
- * ```
29481
- *
29482
- * @internal
29483
- */
29484
- async function emitAnalyticsLog(payload) {
29485
- try {
29486
- const isNode = isNodeEnvironment();
29487
- const userAgent = getUserAgent();
29488
- await fetch(getLogsUrl(), {
29489
- method: 'POST',
29490
- headers: {
29491
- 'Content-Type': 'application/json',
29492
- // Browser restricts setting User-Agent; use X-User-Agent instead.
29493
- ...(isNode
29494
- ? { 'User-Agent': userAgent }
29495
- : { 'X-User-Agent': userAgent }),
29496
- },
29497
- body: JSON.stringify(payload),
29498
- signal: AbortSignal.timeout(5_000),
29499
- });
29500
- }
29501
- catch {
29502
- // Silently swallow — telemetry must never break user operations.
29503
- }
29504
- }
29505
-
29506
- /**
29507
- * Build the `clientContext` object for telemetry payloads.
29508
- *
29509
- * @remarks
29510
- * Uses the exported `getRuntime()` and `isNodeEnvironment()` from
29511
- * `@core/utils` to detect the runtime environment (per Dominik's
29512
- * feedback to reuse existing infrastructure). The returned string
29513
- * is parsed into the structured `ClientContext` fields expected by
29514
- * the server schema.
29515
- *
29516
- * @returns A {@link ClientContext} with platform, OS, and runtime name
29517
- * populated from the current environment.
29518
- *
29519
- * @example
29520
- * ```typescript
29521
- * import { buildClientContext } from './clientContext'
29522
- *
29523
- * const ctx = buildClientContext()
29524
- * // Node: { platform: 'node', os: 'darwin', runtimeName: null }
29525
- * // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
29526
- * ```
29527
- */
29528
- function buildClientContext() {
29529
- const runtime = getRuntime();
29530
- if (runtime.startsWith('browser/')) {
29531
- return {
29532
- platform: 'browser',
29533
- os: null,
29534
- runtimeName: runtime.slice('browser/'.length).toLowerCase(),
29535
- };
29536
- }
29537
- if (runtime.startsWith('node/')) {
29538
- return {
29539
- platform: 'node',
29540
- os: isNodeEnvironment() ? process.platform : null,
29541
- runtimeName: null,
29542
- };
29543
- }
29544
- return {
29545
- platform: 'node',
29546
- os: null,
29547
- runtimeName: null,
29548
- };
29549
- }
29550
-
29551
30146
  /**
29552
30147
  * Event type identifiers accepted by the server-side logs endpoint.
29553
30148
  *
@@ -29560,6 +30155,11 @@ const EVENT_TYPES = {
29560
30155
  DEPOSIT_FOR: 'unified_balance_deposit_for',
29561
30156
  SPEND: 'unified_balance_spend',
29562
30157
  SPEND_FORWARDER: 'unified_balance_spend_forwarder',
30158
+ ESTIMATE_SPEND: 'unified_balance_estimate_spend',
30159
+ GET_BALANCES: 'unified_balance_get_balances',
30160
+ ADD_DELEGATE: 'unified_balance_add_delegate',
30161
+ REMOVE_DELEGATE: 'unified_balance_remove_delegate',
30162
+ GET_DELEGATE_STATUS: 'unified_balance_get_delegate_status',
29563
30163
  INITIATE_REMOVE_FUND: 'unified_balance_initiate_remove_fund',
29564
30164
  REMOVE_FUND: 'unified_balance_remove_fund',
29565
30165
  };
@@ -29597,33 +30197,29 @@ function extractSingleChainResult(data) {
29597
30197
  txHash: data.txHash,
29598
30198
  };
29599
30199
  }
29600
- /**
29601
- * Resolve a chain identifier that may be a string or a
29602
- * `ChainDefinition` object to a plain string.
29603
- *
29604
- * @internal
29605
- */
29606
- function resolveChainString(chain) {
29607
- if (typeof chain === 'string')
29608
- return chain;
29609
- return chain.chain;
29610
- }
29611
30200
  /**
29612
30201
  * Extract log-relevant fields from a spend result.
29613
30202
  *
30203
+ * @remarks
30204
+ * Collects all source chains from allocations and joins them with
30205
+ * commas so multi-chain spends are fully represented.
30206
+ *
29614
30207
  * @internal
29615
30208
  */
29616
30209
  function extractSpend(data) {
29617
- const rawChain = data.allocations?.[0]?.chain;
29618
- let firstSourceChain;
29619
- if (rawChain != null) {
29620
- firstSourceChain = resolveChainString(rawChain);
30210
+ const allocs = data.allocations ?? [];
30211
+ const chains = [];
30212
+ for (const alloc of allocs) {
30213
+ const name = resolveChainName(alloc.chain);
30214
+ if (name != null) {
30215
+ chains.push(name);
30216
+ }
29621
30217
  }
29622
- const hasAllocations = data.allocations?.[0] != null;
30218
+ const sourceChain = chains.length > 0 ? chains.join(',') : undefined;
29623
30219
  return {
29624
- ...(firstSourceChain != null && { sourceChain: firstSourceChain }),
30220
+ ...(sourceChain != null && { sourceChain }),
29625
30221
  destinationChain: data.destinationChain,
29626
- ...(hasAllocations && { tokenIn: 'USDC' }),
30222
+ ...(allocs.length > 0 && { tokenIn: 'USDC' }),
29627
30223
  txHash: data.txHash,
29628
30224
  };
29629
30225
  }
@@ -29722,10 +30318,9 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29722
30318
  * telemetry for successful verb operations.
29723
30319
  *
29724
30320
  * @remarks
29725
- * Follows Dominik's feedback to reuse the existing event bus as the
29726
- * SDK-side hook for telemetry. Registers a handler for each verb's
29727
- * `.succeeded` event. Non-verb and non-succeeded events are not
29728
- * subscribed to.
30321
+ * Registers a handler for each verb's `.succeeded` event using the
30322
+ * shared `registerTelemetryHandler` from `@core/utils`. Non-verb and
30323
+ * non-succeeded events are not subscribed to.
29729
30324
  *
29730
30325
  * The HTTP POST is fire-and-forget — telemetry never blocks or fails
29731
30326
  * user operations.
@@ -29745,20 +30340,7 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29745
30340
  * @internal
29746
30341
  */
29747
30342
  function registerTelemetryHandler(dispatcher, sdkName, sdkVersion) {
29748
- for (const actionName of Object.keys(VERB_EVENT_MAP)) {
29749
- dispatcher.on(actionName, (payload) => {
29750
- try {
29751
- const log = mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion);
29752
- if (log) {
29753
- // Fire-and-forget — intentionally not awaited.
29754
- void emitAnalyticsLog(log);
29755
- }
29756
- }
29757
- catch {
29758
- // Silently swallow — telemetry must never break user operations.
29759
- }
29760
- });
29761
- }
30343
+ registerTelemetryHandler$1(dispatcher, sdkName, sdkVersion, VERB_EVENT_MAP, mapSucceededEventToLog);
29762
30344
  }
29763
30345
 
29764
30346
  /**
@@ -32861,13 +33443,20 @@ async function resolveAllocationsAndIntents(params, destChain, recipientAddress,
32861
33443
  const sourcesArray = rawSources.filter((s) => s != null);
32862
33444
  const networkType = destChain.isTestnet ? 'testnet' : 'mainnet';
32863
33445
  const balanceResults = await Promise.all(sourcesArray.map(async (source) => {
32864
- const querySource = {
32865
- adapter: source.adapter,
32866
- };
32867
- if (source.sourceAccount)
32868
- querySource['account'] = source.sourceAccount;
32869
- if ('address' in source && source.address) {
32870
- querySource['address'] = source.address;
33446
+ // When sourceAccount is set (delegate flow), scope the balance
33447
+ // query to the Gateway depositor — not the signer. Using the
33448
+ // address-only path bypasses adapter address resolution, which
33449
+ // would otherwise return the signer's balance (developer-
33450
+ // controlled) or reject an explicit address (user-controlled).
33451
+ let querySource;
33452
+ if (source.sourceAccount) {
33453
+ querySource = { address: source.sourceAccount };
33454
+ }
33455
+ else {
33456
+ querySource = { adapter: source.adapter };
33457
+ if ('address' in source && source.address) {
33458
+ querySource['address'] = source.address;
33459
+ }
32871
33460
  }
32872
33461
  return getBalances$1({
32873
33462
  token: params.token,
@@ -35788,6 +36377,8 @@ function getSupportedChains(context, token, options) {
35788
36377
  return Object.values(Object.fromEntries(filtered.map((chain) => [chain.chain, chain])));
35789
36378
  }
35790
36379
 
36380
+ /** SDK name used in telemetry payloads. */
36381
+ const SDK_NAME = resolveKitSdkName(pkg.name);
35791
36382
  /**
35792
36383
  * A high-level class-based interface for cross-chain USDC deposits,
35793
36384
  * spending, balance queries, delegation management, and withdrawals.
@@ -35835,6 +36426,10 @@ class UnifiedBalanceKit {
35835
36426
  * The action dispatcher for the kit.
35836
36427
  */
35837
36428
  actionDispatcher;
36429
+ /** Whether error telemetry is disabled. */
36430
+ disableErrorReporting;
36431
+ /** Per-kit telemetry identity for shared helpers. */
36432
+ telemetryConfig;
35838
36433
  /**
35839
36434
  * Create a new UnifiedBalanceKit instance.
35840
36435
  *
@@ -35847,14 +36442,49 @@ class UnifiedBalanceKit {
35847
36442
  constructor(config = {}) {
35848
36443
  this.context = createUnifiedBalanceKitContext(config);
35849
36444
  this.actionDispatcher = new Actionable();
36445
+ this.disableErrorReporting = config.disableErrorReporting === true;
36446
+ this.telemetryConfig = {
36447
+ sdkName: SDK_NAME,
36448
+ sdkVersion: pkg.version,
36449
+ disabled: this.disableErrorReporting,
36450
+ };
35850
36451
  for (const provider of this.context.providers) {
35851
36452
  provider.registerDispatcher(this.actionDispatcher);
35852
36453
  }
35853
36454
  if (!config.disableAnalytics) {
35854
- const sdkName = pkg.name.replace('@circle-fin/', '');
35855
- registerTelemetryHandler(this.actionDispatcher, sdkName, pkg.version);
36455
+ registerTelemetryHandler(this.actionDispatcher, SDK_NAME, pkg.version);
35856
36456
  }
35857
36457
  }
36458
+ /**
36459
+ * Extract comma-separated source chain names from spend params.
36460
+ *
36461
+ * @internal
36462
+ */
36463
+ static extractSpendSourceChains(params) {
36464
+ if (params == null || typeof params !== 'object' || !('from' in params)) {
36465
+ return undefined;
36466
+ }
36467
+ const fromVal = params.from;
36468
+ const sources = Array.isArray(fromVal) ? fromVal : [fromVal];
36469
+ const chains = [];
36470
+ for (const src of sources) {
36471
+ const allocs = src?.allocations;
36472
+ let allocArr;
36473
+ if (allocs == null) {
36474
+ allocArr = [];
36475
+ }
36476
+ else {
36477
+ allocArr = Array.isArray(allocs) ? allocs : [allocs];
36478
+ }
36479
+ for (const alloc of allocArr) {
36480
+ const name = resolveChainName(alloc.chain);
36481
+ if (name != null) {
36482
+ chains.push(name);
36483
+ }
36484
+ }
36485
+ }
36486
+ return chains.length > 0 ? chains.join(',') : undefined;
36487
+ }
35858
36488
  // implementation just forwards to the bus
35859
36489
  on(actionOrWildcard, handler) {
35860
36490
  this.actionDispatcher.on(actionOrWildcard, handler);
@@ -35877,7 +36507,10 @@ class UnifiedBalanceKit {
35877
36507
  * @see UnifiedBalanceKit.depositFor to deposit into another account.
35878
36508
  */
35879
36509
  async deposit(params) {
35880
- return deposit(this.context, params);
36510
+ return withErrorTelemetry(async () => deposit(this.context, params), EVENT_TYPES.DEPOSIT, this.telemetryConfig, {
36511
+ sourceChain: resolveChainName(params.from?.chain),
36512
+ tokenIn: params.token ?? 'USDC',
36513
+ });
35881
36514
  }
35882
36515
  /**
35883
36516
  * Deposit USDC into another account (not the caller's).
@@ -35889,7 +36522,10 @@ class UnifiedBalanceKit {
35889
36522
  * @see UnifiedBalanceKit.deposit to deposit into your own account.
35890
36523
  */
35891
36524
  async depositFor(params) {
35892
- return depositFor(this.context, params);
36525
+ return withErrorTelemetry(async () => depositFor(this.context, params), EVENT_TYPES.DEPOSIT_FOR, this.telemetryConfig, {
36526
+ sourceChain: resolveChainName(params.from?.chain),
36527
+ tokenIn: params.token ?? 'USDC',
36528
+ });
35893
36529
  }
35894
36530
  /**
35895
36531
  * Spend (mint) USDC on a destination chain by pulling funds from one
@@ -35902,7 +36538,17 @@ class UnifiedBalanceKit {
35902
36538
  * @see UnifiedBalanceKit.estimateSpend to preview fees before spending.
35903
36539
  */
35904
36540
  async spend(params) {
35905
- return spend(this.context, params);
36541
+ const destChain = 'to' in params
36542
+ ? resolveChainName(params.to.chain)
36543
+ : undefined;
36544
+ const tokenIn = 'token' in params
36545
+ ? (params.token ?? 'USDC')
36546
+ : 'USDC';
36547
+ return withErrorTelemetry(async () => spend(this.context, params), EVENT_TYPES.SPEND, this.telemetryConfig, {
36548
+ sourceChain: UnifiedBalanceKit.extractSpendSourceChains(params),
36549
+ ...(destChain != null && { destinationChain: destChain }),
36550
+ tokenIn,
36551
+ });
35906
36552
  }
35907
36553
  /**
35908
36554
  * Estimate the fees for a spend operation without executing it.
@@ -35912,7 +36558,17 @@ class UnifiedBalanceKit {
35912
36558
  * @returns Promise resolving to the fee estimate.
35913
36559
  */
35914
36560
  async estimateSpend(params) {
35915
- return estimateSpend(this.context, params);
36561
+ const destChain = 'to' in params
36562
+ ? resolveChainName(params.to.chain)
36563
+ : undefined;
36564
+ const sourceChain = UnifiedBalanceKit.extractSpendSourceChains(params);
36565
+ return withErrorTelemetry(async () => estimateSpend(this.context, params), EVENT_TYPES.ESTIMATE_SPEND, this.telemetryConfig, {
36566
+ ...(sourceChain != null && { sourceChain }),
36567
+ ...(destChain != null && { destinationChain: destChain }),
36568
+ tokenIn: 'token' in params
36569
+ ? (params.token ?? 'USDC')
36570
+ : 'USDC',
36571
+ });
35916
36572
  }
35917
36573
  /**
35918
36574
  * Fetch aggregated and per-chain balances for one or more accounts.
@@ -35921,7 +36577,7 @@ class UnifiedBalanceKit {
35921
36577
  * @returns Promise resolving to the aggregated balance result.
35922
36578
  */
35923
36579
  async getBalances(params) {
35924
- return getBalances(this.context, params);
36580
+ return withErrorTelemetry(async () => getBalances(this.context, params), EVENT_TYPES.GET_BALANCES, this.telemetryConfig);
35925
36581
  }
35926
36582
  /**
35927
36583
  * Grant spending rights to another address on the owner's account.
@@ -35936,7 +36592,7 @@ class UnifiedBalanceKit {
35936
36592
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35937
36593
  */
35938
36594
  async addDelegate(params) {
35939
- return addDelegate(this.context, params);
36595
+ return withErrorTelemetry(async () => addDelegate(this.context, params), EVENT_TYPES.ADD_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35940
36596
  }
35941
36597
  /**
35942
36598
  * Revoke spending rights from a delegate on the owner's account.
@@ -35951,7 +36607,7 @@ class UnifiedBalanceKit {
35951
36607
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35952
36608
  */
35953
36609
  async removeDelegate(params) {
35954
- return removeDelegate(this.context, params);
36610
+ return withErrorTelemetry(async () => removeDelegate(this.context, params), EVENT_TYPES.REMOVE_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35955
36611
  }
35956
36612
  /**
35957
36613
  * Kick off a delayed fund removal from an account.
@@ -35963,7 +36619,10 @@ class UnifiedBalanceKit {
35963
36619
  * @see UnifiedBalanceKit.removeFund to complete the fund removal.
35964
36620
  */
35965
36621
  async initiateRemoveFund(params) {
35966
- return initiateRemoveFund(this.context, params);
36622
+ return withErrorTelemetry(async () => initiateRemoveFund(this.context, params), EVENT_TYPES.INITIATE_REMOVE_FUND, this.telemetryConfig, {
36623
+ sourceChain: resolveChainName(params.from?.chain),
36624
+ tokenIn: params.token ?? 'USDC',
36625
+ });
35967
36626
  }
35968
36627
  /**
35969
36628
  * Complete a fund removal once the activation period has passed.
@@ -35975,7 +36634,10 @@ class UnifiedBalanceKit {
35975
36634
  * @see UnifiedBalanceKit.initiateRemoveFund to start the process.
35976
36635
  */
35977
36636
  async removeFund(params) {
35978
- return removeFund(this.context, params);
36637
+ return withErrorTelemetry(async () => removeFund(this.context, params), EVENT_TYPES.REMOVE_FUND, this.telemetryConfig, {
36638
+ sourceChain: resolveChainName(params.from?.chain),
36639
+ tokenIn: params.token ?? 'USDC',
36640
+ });
35979
36641
  }
35980
36642
  /**
35981
36643
  * Check the finality-aware delegate status of an address.
@@ -36003,7 +36665,7 @@ class UnifiedBalanceKit {
36003
36665
  * ```
36004
36666
  */
36005
36667
  async getDelegateStatus(params) {
36006
- return getDelegateStatus(this.context, params);
36668
+ return withErrorTelemetry(async () => getDelegateStatus(this.context, params), EVENT_TYPES.GET_DELEGATE_STATUS, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
36007
36669
  }
36008
36670
  /**
36009
36671
  * Get all chains supported by the kit.
@@ -36493,8 +37155,18 @@ class AppKit {
36493
37155
  * ```
36494
37156
  */
36495
37157
  constructor(config = {}) {
36496
- this.context = createContext(config);
36497
- this.unifiedBalance = new AppKitUnifiedBalance(config.unifiedBalance);
37158
+ this.context = createContext({
37159
+ ...config,
37160
+ ...(config.disableErrorReporting != null && {
37161
+ disableErrorReporting: config.disableErrorReporting,
37162
+ }),
37163
+ });
37164
+ this.unifiedBalance = new AppKitUnifiedBalance({
37165
+ ...config.unifiedBalance,
37166
+ ...(config.disableErrorReporting != null && {
37167
+ disableErrorReporting: config.disableErrorReporting,
37168
+ }),
37169
+ });
36498
37170
  }
36499
37171
  /**
36500
37172
  * Execute a cross-chain USDC bridge transfer.