@circle-fin/app-kit 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.mjs CHANGED
@@ -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";
@@ -3412,6 +3414,8 @@ var Blockchain;
3412
3414
  Blockchain["Noble_Testnet"] = "Noble_Testnet";
3413
3415
  Blockchain["Optimism"] = "Optimism";
3414
3416
  Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
3417
+ Blockchain["Pharos"] = "Pharos";
3418
+ Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
3415
3419
  Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
3416
3420
  Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
3417
3421
  Blockchain["Plume"] = "Plume";
@@ -3615,11 +3619,13 @@ var BridgeChain;
3615
3619
  BridgeChain["Edge"] = "Edge";
3616
3620
  BridgeChain["Ethereum"] = "Ethereum";
3617
3621
  BridgeChain["HyperEVM"] = "HyperEVM";
3622
+ BridgeChain["Injective"] = "Injective";
3618
3623
  BridgeChain["Ink"] = "Ink";
3619
3624
  BridgeChain["Linea"] = "Linea";
3620
3625
  BridgeChain["Monad"] = "Monad";
3621
3626
  BridgeChain["Morph"] = "Morph";
3622
3627
  BridgeChain["Optimism"] = "Optimism";
3628
+ BridgeChain["Pharos"] = "Pharos";
3623
3629
  BridgeChain["Plume"] = "Plume";
3624
3630
  BridgeChain["Polygon"] = "Polygon";
3625
3631
  BridgeChain["Sei"] = "Sei";
@@ -3637,11 +3643,13 @@ var BridgeChain;
3637
3643
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
3638
3644
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
3639
3645
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
3646
+ BridgeChain["Injective_Testnet"] = "Injective_Testnet";
3640
3647
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
3641
3648
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
3642
3649
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
3643
3650
  BridgeChain["Morph_Testnet"] = "Morph_Testnet";
3644
3651
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
3652
+ BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
3645
3653
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
3646
3654
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
3647
3655
  BridgeChain["Sei_Testnet"] = "Sei_Testnet";
@@ -3987,6 +3995,12 @@ const SWAP_TOKEN_REGISTRY = {
3987
3995
  category: 'wrapped',
3988
3996
  description: 'Wrapped Polygon',
3989
3997
  },
3998
+ CIRBTC: {
3999
+ symbol: 'CIRBTC',
4000
+ decimals: 8,
4001
+ category: 'wrapped',
4002
+ description: 'Circle Bitcoin',
4003
+ },
3990
4004
  };
3991
4005
  /**
3992
4006
  * Special NATIVE token constant for swap operations.
@@ -5074,6 +5088,98 @@ const HyperEVMTestnet = defineChain({
5074
5088
  },
5075
5089
  });
5076
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
+
5077
5183
  /**
5078
5184
  * Ink Mainnet chain definition
5079
5185
  * @remarks
@@ -5683,6 +5789,96 @@ const OptimismSepolia = defineChain({
5683
5789
  },
5684
5790
  });
5685
5791
 
5792
+ /**
5793
+ * Pharos Mainnet chain definition
5794
+ * @remarks
5795
+ * This represents the official production network for the Pharos blockchain.
5796
+ * Pharos is a modular, full-stack parallel Layer 1 blockchain with
5797
+ * sub-second finality and EVM compatibility.
5798
+ */
5799
+ const Pharos = defineChain({
5800
+ type: 'evm',
5801
+ chain: Blockchain.Pharos,
5802
+ name: 'Pharos',
5803
+ title: 'Pharos Mainnet',
5804
+ nativeCurrency: {
5805
+ name: 'Pharos',
5806
+ symbol: 'PHAROS',
5807
+ decimals: 18,
5808
+ },
5809
+ chainId: 1672,
5810
+ isTestnet: false,
5811
+ explorerUrl: 'https://pharos.socialscan.io/tx/{hash}',
5812
+ rpcEndpoints: ['https://rpc.pharos.xyz'],
5813
+ eurcAddress: null,
5814
+ usdcAddress: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
5815
+ usdtAddress: null,
5816
+ cctp: {
5817
+ domain: 31,
5818
+ contracts: {
5819
+ v2: {
5820
+ type: 'split',
5821
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5822
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5823
+ confirmations: 1,
5824
+ fastConfirmations: 1,
5825
+ },
5826
+ },
5827
+ forwarderSupported: {
5828
+ source: false,
5829
+ destination: false,
5830
+ },
5831
+ },
5832
+ kitContracts: {
5833
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5834
+ },
5835
+ });
5836
+
5837
+ /**
5838
+ * Pharos Atlantic Testnet chain definition
5839
+ * @remarks
5840
+ * This represents the official test network for the Pharos blockchain.
5841
+ * Pharos is a modular, full-stack parallel Layer 1 blockchain with
5842
+ * sub-second finality and EVM compatibility.
5843
+ */
5844
+ const PharosTestnet = defineChain({
5845
+ type: 'evm',
5846
+ chain: Blockchain.Pharos_Testnet,
5847
+ name: 'Pharos Atlantic',
5848
+ title: 'Pharos Atlantic Testnet',
5849
+ nativeCurrency: {
5850
+ name: 'Pharos',
5851
+ symbol: 'PHAROS',
5852
+ decimals: 18,
5853
+ },
5854
+ chainId: 688689,
5855
+ isTestnet: true,
5856
+ explorerUrl: 'https://atlantic.pharosscan.xyz/tx/{hash}',
5857
+ rpcEndpoints: ['https://atlantic.dplabs-internal.com'],
5858
+ eurcAddress: null,
5859
+ usdcAddress: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
5860
+ usdtAddress: null,
5861
+ cctp: {
5862
+ domain: 31,
5863
+ contracts: {
5864
+ v2: {
5865
+ type: 'split',
5866
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5867
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5868
+ confirmations: 1,
5869
+ fastConfirmations: 1,
5870
+ },
5871
+ },
5872
+ forwarderSupported: {
5873
+ source: false,
5874
+ destination: false,
5875
+ },
5876
+ },
5877
+ kitContracts: {
5878
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
5879
+ },
5880
+ });
5881
+
5686
5882
  /**
5687
5883
  * Plume Mainnet chain definition
5688
5884
  * @remarks
@@ -5965,7 +6161,7 @@ const Sei = defineChain({
5965
6161
  },
5966
6162
  chainId: 1329,
5967
6163
  isTestnet: false,
5968
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=pacific-1',
6164
+ explorerUrl: 'https://seiscan.io/tx/{hash}',
5969
6165
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
5970
6166
  eurcAddress: null,
5971
6167
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
@@ -6023,7 +6219,7 @@ const SeiTestnet = defineChain({
6023
6219
  },
6024
6220
  chainId: 1328,
6025
6221
  isTestnet: true,
6026
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=atlantic-2',
6222
+ explorerUrl: 'https://testnet.seiscan.io/tx/{hash}',
6027
6223
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
6028
6224
  eurcAddress: null,
6029
6225
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
@@ -6826,6 +7022,8 @@ var Chains = /*#__PURE__*/Object.freeze({
6826
7022
  HederaTestnet: HederaTestnet,
6827
7023
  HyperEVM: HyperEVM,
6828
7024
  HyperEVMTestnet: HyperEVMTestnet,
7025
+ Injective: Injective,
7026
+ InjectiveTestnet: InjectiveTestnet,
6829
7027
  Ink: Ink,
6830
7028
  InkTestnet: InkTestnet,
6831
7029
  Linea: Linea,
@@ -6840,6 +7038,8 @@ var Chains = /*#__PURE__*/Object.freeze({
6840
7038
  NobleTestnet: NobleTestnet,
6841
7039
  Optimism: Optimism,
6842
7040
  OptimismSepolia: OptimismSepolia,
7041
+ Pharos: Pharos,
7042
+ PharosTestnet: PharosTestnet,
6843
7043
  Plume: Plume,
6844
7044
  PlumeTestnet: PlumeTestnet,
6845
7045
  PolkadotAssetHub: PolkadotAssetHub,
@@ -7445,6 +7645,44 @@ function resolveChainIdentifier(chainIdentifier) {
7445
7645
  throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
7446
7646
  }
7447
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
+
7448
7686
  /**
7449
7687
  * @packageDocumentation
7450
7688
  * @module SwapTokenUtils
@@ -9101,6 +9339,7 @@ const USDC = {
9101
9339
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9102
9340
  [Blockchain.Hedera]: '0.0.456858',
9103
9341
  [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
9342
+ [Blockchain.Injective]: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
9104
9343
  [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
9105
9344
  [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
9106
9345
  [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
@@ -9108,6 +9347,7 @@ const USDC = {
9108
9347
  [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
9109
9348
  [Blockchain.Noble]: 'uusdc',
9110
9349
  [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
9350
+ [Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
9111
9351
  [Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
9112
9352
  [Blockchain.Polkadot_Asset_Hub]: '1337',
9113
9353
  [Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
@@ -9132,6 +9372,7 @@ const USDC = {
9132
9372
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9133
9373
  [Blockchain.Hedera_Testnet]: '0.0.429274',
9134
9374
  [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
9375
+ [Blockchain.Injective_Testnet]: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
9135
9376
  [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
9136
9377
  [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
9137
9378
  [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
@@ -9139,6 +9380,7 @@ const USDC = {
9139
9380
  [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
9140
9381
  [Blockchain.Noble_Testnet]: 'uusdc',
9141
9382
  [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
9383
+ [Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
9142
9384
  [Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
9143
9385
  [Blockchain.Polkadot_Westmint]: '31337',
9144
9386
  [Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
@@ -9419,6 +9661,32 @@ const MON = {
9419
9661
  },
9420
9662
  };
9421
9663
 
9664
+ /**
9665
+ * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
9666
+ *
9667
+ * @remarks
9668
+ * Built-in cirBTC definition for the TokenRegistry. Currently deployed
9669
+ * on Arc Testnet.
9670
+ *
9671
+ * @example
9672
+ * ```typescript
9673
+ * import { CIRBTC } from '@core/tokens'
9674
+ * import { Blockchain } from '@core/chains'
9675
+ *
9676
+ * console.log(CIRBTC.symbol) // 'cirBTC'
9677
+ * console.log(CIRBTC.decimals) // 8
9678
+ * console.log(CIRBTC.locators[Blockchain.Arc_Testnet])
9679
+ * // '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
9680
+ * ```
9681
+ */
9682
+ const CIRBTC = {
9683
+ symbol: 'cirBTC',
9684
+ decimals: 8,
9685
+ locators: {
9686
+ [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
9687
+ },
9688
+ };
9689
+
9422
9690
  // Re-export for consumers
9423
9691
  /**
9424
9692
  * All default token definitions.
@@ -9427,7 +9695,7 @@ const MON = {
9427
9695
  * These tokens are automatically included in the TokenRegistry when created
9428
9696
  * without explicit defaults. Extensions can override these definitions.
9429
9697
  * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
9430
- * WPOL, ETH, POL, PLUME, and MON.
9698
+ * WPOL, ETH, POL, PLUME, MON, and cirBTC.
9431
9699
  *
9432
9700
  * @example
9433
9701
  * ```typescript
@@ -9458,6 +9726,7 @@ const DEFAULT_TOKENS = [
9458
9726
  POL,
9459
9727
  PLUME,
9460
9728
  MON,
9729
+ CIRBTC,
9461
9730
  ];
9462
9731
  /**
9463
9732
  * Uppercased token symbols approved for swap fee collection.
@@ -10205,8 +10474,325 @@ async function retryAsync(fn, options) {
10205
10474
  throw new Error('retryAsync: unreachable');
10206
10475
  }
10207
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
+
10208
10794
  var name$2 = "@circle-fin/bridge-kit";
10209
- var version$3 = "1.8.3";
10795
+ var version$3 = "1.10.0";
10210
10796
  var pkg$3 = {
10211
10797
  name: name$2,
10212
10798
  version: version$3};
@@ -13171,18 +13757,16 @@ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet) => {
13171
13757
  };
13172
13758
  /**
13173
13759
  * Fetches attestation data from the IRIS API with retry and timeout handling.
13174
- * Since fetching starts after a confirmed burn transaction, we attempt up to 10 retries.
13175
- * Implements a conservative delay between requests to respect the API rate
13176
- * limit of 35 requests per second. To avoid rate limiting when multiple processes are
13177
- * running concurrently, we enforce a 200ms delay between retries.
13178
13760
  *
13179
- * Each HTTP request is given a maximum of 2 seconds before it is aborted.
13180
- * 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.
13181
13764
  *
13182
- * The total maximum time this function might take (worst case) is:
13183
- * - Perattempt timeout: 2 000 ms
13184
- * - Retry delays: 9 × 200 ms = 1 800 ms
13185
- * - 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
13186
13770
  *
13187
13771
  * @param sourceDomainId - The CCTP domain ID.
13188
13772
  * @param transactionHash - The transaction hash to fetch attestation for.
@@ -14337,11 +14921,18 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
14337
14921
  step.explorerUrl = buildExplorerUrl(chain, txHash);
14338
14922
  if (outcome.errorMessage) {
14339
14923
  step.errorMessage = outcome.errorMessage;
14924
+ // Transaction was mined but reverted on-chain.
14925
+ step.errorCategory = 'chain_revert';
14340
14926
  }
14341
14927
  }
14342
14928
  catch (err) {
14343
14929
  step.state = 'error';
14344
14930
  step.error = err;
14931
+ // Sequential path does not yet attempt fine-grained classification of
14932
+ // pre-submission errors (user_rejected, capability errors, etc.). Mark
14933
+ // as `unknown` so consumers can at least detect the category is
14934
+ // populated uniformly across batched and sequential flows.
14935
+ step.errorCategory = 'unknown';
14345
14936
  // Optionally parse for common blockchain error formats
14346
14937
  if (err instanceof Error) {
14347
14938
  step.errorMessage = err.message;
@@ -15007,16 +15598,70 @@ async function executeBatchedApproveAndBurn(params, provider) {
15007
15598
  const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
15008
15599
  const approveReceipt = batchResult.receipts[0];
15009
15600
  const burnReceipt = batchResult.receipts[1];
15010
- const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
15011
- const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
15601
+ const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
15602
+ const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
15012
15603
  if (burnStep.state !== 'error' && !burnStep.txHash) {
15013
15604
  burnStep.state = 'error';
15014
15605
  burnStep.errorMessage =
15015
15606
  'Batched burn step completed but no transaction hash was returned.';
15607
+ burnStep.errorCategory = 'unknown';
15016
15608
  }
15017
15609
  const context = { burnTxHash: burnStep.txHash ?? '' };
15018
15610
  return { approveStep, burnStep, context };
15019
15611
  }
15612
+ /**
15613
+ * Derive a {@link BridgeStepErrorCategory} for a missing receipt.
15614
+ *
15615
+ * Combines the EIP-5792 `statusCode` (when present) with the underlying
15616
+ * polling error (when set) to produce the most specific category available.
15617
+ * Falls back to `'unknown'` when neither signal is conclusive.
15618
+ *
15619
+ * @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
15620
+ * @param batchError - The polling error from `batchExecute`, if any.
15621
+ * @returns The derived error category for a missing-receipt step.
15622
+ *
15623
+ * @internal
15624
+ */
15625
+ function categorizeMissingReceipt(statusCode, batchError) {
15626
+ if (statusCode === 400)
15627
+ return 'failed_offchain';
15628
+ if (statusCode === 500)
15629
+ return 'reverted_onchain';
15630
+ if (statusCode === 600)
15631
+ return 'partial_reverted';
15632
+ if (batchError instanceof KitError &&
15633
+ batchError.code === NetworkError.TIMEOUT.code) {
15634
+ return 'polling_timeout';
15635
+ }
15636
+ return 'unknown';
15637
+ }
15638
+ /**
15639
+ * Derive a {@link BridgeStepErrorCategory} for a receipt that was returned
15640
+ * but whose per-call `status` is not `'success'`.
15641
+ *
15642
+ * A numeric EIP-5792 `statusCode` of 500 or 600 tells us the whole batch
15643
+ * reverted on-chain (completely or partially); otherwise the receipt
15644
+ * itself signalled a revert without a distinguishing code, so classify
15645
+ * as a plain on-chain revert.
15646
+ *
15647
+ * @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
15648
+ * @returns The derived error category for a failed-receipt step.
15649
+ *
15650
+ * @internal
15651
+ */
15652
+ function categorizeFailedReceipt(statusCode) {
15653
+ // Mirror categorizeMissingReceipt: if the wallet's terminal status is
15654
+ // 400 ("batch not included onchain"), that judgement is authoritative
15655
+ // even when a non-success receipt is attached. Without this, a wrapped
15656
+ // 400 receipt would fall through to `chain_revert` and mislead UX.
15657
+ if (statusCode === 400)
15658
+ return 'failed_offchain';
15659
+ if (statusCode === 600)
15660
+ return 'partial_reverted';
15661
+ if (statusCode === 500)
15662
+ return 'reverted_onchain';
15663
+ return 'chain_revert';
15664
+ }
15020
15665
  /**
15021
15666
  * Build a {@link BridgeStep} from a single receipt within a batch.
15022
15667
  *
@@ -15031,11 +15676,17 @@ async function executeBatchedApproveAndBurn(params, provider) {
15031
15676
  * @param batchId - Wallet-assigned batch identifier.
15032
15677
  * @param adapter - The batch-capable adapter (used for confirmation).
15033
15678
  * @param chain - The EVM chain the batch was executed on.
15679
+ * @param statusCode - Optional EIP-5792 `statusCode` for the batch.
15680
+ * Used to classify the step's error category when the receipt is
15681
+ * missing or failed.
15682
+ * @param batchError - Optional polling error from `batchExecute`.
15683
+ * Preserved on the step so callers can inspect underlying timeouts
15684
+ * or RPC failures.
15034
15685
  * @returns A fully-populated bridge step with state, tx hash and explorer URL.
15035
15686
  *
15036
15687
  * @internal
15037
15688
  */
15038
- async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15689
+ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCode, batchError) {
15039
15690
  const step = {
15040
15691
  name,
15041
15692
  state: 'pending',
@@ -15045,6 +15696,10 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15045
15696
  if (!receipt) {
15046
15697
  step.state = 'error';
15047
15698
  step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
15699
+ step.errorCategory = categorizeMissingReceipt(statusCode, batchError);
15700
+ if (batchError !== undefined) {
15701
+ step.error = batchError;
15702
+ }
15048
15703
  return step;
15049
15704
  }
15050
15705
  step.txHash = receipt.txHash;
@@ -15054,11 +15709,13 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15054
15709
  if (receipt.status !== 'success') {
15055
15710
  step.state = 'error';
15056
15711
  step.errorMessage = `${name} call failed within batch ${batchId}.`;
15712
+ step.errorCategory = categorizeFailedReceipt(statusCode);
15057
15713
  return step;
15058
15714
  }
15059
15715
  if (!receipt.txHash) {
15060
15716
  step.state = 'error';
15061
15717
  step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
15718
+ step.errorCategory = 'unknown';
15062
15719
  return step;
15063
15720
  }
15064
15721
  try {
@@ -15073,6 +15730,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15073
15730
  step.data = transaction;
15074
15731
  if (outcome.errorMessage) {
15075
15732
  step.errorMessage = outcome.errorMessage;
15733
+ step.errorCategory = 'chain_revert';
15076
15734
  }
15077
15735
  }
15078
15736
  catch (err) {
@@ -15080,11 +15738,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15080
15738
  step.error = err;
15081
15739
  step.errorMessage =
15082
15740
  err instanceof Error ? err.message : 'Unknown error during confirmation.';
15741
+ step.errorCategory = 'unknown';
15083
15742
  }
15084
15743
  return step;
15085
15744
  }
15086
15745
 
15087
- var version$2 = "1.6.3";
15746
+ var version$2 = "1.8.0";
15088
15747
  var pkg$2 = {
15089
15748
  version: version$2};
15090
15749
 
@@ -15160,6 +15819,7 @@ async function executeBatchedPath(params, provider, result, invocation) {
15160
15819
  errorMessage: error_ instanceof Error
15161
15820
  ? error_.message
15162
15821
  : 'Batched approve + burn failed.',
15822
+ errorCategory: classifyPreSubmissionError(error_),
15163
15823
  });
15164
15824
  return undefined;
15165
15825
  }
@@ -15174,6 +15834,89 @@ function ensureStepErrorMessage(name, step) {
15174
15834
  step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
15175
15835
  }
15176
15836
  }
15837
+ /**
15838
+ * Coerce a raw JSON-RPC `code` to a number.
15839
+ *
15840
+ * Some wallet SDKs serialize the JSON-RPC `code` as a string ("4001")
15841
+ * after round-tripping through JSON; accept both shapes so strict `===`
15842
+ * comparisons downstream still classify 5720/5730/5740 correctly — those
15843
+ * codes have no message-pattern fallback.
15844
+ *
15845
+ * @param rawCode - The raw `code` extracted from the error object.
15846
+ * @returns The numeric code, or `undefined` if the value cannot be parsed.
15847
+ *
15848
+ * @internal
15849
+ */
15850
+ function coerceRpcCode(rawCode) {
15851
+ if (typeof rawCode === 'number') {
15852
+ return rawCode;
15853
+ }
15854
+ if (typeof rawCode === 'string') {
15855
+ return Number.parseInt(rawCode, 10);
15856
+ }
15857
+ return undefined;
15858
+ }
15859
+ /**
15860
+ * Classify a pre-submission error thrown during `wallet_sendCalls`.
15861
+ *
15862
+ * Inspect the error's JSON-RPC `code` (falling back to message pattern
15863
+ * matching for wrapper errors like viem's `ChainMismatchError`) and map
15864
+ * it to a {@link BridgeStepErrorCategory}. This lets downstream consumers
15865
+ * distinguish user rejections, wallet capability gaps, and unknown
15866
+ * failures without parsing error messages.
15867
+ *
15868
+ * @remarks
15869
+ * Does NOT alter control flow — the SDK continues to surface a
15870
+ * `state: 'error'` step. Auto-fallback to sequential execution is
15871
+ * intentionally out of scope for this helper.
15872
+ *
15873
+ * @param err - The error thrown by `wallet_sendCalls`.
15874
+ * @returns The derived error category, or `'unknown'` if no match.
15875
+ *
15876
+ * @internal
15877
+ */
15878
+ function classifyPreSubmissionError(err) {
15879
+ // Cross-realm-safe duck typing: `instanceof Error` returns false for
15880
+ // errors thrown in a different JavaScript realm (e.g., a wallet
15881
+ // provider running inside an iframe, which is common with WalletConnect
15882
+ // and the Coinbase Wallet SDK).
15883
+ if (typeof err !== 'object' || err === null || !('message' in err)) {
15884
+ return 'unknown';
15885
+ }
15886
+ const code = coerceRpcCode(err.code);
15887
+ const message = String(err.message);
15888
+ // Numeric JSON-RPC codes are authoritative; check them before falling
15889
+ // back to message-pattern matching. Order matters: an error carrying
15890
+ // `code === 5750` with a message like "user rejected the upgrade"
15891
+ // is a capability problem, not a plain user rejection.
15892
+ if (code === 4001) {
15893
+ return 'user_rejected';
15894
+ }
15895
+ if (code === 5700 || code === 5710 || code === 5750) {
15896
+ return 'atomic_unsupported';
15897
+ }
15898
+ if (code === 5720) {
15899
+ return 'duplicate_batch_id';
15900
+ }
15901
+ if (code === 5730) {
15902
+ return 'unknown_bundle';
15903
+ }
15904
+ if (code === 5740) {
15905
+ return 'batch_too_large';
15906
+ }
15907
+ // Fall back to message patterns when no specific code is available —
15908
+ // viem (and other wrapper layers) sometimes strip the numeric code
15909
+ // while preserving the original wallet message in `Details:`.
15910
+ if (/EIP-7702 not supported/i.test(message) ||
15911
+ /does not support the requested chain/i.test(message) ||
15912
+ /rejected the upgrade/i.test(message)) {
15913
+ return 'atomic_unsupported';
15914
+ }
15915
+ if (/user rejected/i.test(message)) {
15916
+ return 'user_rejected';
15917
+ }
15918
+ return 'unknown';
15919
+ }
15177
15920
  /**
15178
15921
  * Execute a cross-chain USDC bridge using the CCTP v2 protocol.
15179
15922
  *
@@ -16666,6 +17409,35 @@ const formatBridgeResult = (result, formatDirection) => {
16666
17409
  };
16667
17410
  };
16668
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);
16669
17441
  /**
16670
17442
  * BridgeKit caller component for retry and estimate operations.
16671
17443
  */
@@ -16749,6 +17521,10 @@ class BridgeKit {
16749
17521
  * A custom fee policy for the kit.
16750
17522
  */
16751
17523
  customFeePolicy;
17524
+ /** Whether error telemetry is disabled. */
17525
+ disableErrorReporting;
17526
+ /** Per-kit telemetry identity for shared helpers. */
17527
+ telemetryConfig;
16752
17528
  /**
16753
17529
  * Create a new BridgeKit instance.
16754
17530
  *
@@ -16766,6 +17542,12 @@ class BridgeKit {
16766
17542
  const defaultProviders = getDefaultProviders$2();
16767
17543
  this.providers = [...defaultProviders, ...(config.providers ?? [])];
16768
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
+ };
16769
17551
  for (const provider of this.providers) {
16770
17552
  provider.registerDispatcher(this.actionDispatcher);
16771
17553
  }
@@ -16839,19 +17621,36 @@ class BridgeKit {
16839
17621
  * ```
16840
17622
  */
16841
17623
  async bridge(params) {
16842
- // First validate the parameters
16843
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
16844
- // Then resolve chain definitions (includes adapter chain support validation)
16845
- const resolvedParams = await resolveBridgeParams(params);
16846
- // Validate network compatibility
16847
- this.validateNetworkCompatibility(resolvedParams);
16848
- // Merge the custom fee config into the resolved params
16849
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
16850
- // Find a provider that supports this route
16851
- const provider = this.findProviderForRoute(finalResolvedParams);
16852
- // Execute the transfer using the provider
16853
- // Format the bridge result into human-readable string values for the user
16854
- 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
+ });
16855
17654
  }
16856
17655
  /**
16857
17656
  * Retry a failed or incomplete cross-chain USDC bridge operation.
@@ -16923,17 +17722,33 @@ class BridgeKit {
16923
17722
  * ```
16924
17723
  */
16925
17724
  async retry(result, context, invocationMeta) {
16926
- const provider = this.providers.find((p) => p.name === result.provider);
16927
- if (!provider) {
16928
- throw new Error(`Provider ${result.provider} not found`);
16929
- }
16930
- // Merge BridgeKit caller into invocation metadata for retry operation
16931
- const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
16932
- // Format the bridge result into bigint string values for internal use
16933
- const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
16934
- // Execute the retry using the provider
16935
- // Format the bridge result into human-readable string values for the user
16936
- 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
+ });
16937
17752
  }
16938
17753
  /**
16939
17754
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -16972,18 +17787,24 @@ class BridgeKit {
16972
17787
  * ```
16973
17788
  */
16974
17789
  async estimate(params) {
16975
- // First validate the parameters
16976
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
16977
- // Then resolve chain definitions (includes adapter chain support validation)
16978
- const resolvedParams = await resolveBridgeParams(params);
16979
- // Validate network compatibility
16980
- this.validateNetworkCompatibility(resolvedParams);
16981
- // Merge the custom fee config into the resolved params
16982
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
16983
- // Find a provider that supports this route
16984
- const provider = this.findProviderForRoute(finalResolvedParams);
16985
- // Estimate the transfer using the provider and format amounts to human-readable strings
16986
- 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
+ });
16987
17808
  }
16988
17809
  /**
16989
17810
  * Get all chains supported by any provider in the kit, with optional filtering.
@@ -17300,7 +18121,11 @@ const createBridgeKit = (context) => {
17300
18121
  const getFee = context.getFee?.bind(context);
17301
18122
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
17302
18123
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
17303
- const kit = new BridgeKit();
18124
+ const kit = new BridgeKit({
18125
+ ...(context.disableErrorReporting != null && {
18126
+ disableErrorReporting: context.disableErrorReporting,
18127
+ }),
18128
+ });
17304
18129
  if (hasBoth) {
17305
18130
  kit.setCustomFeePolicy({
17306
18131
  calculateFee: async (params) => {
@@ -17317,7 +18142,7 @@ const createBridgeKit = (context) => {
17317
18142
  };
17318
18143
 
17319
18144
  var name$1 = "@circle-fin/swap-kit";
17320
- var version$1 = "1.1.0";
18145
+ var version$1 = "1.2.0";
17321
18146
  var pkg$1 = {
17322
18147
  name: name$1,
17323
18148
  version: version$1};
@@ -27778,6 +28603,18 @@ function createSwapKitContext(config = {}) {
27778
28603
  return context;
27779
28604
  }
27780
28605
 
28606
+ /**
28607
+ * Telemetry event type identifiers for swap-kit operations.
28608
+ *
28609
+ * @internal
28610
+ */
28611
+ const SWAP_EVENT_TYPES = {
28612
+ SWAP: 'swap_swap',
28613
+ ESTIMATE: 'swap_estimate',
28614
+ };
28615
+
28616
+ /** SDK name used in telemetry payloads. */
28617
+ const SDK_NAME$1 = resolveKitSdkName(pkg$1.name);
27781
28618
  /**
27782
28619
  * A high-level class-based interface for single-chain token swap operations.
27783
28620
  *
@@ -27849,6 +28686,10 @@ function createSwapKitContext(config = {}) {
27849
28686
  */
27850
28687
  class SwapKit {
27851
28688
  context;
28689
+ /** Whether error telemetry is disabled. */
28690
+ disableErrorReporting;
28691
+ /** Per-kit telemetry identity for shared helpers. */
28692
+ telemetryConfig;
27852
28693
  /**
27853
28694
  * Create a new SwapKit instance.
27854
28695
  *
@@ -27897,6 +28738,12 @@ class SwapKit {
27897
28738
  */
27898
28739
  constructor(config = {}) {
27899
28740
  this.context = createSwapKitContext(config);
28741
+ this.disableErrorReporting = config.disableErrorReporting === true;
28742
+ this.telemetryConfig = {
28743
+ sdkName: SDK_NAME$1,
28744
+ sdkVersion: pkg$1.version,
28745
+ disabled: this.disableErrorReporting,
28746
+ };
27900
28747
  }
27901
28748
  /**
27902
28749
  * Estimate the output amount and fees for a swap operation.
@@ -27938,7 +28785,11 @@ class SwapKit {
27938
28785
  * ```
27939
28786
  */
27940
28787
  async estimate(params) {
27941
- return estimate(this.context, params);
28788
+ return withErrorTelemetry(async () => estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
28789
+ sourceChain: resolveChainName(params.from.chain),
28790
+ tokenIn: params.tokenIn,
28791
+ tokenOut: params.tokenOut,
28792
+ });
27942
28793
  }
27943
28794
  /**
27944
28795
  * Execute a token swap operation on a single chain.
@@ -27994,7 +28845,11 @@ class SwapKit {
27994
28845
  * ```
27995
28846
  */
27996
28847
  async swap(params) {
27997
- return swap$1(this.context, params);
28848
+ return withErrorTelemetry(async () => swap$1(this.context, params), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, {
28849
+ sourceChain: resolveChainName(params.from.chain),
28850
+ tokenIn: params.tokenIn,
28851
+ tokenOut: params.tokenOut,
28852
+ });
27998
28853
  }
27999
28854
  /**
28000
28855
  * Get all chains supported by the configured swap providers.
@@ -28187,7 +29042,11 @@ const createSwapKit = (context) => {
28187
29042
  const getFee = context.getFee?.bind(context);
28188
29043
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
28189
29044
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
28190
- const kit = new SwapKit();
29045
+ const kit = new SwapKit({
29046
+ ...(context.disableErrorReporting != null && {
29047
+ disableErrorReporting: context.disableErrorReporting,
29048
+ }),
29049
+ });
28191
29050
  if (hasBoth) {
28192
29051
  kit.setCustomFeePolicy({
28193
29052
  computeFee: async (params) => {
@@ -28691,7 +29550,7 @@ const prepareSend = async (params) => {
28691
29550
  const fromContext = params.from;
28692
29551
  const fromAdapter = fromContext.adapter;
28693
29552
  const fromChain = resolveChainIdentifier(fromContext.chain);
28694
- const fromAddress = await fromAdapter.getAddress(fromChain);
29553
+ const fromAddress = fromContext.address ?? (await fromAdapter.getAddress(fromChain));
28695
29554
  // resolve input parameters
28696
29555
  const token = params.token ?? 'USDC';
28697
29556
  // Validate token and determine if it's an alias or custom address
@@ -29136,121 +29995,11 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
29136
29995
  };
29137
29996
 
29138
29997
  var name = "@circle-fin/unified-balance-kit";
29139
- var version = "1.0.1";
29998
+ var version = "1.1.0";
29140
29999
  var pkg = {
29141
30000
  name: name,
29142
30001
  version: version};
29143
30002
 
29144
- /**
29145
- * Default telemetry endpoint.
29146
- *
29147
- * Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
29148
- * (e.g. for staging or local development).
29149
- *
29150
- * @internal
29151
- */
29152
- const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
29153
- /**
29154
- * Resolve the telemetry endpoint URL.
29155
- *
29156
- * @internal
29157
- */
29158
- function getLogsUrl() {
29159
- if (isNodeEnvironment() &&
29160
- typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' &&
29161
- process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
29162
- return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
29163
- }
29164
- return DEFAULT_LOGS_URL;
29165
- }
29166
- /**
29167
- * Send a telemetry event to the proxy service.
29168
- *
29169
- * @remarks
29170
- * Fire-and-forget: the returned promise is intentionally not awaited
29171
- * by the caller. A fetch failure (network error, non-2xx, timeout)
29172
- * is silently swallowed so telemetry never blocks or fails user
29173
- * operations.
29174
- *
29175
- * @param payload - The structured log payload matching the server schema.
29176
- *
29177
- * @example
29178
- * ```typescript
29179
- * import { emitAnalyticsLog } from './emitLog'
29180
- *
29181
- * // Fire-and-forget — do not await
29182
- * emitAnalyticsLog(payload).catch(() => {})
29183
- * ```
29184
- *
29185
- * @internal
29186
- */
29187
- async function emitAnalyticsLog(payload) {
29188
- try {
29189
- const isNode = isNodeEnvironment();
29190
- const userAgent = getUserAgent();
29191
- await fetch(getLogsUrl(), {
29192
- method: 'POST',
29193
- headers: {
29194
- 'Content-Type': 'application/json',
29195
- // Browser restricts setting User-Agent; use X-User-Agent instead.
29196
- ...(isNode
29197
- ? { 'User-Agent': userAgent }
29198
- : { 'X-User-Agent': userAgent }),
29199
- },
29200
- body: JSON.stringify(payload),
29201
- signal: AbortSignal.timeout(5_000),
29202
- });
29203
- }
29204
- catch {
29205
- // Silently swallow — telemetry must never break user operations.
29206
- }
29207
- }
29208
-
29209
- /**
29210
- * Build the `clientContext` object for telemetry payloads.
29211
- *
29212
- * @remarks
29213
- * Uses the exported `getRuntime()` and `isNodeEnvironment()` from
29214
- * `@core/utils` to detect the runtime environment (per Dominik's
29215
- * feedback to reuse existing infrastructure). The returned string
29216
- * is parsed into the structured `ClientContext` fields expected by
29217
- * the server schema.
29218
- *
29219
- * @returns A {@link ClientContext} with platform, OS, and runtime name
29220
- * populated from the current environment.
29221
- *
29222
- * @example
29223
- * ```typescript
29224
- * import { buildClientContext } from './clientContext'
29225
- *
29226
- * const ctx = buildClientContext()
29227
- * // Node: { platform: 'node', os: 'darwin', runtimeName: null }
29228
- * // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
29229
- * ```
29230
- */
29231
- function buildClientContext() {
29232
- const runtime = getRuntime();
29233
- if (runtime.startsWith('browser/')) {
29234
- return {
29235
- platform: 'browser',
29236
- os: null,
29237
- runtimeName: runtime.slice('browser/'.length).toLowerCase(),
29238
- };
29239
- }
29240
- if (runtime.startsWith('node/')) {
29241
- return {
29242
- platform: 'node',
29243
- os: isNodeEnvironment() ? process.platform : null,
29244
- runtimeName: null,
29245
- };
29246
- }
29247
- return {
29248
- platform: 'node',
29249
- os: null,
29250
- runtimeName: null,
29251
- };
29252
- }
29253
-
29254
30003
  /**
29255
30004
  * Event type identifiers accepted by the server-side logs endpoint.
29256
30005
  *
@@ -29263,6 +30012,11 @@ const EVENT_TYPES = {
29263
30012
  DEPOSIT_FOR: 'unified_balance_deposit_for',
29264
30013
  SPEND: 'unified_balance_spend',
29265
30014
  SPEND_FORWARDER: 'unified_balance_spend_forwarder',
30015
+ ESTIMATE_SPEND: 'unified_balance_estimate_spend',
30016
+ GET_BALANCES: 'unified_balance_get_balances',
30017
+ ADD_DELEGATE: 'unified_balance_add_delegate',
30018
+ REMOVE_DELEGATE: 'unified_balance_remove_delegate',
30019
+ GET_DELEGATE_STATUS: 'unified_balance_get_delegate_status',
29266
30020
  INITIATE_REMOVE_FUND: 'unified_balance_initiate_remove_fund',
29267
30021
  REMOVE_FUND: 'unified_balance_remove_fund',
29268
30022
  };
@@ -29300,33 +30054,29 @@ function extractSingleChainResult(data) {
29300
30054
  txHash: data.txHash,
29301
30055
  };
29302
30056
  }
29303
- /**
29304
- * Resolve a chain identifier that may be a string or a
29305
- * `ChainDefinition` object to a plain string.
29306
- *
29307
- * @internal
29308
- */
29309
- function resolveChainString(chain) {
29310
- if (typeof chain === 'string')
29311
- return chain;
29312
- return chain.chain;
29313
- }
29314
30057
  /**
29315
30058
  * Extract log-relevant fields from a spend result.
29316
30059
  *
30060
+ * @remarks
30061
+ * Collects all source chains from allocations and joins them with
30062
+ * commas so multi-chain spends are fully represented.
30063
+ *
29317
30064
  * @internal
29318
30065
  */
29319
30066
  function extractSpend(data) {
29320
- const rawChain = data.allocations?.[0]?.chain;
29321
- let firstSourceChain;
29322
- if (rawChain != null) {
29323
- firstSourceChain = resolveChainString(rawChain);
30067
+ const allocs = data.allocations ?? [];
30068
+ const chains = [];
30069
+ for (const alloc of allocs) {
30070
+ const name = resolveChainName(alloc.chain);
30071
+ if (name != null) {
30072
+ chains.push(name);
30073
+ }
29324
30074
  }
29325
- const hasAllocations = data.allocations?.[0] != null;
30075
+ const sourceChain = chains.length > 0 ? chains.join(',') : undefined;
29326
30076
  return {
29327
- ...(firstSourceChain != null && { sourceChain: firstSourceChain }),
30077
+ ...(sourceChain != null && { sourceChain }),
29328
30078
  destinationChain: data.destinationChain,
29329
- ...(hasAllocations && { tokenIn: 'USDC' }),
30079
+ ...(allocs.length > 0 && { tokenIn: 'USDC' }),
29330
30080
  txHash: data.txHash,
29331
30081
  };
29332
30082
  }
@@ -29425,10 +30175,9 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29425
30175
  * telemetry for successful verb operations.
29426
30176
  *
29427
30177
  * @remarks
29428
- * Follows Dominik's feedback to reuse the existing event bus as the
29429
- * SDK-side hook for telemetry. Registers a handler for each verb's
29430
- * `.succeeded` event. Non-verb and non-succeeded events are not
29431
- * subscribed to.
30178
+ * Registers a handler for each verb's `.succeeded` event using the
30179
+ * shared `registerTelemetryHandler` from `@core/utils`. Non-verb and
30180
+ * non-succeeded events are not subscribed to.
29432
30181
  *
29433
30182
  * The HTTP POST is fire-and-forget — telemetry never blocks or fails
29434
30183
  * user operations.
@@ -29448,20 +30197,7 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29448
30197
  * @internal
29449
30198
  */
29450
30199
  function registerTelemetryHandler(dispatcher, sdkName, sdkVersion) {
29451
- for (const actionName of Object.keys(VERB_EVENT_MAP)) {
29452
- dispatcher.on(actionName, (payload) => {
29453
- try {
29454
- const log = mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion);
29455
- if (log) {
29456
- // Fire-and-forget — intentionally not awaited.
29457
- void emitAnalyticsLog(log);
29458
- }
29459
- }
29460
- catch {
29461
- // Silently swallow — telemetry must never break user operations.
29462
- }
29463
- });
29464
- }
30200
+ registerTelemetryHandler$1(dispatcher, sdkName, sdkVersion, VERB_EVENT_MAP, mapSucceededEventToLog);
29465
30201
  }
29466
30202
 
29467
30203
  /**
@@ -35491,6 +36227,8 @@ function getSupportedChains(context, token, options) {
35491
36227
  return Object.values(Object.fromEntries(filtered.map((chain) => [chain.chain, chain])));
35492
36228
  }
35493
36229
 
36230
+ /** SDK name used in telemetry payloads. */
36231
+ const SDK_NAME = resolveKitSdkName(pkg.name);
35494
36232
  /**
35495
36233
  * A high-level class-based interface for cross-chain USDC deposits,
35496
36234
  * spending, balance queries, delegation management, and withdrawals.
@@ -35538,6 +36276,10 @@ class UnifiedBalanceKit {
35538
36276
  * The action dispatcher for the kit.
35539
36277
  */
35540
36278
  actionDispatcher;
36279
+ /** Whether error telemetry is disabled. */
36280
+ disableErrorReporting;
36281
+ /** Per-kit telemetry identity for shared helpers. */
36282
+ telemetryConfig;
35541
36283
  /**
35542
36284
  * Create a new UnifiedBalanceKit instance.
35543
36285
  *
@@ -35550,14 +36292,49 @@ class UnifiedBalanceKit {
35550
36292
  constructor(config = {}) {
35551
36293
  this.context = createUnifiedBalanceKitContext(config);
35552
36294
  this.actionDispatcher = new Actionable();
36295
+ this.disableErrorReporting = config.disableErrorReporting === true;
36296
+ this.telemetryConfig = {
36297
+ sdkName: SDK_NAME,
36298
+ sdkVersion: pkg.version,
36299
+ disabled: this.disableErrorReporting,
36300
+ };
35553
36301
  for (const provider of this.context.providers) {
35554
36302
  provider.registerDispatcher(this.actionDispatcher);
35555
36303
  }
35556
36304
  if (!config.disableAnalytics) {
35557
- const sdkName = pkg.name.replace('@circle-fin/', '');
35558
- registerTelemetryHandler(this.actionDispatcher, sdkName, pkg.version);
36305
+ registerTelemetryHandler(this.actionDispatcher, SDK_NAME, pkg.version);
35559
36306
  }
35560
36307
  }
36308
+ /**
36309
+ * Extract comma-separated source chain names from spend params.
36310
+ *
36311
+ * @internal
36312
+ */
36313
+ static extractSpendSourceChains(params) {
36314
+ if (params == null || typeof params !== 'object' || !('from' in params)) {
36315
+ return undefined;
36316
+ }
36317
+ const fromVal = params.from;
36318
+ const sources = Array.isArray(fromVal) ? fromVal : [fromVal];
36319
+ const chains = [];
36320
+ for (const src of sources) {
36321
+ const allocs = src?.allocations;
36322
+ let allocArr;
36323
+ if (allocs == null) {
36324
+ allocArr = [];
36325
+ }
36326
+ else {
36327
+ allocArr = Array.isArray(allocs) ? allocs : [allocs];
36328
+ }
36329
+ for (const alloc of allocArr) {
36330
+ const name = resolveChainName(alloc.chain);
36331
+ if (name != null) {
36332
+ chains.push(name);
36333
+ }
36334
+ }
36335
+ }
36336
+ return chains.length > 0 ? chains.join(',') : undefined;
36337
+ }
35561
36338
  // implementation just forwards to the bus
35562
36339
  on(actionOrWildcard, handler) {
35563
36340
  this.actionDispatcher.on(actionOrWildcard, handler);
@@ -35580,7 +36357,10 @@ class UnifiedBalanceKit {
35580
36357
  * @see UnifiedBalanceKit.depositFor to deposit into another account.
35581
36358
  */
35582
36359
  async deposit(params) {
35583
- return deposit(this.context, params);
36360
+ return withErrorTelemetry(async () => deposit(this.context, params), EVENT_TYPES.DEPOSIT, this.telemetryConfig, {
36361
+ sourceChain: resolveChainName(params.from?.chain),
36362
+ tokenIn: params.token ?? 'USDC',
36363
+ });
35584
36364
  }
35585
36365
  /**
35586
36366
  * Deposit USDC into another account (not the caller's).
@@ -35592,7 +36372,10 @@ class UnifiedBalanceKit {
35592
36372
  * @see UnifiedBalanceKit.deposit to deposit into your own account.
35593
36373
  */
35594
36374
  async depositFor(params) {
35595
- return depositFor(this.context, params);
36375
+ return withErrorTelemetry(async () => depositFor(this.context, params), EVENT_TYPES.DEPOSIT_FOR, this.telemetryConfig, {
36376
+ sourceChain: resolveChainName(params.from?.chain),
36377
+ tokenIn: params.token ?? 'USDC',
36378
+ });
35596
36379
  }
35597
36380
  /**
35598
36381
  * Spend (mint) USDC on a destination chain by pulling funds from one
@@ -35605,7 +36388,17 @@ class UnifiedBalanceKit {
35605
36388
  * @see UnifiedBalanceKit.estimateSpend to preview fees before spending.
35606
36389
  */
35607
36390
  async spend(params) {
35608
- return spend(this.context, params);
36391
+ const destChain = 'to' in params
36392
+ ? resolveChainName(params.to.chain)
36393
+ : undefined;
36394
+ const tokenIn = 'token' in params
36395
+ ? (params.token ?? 'USDC')
36396
+ : 'USDC';
36397
+ return withErrorTelemetry(async () => spend(this.context, params), EVENT_TYPES.SPEND, this.telemetryConfig, {
36398
+ sourceChain: UnifiedBalanceKit.extractSpendSourceChains(params),
36399
+ ...(destChain != null && { destinationChain: destChain }),
36400
+ tokenIn,
36401
+ });
35609
36402
  }
35610
36403
  /**
35611
36404
  * Estimate the fees for a spend operation without executing it.
@@ -35615,7 +36408,17 @@ class UnifiedBalanceKit {
35615
36408
  * @returns Promise resolving to the fee estimate.
35616
36409
  */
35617
36410
  async estimateSpend(params) {
35618
- return estimateSpend(this.context, params);
36411
+ const destChain = 'to' in params
36412
+ ? resolveChainName(params.to.chain)
36413
+ : undefined;
36414
+ const sourceChain = UnifiedBalanceKit.extractSpendSourceChains(params);
36415
+ return withErrorTelemetry(async () => estimateSpend(this.context, params), EVENT_TYPES.ESTIMATE_SPEND, this.telemetryConfig, {
36416
+ ...(sourceChain != null && { sourceChain }),
36417
+ ...(destChain != null && { destinationChain: destChain }),
36418
+ tokenIn: 'token' in params
36419
+ ? (params.token ?? 'USDC')
36420
+ : 'USDC',
36421
+ });
35619
36422
  }
35620
36423
  /**
35621
36424
  * Fetch aggregated and per-chain balances for one or more accounts.
@@ -35624,7 +36427,7 @@ class UnifiedBalanceKit {
35624
36427
  * @returns Promise resolving to the aggregated balance result.
35625
36428
  */
35626
36429
  async getBalances(params) {
35627
- return getBalances(this.context, params);
36430
+ return withErrorTelemetry(async () => getBalances(this.context, params), EVENT_TYPES.GET_BALANCES, this.telemetryConfig);
35628
36431
  }
35629
36432
  /**
35630
36433
  * Grant spending rights to another address on the owner's account.
@@ -35639,7 +36442,7 @@ class UnifiedBalanceKit {
35639
36442
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35640
36443
  */
35641
36444
  async addDelegate(params) {
35642
- return addDelegate(this.context, params);
36445
+ return withErrorTelemetry(async () => addDelegate(this.context, params), EVENT_TYPES.ADD_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35643
36446
  }
35644
36447
  /**
35645
36448
  * Revoke spending rights from a delegate on the owner's account.
@@ -35654,7 +36457,7 @@ class UnifiedBalanceKit {
35654
36457
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35655
36458
  */
35656
36459
  async removeDelegate(params) {
35657
- return removeDelegate(this.context, params);
36460
+ return withErrorTelemetry(async () => removeDelegate(this.context, params), EVENT_TYPES.REMOVE_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35658
36461
  }
35659
36462
  /**
35660
36463
  * Kick off a delayed fund removal from an account.
@@ -35666,7 +36469,10 @@ class UnifiedBalanceKit {
35666
36469
  * @see UnifiedBalanceKit.removeFund to complete the fund removal.
35667
36470
  */
35668
36471
  async initiateRemoveFund(params) {
35669
- return initiateRemoveFund(this.context, params);
36472
+ return withErrorTelemetry(async () => initiateRemoveFund(this.context, params), EVENT_TYPES.INITIATE_REMOVE_FUND, this.telemetryConfig, {
36473
+ sourceChain: resolveChainName(params.from?.chain),
36474
+ tokenIn: params.token ?? 'USDC',
36475
+ });
35670
36476
  }
35671
36477
  /**
35672
36478
  * Complete a fund removal once the activation period has passed.
@@ -35678,7 +36484,10 @@ class UnifiedBalanceKit {
35678
36484
  * @see UnifiedBalanceKit.initiateRemoveFund to start the process.
35679
36485
  */
35680
36486
  async removeFund(params) {
35681
- return removeFund(this.context, params);
36487
+ return withErrorTelemetry(async () => removeFund(this.context, params), EVENT_TYPES.REMOVE_FUND, this.telemetryConfig, {
36488
+ sourceChain: resolveChainName(params.from?.chain),
36489
+ tokenIn: params.token ?? 'USDC',
36490
+ });
35682
36491
  }
35683
36492
  /**
35684
36493
  * Check the finality-aware delegate status of an address.
@@ -35706,7 +36515,7 @@ class UnifiedBalanceKit {
35706
36515
  * ```
35707
36516
  */
35708
36517
  async getDelegateStatus(params) {
35709
- return getDelegateStatus(this.context, params);
36518
+ return withErrorTelemetry(async () => getDelegateStatus(this.context, params), EVENT_TYPES.GET_DELEGATE_STATUS, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35710
36519
  }
35711
36520
  /**
35712
36521
  * Get all chains supported by the kit.
@@ -36196,8 +37005,18 @@ class AppKit {
36196
37005
  * ```
36197
37006
  */
36198
37007
  constructor(config = {}) {
36199
- this.context = createContext(config);
36200
- this.unifiedBalance = new AppKitUnifiedBalance(config.unifiedBalance);
37008
+ this.context = createContext({
37009
+ ...config,
37010
+ ...(config.disableErrorReporting != null && {
37011
+ disableErrorReporting: config.disableErrorReporting,
37012
+ }),
37013
+ });
37014
+ this.unifiedBalance = new AppKitUnifiedBalance({
37015
+ ...config.unifiedBalance,
37016
+ ...(config.disableErrorReporting != null && {
37017
+ disableErrorReporting: config.disableErrorReporting,
37018
+ }),
37019
+ });
36201
37020
  }
36202
37021
  /**
36203
37022
  * Execute a cross-chain USDC bridge transfer.