@circle-fin/app-kit 1.10.0 → 1.12.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/estimateSwap.cjs CHANGED
@@ -18,6 +18,17 @@
18
18
 
19
19
  'use strict';
20
20
 
21
+ // Buffer polyfill setup - executes before any other code
22
+ // Ensures globalThis.Buffer is available for Solana libraries
23
+ const { Buffer } = require('buffer');
24
+ if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
25
+ globalThis.Buffer = Buffer;
26
+ }
27
+ if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
28
+ window.Buffer = Buffer;
29
+ }
30
+
31
+
21
32
  var zod = require('zod');
22
33
  require('pino');
23
34
  var bytes = require('@ethersproject/bytes');
@@ -29,6 +40,7 @@ require('@coral-xyz/anchor');
29
40
  var bs58 = require('bs58');
30
41
  require('@noble/curves/ed25519');
31
42
  var units = require('@ethersproject/units');
43
+ require('viem');
32
44
  var keccak256 = require('@ethersproject/keccak256');
33
45
 
34
46
  function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
@@ -50,6 +62,51 @@ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
50
62
  * }
51
63
  * ```
52
64
  */ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
65
+ /**
66
+ * Check whether the current runtime exposes a browser DOM.
67
+ *
68
+ * @remarks
69
+ * This intentionally does not treat every non-Node runtime as a browser.
70
+ * Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
71
+ * Edge Functions do not expose Node globals but can safely use server
72
+ * credentials. A Node.js runtime remains server-side even when a test or SSR
73
+ * environment provides a DOM shim.
74
+ *
75
+ * @returns `true` when running in a browser window, `false` otherwise.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * import { isBrowserEnvironment } from '@core/utils'
80
+ *
81
+ * if (isBrowserEnvironment()) {
82
+ * throw new Error('Server-only secrets must not be used in the browser')
83
+ * }
84
+ * ```
85
+ */ const isBrowserEnvironment = ()=>{
86
+ const browserWindow = globalThis.window;
87
+ return !isNodeEnvironment() && browserWindow?.document !== undefined;
88
+ };
89
+ /**
90
+ * Return the SDK User-Agent request header only when running in Node.js.
91
+ *
92
+ * Browsers forbid manually setting `User-Agent`, and a custom fallback header
93
+ * can trigger CORS preflight. Non-Node server runtimes also omit this optional
94
+ * attribution header because they cannot set it reliably.
95
+ *
96
+ * @returns A User-Agent header in Node.js, or an empty object otherwise.
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * import { getNodeUserAgentHeader } from '@core/utils'
101
+ *
102
+ * const headers = {
103
+ * 'Content-Type': 'application/json',
104
+ * ...getNodeUserAgentHeader(),
105
+ * }
106
+ * ```
107
+ */ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
108
+ 'User-Agent': getUserAgent()
109
+ } : {};
53
110
  /**
54
111
  * Detect the runtime environment and return a shortened identifier.
55
112
  *
@@ -2952,6 +3009,8 @@ class KitError extends Error {
2952
3009
  Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
2953
3010
  Blockchain["XDC"] = "XDC";
2954
3011
  Blockchain["XDC_Apothem"] = "XDC_Apothem";
3012
+ Blockchain["X_Layer"] = "X_Layer";
3013
+ Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
2955
3014
  Blockchain["ZKSync_Era"] = "ZKSync_Era";
2956
3015
  Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
2957
3016
  })(Blockchain || (Blockchain = {}));
@@ -3005,6 +3064,7 @@ var BridgeChain;
3005
3064
  BridgeChain["Unichain"] = "Unichain";
3006
3065
  BridgeChain["World_Chain"] = "World_Chain";
3007
3066
  BridgeChain["XDC"] = "XDC";
3067
+ BridgeChain["X_Layer"] = "X_Layer";
3008
3068
  // Testnet chains with CCTPv2 support
3009
3069
  BridgeChain["Arc_Testnet"] = "Arc_Testnet";
3010
3070
  BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
@@ -3030,6 +3090,7 @@ var BridgeChain;
3030
3090
  BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
3031
3091
  BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
3032
3092
  BridgeChain["XDC_Apothem"] = "XDC_Apothem";
3093
+ BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
3033
3094
  })(BridgeChain || (BridgeChain = {}));
3034
3095
  var UnifiedBalanceChain;
3035
3096
  (function(UnifiedBalanceChain) {
@@ -5578,7 +5639,8 @@ var EarnChain;
5578
5639
  isTestnet: true,
5579
5640
  explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
5580
5641
  rpcEndpoints: [
5581
- 'https://rpc-amoy.polygon.technology'
5642
+ 'https://polygon-amoy-bor-rpc.publicnode.com',
5643
+ 'https://polygon-amoy.drpc.org'
5582
5644
  ],
5583
5645
  eurcAddress: null,
5584
5646
  usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
@@ -6443,6 +6505,104 @@ var EarnChain;
6443
6505
  }
6444
6506
  });
6445
6507
 
6508
+ /**
6509
+ * X Layer Mainnet chain definition
6510
+ * @remarks
6511
+ * This represents the official production network for the X Layer blockchain.
6512
+ * X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
6513
+ * using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
6514
+ * OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
6515
+ */ const XLayer = defineChain({
6516
+ type: 'evm',
6517
+ chain: Blockchain.X_Layer,
6518
+ name: 'X Layer',
6519
+ title: 'X Layer Mainnet',
6520
+ nativeCurrency: {
6521
+ name: 'OKB',
6522
+ symbol: 'OKB',
6523
+ decimals: 18
6524
+ },
6525
+ chainId: 196,
6526
+ isTestnet: false,
6527
+ explorerUrl: 'https://www.oklink.com/xlayer/tx/{hash}',
6528
+ rpcEndpoints: [
6529
+ 'https://xlayerrpc.okx.com'
6530
+ ],
6531
+ eurcAddress: null,
6532
+ usdcAddress: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
6533
+ usdtAddress: null,
6534
+ cctp: {
6535
+ domain: 37,
6536
+ contracts: {
6537
+ v2: {
6538
+ type: 'split',
6539
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
6540
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
6541
+ confirmations: 65,
6542
+ fastConfirmations: 1
6543
+ }
6544
+ },
6545
+ forwarderSupported: {
6546
+ source: false,
6547
+ destination: false
6548
+ }
6549
+ },
6550
+ kitContracts: {
6551
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
6552
+ }
6553
+ });
6554
+
6555
+ /**
6556
+ * X Layer Testnet chain definition
6557
+ * @remarks
6558
+ * This represents the official test network for the X Layer blockchain.
6559
+ * X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
6560
+ * using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
6561
+ * OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
6562
+ */ const XLayerTestnet = defineChain({
6563
+ type: 'evm',
6564
+ chain: Blockchain.X_Layer_Testnet,
6565
+ name: 'X Layer Testnet',
6566
+ title: 'X Layer Testnet',
6567
+ nativeCurrency: {
6568
+ name: 'OKB',
6569
+ symbol: 'OKB',
6570
+ decimals: 18
6571
+ },
6572
+ chainId: 1952,
6573
+ isTestnet: true,
6574
+ // Deliberately not oklink.com (used for mainnet): viem's bundled OKLink
6575
+ // testnet URL targets the deprecated pre-rebrand chain ID 195, not this
6576
+ // chain's ID (1952). Verified against the internal chain-expansion-scripts
6577
+ // config (`v2config.sandbox.yml`) — do not "normalize" this to match mainnet.
6578
+ explorerUrl: 'https://web3.okx.com/explorer/x-layer-testnet/tx/{hash}',
6579
+ rpcEndpoints: [
6580
+ 'https://testrpc.xlayer.tech'
6581
+ ],
6582
+ eurcAddress: null,
6583
+ usdcAddress: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
6584
+ usdtAddress: null,
6585
+ cctp: {
6586
+ domain: 37,
6587
+ contracts: {
6588
+ v2: {
6589
+ type: 'split',
6590
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
6591
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
6592
+ confirmations: 65,
6593
+ fastConfirmations: 1
6594
+ }
6595
+ },
6596
+ forwarderSupported: {
6597
+ source: false,
6598
+ destination: false
6599
+ }
6600
+ },
6601
+ kitContracts: {
6602
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
6603
+ }
6604
+ });
6605
+
6446
6606
  /**
6447
6607
  * ZKSync Era Mainnet chain definition
6448
6608
  * @remarks
@@ -6562,6 +6722,8 @@ var Chains = {
6562
6722
  WorldChainSepolia: WorldChainSepolia,
6563
6723
  XDC: XDC,
6564
6724
  XDCApothem: XDCApothem,
6725
+ XLayer: XLayer,
6726
+ XLayerTestnet: XLayerTestnet,
6565
6727
  ZKSyncEra: ZKSyncEra,
6566
6728
  ZKSyncEraSepolia: ZKSyncEraSepolia
6567
6729
  };
@@ -8027,13 +8189,12 @@ const swapTokenEnumSchema = zod.z.enum([
8027
8189
  headers: {
8028
8190
  ...DEFAULT_CONFIG$1.headers,
8029
8191
  ...config.headers ?? {},
8030
- // In browser environments, directly setting the 'User-Agent' or similar headers is restricted and may be ignored or cause errors.
8031
- // This is why we use the 'X-User-Agent' header instead.
8032
- ...typeof window === 'undefined' ? {
8033
- 'User-Agent': getUserAgent()
8034
- } : {
8035
- 'X-User-Agent': getUserAgent()
8036
- }
8192
+ // Browsers forbid setting a user-agent request header, and the custom
8193
+ // fallback header the SDK used instead trips CORS preflight against the
8194
+ // Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
8195
+ // blocking the request. So send the SDK user agent only in Node;
8196
+ // browsers omit it entirely.
8197
+ ...getNodeUserAgentHeader()
8037
8198
  }
8038
8199
  };
8039
8200
  let lastError;
@@ -8591,6 +8752,7 @@ const swapTokenEnumSchema = zod.z.enum([
8591
8752
  [Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
8592
8753
  [Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
8593
8754
  [Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
8755
+ [Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
8594
8756
  [Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
8595
8757
  // =========================================================================
8596
8758
  // Testnets (alphabetically sorted)
@@ -8625,6 +8787,7 @@ const swapTokenEnumSchema = zod.z.enum([
8625
8787
  [Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
8626
8788
  [Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
8627
8789
  [Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
8790
+ [Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
8628
8791
  [Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
8629
8792
  }
8630
8793
  };
@@ -9534,6 +9697,7 @@ const swapTokenEnumSchema = zod.z.enum([
9534
9697
  if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
9535
9698
  if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
9536
9699
  if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
9700
+ if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
9537
9701
  if (payload.errorDetails !== undefined) {
9538
9702
  const errorDetails = {
9539
9703
  ...payload.errorDetails.errorCode !== undefined && {
@@ -9604,18 +9768,15 @@ const swapTokenEnumSchema = zod.z.enum([
9604
9768
  timeoutHandle.unref();
9605
9769
  }
9606
9770
  try {
9607
- const isNode = isNodeEnvironment();
9608
- const userAgent = getUserAgent();
9609
9771
  await fetch(getLogsUrl(), {
9610
9772
  method: 'POST',
9611
9773
  headers: {
9612
9774
  'Content-Type': 'application/json',
9613
- // Browser restricts setting User-Agent; use X-User-Agent instead.
9614
- ...isNode ? {
9615
- 'User-Agent': userAgent
9616
- } : {
9617
- 'X-User-Agent': userAgent
9618
- }
9775
+ // Browsers forbid setting a user-agent request header, and the custom
9776
+ // fallback header the SDK used instead trips CORS preflight (it isn't
9777
+ // in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
9778
+ // it only in Node; browsers omit it entirely.
9779
+ ...getNodeUserAgentHeader()
9619
9780
  },
9620
9781
  body: JSON.stringify(toSafePayload(payload)),
9621
9782
  signal: controller.signal
@@ -9783,7 +9944,7 @@ const swapTokenEnumSchema = zod.z.enum([
9783
9944
  // discards the stack trace, nested `cause`, and any custom Error
9784
9945
  // properties — exactly the context an on-call needs when a
9785
9946
  // resolver-closure regression triggers this path.
9786
- console.warn(`[stablecoin-kits telemetry] dropped error event '${eventType}':`, cause);
9947
+ console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
9787
9948
  } catch {
9788
9949
  // console.warn itself throwing is the user's environment; nothing more we
9789
9950
  // can do without risking the original operation error.
@@ -9799,7 +9960,9 @@ const swapTokenEnumSchema = zod.z.enum([
9799
9960
  sdkVersion: config.sdkVersion,
9800
9961
  eventType,
9801
9962
  timestamp: new Date().toISOString(),
9802
- errorDetails,
9963
+ ...errorDetails !== undefined && {
9964
+ errorDetails
9965
+ },
9803
9966
  clientContext: buildClientContext(),
9804
9967
  ...context?.sourceChain != null && {
9805
9968
  sourceChain: context.sourceChain
@@ -9815,9 +9978,45 @@ const swapTokenEnumSchema = zod.z.enum([
9815
9978
  },
9816
9979
  ...context?.txHash != null && {
9817
9980
  txHash: context.txHash
9981
+ },
9982
+ ...context?.correlationId != null && {
9983
+ correlationId: context.correlationId
9818
9984
  }
9819
9985
  };
9820
9986
  }
9987
+ /**
9988
+ * Emit telemetry for a completed operation without affecting its caller.
9989
+ *
9990
+ * No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
9991
+ * failures while constructing or submitting the telemetry payload are reported
9992
+ * as a soft warning and never change a completed operation's result.
9993
+ *
9994
+ * @param eventType - The telemetry event type for the completed operation.
9995
+ * @param config - Per-kit SDK identity and disabled flag.
9996
+ * @param context - Optional chain, token, and transaction context.
9997
+ * @returns Nothing.
9998
+ * @throws Never — telemetry failures are reported as warnings.
9999
+ *
10000
+ * @example
10001
+ * ```typescript
10002
+ * import { emitSuccessTelemetry } from '@core/utils'
10003
+ *
10004
+ * emitSuccessTelemetry(
10005
+ * 'bridge_bridge',
10006
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
10007
+ * { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
10008
+ * )
10009
+ * ```
10010
+ */ function emitSuccessTelemetry(eventType, config, context) {
10011
+ if (config.disabled) {
10012
+ return;
10013
+ }
10014
+ try {
10015
+ void emitAnalyticsLog(buildPayload(config, eventType, undefined, context));
10016
+ } catch (telemetryError) {
10017
+ warnTelemetryDrop(eventType, telemetryError);
10018
+ }
10019
+ }
9821
10020
  /**
9822
10021
  * Wrap an async operation with error telemetry.
9823
10022
  *
@@ -9876,7 +10075,7 @@ const swapTokenEnumSchema = zod.z.enum([
9876
10075
  }
9877
10076
 
9878
10077
  var name$2 = "@circle-fin/bridge-kit";
9879
- var version$2 = "1.12.1";
10078
+ var version$2 = "1.13.0";
9880
10079
  var pkg$2 = {
9881
10080
  name: name$2,
9882
10081
  version: version$2};
@@ -9912,13 +10111,21 @@ var pkg$2 = {
9912
10111
  computeFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
9913
10112
  calculateFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
9914
10113
  resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string())))
9915
- }).strict().refine((data)=>{
10114
+ }).strict().superRefine((data, ctx)=>{
9916
10115
  const hasComputeFee = data.computeFee !== undefined;
9917
10116
  const hasCalculateFee = data.calculateFee !== undefined;
9918
- // XOR: exactly one must be provided
9919
- return hasComputeFee !== hasCalculateFee;
9920
- }, {
9921
- message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
10117
+ if (hasComputeFee && hasCalculateFee) {
10118
+ ctx.addIssue({
10119
+ code: zod.z.ZodIssueCode.custom,
10120
+ message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
10121
+ });
10122
+ }
10123
+ if (!hasComputeFee && !hasCalculateFee) {
10124
+ ctx.addIssue({
10125
+ code: zod.z.ZodIssueCode.custom,
10126
+ message: 'Provide either computeFee or calculateFee. Use computeFee (recommended) for human-readable amounts.'
10127
+ });
10128
+ }
9922
10129
  });
9923
10130
 
9924
10131
  /**
@@ -10737,7 +10944,13 @@ var TransferSpeed;
10737
10944
  /**
10738
10945
  * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
10739
10946
  * hookData must start with.
10740
- */ Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
10947
+ *
10948
+ * Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
10949
+ * so this module-level constant does not reference the Node `Buffer` global at
10950
+ * import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
10951
+ * bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
10952
+ * that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
10953
+ */ Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
10741
10954
 
10742
10955
  /**
10743
10956
  * The minimum finality threshold for CCTPv2 transfers.
@@ -10771,7 +10984,7 @@ var TransferSpeed;
10771
10984
  registerKit(`${pkg$2.name}/${pkg$2.version}`);
10772
10985
 
10773
10986
  var name$1 = "@circle-fin/swap-kit";
10774
- var version$1 = "1.4.0";
10987
+ var version$1 = "1.5.1";
10775
10988
  var pkg$1 = {
10776
10989
  name: name$1,
10777
10990
  version: version$1};
@@ -11604,6 +11817,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11604
11817
  required_error: 'estimatedAmount is required',
11605
11818
  invalid_type_error: 'estimatedAmount must be a string'
11606
11819
  }).min(1, 'estimatedAmount must be a non-empty string'),
11820
+ // Per-swap join key echoed back verbatim on success telemetry. Optional so a
11821
+ // not-yet-upgraded service (no field) still validates during rollout. A
11822
+ // malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
11823
+ // than throwing: this is a telemetry-only field (stripped from the developer
11824
+ // result, never used for control flow), so it must not be able to abort the
11825
+ // swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
11826
+ // never-throw contract of the rest of the telemetry stack. Implemented with
11827
+ // `preprocess` rather than Zod's `.catch()` because static analysis misreads
11828
+ // `.catch` on the schema chain as an unhandled Promise (S7785).
11829
+ correlationId: zod.z.preprocess((value)=>zod.z.string().uuid().safeParse(value).success ? value : undefined, zod.z.string().optional()),
11607
11830
  config: createSwapRequestBaseSchema.shape.config.optional(),
11608
11831
  fees: createSwapFeesSchema.optional(),
11609
11832
  transaction: createSwapTransactionSchema
@@ -11690,6 +11913,45 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11690
11913
  * }
11691
11914
  * ```
11692
11915
  */ const isGetTokenRatesResponse = (obj)=>getTokenRatesResponseSchema.safeParse(obj).success;
11916
+ /**
11917
+ * Assert that a Stablecoin Service kit key is not being supplied from a browser.
11918
+ *
11919
+ * The kit key (`KIT_KEY:<id>:<secret>`) is a server-only secret. Every
11920
+ * Stablecoin Service request that attaches an `Authorization: Bearer` header
11921
+ * funnels through this package, so calling this guard before that header is
11922
+ * built prevents the secret from being sent from — and thus bundled into — a
11923
+ * client application. In Node.js the check is a no-op, preserving the
11924
+ * legitimate "hold the kit key on the server, forward the prepared transaction
11925
+ * to the client" flow. When no kit key is supplied the permissionless (keyless)
11926
+ * client path remains fully allowed.
11927
+ *
11928
+ * @param apiKey - The inline kit key for the request, or `undefined` when none
11929
+ * was supplied (permissionless mode).
11930
+ * @returns Nothing.
11931
+ * @throws KitError with VALIDATION_FAILED when a kit key is supplied while
11932
+ * running in a browser environment. The secret value is never echoed.
11933
+ *
11934
+ * @example
11935
+ * ```typescript
11936
+ * import { assertBrowserSafeApiKey } from '@core/service-client'
11937
+ *
11938
+ * // Server (Node.js): no-op, request proceeds with the Authorization header.
11939
+ * assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
11940
+ *
11941
+ * // Browser: throws to stop the secret from leaking into the client bundle.
11942
+ * assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
11943
+ *
11944
+ * // Browser, permissionless: allowed.
11945
+ * assertBrowserSafeApiKey(undefined)
11946
+ * ```
11947
+ */ const assertBrowserSafeApiKey = (apiKey)=>{
11948
+ if (apiKey === undefined) {
11949
+ return;
11950
+ }
11951
+ if (isBrowserEnvironment()) {
11952
+ throw createValidationFailedError$1('kitKey', '[redacted]', 'kitKey must not be provided in a browser environment — it is a server-only secret. Run kit operations that use a kit key on your server, or omit kitKey to use the permissionless (keyless) client path');
11953
+ }
11954
+ };
11693
11955
 
11694
11956
  /**
11695
11957
  * Create a cross-chain bridge and swap transaction through the Stablecoin Service.
@@ -11747,6 +12009,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11747
12009
  const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
11748
12010
  // Remove the API key from the request body
11749
12011
  const { apiKey, ...requestBody } = validatedParams;
12012
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
12013
+ assertBrowserSafeApiKey(apiKey);
11750
12014
  const effectiveConfig = {
11751
12015
  ...DEFAULT_CONFIG,
11752
12016
  headers: {
@@ -11901,6 +12165,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11901
12165
  }
11902
12166
  // Use validated data
11903
12167
  const validatedParams = result.data;
12168
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
12169
+ assertBrowserSafeApiKey(validatedParams.apiKey);
11904
12170
  // Build the API URL
11905
12171
  const url = buildQuoteUrl(validatedParams);
11906
12172
  // Merge default config with Authorization header
@@ -11973,6 +12239,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11973
12239
  toChain: result.data.toChain
11974
12240
  }
11975
12241
  };
12242
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
12243
+ assertBrowserSafeApiKey(validatedParams.apiKey);
11976
12244
  const url = buildSwapStatusUrl(validatedParams);
11977
12245
  const effectiveConfig = {
11978
12246
  ...DEFAULT_CONFIG,
@@ -12077,6 +12345,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
12077
12345
  addresses: result.data.addresses
12078
12346
  }
12079
12347
  };
12348
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
12349
+ assertBrowserSafeApiKey(validatedParams.apiKey);
12080
12350
  const url = buildTokenRatesUrl(validatedParams);
12081
12351
  const effectiveConfig = {
12082
12352
  ...DEFAULT_CONFIG,
@@ -16503,6 +16773,11 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16503
16773
  apiKey: serviceParams.apiKey
16504
16774
  }
16505
16775
  });
16776
+ // Per-swap correlation id returned by the service as a top-level response
16777
+ // field for every chain (EVM + Solana). Attached to success telemetry so a
16778
+ // swap can be correlated across records; never used for control flow.
16779
+ // Undefined only against a not-yet-upgraded service that omits it.
16780
+ const correlationId = serviceResponse.correlationId;
16506
16781
  // Build and return SwapResult
16507
16782
  return {
16508
16783
  tokenIn,
@@ -16512,6 +16787,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16512
16787
  fromAddress: serviceParams.fromAddress,
16513
16788
  toAddress: serviceParams.toAddress,
16514
16789
  txHash,
16790
+ ...correlationId !== undefined && {
16791
+ correlationId
16792
+ },
16515
16793
  executedTransactions,
16516
16794
  ...config !== undefined && {
16517
16795
  config
@@ -19411,7 +19689,13 @@ async function resolveTokenDecimals(context, resolvedParams) {
19411
19689
  * amountIn: '50.00'
19412
19690
  * })
19413
19691
  * ```
19414
- */ async function swap(context, params, /** @internal */ onBroadcast) {
19692
+ */ async function swap(context, params, /**
19693
+ * @internal
19694
+ * Invoked after a successful broadcast with the on-chain `txHash` and the
19695
+ * service-issued `correlationId` (join key for success telemetry). The
19696
+ * service returns `correlationId` for every chain (EVM + Solana); it is
19697
+ * undefined only against a not-yet-upgraded service that omits the field.
19698
+ */ onBroadcast) {
19415
19699
  // Step 1: Validate parameters using schema
19416
19700
  assertSwapParams(params, swapParamsSchema);
19417
19701
  // Step 2: Resolve parameters (chain definitions, wallet addresses, token aliases preserved)
@@ -19431,13 +19715,16 @@ async function resolveTokenDecimals(context, resolvedParams) {
19431
19715
  // Step 5: Execute swap via provider
19432
19716
  const swapParams = buildServiceSwapParams(resolvedParams, paramsWithFees);
19433
19717
  const providerResult = await provider.swap(swapParams);
19718
+ // `correlationId` is an internal telemetry join key, not part of the public
19719
+ // SwapResult — strip it here so it never leaks into the formatted result.
19720
+ const { correlationId, ...providerResultPublic } = providerResult;
19434
19721
  // A throwing `onBroadcast` must never strand the caller after a
19435
19722
  // successful swap broadcast — the chain has moved. `safeInvokeCallback`
19436
19723
  // swallows the error and surfaces a `console.warn` prefixed with
19437
19724
  // `[stablecoin-kits swap-kit] callback threw and was swallowed:` so a
19438
19725
  // kit-side closure bug stays debuggable rather than vanishing.
19439
19726
  safeInvokeCallback('swap-kit', ()=>{
19440
- onBroadcast?.(providerResult.txHash);
19727
+ onBroadcast?.(providerResultPublic.txHash, correlationId);
19441
19728
  });
19442
19729
  const { tokenInDecimals, tokenOutDecimals } = await resolveTokenDecimals(context, resolvedParams);
19443
19730
  // Step 6: Compose chain identity (owned by the kit, derived from the
@@ -19446,10 +19733,10 @@ async function resolveTokenDecimals(context, resolvedParams) {
19446
19733
  // resolves `progress` — the provider's snapshot, or a terminal `'DONE'` for
19447
19734
  // a provider that omits it (a synchronous same-chain completion).
19448
19735
  const composedResult = {
19449
- ...providerResult,
19736
+ ...providerResultPublic,
19450
19737
  chainIn: resolvedParams.from.chain,
19451
19738
  chainOut: resolvedParams.toChain ?? resolvedParams.from.chain,
19452
- progress: providerResult.progress ?? {
19739
+ progress: providerResultPublic.progress ?? {
19453
19740
  status: 'DONE'
19454
19741
  }
19455
19742
  };
@@ -20302,7 +20589,13 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20302
20589
  */ class SwapKit {
20303
20590
  context;
20304
20591
  /** Whether error telemetry is disabled. */ disableErrorReporting;
20305
- /** Per-kit telemetry identity for shared helpers. */ telemetryConfig;
20592
+ /** Per-kit telemetry identity for error reporting. */ telemetryConfig;
20593
+ /**
20594
+ * Per-kit telemetry identity for success/analytics events. Gated by
20595
+ * `disableAnalytics` (independent of `disableErrorReporting`) so a developer
20596
+ * can opt out of volume analytics without also silencing error reports —
20597
+ * mirrors EarnKit.
20598
+ */ analyticsTelemetryConfig;
20306
20599
  /**
20307
20600
  * Create a new SwapKit instance.
20308
20601
  *
@@ -20356,6 +20649,11 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20356
20649
  sdkVersion: pkg$1.version,
20357
20650
  disabled: this.disableErrorReporting
20358
20651
  };
20652
+ this.analyticsTelemetryConfig = {
20653
+ sdkName: SDK_NAME,
20654
+ sdkVersion: pkg$1.version,
20655
+ disabled: config.disableAnalytics === true
20656
+ };
20359
20657
  }
20360
20658
  /**
20361
20659
  * Estimate the output amount and fees for a swap operation.
@@ -20396,8 +20694,12 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20396
20694
  * console.log(`Fees:`, quote.fees)
20397
20695
  * ```
20398
20696
  */ async estimate(params) {
20697
+ const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
20399
20698
  return withErrorTelemetry(async ()=>estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
20400
20699
  sourceChain: resolveChainName(params.from.chain),
20700
+ ...destinationChain != null && {
20701
+ destinationChain
20702
+ },
20401
20703
  tokenIn: params.tokenIn,
20402
20704
  tokenOut: params.tokenOut
20403
20705
  });
@@ -20456,16 +20758,54 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20456
20758
  * ```
20457
20759
  */ async swap(params) {
20458
20760
  let txHash;
20459
- return withErrorTelemetry(async ()=>swap(this.context, params, (h)=>{
20460
- txHash = h;
20461
- }), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, ()=>({
20761
+ let correlationId;
20762
+ // Shared context builder so the error resolver and the success emit stay in
20763
+ // lockstep — a field added here reaches both call sites. Reads the per-call
20764
+ // locals lazily, so txHash/correlationId (set during the swap) are captured
20765
+ // whenever it is invoked.
20766
+ // Destination chain is the primary attribution dimension for cross-chain
20767
+ // swaps; resolved once from the (static) params. Omitted for same-chain
20768
+ // swaps that leave `to.chain` unset (destination == source).
20769
+ const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
20770
+ const buildTelemetryContext = ()=>({
20462
20771
  sourceChain: resolveChainName(params.from.chain),
20772
+ ...destinationChain != null && {
20773
+ destinationChain
20774
+ },
20463
20775
  tokenIn: params.tokenIn,
20464
20776
  tokenOut: params.tokenOut,
20465
20777
  ...txHash != null && {
20466
20778
  txHash
20779
+ },
20780
+ ...correlationId != null && {
20781
+ correlationId
20467
20782
  }
20468
- }));
20783
+ });
20784
+ const result = await withErrorTelemetry(async ()=>swap(this.context, params, (h, cId)=>{
20785
+ txHash = h;
20786
+ correlationId = cId;
20787
+ }), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, buildTelemetryContext);
20788
+ // withErrorTelemetry only emits on failure. Record the successful swap here
20789
+ // so the backend can attribute swap volume to a developer: the client event
20790
+ // carries the (burn) txHash + correlationId, which joins to the
20791
+ // server-emitted event carrying entity_id. Best-effort; never throws.
20792
+ //
20793
+ // Emit whenever a broadcast happened, i.e. we have the source/burn txHash.
20794
+ // For a cross-chain swap that is the source-chain burn (progress is still
20795
+ // PENDING while the destination mint settles) — we intentionally attribute
20796
+ // at broadcast using the burn txHash rather than tracking the destination
20797
+ // leg, which keeps the capture simple and self-contained in swap().
20798
+ //
20799
+ // Guard against a terminal-failure result: the EVM provider throws on
20800
+ // revert today, but the kit is provider-agnostic, so a provider that
20801
+ // returns a FAILED/NOT_FOUND result without throwing must not be recorded
20802
+ // as a successful swap. Routed through analyticsTelemetryConfig so it is
20803
+ // gated by disableAnalytics, independent of error reporting.
20804
+ const status = result.progress?.status;
20805
+ if (txHash != null && status !== 'FAILED' && status !== 'NOT_FOUND') {
20806
+ emitSuccessTelemetry(SWAP_EVENT_TYPES.SWAP, this.analyticsTelemetryConfig, buildTelemetryContext());
20807
+ }
20808
+ return result;
20469
20809
  }
20470
20810
  /**
20471
20811
  * Fetch the current status of a swap from the Stablecoin Service.
@@ -20844,9 +21184,14 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
20844
21184
  const kit = new SwapKit({
20845
21185
  ...context.disableErrorReporting != null && {
20846
21186
  disableErrorReporting: context.disableErrorReporting
21187
+ },
21188
+ ...context.disableAnalytics != null && {
21189
+ disableAnalytics: context.disableAnalytics
20847
21190
  }
20848
21191
  });
20849
- if (hasBoth) {
21192
+ if (context.customFeePolicy?.swap != null) {
21193
+ kit.setCustomFeePolicy(context.customFeePolicy.swap);
21194
+ } else if (hasBoth) {
20850
21195
  kit.setCustomFeePolicy({
20851
21196
  computeFee: async (params)=>{
20852
21197
  // Adapt provider-level params (with tokenIn/tokenOut)
@@ -20905,7 +21250,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
20905
21250
  };
20906
21251
 
20907
21252
  var name = "@circle-fin/earn-kit";
20908
- var version = "1.3.0";
21253
+ var version = "1.5.0";
20909
21254
  var pkg = {
20910
21255
  name: name,
20911
21256
  version: version};
@@ -21916,11 +22261,16 @@ const sourceAdapterContextSchema = zod.z.object({
21916
22261
  *
21917
22262
  * Validate the optional Kit Key field using the standard `apiKeySchema`
21918
22263
  * format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
21919
- * operates in permissionless mode.
22264
+ * operates in permissionless mode. `baseUrl` overrides the Earn Service
22265
+ * endpoint (e.g. staging); `batchTransactions: false` opts out of atomic
22266
+ * batched execution. Both are forwarded to the provider, so this `.strict()`
22267
+ * schema must accept them or a valid config object is rejected.
21920
22268
  *
21921
22269
  * @internal
21922
22270
  */ const earnConfigSchema = zod.z.object({
21923
- kitKey: apiKeySchema.optional()
22271
+ kitKey: apiKeySchema.optional(),
22272
+ baseUrl: zod.z.string().optional(),
22273
+ batchTransactions: zod.z.boolean().optional()
21924
22274
  }).strict();
21925
22275
  /**
21926
22276
  * Canonical decimal form: a leading digit with no leading zeros (a single
@@ -22369,6 +22719,8 @@ function hasCrossChainDepositQuoteShape(params) {
22369
22719
  config: earnConfigSchema.optional()
22370
22720
  });
22371
22721
 
22722
+ /** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg.name);
22723
+
22372
22724
  // Auto-register this kit for user agent tracking
22373
22725
  registerKit(`${pkg.name}/${pkg.version}`);
22374
22726