@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.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";
@@ -3419,6 +3421,8 @@ exports.Blockchain = void 0;
3419
3421
  Blockchain["Noble_Testnet"] = "Noble_Testnet";
3420
3422
  Blockchain["Optimism"] = "Optimism";
3421
3423
  Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
3424
+ Blockchain["Pharos"] = "Pharos";
3425
+ Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
3422
3426
  Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
3423
3427
  Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
3424
3428
  Blockchain["Plume"] = "Plume";
@@ -3622,11 +3626,13 @@ exports.BridgeChain = void 0;
3622
3626
  BridgeChain["Edge"] = "Edge";
3623
3627
  BridgeChain["Ethereum"] = "Ethereum";
3624
3628
  BridgeChain["HyperEVM"] = "HyperEVM";
3629
+ BridgeChain["Injective"] = "Injective";
3625
3630
  BridgeChain["Ink"] = "Ink";
3626
3631
  BridgeChain["Linea"] = "Linea";
3627
3632
  BridgeChain["Monad"] = "Monad";
3628
3633
  BridgeChain["Morph"] = "Morph";
3629
3634
  BridgeChain["Optimism"] = "Optimism";
3635
+ BridgeChain["Pharos"] = "Pharos";
3630
3636
  BridgeChain["Plume"] = "Plume";
3631
3637
  BridgeChain["Polygon"] = "Polygon";
3632
3638
  BridgeChain["Sei"] = "Sei";
@@ -3644,11 +3650,13 @@ exports.BridgeChain = void 0;
3644
3650
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
3645
3651
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
3646
3652
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
3653
+ BridgeChain["Injective_Testnet"] = "Injective_Testnet";
3647
3654
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
3648
3655
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
3649
3656
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
3650
3657
  BridgeChain["Morph_Testnet"] = "Morph_Testnet";
3651
3658
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
3659
+ BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
3652
3660
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
3653
3661
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
3654
3662
  BridgeChain["Sei_Testnet"] = "Sei_Testnet";
@@ -3994,6 +4002,12 @@ const SWAP_TOKEN_REGISTRY = {
3994
4002
  category: 'wrapped',
3995
4003
  description: 'Wrapped Polygon',
3996
4004
  },
4005
+ CIRBTC: {
4006
+ symbol: 'CIRBTC',
4007
+ decimals: 8,
4008
+ category: 'wrapped',
4009
+ description: 'Circle Bitcoin',
4010
+ },
3997
4011
  };
3998
4012
  /**
3999
4013
  * Special NATIVE token constant for swap operations.
@@ -5081,6 +5095,98 @@ const HyperEVMTestnet = defineChain({
5081
5095
  },
5082
5096
  });
5083
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
+
5084
5190
  /**
5085
5191
  * Ink Mainnet chain definition
5086
5192
  * @remarks
@@ -5690,6 +5796,96 @@ const OptimismSepolia = defineChain({
5690
5796
  },
5691
5797
  });
5692
5798
 
5799
+ /**
5800
+ * Pharos Mainnet chain definition
5801
+ * @remarks
5802
+ * This represents the official production network for the Pharos blockchain.
5803
+ * Pharos is a modular, full-stack parallel Layer 1 blockchain with
5804
+ * sub-second finality and EVM compatibility.
5805
+ */
5806
+ const Pharos = defineChain({
5807
+ type: 'evm',
5808
+ chain: exports.Blockchain.Pharos,
5809
+ name: 'Pharos',
5810
+ title: 'Pharos Mainnet',
5811
+ nativeCurrency: {
5812
+ name: 'Pharos',
5813
+ symbol: 'PHAROS',
5814
+ decimals: 18,
5815
+ },
5816
+ chainId: 1672,
5817
+ isTestnet: false,
5818
+ explorerUrl: 'https://pharos.socialscan.io/tx/{hash}',
5819
+ rpcEndpoints: ['https://rpc.pharos.xyz'],
5820
+ eurcAddress: null,
5821
+ usdcAddress: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
5822
+ usdtAddress: null,
5823
+ cctp: {
5824
+ domain: 31,
5825
+ contracts: {
5826
+ v2: {
5827
+ type: 'split',
5828
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5829
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5830
+ confirmations: 1,
5831
+ fastConfirmations: 1,
5832
+ },
5833
+ },
5834
+ forwarderSupported: {
5835
+ source: false,
5836
+ destination: false,
5837
+ },
5838
+ },
5839
+ kitContracts: {
5840
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
5841
+ },
5842
+ });
5843
+
5844
+ /**
5845
+ * Pharos Atlantic Testnet chain definition
5846
+ * @remarks
5847
+ * This represents the official test network for the Pharos blockchain.
5848
+ * Pharos is a modular, full-stack parallel Layer 1 blockchain with
5849
+ * sub-second finality and EVM compatibility.
5850
+ */
5851
+ const PharosTestnet = defineChain({
5852
+ type: 'evm',
5853
+ chain: exports.Blockchain.Pharos_Testnet,
5854
+ name: 'Pharos Atlantic',
5855
+ title: 'Pharos Atlantic Testnet',
5856
+ nativeCurrency: {
5857
+ name: 'Pharos',
5858
+ symbol: 'PHAROS',
5859
+ decimals: 18,
5860
+ },
5861
+ chainId: 688689,
5862
+ isTestnet: true,
5863
+ explorerUrl: 'https://atlantic.pharosscan.xyz/tx/{hash}',
5864
+ rpcEndpoints: ['https://atlantic.dplabs-internal.com'],
5865
+ eurcAddress: null,
5866
+ usdcAddress: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
5867
+ usdtAddress: null,
5868
+ cctp: {
5869
+ domain: 31,
5870
+ contracts: {
5871
+ v2: {
5872
+ type: 'split',
5873
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5874
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5875
+ confirmations: 1,
5876
+ fastConfirmations: 1,
5877
+ },
5878
+ },
5879
+ forwarderSupported: {
5880
+ source: false,
5881
+ destination: false,
5882
+ },
5883
+ },
5884
+ kitContracts: {
5885
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
5886
+ },
5887
+ });
5888
+
5693
5889
  /**
5694
5890
  * Plume Mainnet chain definition
5695
5891
  * @remarks
@@ -5972,7 +6168,7 @@ const Sei = defineChain({
5972
6168
  },
5973
6169
  chainId: 1329,
5974
6170
  isTestnet: false,
5975
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=pacific-1',
6171
+ explorerUrl: 'https://seiscan.io/tx/{hash}',
5976
6172
  rpcEndpoints: ['https://evm-rpc.sei-apis.com'],
5977
6173
  eurcAddress: null,
5978
6174
  usdcAddress: '0xe15fC38F6D8c56aF07bbCBe3BAf5708A2Bf42392',
@@ -6030,7 +6226,7 @@ const SeiTestnet = defineChain({
6030
6226
  },
6031
6227
  chainId: 1328,
6032
6228
  isTestnet: true,
6033
- explorerUrl: 'https://seitrace.com/tx/{hash}?chain=atlantic-2',
6229
+ explorerUrl: 'https://testnet.seiscan.io/tx/{hash}',
6034
6230
  rpcEndpoints: ['https://evm-rpc-testnet.sei-apis.com'],
6035
6231
  eurcAddress: null,
6036
6232
  usdcAddress: '0x4fCF1784B31630811181f670Aea7A7bEF803eaED',
@@ -6833,6 +7029,8 @@ var Chains = {
6833
7029
  HederaTestnet: HederaTestnet,
6834
7030
  HyperEVM: HyperEVM,
6835
7031
  HyperEVMTestnet: HyperEVMTestnet,
7032
+ Injective: Injective,
7033
+ InjectiveTestnet: InjectiveTestnet,
6836
7034
  Ink: Ink,
6837
7035
  InkTestnet: InkTestnet,
6838
7036
  Linea: Linea,
@@ -6847,6 +7045,8 @@ var Chains = {
6847
7045
  NobleTestnet: NobleTestnet,
6848
7046
  Optimism: Optimism,
6849
7047
  OptimismSepolia: OptimismSepolia,
7048
+ Pharos: Pharos,
7049
+ PharosTestnet: PharosTestnet,
6850
7050
  Plume: Plume,
6851
7051
  PlumeTestnet: PlumeTestnet,
6852
7052
  PolkadotAssetHub: PolkadotAssetHub,
@@ -7452,6 +7652,44 @@ function resolveChainIdentifier(chainIdentifier) {
7452
7652
  throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
7453
7653
  }
7454
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
+
7455
7693
  /**
7456
7694
  * @packageDocumentation
7457
7695
  * @module SwapTokenUtils
@@ -9108,6 +9346,7 @@ const USDC = {
9108
9346
  [exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9109
9347
  [exports.Blockchain.Hedera]: '0.0.456858',
9110
9348
  [exports.Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
9349
+ [exports.Blockchain.Injective]: '0xa00C59fF5a080D2b954d0c75e46E22a0c371235a',
9111
9350
  [exports.Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
9112
9351
  [exports.Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
9113
9352
  [exports.Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
@@ -9115,6 +9354,7 @@ const USDC = {
9115
9354
  [exports.Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
9116
9355
  [exports.Blockchain.Noble]: 'uusdc',
9117
9356
  [exports.Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
9357
+ [exports.Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
9118
9358
  [exports.Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
9119
9359
  [exports.Blockchain.Polkadot_Asset_Hub]: '1337',
9120
9360
  [exports.Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
@@ -9139,6 +9379,7 @@ const USDC = {
9139
9379
  [exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9140
9380
  [exports.Blockchain.Hedera_Testnet]: '0.0.429274',
9141
9381
  [exports.Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
9382
+ [exports.Blockchain.Injective_Testnet]: '0x0C382e685bbeeFE5d3d9C29e29E341fEE8E84C5d',
9142
9383
  [exports.Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
9143
9384
  [exports.Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
9144
9385
  [exports.Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
@@ -9146,6 +9387,7 @@ const USDC = {
9146
9387
  [exports.Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
9147
9388
  [exports.Blockchain.Noble_Testnet]: 'uusdc',
9148
9389
  [exports.Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
9390
+ [exports.Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
9149
9391
  [exports.Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
9150
9392
  [exports.Blockchain.Polkadot_Westmint]: '31337',
9151
9393
  [exports.Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
@@ -9426,6 +9668,32 @@ const MON = {
9426
9668
  },
9427
9669
  };
9428
9670
 
9671
+ /**
9672
+ * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
9673
+ *
9674
+ * @remarks
9675
+ * Built-in cirBTC definition for the TokenRegistry. Currently deployed
9676
+ * on Arc Testnet.
9677
+ *
9678
+ * @example
9679
+ * ```typescript
9680
+ * import { CIRBTC } from '@core/tokens'
9681
+ * import { Blockchain } from '@core/chains'
9682
+ *
9683
+ * console.log(CIRBTC.symbol) // 'cirBTC'
9684
+ * console.log(CIRBTC.decimals) // 8
9685
+ * console.log(CIRBTC.locators[Blockchain.Arc_Testnet])
9686
+ * // '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
9687
+ * ```
9688
+ */
9689
+ const CIRBTC = {
9690
+ symbol: 'cirBTC',
9691
+ decimals: 8,
9692
+ locators: {
9693
+ [exports.Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
9694
+ },
9695
+ };
9696
+
9429
9697
  // Re-export for consumers
9430
9698
  /**
9431
9699
  * All default token definitions.
@@ -9434,7 +9702,7 @@ const MON = {
9434
9702
  * These tokens are automatically included in the TokenRegistry when created
9435
9703
  * without explicit defaults. Extensions can override these definitions.
9436
9704
  * Includes USDC, USDT, EURC, DAI, USDE, PYUSD, WETH, WBTC, WSOL, WAVAX,
9437
- * WPOL, ETH, POL, PLUME, and MON.
9705
+ * WPOL, ETH, POL, PLUME, MON, and cirBTC.
9438
9706
  *
9439
9707
  * @example
9440
9708
  * ```typescript
@@ -9465,6 +9733,7 @@ const DEFAULT_TOKENS = [
9465
9733
  POL,
9466
9734
  PLUME,
9467
9735
  MON,
9736
+ CIRBTC,
9468
9737
  ];
9469
9738
  /**
9470
9739
  * Uppercased token symbols approved for swap fee collection.
@@ -10212,8 +10481,325 @@ async function retryAsync(fn, options) {
10212
10481
  throw new Error('retryAsync: unreachable');
10213
10482
  }
10214
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
+
10215
10801
  var name$2 = "@circle-fin/bridge-kit";
10216
- var version$3 = "1.8.3";
10802
+ var version$3 = "1.10.0";
10217
10803
  var pkg$3 = {
10218
10804
  name: name$2,
10219
10805
  version: version$3};
@@ -13178,18 +13764,16 @@ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet) => {
13178
13764
  };
13179
13765
  /**
13180
13766
  * Fetches attestation data from the IRIS API with retry and timeout handling.
13181
- * Since fetching starts after a confirmed burn transaction, we attempt up to 10 retries.
13182
- * Implements a conservative delay between requests to respect the API rate
13183
- * limit of 35 requests per second. To avoid rate limiting when multiple processes are
13184
- * running concurrently, we enforce a 200ms delay between retries.
13185
13767
  *
13186
- * Each HTTP request is given a maximum of 2 seconds before it is aborted.
13187
- * 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.
13188
13771
  *
13189
- * The total maximum time this function might take (worst case) is:
13190
- * - Perattempt timeout: 2 000 ms
13191
- * - Retry delays: 9 × 200 ms = 1 800 ms
13192
- * - 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
13193
13777
  *
13194
13778
  * @param sourceDomainId - The CCTP domain ID.
13195
13779
  * @param transactionHash - The transaction hash to fetch attestation for.
@@ -14344,11 +14928,18 @@ async function executePreparedChainRequest({ name, request, adapter, chain, conf
14344
14928
  step.explorerUrl = buildExplorerUrl(chain, txHash);
14345
14929
  if (outcome.errorMessage) {
14346
14930
  step.errorMessage = outcome.errorMessage;
14931
+ // Transaction was mined but reverted on-chain.
14932
+ step.errorCategory = 'chain_revert';
14347
14933
  }
14348
14934
  }
14349
14935
  catch (err) {
14350
14936
  step.state = 'error';
14351
14937
  step.error = err;
14938
+ // Sequential path does not yet attempt fine-grained classification of
14939
+ // pre-submission errors (user_rejected, capability errors, etc.). Mark
14940
+ // as `unknown` so consumers can at least detect the category is
14941
+ // populated uniformly across batched and sequential flows.
14942
+ step.errorCategory = 'unknown';
14352
14943
  // Optionally parse for common blockchain error formats
14353
14944
  if (err instanceof Error) {
14354
14945
  step.errorMessage = err.message;
@@ -15014,16 +15605,70 @@ async function executeBatchedApproveAndBurn(params, provider) {
15014
15605
  const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
15015
15606
  const approveReceipt = batchResult.receipts[0];
15016
15607
  const burnReceipt = batchResult.receipts[1];
15017
- const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
15018
- const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
15608
+ const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
15609
+ const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
15019
15610
  if (burnStep.state !== 'error' && !burnStep.txHash) {
15020
15611
  burnStep.state = 'error';
15021
15612
  burnStep.errorMessage =
15022
15613
  'Batched burn step completed but no transaction hash was returned.';
15614
+ burnStep.errorCategory = 'unknown';
15023
15615
  }
15024
15616
  const context = { burnTxHash: burnStep.txHash ?? '' };
15025
15617
  return { approveStep, burnStep, context };
15026
15618
  }
15619
+ /**
15620
+ * Derive a {@link BridgeStepErrorCategory} for a missing receipt.
15621
+ *
15622
+ * Combines the EIP-5792 `statusCode` (when present) with the underlying
15623
+ * polling error (when set) to produce the most specific category available.
15624
+ * Falls back to `'unknown'` when neither signal is conclusive.
15625
+ *
15626
+ * @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
15627
+ * @param batchError - The polling error from `batchExecute`, if any.
15628
+ * @returns The derived error category for a missing-receipt step.
15629
+ *
15630
+ * @internal
15631
+ */
15632
+ function categorizeMissingReceipt(statusCode, batchError) {
15633
+ if (statusCode === 400)
15634
+ return 'failed_offchain';
15635
+ if (statusCode === 500)
15636
+ return 'reverted_onchain';
15637
+ if (statusCode === 600)
15638
+ return 'partial_reverted';
15639
+ if (batchError instanceof KitError &&
15640
+ batchError.code === NetworkError.TIMEOUT.code) {
15641
+ return 'polling_timeout';
15642
+ }
15643
+ return 'unknown';
15644
+ }
15645
+ /**
15646
+ * Derive a {@link BridgeStepErrorCategory} for a receipt that was returned
15647
+ * but whose per-call `status` is not `'success'`.
15648
+ *
15649
+ * A numeric EIP-5792 `statusCode` of 500 or 600 tells us the whole batch
15650
+ * reverted on-chain (completely or partially); otherwise the receipt
15651
+ * itself signalled a revert without a distinguishing code, so classify
15652
+ * as a plain on-chain revert.
15653
+ *
15654
+ * @param statusCode - The terminal `statusCode` from `wallet_getCallsStatus`, if any.
15655
+ * @returns The derived error category for a failed-receipt step.
15656
+ *
15657
+ * @internal
15658
+ */
15659
+ function categorizeFailedReceipt(statusCode) {
15660
+ // Mirror categorizeMissingReceipt: if the wallet's terminal status is
15661
+ // 400 ("batch not included onchain"), that judgement is authoritative
15662
+ // even when a non-success receipt is attached. Without this, a wrapped
15663
+ // 400 receipt would fall through to `chain_revert` and mislead UX.
15664
+ if (statusCode === 400)
15665
+ return 'failed_offchain';
15666
+ if (statusCode === 600)
15667
+ return 'partial_reverted';
15668
+ if (statusCode === 500)
15669
+ return 'reverted_onchain';
15670
+ return 'chain_revert';
15671
+ }
15027
15672
  /**
15028
15673
  * Build a {@link BridgeStep} from a single receipt within a batch.
15029
15674
  *
@@ -15038,11 +15683,17 @@ async function executeBatchedApproveAndBurn(params, provider) {
15038
15683
  * @param batchId - Wallet-assigned batch identifier.
15039
15684
  * @param adapter - The batch-capable adapter (used for confirmation).
15040
15685
  * @param chain - The EVM chain the batch was executed on.
15686
+ * @param statusCode - Optional EIP-5792 `statusCode` for the batch.
15687
+ * Used to classify the step's error category when the receipt is
15688
+ * missing or failed.
15689
+ * @param batchError - Optional polling error from `batchExecute`.
15690
+ * Preserved on the step so callers can inspect underlying timeouts
15691
+ * or RPC failures.
15041
15692
  * @returns A fully-populated bridge step with state, tx hash and explorer URL.
15042
15693
  *
15043
15694
  * @internal
15044
15695
  */
15045
- async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15696
+ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCode, batchError) {
15046
15697
  const step = {
15047
15698
  name,
15048
15699
  state: 'pending',
@@ -15052,6 +15703,10 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15052
15703
  if (!receipt) {
15053
15704
  step.state = 'error';
15054
15705
  step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
15706
+ step.errorCategory = categorizeMissingReceipt(statusCode, batchError);
15707
+ if (batchError !== undefined) {
15708
+ step.error = batchError;
15709
+ }
15055
15710
  return step;
15056
15711
  }
15057
15712
  step.txHash = receipt.txHash;
@@ -15061,11 +15716,13 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15061
15716
  if (receipt.status !== 'success') {
15062
15717
  step.state = 'error';
15063
15718
  step.errorMessage = `${name} call failed within batch ${batchId}.`;
15719
+ step.errorCategory = categorizeFailedReceipt(statusCode);
15064
15720
  return step;
15065
15721
  }
15066
15722
  if (!receipt.txHash) {
15067
15723
  step.state = 'error';
15068
15724
  step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
15725
+ step.errorCategory = 'unknown';
15069
15726
  return step;
15070
15727
  }
15071
15728
  try {
@@ -15080,6 +15737,7 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15080
15737
  step.data = transaction;
15081
15738
  if (outcome.errorMessage) {
15082
15739
  step.errorMessage = outcome.errorMessage;
15740
+ step.errorCategory = 'chain_revert';
15083
15741
  }
15084
15742
  }
15085
15743
  catch (err) {
@@ -15087,11 +15745,12 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
15087
15745
  step.error = err;
15088
15746
  step.errorMessage =
15089
15747
  err instanceof Error ? err.message : 'Unknown error during confirmation.';
15748
+ step.errorCategory = 'unknown';
15090
15749
  }
15091
15750
  return step;
15092
15751
  }
15093
15752
 
15094
- var version$2 = "1.6.3";
15753
+ var version$2 = "1.8.0";
15095
15754
  var pkg$2 = {
15096
15755
  version: version$2};
15097
15756
 
@@ -15167,6 +15826,7 @@ async function executeBatchedPath(params, provider, result, invocation) {
15167
15826
  errorMessage: error_ instanceof Error
15168
15827
  ? error_.message
15169
15828
  : 'Batched approve + burn failed.',
15829
+ errorCategory: classifyPreSubmissionError(error_),
15170
15830
  });
15171
15831
  return undefined;
15172
15832
  }
@@ -15181,6 +15841,89 @@ function ensureStepErrorMessage(name, step) {
15181
15841
  step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
15182
15842
  }
15183
15843
  }
15844
+ /**
15845
+ * Coerce a raw JSON-RPC `code` to a number.
15846
+ *
15847
+ * Some wallet SDKs serialize the JSON-RPC `code` as a string ("4001")
15848
+ * after round-tripping through JSON; accept both shapes so strict `===`
15849
+ * comparisons downstream still classify 5720/5730/5740 correctly — those
15850
+ * codes have no message-pattern fallback.
15851
+ *
15852
+ * @param rawCode - The raw `code` extracted from the error object.
15853
+ * @returns The numeric code, or `undefined` if the value cannot be parsed.
15854
+ *
15855
+ * @internal
15856
+ */
15857
+ function coerceRpcCode(rawCode) {
15858
+ if (typeof rawCode === 'number') {
15859
+ return rawCode;
15860
+ }
15861
+ if (typeof rawCode === 'string') {
15862
+ return Number.parseInt(rawCode, 10);
15863
+ }
15864
+ return undefined;
15865
+ }
15866
+ /**
15867
+ * Classify a pre-submission error thrown during `wallet_sendCalls`.
15868
+ *
15869
+ * Inspect the error's JSON-RPC `code` (falling back to message pattern
15870
+ * matching for wrapper errors like viem's `ChainMismatchError`) and map
15871
+ * it to a {@link BridgeStepErrorCategory}. This lets downstream consumers
15872
+ * distinguish user rejections, wallet capability gaps, and unknown
15873
+ * failures without parsing error messages.
15874
+ *
15875
+ * @remarks
15876
+ * Does NOT alter control flow — the SDK continues to surface a
15877
+ * `state: 'error'` step. Auto-fallback to sequential execution is
15878
+ * intentionally out of scope for this helper.
15879
+ *
15880
+ * @param err - The error thrown by `wallet_sendCalls`.
15881
+ * @returns The derived error category, or `'unknown'` if no match.
15882
+ *
15883
+ * @internal
15884
+ */
15885
+ function classifyPreSubmissionError(err) {
15886
+ // Cross-realm-safe duck typing: `instanceof Error` returns false for
15887
+ // errors thrown in a different JavaScript realm (e.g., a wallet
15888
+ // provider running inside an iframe, which is common with WalletConnect
15889
+ // and the Coinbase Wallet SDK).
15890
+ if (typeof err !== 'object' || err === null || !('message' in err)) {
15891
+ return 'unknown';
15892
+ }
15893
+ const code = coerceRpcCode(err.code);
15894
+ const message = String(err.message);
15895
+ // Numeric JSON-RPC codes are authoritative; check them before falling
15896
+ // back to message-pattern matching. Order matters: an error carrying
15897
+ // `code === 5750` with a message like "user rejected the upgrade"
15898
+ // is a capability problem, not a plain user rejection.
15899
+ if (code === 4001) {
15900
+ return 'user_rejected';
15901
+ }
15902
+ if (code === 5700 || code === 5710 || code === 5750) {
15903
+ return 'atomic_unsupported';
15904
+ }
15905
+ if (code === 5720) {
15906
+ return 'duplicate_batch_id';
15907
+ }
15908
+ if (code === 5730) {
15909
+ return 'unknown_bundle';
15910
+ }
15911
+ if (code === 5740) {
15912
+ return 'batch_too_large';
15913
+ }
15914
+ // Fall back to message patterns when no specific code is available —
15915
+ // viem (and other wrapper layers) sometimes strip the numeric code
15916
+ // while preserving the original wallet message in `Details:`.
15917
+ if (/EIP-7702 not supported/i.test(message) ||
15918
+ /does not support the requested chain/i.test(message) ||
15919
+ /rejected the upgrade/i.test(message)) {
15920
+ return 'atomic_unsupported';
15921
+ }
15922
+ if (/user rejected/i.test(message)) {
15923
+ return 'user_rejected';
15924
+ }
15925
+ return 'unknown';
15926
+ }
15184
15927
  /**
15185
15928
  * Execute a cross-chain USDC bridge using the CCTP v2 protocol.
15186
15929
  *
@@ -16673,6 +17416,35 @@ const formatBridgeResult = (result, formatDirection) => {
16673
17416
  };
16674
17417
  };
16675
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);
16676
17448
  /**
16677
17449
  * BridgeKit caller component for retry and estimate operations.
16678
17450
  */
@@ -16756,6 +17528,10 @@ class BridgeKit {
16756
17528
  * A custom fee policy for the kit.
16757
17529
  */
16758
17530
  customFeePolicy;
17531
+ /** Whether error telemetry is disabled. */
17532
+ disableErrorReporting;
17533
+ /** Per-kit telemetry identity for shared helpers. */
17534
+ telemetryConfig;
16759
17535
  /**
16760
17536
  * Create a new BridgeKit instance.
16761
17537
  *
@@ -16773,6 +17549,12 @@ class BridgeKit {
16773
17549
  const defaultProviders = getDefaultProviders$2();
16774
17550
  this.providers = [...defaultProviders, ...(config.providers ?? [])];
16775
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
+ };
16776
17558
  for (const provider of this.providers) {
16777
17559
  provider.registerDispatcher(this.actionDispatcher);
16778
17560
  }
@@ -16846,19 +17628,36 @@ class BridgeKit {
16846
17628
  * ```
16847
17629
  */
16848
17630
  async bridge(params) {
16849
- // First validate the parameters
16850
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
16851
- // Then resolve chain definitions (includes adapter chain support validation)
16852
- const resolvedParams = await resolveBridgeParams(params);
16853
- // Validate network compatibility
16854
- this.validateNetworkCompatibility(resolvedParams);
16855
- // Merge the custom fee config into the resolved params
16856
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
16857
- // Find a provider that supports this route
16858
- const provider = this.findProviderForRoute(finalResolvedParams);
16859
- // Execute the transfer using the provider
16860
- // Format the bridge result into human-readable string values for the user
16861
- 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
+ });
16862
17661
  }
16863
17662
  /**
16864
17663
  * Retry a failed or incomplete cross-chain USDC bridge operation.
@@ -16930,17 +17729,33 @@ class BridgeKit {
16930
17729
  * ```
16931
17730
  */
16932
17731
  async retry(result, context, invocationMeta) {
16933
- const provider = this.providers.find((p) => p.name === result.provider);
16934
- if (!provider) {
16935
- throw new Error(`Provider ${result.provider} not found`);
16936
- }
16937
- // Merge BridgeKit caller into invocation metadata for retry operation
16938
- const mergedMeta = mergeRetryInvocationMeta(invocationMeta);
16939
- // Format the bridge result into bigint string values for internal use
16940
- const formattedBridgeResultInternal = formatBridgeResult(result, 'to-internal');
16941
- // Execute the retry using the provider
16942
- // Format the bridge result into human-readable string values for the user
16943
- 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
+ });
16944
17759
  }
16945
17760
  /**
16946
17761
  * Estimate the cost and fees for a cross-chain USDC bridge operation.
@@ -16979,18 +17794,24 @@ class BridgeKit {
16979
17794
  * ```
16980
17795
  */
16981
17796
  async estimate(params) {
16982
- // First validate the parameters
16983
- assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
16984
- // Then resolve chain definitions (includes adapter chain support validation)
16985
- const resolvedParams = await resolveBridgeParams(params);
16986
- // Validate network compatibility
16987
- this.validateNetworkCompatibility(resolvedParams);
16988
- // Merge the custom fee config into the resolved params
16989
- const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
16990
- // Find a provider that supports this route
16991
- const provider = this.findProviderForRoute(finalResolvedParams);
16992
- // Estimate the transfer using the provider and format amounts to human-readable strings
16993
- 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
+ });
16994
17815
  }
16995
17816
  /**
16996
17817
  * Get all chains supported by any provider in the kit, with optional filtering.
@@ -17307,7 +18128,11 @@ const createBridgeKit = (context) => {
17307
18128
  const getFee = context.getFee?.bind(context);
17308
18129
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
17309
18130
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
17310
- const kit = new BridgeKit();
18131
+ const kit = new BridgeKit({
18132
+ ...(context.disableErrorReporting != null && {
18133
+ disableErrorReporting: context.disableErrorReporting,
18134
+ }),
18135
+ });
17311
18136
  if (hasBoth) {
17312
18137
  kit.setCustomFeePolicy({
17313
18138
  calculateFee: async (params) => {
@@ -17324,7 +18149,7 @@ const createBridgeKit = (context) => {
17324
18149
  };
17325
18150
 
17326
18151
  var name$1 = "@circle-fin/swap-kit";
17327
- var version$1 = "1.1.0";
18152
+ var version$1 = "1.2.0";
17328
18153
  var pkg$1 = {
17329
18154
  name: name$1,
17330
18155
  version: version$1};
@@ -27785,6 +28610,18 @@ function createSwapKitContext(config = {}) {
27785
28610
  return context;
27786
28611
  }
27787
28612
 
28613
+ /**
28614
+ * Telemetry event type identifiers for swap-kit operations.
28615
+ *
28616
+ * @internal
28617
+ */
28618
+ const SWAP_EVENT_TYPES = {
28619
+ SWAP: 'swap_swap',
28620
+ ESTIMATE: 'swap_estimate',
28621
+ };
28622
+
28623
+ /** SDK name used in telemetry payloads. */
28624
+ const SDK_NAME$1 = resolveKitSdkName(pkg$1.name);
27788
28625
  /**
27789
28626
  * A high-level class-based interface for single-chain token swap operations.
27790
28627
  *
@@ -27856,6 +28693,10 @@ function createSwapKitContext(config = {}) {
27856
28693
  */
27857
28694
  class SwapKit {
27858
28695
  context;
28696
+ /** Whether error telemetry is disabled. */
28697
+ disableErrorReporting;
28698
+ /** Per-kit telemetry identity for shared helpers. */
28699
+ telemetryConfig;
27859
28700
  /**
27860
28701
  * Create a new SwapKit instance.
27861
28702
  *
@@ -27904,6 +28745,12 @@ class SwapKit {
27904
28745
  */
27905
28746
  constructor(config = {}) {
27906
28747
  this.context = createSwapKitContext(config);
28748
+ this.disableErrorReporting = config.disableErrorReporting === true;
28749
+ this.telemetryConfig = {
28750
+ sdkName: SDK_NAME$1,
28751
+ sdkVersion: pkg$1.version,
28752
+ disabled: this.disableErrorReporting,
28753
+ };
27907
28754
  }
27908
28755
  /**
27909
28756
  * Estimate the output amount and fees for a swap operation.
@@ -27945,7 +28792,11 @@ class SwapKit {
27945
28792
  * ```
27946
28793
  */
27947
28794
  async estimate(params) {
27948
- return estimate(this.context, params);
28795
+ return withErrorTelemetry(async () => estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
28796
+ sourceChain: resolveChainName(params.from.chain),
28797
+ tokenIn: params.tokenIn,
28798
+ tokenOut: params.tokenOut,
28799
+ });
27949
28800
  }
27950
28801
  /**
27951
28802
  * Execute a token swap operation on a single chain.
@@ -28001,7 +28852,11 @@ class SwapKit {
28001
28852
  * ```
28002
28853
  */
28003
28854
  async swap(params) {
28004
- return swap$1(this.context, params);
28855
+ return withErrorTelemetry(async () => swap$1(this.context, params), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, {
28856
+ sourceChain: resolveChainName(params.from.chain),
28857
+ tokenIn: params.tokenIn,
28858
+ tokenOut: params.tokenOut,
28859
+ });
28005
28860
  }
28006
28861
  /**
28007
28862
  * Get all chains supported by the configured swap providers.
@@ -28194,7 +29049,11 @@ const createSwapKit = (context) => {
28194
29049
  const getFee = context.getFee?.bind(context);
28195
29050
  const getFeeRecipient = context.getFeeRecipient?.bind(context);
28196
29051
  const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
28197
- const kit = new SwapKit();
29052
+ const kit = new SwapKit({
29053
+ ...(context.disableErrorReporting != null && {
29054
+ disableErrorReporting: context.disableErrorReporting,
29055
+ }),
29056
+ });
28198
29057
  if (hasBoth) {
28199
29058
  kit.setCustomFeePolicy({
28200
29059
  computeFee: async (params) => {
@@ -28698,7 +29557,7 @@ const prepareSend = async (params) => {
28698
29557
  const fromContext = params.from;
28699
29558
  const fromAdapter = fromContext.adapter;
28700
29559
  const fromChain = resolveChainIdentifier(fromContext.chain);
28701
- const fromAddress = await fromAdapter.getAddress(fromChain);
29560
+ const fromAddress = fromContext.address ?? (await fromAdapter.getAddress(fromChain));
28702
29561
  // resolve input parameters
28703
29562
  const token = params.token ?? 'USDC';
28704
29563
  // Validate token and determine if it's an alias or custom address
@@ -29143,121 +30002,11 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
29143
30002
  };
29144
30003
 
29145
30004
  var name = "@circle-fin/unified-balance-kit";
29146
- var version = "1.0.1";
30005
+ var version = "1.1.0";
29147
30006
  var pkg = {
29148
30007
  name: name,
29149
30008
  version: version};
29150
30009
 
29151
- /**
29152
- * Default telemetry endpoint.
29153
- *
29154
- * Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
29155
- * (e.g. for staging or local development).
29156
- *
29157
- * @internal
29158
- */
29159
- const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
29160
- /**
29161
- * Resolve the telemetry endpoint URL.
29162
- *
29163
- * @internal
29164
- */
29165
- function getLogsUrl() {
29166
- if (isNodeEnvironment() &&
29167
- typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' &&
29168
- process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
29169
- return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
29170
- }
29171
- return DEFAULT_LOGS_URL;
29172
- }
29173
- /**
29174
- * Send a telemetry event to the proxy service.
29175
- *
29176
- * @remarks
29177
- * Fire-and-forget: the returned promise is intentionally not awaited
29178
- * by the caller. A fetch failure (network error, non-2xx, timeout)
29179
- * is silently swallowed so telemetry never blocks or fails user
29180
- * operations.
29181
- *
29182
- * @param payload - The structured log payload matching the server schema.
29183
- *
29184
- * @example
29185
- * ```typescript
29186
- * import { emitAnalyticsLog } from './emitLog'
29187
- *
29188
- * // Fire-and-forget — do not await
29189
- * emitAnalyticsLog(payload).catch(() => {})
29190
- * ```
29191
- *
29192
- * @internal
29193
- */
29194
- async function emitAnalyticsLog(payload) {
29195
- try {
29196
- const isNode = isNodeEnvironment();
29197
- const userAgent = getUserAgent();
29198
- await fetch(getLogsUrl(), {
29199
- method: 'POST',
29200
- headers: {
29201
- 'Content-Type': 'application/json',
29202
- // Browser restricts setting User-Agent; use X-User-Agent instead.
29203
- ...(isNode
29204
- ? { 'User-Agent': userAgent }
29205
- : { 'X-User-Agent': userAgent }),
29206
- },
29207
- body: JSON.stringify(payload),
29208
- signal: AbortSignal.timeout(5_000),
29209
- });
29210
- }
29211
- catch {
29212
- // Silently swallow — telemetry must never break user operations.
29213
- }
29214
- }
29215
-
29216
- /**
29217
- * Build the `clientContext` object for telemetry payloads.
29218
- *
29219
- * @remarks
29220
- * Uses the exported `getRuntime()` and `isNodeEnvironment()` from
29221
- * `@core/utils` to detect the runtime environment (per Dominik's
29222
- * feedback to reuse existing infrastructure). The returned string
29223
- * is parsed into the structured `ClientContext` fields expected by
29224
- * the server schema.
29225
- *
29226
- * @returns A {@link ClientContext} with platform, OS, and runtime name
29227
- * populated from the current environment.
29228
- *
29229
- * @example
29230
- * ```typescript
29231
- * import { buildClientContext } from './clientContext'
29232
- *
29233
- * const ctx = buildClientContext()
29234
- * // Node: { platform: 'node', os: 'darwin', runtimeName: null }
29235
- * // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
29236
- * ```
29237
- */
29238
- function buildClientContext() {
29239
- const runtime = getRuntime();
29240
- if (runtime.startsWith('browser/')) {
29241
- return {
29242
- platform: 'browser',
29243
- os: null,
29244
- runtimeName: runtime.slice('browser/'.length).toLowerCase(),
29245
- };
29246
- }
29247
- if (runtime.startsWith('node/')) {
29248
- return {
29249
- platform: 'node',
29250
- os: isNodeEnvironment() ? process.platform : null,
29251
- runtimeName: null,
29252
- };
29253
- }
29254
- return {
29255
- platform: 'node',
29256
- os: null,
29257
- runtimeName: null,
29258
- };
29259
- }
29260
-
29261
30010
  /**
29262
30011
  * Event type identifiers accepted by the server-side logs endpoint.
29263
30012
  *
@@ -29270,6 +30019,11 @@ const EVENT_TYPES = {
29270
30019
  DEPOSIT_FOR: 'unified_balance_deposit_for',
29271
30020
  SPEND: 'unified_balance_spend',
29272
30021
  SPEND_FORWARDER: 'unified_balance_spend_forwarder',
30022
+ ESTIMATE_SPEND: 'unified_balance_estimate_spend',
30023
+ GET_BALANCES: 'unified_balance_get_balances',
30024
+ ADD_DELEGATE: 'unified_balance_add_delegate',
30025
+ REMOVE_DELEGATE: 'unified_balance_remove_delegate',
30026
+ GET_DELEGATE_STATUS: 'unified_balance_get_delegate_status',
29273
30027
  INITIATE_REMOVE_FUND: 'unified_balance_initiate_remove_fund',
29274
30028
  REMOVE_FUND: 'unified_balance_remove_fund',
29275
30029
  };
@@ -29307,33 +30061,29 @@ function extractSingleChainResult(data) {
29307
30061
  txHash: data.txHash,
29308
30062
  };
29309
30063
  }
29310
- /**
29311
- * Resolve a chain identifier that may be a string or a
29312
- * `ChainDefinition` object to a plain string.
29313
- *
29314
- * @internal
29315
- */
29316
- function resolveChainString(chain) {
29317
- if (typeof chain === 'string')
29318
- return chain;
29319
- return chain.chain;
29320
- }
29321
30064
  /**
29322
30065
  * Extract log-relevant fields from a spend result.
29323
30066
  *
30067
+ * @remarks
30068
+ * Collects all source chains from allocations and joins them with
30069
+ * commas so multi-chain spends are fully represented.
30070
+ *
29324
30071
  * @internal
29325
30072
  */
29326
30073
  function extractSpend(data) {
29327
- const rawChain = data.allocations?.[0]?.chain;
29328
- let firstSourceChain;
29329
- if (rawChain != null) {
29330
- firstSourceChain = resolveChainString(rawChain);
30074
+ const allocs = data.allocations ?? [];
30075
+ const chains = [];
30076
+ for (const alloc of allocs) {
30077
+ const name = resolveChainName(alloc.chain);
30078
+ if (name != null) {
30079
+ chains.push(name);
30080
+ }
29331
30081
  }
29332
- const hasAllocations = data.allocations?.[0] != null;
30082
+ const sourceChain = chains.length > 0 ? chains.join(',') : undefined;
29333
30083
  return {
29334
- ...(firstSourceChain != null && { sourceChain: firstSourceChain }),
30084
+ ...(sourceChain != null && { sourceChain }),
29335
30085
  destinationChain: data.destinationChain,
29336
- ...(hasAllocations && { tokenIn: 'USDC' }),
30086
+ ...(allocs.length > 0 && { tokenIn: 'USDC' }),
29337
30087
  txHash: data.txHash,
29338
30088
  };
29339
30089
  }
@@ -29432,10 +30182,9 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29432
30182
  * telemetry for successful verb operations.
29433
30183
  *
29434
30184
  * @remarks
29435
- * Follows Dominik's feedback to reuse the existing event bus as the
29436
- * SDK-side hook for telemetry. Registers a handler for each verb's
29437
- * `.succeeded` event. Non-verb and non-succeeded events are not
29438
- * subscribed to.
30185
+ * Registers a handler for each verb's `.succeeded` event using the
30186
+ * shared `registerTelemetryHandler` from `@core/utils`. Non-verb and
30187
+ * non-succeeded events are not subscribed to.
29439
30188
  *
29440
30189
  * The HTTP POST is fire-and-forget — telemetry never blocks or fails
29441
30190
  * user operations.
@@ -29455,20 +30204,7 @@ function mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion) {
29455
30204
  * @internal
29456
30205
  */
29457
30206
  function registerTelemetryHandler(dispatcher, sdkName, sdkVersion) {
29458
- for (const actionName of Object.keys(VERB_EVENT_MAP)) {
29459
- dispatcher.on(actionName, (payload) => {
29460
- try {
29461
- const log = mapSucceededEventToLog(actionName, payload, sdkName, sdkVersion);
29462
- if (log) {
29463
- // Fire-and-forget — intentionally not awaited.
29464
- void emitAnalyticsLog(log);
29465
- }
29466
- }
29467
- catch {
29468
- // Silently swallow — telemetry must never break user operations.
29469
- }
29470
- });
29471
- }
30207
+ registerTelemetryHandler$1(dispatcher, sdkName, sdkVersion, VERB_EVENT_MAP, mapSucceededEventToLog);
29472
30208
  }
29473
30209
 
29474
30210
  /**
@@ -35498,6 +36234,8 @@ function getSupportedChains(context, token, options) {
35498
36234
  return Object.values(Object.fromEntries(filtered.map((chain) => [chain.chain, chain])));
35499
36235
  }
35500
36236
 
36237
+ /** SDK name used in telemetry payloads. */
36238
+ const SDK_NAME = resolveKitSdkName(pkg.name);
35501
36239
  /**
35502
36240
  * A high-level class-based interface for cross-chain USDC deposits,
35503
36241
  * spending, balance queries, delegation management, and withdrawals.
@@ -35545,6 +36283,10 @@ class UnifiedBalanceKit {
35545
36283
  * The action dispatcher for the kit.
35546
36284
  */
35547
36285
  actionDispatcher;
36286
+ /** Whether error telemetry is disabled. */
36287
+ disableErrorReporting;
36288
+ /** Per-kit telemetry identity for shared helpers. */
36289
+ telemetryConfig;
35548
36290
  /**
35549
36291
  * Create a new UnifiedBalanceKit instance.
35550
36292
  *
@@ -35557,14 +36299,49 @@ class UnifiedBalanceKit {
35557
36299
  constructor(config = {}) {
35558
36300
  this.context = createUnifiedBalanceKitContext(config);
35559
36301
  this.actionDispatcher = new Actionable();
36302
+ this.disableErrorReporting = config.disableErrorReporting === true;
36303
+ this.telemetryConfig = {
36304
+ sdkName: SDK_NAME,
36305
+ sdkVersion: pkg.version,
36306
+ disabled: this.disableErrorReporting,
36307
+ };
35560
36308
  for (const provider of this.context.providers) {
35561
36309
  provider.registerDispatcher(this.actionDispatcher);
35562
36310
  }
35563
36311
  if (!config.disableAnalytics) {
35564
- const sdkName = pkg.name.replace('@circle-fin/', '');
35565
- registerTelemetryHandler(this.actionDispatcher, sdkName, pkg.version);
36312
+ registerTelemetryHandler(this.actionDispatcher, SDK_NAME, pkg.version);
35566
36313
  }
35567
36314
  }
36315
+ /**
36316
+ * Extract comma-separated source chain names from spend params.
36317
+ *
36318
+ * @internal
36319
+ */
36320
+ static extractSpendSourceChains(params) {
36321
+ if (params == null || typeof params !== 'object' || !('from' in params)) {
36322
+ return undefined;
36323
+ }
36324
+ const fromVal = params.from;
36325
+ const sources = Array.isArray(fromVal) ? fromVal : [fromVal];
36326
+ const chains = [];
36327
+ for (const src of sources) {
36328
+ const allocs = src?.allocations;
36329
+ let allocArr;
36330
+ if (allocs == null) {
36331
+ allocArr = [];
36332
+ }
36333
+ else {
36334
+ allocArr = Array.isArray(allocs) ? allocs : [allocs];
36335
+ }
36336
+ for (const alloc of allocArr) {
36337
+ const name = resolveChainName(alloc.chain);
36338
+ if (name != null) {
36339
+ chains.push(name);
36340
+ }
36341
+ }
36342
+ }
36343
+ return chains.length > 0 ? chains.join(',') : undefined;
36344
+ }
35568
36345
  // implementation just forwards to the bus
35569
36346
  on(actionOrWildcard, handler) {
35570
36347
  this.actionDispatcher.on(actionOrWildcard, handler);
@@ -35587,7 +36364,10 @@ class UnifiedBalanceKit {
35587
36364
  * @see UnifiedBalanceKit.depositFor to deposit into another account.
35588
36365
  */
35589
36366
  async deposit(params) {
35590
- return deposit(this.context, params);
36367
+ return withErrorTelemetry(async () => deposit(this.context, params), EVENT_TYPES.DEPOSIT, this.telemetryConfig, {
36368
+ sourceChain: resolveChainName(params.from?.chain),
36369
+ tokenIn: params.token ?? 'USDC',
36370
+ });
35591
36371
  }
35592
36372
  /**
35593
36373
  * Deposit USDC into another account (not the caller's).
@@ -35599,7 +36379,10 @@ class UnifiedBalanceKit {
35599
36379
  * @see UnifiedBalanceKit.deposit to deposit into your own account.
35600
36380
  */
35601
36381
  async depositFor(params) {
35602
- return depositFor(this.context, params);
36382
+ return withErrorTelemetry(async () => depositFor(this.context, params), EVENT_TYPES.DEPOSIT_FOR, this.telemetryConfig, {
36383
+ sourceChain: resolveChainName(params.from?.chain),
36384
+ tokenIn: params.token ?? 'USDC',
36385
+ });
35603
36386
  }
35604
36387
  /**
35605
36388
  * Spend (mint) USDC on a destination chain by pulling funds from one
@@ -35612,7 +36395,17 @@ class UnifiedBalanceKit {
35612
36395
  * @see UnifiedBalanceKit.estimateSpend to preview fees before spending.
35613
36396
  */
35614
36397
  async spend(params) {
35615
- return spend(this.context, params);
36398
+ const destChain = 'to' in params
36399
+ ? resolveChainName(params.to.chain)
36400
+ : undefined;
36401
+ const tokenIn = 'token' in params
36402
+ ? (params.token ?? 'USDC')
36403
+ : 'USDC';
36404
+ return withErrorTelemetry(async () => spend(this.context, params), EVENT_TYPES.SPEND, this.telemetryConfig, {
36405
+ sourceChain: UnifiedBalanceKit.extractSpendSourceChains(params),
36406
+ ...(destChain != null && { destinationChain: destChain }),
36407
+ tokenIn,
36408
+ });
35616
36409
  }
35617
36410
  /**
35618
36411
  * Estimate the fees for a spend operation without executing it.
@@ -35622,7 +36415,17 @@ class UnifiedBalanceKit {
35622
36415
  * @returns Promise resolving to the fee estimate.
35623
36416
  */
35624
36417
  async estimateSpend(params) {
35625
- return estimateSpend(this.context, params);
36418
+ const destChain = 'to' in params
36419
+ ? resolveChainName(params.to.chain)
36420
+ : undefined;
36421
+ const sourceChain = UnifiedBalanceKit.extractSpendSourceChains(params);
36422
+ return withErrorTelemetry(async () => estimateSpend(this.context, params), EVENT_TYPES.ESTIMATE_SPEND, this.telemetryConfig, {
36423
+ ...(sourceChain != null && { sourceChain }),
36424
+ ...(destChain != null && { destinationChain: destChain }),
36425
+ tokenIn: 'token' in params
36426
+ ? (params.token ?? 'USDC')
36427
+ : 'USDC',
36428
+ });
35626
36429
  }
35627
36430
  /**
35628
36431
  * Fetch aggregated and per-chain balances for one or more accounts.
@@ -35631,7 +36434,7 @@ class UnifiedBalanceKit {
35631
36434
  * @returns Promise resolving to the aggregated balance result.
35632
36435
  */
35633
36436
  async getBalances(params) {
35634
- return getBalances(this.context, params);
36437
+ return withErrorTelemetry(async () => getBalances(this.context, params), EVENT_TYPES.GET_BALANCES, this.telemetryConfig);
35635
36438
  }
35636
36439
  /**
35637
36440
  * Grant spending rights to another address on the owner's account.
@@ -35646,7 +36449,7 @@ class UnifiedBalanceKit {
35646
36449
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35647
36450
  */
35648
36451
  async addDelegate(params) {
35649
- return addDelegate(this.context, params);
36452
+ return withErrorTelemetry(async () => addDelegate(this.context, params), EVENT_TYPES.ADD_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35650
36453
  }
35651
36454
  /**
35652
36455
  * Revoke spending rights from a delegate on the owner's account.
@@ -35661,7 +36464,7 @@ class UnifiedBalanceKit {
35661
36464
  * @see UnifiedBalanceKit.getDelegateStatus to check delegate status.
35662
36465
  */
35663
36466
  async removeDelegate(params) {
35664
- return removeDelegate(this.context, params);
36467
+ return withErrorTelemetry(async () => removeDelegate(this.context, params), EVENT_TYPES.REMOVE_DELEGATE, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35665
36468
  }
35666
36469
  /**
35667
36470
  * Kick off a delayed fund removal from an account.
@@ -35673,7 +36476,10 @@ class UnifiedBalanceKit {
35673
36476
  * @see UnifiedBalanceKit.removeFund to complete the fund removal.
35674
36477
  */
35675
36478
  async initiateRemoveFund(params) {
35676
- return initiateRemoveFund(this.context, params);
36479
+ return withErrorTelemetry(async () => initiateRemoveFund(this.context, params), EVENT_TYPES.INITIATE_REMOVE_FUND, this.telemetryConfig, {
36480
+ sourceChain: resolveChainName(params.from?.chain),
36481
+ tokenIn: params.token ?? 'USDC',
36482
+ });
35677
36483
  }
35678
36484
  /**
35679
36485
  * Complete a fund removal once the activation period has passed.
@@ -35685,7 +36491,10 @@ class UnifiedBalanceKit {
35685
36491
  * @see UnifiedBalanceKit.initiateRemoveFund to start the process.
35686
36492
  */
35687
36493
  async removeFund(params) {
35688
- return removeFund(this.context, params);
36494
+ return withErrorTelemetry(async () => removeFund(this.context, params), EVENT_TYPES.REMOVE_FUND, this.telemetryConfig, {
36495
+ sourceChain: resolveChainName(params.from?.chain),
36496
+ tokenIn: params.token ?? 'USDC',
36497
+ });
35689
36498
  }
35690
36499
  /**
35691
36500
  * Check the finality-aware delegate status of an address.
@@ -35713,7 +36522,7 @@ class UnifiedBalanceKit {
35713
36522
  * ```
35714
36523
  */
35715
36524
  async getDelegateStatus(params) {
35716
- return getDelegateStatus(this.context, params);
36525
+ return withErrorTelemetry(async () => getDelegateStatus(this.context, params), EVENT_TYPES.GET_DELEGATE_STATUS, this.telemetryConfig, { sourceChain: resolveChainName(params.from?.chain) });
35717
36526
  }
35718
36527
  /**
35719
36528
  * Get all chains supported by the kit.
@@ -36203,8 +37012,18 @@ class AppKit {
36203
37012
  * ```
36204
37013
  */
36205
37014
  constructor(config = {}) {
36206
- this.context = createContext(config);
36207
- this.unifiedBalance = new AppKitUnifiedBalance(config.unifiedBalance);
37015
+ this.context = createContext({
37016
+ ...config,
37017
+ ...(config.disableErrorReporting != null && {
37018
+ disableErrorReporting: config.disableErrorReporting,
37019
+ }),
37020
+ });
37021
+ this.unifiedBalance = new AppKitUnifiedBalance({
37022
+ ...config.unifiedBalance,
37023
+ ...(config.disableErrorReporting != null && {
37024
+ disableErrorReporting: config.disableErrorReporting,
37025
+ }),
37026
+ });
36208
37027
  }
36209
37028
  /**
36210
37029
  * Execute a cross-chain USDC bridge transfer.