@circle-fin/app-kit 1.10.0 → 1.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circle-fin/app-kit",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "A one-stop Circle SDK solution for building stablecoin (e.g. USDC) applications, with bridging, swapping, and more on-chain operations",
5
5
  "keywords": [
6
6
  "circle",
@@ -36,15 +36,16 @@
36
36
  "module": "./index.mjs",
37
37
  "types": "./index.d.cts",
38
38
  "dependencies": {
39
- "@circle-fin/unified-balance-kit": "1.3.0",
40
- "@circle-fin/provider-gateway-v1": "1.1.3",
41
- "@circle-fin/earn-kit": "1.3.0",
42
- "@circle-fin/bridge-kit": "1.12.1",
43
- "@circle-fin/swap-kit": "1.4.0",
39
+ "@circle-fin/unified-balance-kit": "1.3.1",
40
+ "@circle-fin/provider-gateway-v1": "1.1.4",
41
+ "@circle-fin/earn-kit": "1.4.0",
42
+ "@circle-fin/bridge-kit": "1.12.2",
43
+ "@circle-fin/swap-kit": "1.5.0",
44
44
  "@coral-xyz/anchor": "^0.31.1",
45
45
  "@noble/curves": "1.4.2",
46
46
  "bn.js": "^5.2.3",
47
47
  "@ethersproject/keccak256": "^5.8.0",
48
+ "buffer": "^6.0.3",
48
49
  "pino": "10.1.0",
49
50
  "zod": "3.25.67",
50
51
  "@ethersproject/abi": "^5.8.0",
package/swap.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');
@@ -50,6 +61,51 @@ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
50
61
  * }
51
62
  * ```
52
63
  */ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
64
+ /**
65
+ * Check whether the current runtime exposes a browser DOM.
66
+ *
67
+ * @remarks
68
+ * This intentionally does not treat every non-Node runtime as a browser.
69
+ * Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
70
+ * Edge Functions do not expose Node globals but can safely use server
71
+ * credentials. A Node.js runtime remains server-side even when a test or SSR
72
+ * environment provides a DOM shim.
73
+ *
74
+ * @returns `true` when running in a browser window, `false` otherwise.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * import { isBrowserEnvironment } from '@core/utils'
79
+ *
80
+ * if (isBrowserEnvironment()) {
81
+ * throw new Error('Server-only secrets must not be used in the browser')
82
+ * }
83
+ * ```
84
+ */ const isBrowserEnvironment = ()=>{
85
+ const browserWindow = globalThis.window;
86
+ return !isNodeEnvironment() && browserWindow?.document !== undefined;
87
+ };
88
+ /**
89
+ * Return the SDK User-Agent request header only when running in Node.js.
90
+ *
91
+ * Browsers forbid manually setting `User-Agent`, and a custom fallback header
92
+ * can trigger CORS preflight. Non-Node server runtimes also omit this optional
93
+ * attribution header because they cannot set it reliably.
94
+ *
95
+ * @returns A User-Agent header in Node.js, or an empty object otherwise.
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * import { getNodeUserAgentHeader } from '@core/utils'
100
+ *
101
+ * const headers = {
102
+ * 'Content-Type': 'application/json',
103
+ * ...getNodeUserAgentHeader(),
104
+ * }
105
+ * ```
106
+ */ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
107
+ 'User-Agent': getUserAgent()
108
+ } : {};
53
109
  /**
54
110
  * Detect the runtime environment and return a shortened identifier.
55
111
  *
@@ -8027,13 +8083,12 @@ const swapTokenEnumSchema = zod.z.enum([
8027
8083
  headers: {
8028
8084
  ...DEFAULT_CONFIG$1.headers,
8029
8085
  ...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
- }
8086
+ // Browsers forbid setting a user-agent request header, and the custom
8087
+ // fallback header the SDK used instead trips CORS preflight against the
8088
+ // Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
8089
+ // blocking the request. So send the SDK user agent only in Node;
8090
+ // browsers omit it entirely.
8091
+ ...getNodeUserAgentHeader()
8037
8092
  }
8038
8093
  };
8039
8094
  let lastError;
@@ -9534,6 +9589,7 @@ const swapTokenEnumSchema = zod.z.enum([
9534
9589
  if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
9535
9590
  if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
9536
9591
  if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
9592
+ if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
9537
9593
  if (payload.errorDetails !== undefined) {
9538
9594
  const errorDetails = {
9539
9595
  ...payload.errorDetails.errorCode !== undefined && {
@@ -9604,18 +9660,15 @@ const swapTokenEnumSchema = zod.z.enum([
9604
9660
  timeoutHandle.unref();
9605
9661
  }
9606
9662
  try {
9607
- const isNode = isNodeEnvironment();
9608
- const userAgent = getUserAgent();
9609
9663
  await fetch(getLogsUrl(), {
9610
9664
  method: 'POST',
9611
9665
  headers: {
9612
9666
  '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
- }
9667
+ // Browsers forbid setting a user-agent request header, and the custom
9668
+ // fallback header the SDK used instead trips CORS preflight (it isn't
9669
+ // in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
9670
+ // it only in Node; browsers omit it entirely.
9671
+ ...getNodeUserAgentHeader()
9619
9672
  },
9620
9673
  body: JSON.stringify(toSafePayload(payload)),
9621
9674
  signal: controller.signal
@@ -9783,7 +9836,7 @@ const swapTokenEnumSchema = zod.z.enum([
9783
9836
  // discards the stack trace, nested `cause`, and any custom Error
9784
9837
  // properties — exactly the context an on-call needs when a
9785
9838
  // resolver-closure regression triggers this path.
9786
- console.warn(`[stablecoin-kits telemetry] dropped error event '${eventType}':`, cause);
9839
+ console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
9787
9840
  } catch {
9788
9841
  // console.warn itself throwing is the user's environment; nothing more we
9789
9842
  // can do without risking the original operation error.
@@ -9799,7 +9852,9 @@ const swapTokenEnumSchema = zod.z.enum([
9799
9852
  sdkVersion: config.sdkVersion,
9800
9853
  eventType,
9801
9854
  timestamp: new Date().toISOString(),
9802
- errorDetails,
9855
+ ...errorDetails !== undefined && {
9856
+ errorDetails
9857
+ },
9803
9858
  clientContext: buildClientContext(),
9804
9859
  ...context?.sourceChain != null && {
9805
9860
  sourceChain: context.sourceChain
@@ -9815,9 +9870,45 @@ const swapTokenEnumSchema = zod.z.enum([
9815
9870
  },
9816
9871
  ...context?.txHash != null && {
9817
9872
  txHash: context.txHash
9873
+ },
9874
+ ...context?.correlationId != null && {
9875
+ correlationId: context.correlationId
9818
9876
  }
9819
9877
  };
9820
9878
  }
9879
+ /**
9880
+ * Emit telemetry for a completed operation without affecting its caller.
9881
+ *
9882
+ * No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
9883
+ * failures while constructing or submitting the telemetry payload are reported
9884
+ * as a soft warning and never change a completed operation's result.
9885
+ *
9886
+ * @param eventType - The telemetry event type for the completed operation.
9887
+ * @param config - Per-kit SDK identity and disabled flag.
9888
+ * @param context - Optional chain, token, and transaction context.
9889
+ * @returns Nothing.
9890
+ * @throws Never — telemetry failures are reported as warnings.
9891
+ *
9892
+ * @example
9893
+ * ```typescript
9894
+ * import { emitSuccessTelemetry } from '@core/utils'
9895
+ *
9896
+ * emitSuccessTelemetry(
9897
+ * 'bridge_bridge',
9898
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
9899
+ * { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
9900
+ * )
9901
+ * ```
9902
+ */ function emitSuccessTelemetry(eventType, config, context) {
9903
+ if (config.disabled) {
9904
+ return;
9905
+ }
9906
+ try {
9907
+ void emitAnalyticsLog(buildPayload(config, eventType, undefined, context));
9908
+ } catch (telemetryError) {
9909
+ warnTelemetryDrop(eventType, telemetryError);
9910
+ }
9911
+ }
9821
9912
  /**
9822
9913
  * Wrap an async operation with error telemetry.
9823
9914
  *
@@ -9876,7 +9967,7 @@ const swapTokenEnumSchema = zod.z.enum([
9876
9967
  }
9877
9968
 
9878
9969
  var name$2 = "@circle-fin/bridge-kit";
9879
- var version$2 = "1.12.1";
9970
+ var version$2 = "1.12.2";
9880
9971
  var pkg$2 = {
9881
9972
  name: name$2,
9882
9973
  version: version$2};
@@ -10737,7 +10828,13 @@ var TransferSpeed;
10737
10828
  /**
10738
10829
  * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
10739
10830
  * hookData must start with.
10740
- */ Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
10831
+ *
10832
+ * Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
10833
+ * so this module-level constant does not reference the Node `Buffer` global at
10834
+ * import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
10835
+ * bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
10836
+ * that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
10837
+ */ Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
10741
10838
 
10742
10839
  /**
10743
10840
  * The minimum finality threshold for CCTPv2 transfers.
@@ -10771,7 +10868,7 @@ var TransferSpeed;
10771
10868
  registerKit(`${pkg$2.name}/${pkg$2.version}`);
10772
10869
 
10773
10870
  var name$1 = "@circle-fin/swap-kit";
10774
- var version$1 = "1.4.0";
10871
+ var version$1 = "1.5.0";
10775
10872
  var pkg$1 = {
10776
10873
  name: name$1,
10777
10874
  version: version$1};
@@ -11604,6 +11701,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11604
11701
  required_error: 'estimatedAmount is required',
11605
11702
  invalid_type_error: 'estimatedAmount must be a string'
11606
11703
  }).min(1, 'estimatedAmount must be a non-empty string'),
11704
+ // Per-swap join key echoed back verbatim on success telemetry. Optional so a
11705
+ // not-yet-upgraded service (no field) still validates during rollout. A
11706
+ // malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
11707
+ // than throwing: this is a telemetry-only field (stripped from the developer
11708
+ // result, never used for control flow), so it must not be able to abort the
11709
+ // swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
11710
+ // never-throw contract of the rest of the telemetry stack. Implemented with
11711
+ // `preprocess` rather than Zod's `.catch()` because static analysis misreads
11712
+ // `.catch` on the schema chain as an unhandled Promise (S7785).
11713
+ correlationId: zod.z.preprocess((value)=>zod.z.string().uuid().safeParse(value).success ? value : undefined, zod.z.string().optional()),
11607
11714
  config: createSwapRequestBaseSchema.shape.config.optional(),
11608
11715
  fees: createSwapFeesSchema.optional(),
11609
11716
  transaction: createSwapTransactionSchema
@@ -11690,6 +11797,45 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11690
11797
  * }
11691
11798
  * ```
11692
11799
  */ const isGetTokenRatesResponse = (obj)=>getTokenRatesResponseSchema.safeParse(obj).success;
11800
+ /**
11801
+ * Assert that a Stablecoin Service kit key is not being supplied from a browser.
11802
+ *
11803
+ * The kit key (`KIT_KEY:<id>:<secret>`) is a server-only secret. Every
11804
+ * Stablecoin Service request that attaches an `Authorization: Bearer` header
11805
+ * funnels through this package, so calling this guard before that header is
11806
+ * built prevents the secret from being sent from — and thus bundled into — a
11807
+ * client application. In Node.js the check is a no-op, preserving the
11808
+ * legitimate "hold the kit key on the server, forward the prepared transaction
11809
+ * to the client" flow. When no kit key is supplied the permissionless (keyless)
11810
+ * client path remains fully allowed.
11811
+ *
11812
+ * @param apiKey - The inline kit key for the request, or `undefined` when none
11813
+ * was supplied (permissionless mode).
11814
+ * @returns Nothing.
11815
+ * @throws KitError with VALIDATION_FAILED when a kit key is supplied while
11816
+ * running in a browser environment. The secret value is never echoed.
11817
+ *
11818
+ * @example
11819
+ * ```typescript
11820
+ * import { assertBrowserSafeApiKey } from '@core/service-client'
11821
+ *
11822
+ * // Server (Node.js): no-op, request proceeds with the Authorization header.
11823
+ * assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
11824
+ *
11825
+ * // Browser: throws to stop the secret from leaking into the client bundle.
11826
+ * assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
11827
+ *
11828
+ * // Browser, permissionless: allowed.
11829
+ * assertBrowserSafeApiKey(undefined)
11830
+ * ```
11831
+ */ const assertBrowserSafeApiKey = (apiKey)=>{
11832
+ if (apiKey === undefined) {
11833
+ return;
11834
+ }
11835
+ if (isBrowserEnvironment()) {
11836
+ 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');
11837
+ }
11838
+ };
11693
11839
 
11694
11840
  /**
11695
11841
  * Create a cross-chain bridge and swap transaction through the Stablecoin Service.
@@ -11747,6 +11893,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11747
11893
  const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
11748
11894
  // Remove the API key from the request body
11749
11895
  const { apiKey, ...requestBody } = validatedParams;
11896
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
11897
+ assertBrowserSafeApiKey(apiKey);
11750
11898
  const effectiveConfig = {
11751
11899
  ...DEFAULT_CONFIG,
11752
11900
  headers: {
@@ -11901,6 +12049,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11901
12049
  }
11902
12050
  // Use validated data
11903
12051
  const validatedParams = result.data;
12052
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
12053
+ assertBrowserSafeApiKey(validatedParams.apiKey);
11904
12054
  // Build the API URL
11905
12055
  const url = buildQuoteUrl(validatedParams);
11906
12056
  // Merge default config with Authorization header
@@ -11973,6 +12123,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11973
12123
  toChain: result.data.toChain
11974
12124
  }
11975
12125
  };
12126
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
12127
+ assertBrowserSafeApiKey(validatedParams.apiKey);
11976
12128
  const url = buildSwapStatusUrl(validatedParams);
11977
12129
  const effectiveConfig = {
11978
12130
  ...DEFAULT_CONFIG,
@@ -12077,6 +12229,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
12077
12229
  addresses: result.data.addresses
12078
12230
  }
12079
12231
  };
12232
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
12233
+ assertBrowserSafeApiKey(validatedParams.apiKey);
12080
12234
  const url = buildTokenRatesUrl(validatedParams);
12081
12235
  const effectiveConfig = {
12082
12236
  ...DEFAULT_CONFIG,
@@ -16503,6 +16657,11 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16503
16657
  apiKey: serviceParams.apiKey
16504
16658
  }
16505
16659
  });
16660
+ // Per-swap correlation id returned by the service as a top-level response
16661
+ // field for every chain (EVM + Solana). Attached to success telemetry so a
16662
+ // swap can be correlated across records; never used for control flow.
16663
+ // Undefined only against a not-yet-upgraded service that omits it.
16664
+ const correlationId = serviceResponse.correlationId;
16506
16665
  // Build and return SwapResult
16507
16666
  return {
16508
16667
  tokenIn,
@@ -16512,6 +16671,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16512
16671
  fromAddress: serviceParams.fromAddress,
16513
16672
  toAddress: serviceParams.toAddress,
16514
16673
  txHash,
16674
+ ...correlationId !== undefined && {
16675
+ correlationId
16676
+ },
16515
16677
  executedTransactions,
16516
16678
  ...config !== undefined && {
16517
16679
  config
@@ -19411,7 +19573,13 @@ async function resolveTokenDecimals(context, resolvedParams) {
19411
19573
  * amountIn: '50.00'
19412
19574
  * })
19413
19575
  * ```
19414
- */ async function swap$1(context, params, /** @internal */ onBroadcast) {
19576
+ */ async function swap$1(context, params, /**
19577
+ * @internal
19578
+ * Invoked after a successful broadcast with the on-chain `txHash` and the
19579
+ * service-issued `correlationId` (join key for success telemetry). The
19580
+ * service returns `correlationId` for every chain (EVM + Solana); it is
19581
+ * undefined only against a not-yet-upgraded service that omits the field.
19582
+ */ onBroadcast) {
19415
19583
  // Step 1: Validate parameters using schema
19416
19584
  assertSwapParams(params, swapParamsSchema);
19417
19585
  // Step 2: Resolve parameters (chain definitions, wallet addresses, token aliases preserved)
@@ -19431,13 +19599,16 @@ async function resolveTokenDecimals(context, resolvedParams) {
19431
19599
  // Step 5: Execute swap via provider
19432
19600
  const swapParams = buildServiceSwapParams(resolvedParams, paramsWithFees);
19433
19601
  const providerResult = await provider.swap(swapParams);
19602
+ // `correlationId` is an internal telemetry join key, not part of the public
19603
+ // SwapResult — strip it here so it never leaks into the formatted result.
19604
+ const { correlationId, ...providerResultPublic } = providerResult;
19434
19605
  // A throwing `onBroadcast` must never strand the caller after a
19435
19606
  // successful swap broadcast — the chain has moved. `safeInvokeCallback`
19436
19607
  // swallows the error and surfaces a `console.warn` prefixed with
19437
19608
  // `[stablecoin-kits swap-kit] callback threw and was swallowed:` so a
19438
19609
  // kit-side closure bug stays debuggable rather than vanishing.
19439
19610
  safeInvokeCallback('swap-kit', ()=>{
19440
- onBroadcast?.(providerResult.txHash);
19611
+ onBroadcast?.(providerResultPublic.txHash, correlationId);
19441
19612
  });
19442
19613
  const { tokenInDecimals, tokenOutDecimals } = await resolveTokenDecimals(context, resolvedParams);
19443
19614
  // Step 6: Compose chain identity (owned by the kit, derived from the
@@ -19446,10 +19617,10 @@ async function resolveTokenDecimals(context, resolvedParams) {
19446
19617
  // resolves `progress` — the provider's snapshot, or a terminal `'DONE'` for
19447
19618
  // a provider that omits it (a synchronous same-chain completion).
19448
19619
  const composedResult = {
19449
- ...providerResult,
19620
+ ...providerResultPublic,
19450
19621
  chainIn: resolvedParams.from.chain,
19451
19622
  chainOut: resolvedParams.toChain ?? resolvedParams.from.chain,
19452
- progress: providerResult.progress ?? {
19623
+ progress: providerResultPublic.progress ?? {
19453
19624
  status: 'DONE'
19454
19625
  }
19455
19626
  };
@@ -20302,7 +20473,13 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20302
20473
  */ class SwapKit {
20303
20474
  context;
20304
20475
  /** Whether error telemetry is disabled. */ disableErrorReporting;
20305
- /** Per-kit telemetry identity for shared helpers. */ telemetryConfig;
20476
+ /** Per-kit telemetry identity for error reporting. */ telemetryConfig;
20477
+ /**
20478
+ * Per-kit telemetry identity for success/analytics events. Gated by
20479
+ * `disableAnalytics` (independent of `disableErrorReporting`) so a developer
20480
+ * can opt out of volume analytics without also silencing error reports —
20481
+ * mirrors EarnKit.
20482
+ */ analyticsTelemetryConfig;
20306
20483
  /**
20307
20484
  * Create a new SwapKit instance.
20308
20485
  *
@@ -20356,6 +20533,11 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20356
20533
  sdkVersion: pkg$1.version,
20357
20534
  disabled: this.disableErrorReporting
20358
20535
  };
20536
+ this.analyticsTelemetryConfig = {
20537
+ sdkName: SDK_NAME,
20538
+ sdkVersion: pkg$1.version,
20539
+ disabled: config.disableAnalytics === true
20540
+ };
20359
20541
  }
20360
20542
  /**
20361
20543
  * Estimate the output amount and fees for a swap operation.
@@ -20396,8 +20578,12 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20396
20578
  * console.log(`Fees:`, quote.fees)
20397
20579
  * ```
20398
20580
  */ async estimate(params) {
20581
+ const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
20399
20582
  return withErrorTelemetry(async ()=>estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
20400
20583
  sourceChain: resolveChainName(params.from.chain),
20584
+ ...destinationChain != null && {
20585
+ destinationChain
20586
+ },
20401
20587
  tokenIn: params.tokenIn,
20402
20588
  tokenOut: params.tokenOut
20403
20589
  });
@@ -20456,16 +20642,54 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
20456
20642
  * ```
20457
20643
  */ async swap(params) {
20458
20644
  let txHash;
20459
- return withErrorTelemetry(async ()=>swap$1(this.context, params, (h)=>{
20460
- txHash = h;
20461
- }), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, ()=>({
20645
+ let correlationId;
20646
+ // Shared context builder so the error resolver and the success emit stay in
20647
+ // lockstep — a field added here reaches both call sites. Reads the per-call
20648
+ // locals lazily, so txHash/correlationId (set during the swap) are captured
20649
+ // whenever it is invoked.
20650
+ // Destination chain is the primary attribution dimension for cross-chain
20651
+ // swaps; resolved once from the (static) params. Omitted for same-chain
20652
+ // swaps that leave `to.chain` unset (destination == source).
20653
+ const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
20654
+ const buildTelemetryContext = ()=>({
20462
20655
  sourceChain: resolveChainName(params.from.chain),
20656
+ ...destinationChain != null && {
20657
+ destinationChain
20658
+ },
20463
20659
  tokenIn: params.tokenIn,
20464
20660
  tokenOut: params.tokenOut,
20465
20661
  ...txHash != null && {
20466
20662
  txHash
20663
+ },
20664
+ ...correlationId != null && {
20665
+ correlationId
20467
20666
  }
20468
- }));
20667
+ });
20668
+ const result = await withErrorTelemetry(async ()=>swap$1(this.context, params, (h, cId)=>{
20669
+ txHash = h;
20670
+ correlationId = cId;
20671
+ }), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, buildTelemetryContext);
20672
+ // withErrorTelemetry only emits on failure. Record the successful swap here
20673
+ // so the backend can attribute swap volume to a developer: the client event
20674
+ // carries the (burn) txHash + correlationId, which joins to the
20675
+ // server-emitted event carrying entity_id. Best-effort; never throws.
20676
+ //
20677
+ // Emit whenever a broadcast happened, i.e. we have the source/burn txHash.
20678
+ // For a cross-chain swap that is the source-chain burn (progress is still
20679
+ // PENDING while the destination mint settles) — we intentionally attribute
20680
+ // at broadcast using the burn txHash rather than tracking the destination
20681
+ // leg, which keeps the capture simple and self-contained in swap().
20682
+ //
20683
+ // Guard against a terminal-failure result: the EVM provider throws on
20684
+ // revert today, but the kit is provider-agnostic, so a provider that
20685
+ // returns a FAILED/NOT_FOUND result without throwing must not be recorded
20686
+ // as a successful swap. Routed through analyticsTelemetryConfig so it is
20687
+ // gated by disableAnalytics, independent of error reporting.
20688
+ const status = result.progress?.status;
20689
+ if (txHash != null && status !== 'FAILED' && status !== 'NOT_FOUND') {
20690
+ emitSuccessTelemetry(SWAP_EVENT_TYPES.SWAP, this.analyticsTelemetryConfig, buildTelemetryContext());
20691
+ }
20692
+ return result;
20469
20693
  }
20470
20694
  /**
20471
20695
  * Fetch the current status of a swap from the Stablecoin Service.
@@ -20844,6 +21068,9 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
20844
21068
  const kit = new SwapKit({
20845
21069
  ...context.disableErrorReporting != null && {
20846
21070
  disableErrorReporting: context.disableErrorReporting
21071
+ },
21072
+ ...context.disableAnalytics != null && {
21073
+ disableAnalytics: context.disableAnalytics
20847
21074
  }
20848
21075
  });
20849
21076
  if (hasBoth) {
@@ -20905,7 +21132,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
20905
21132
  };
20906
21133
 
20907
21134
  var name = "@circle-fin/earn-kit";
20908
- var version = "1.3.0";
21135
+ var version = "1.4.0";
20909
21136
  var pkg = {
20910
21137
  name: name,
20911
21138
  version: version};
@@ -22369,6 +22596,8 @@ function hasCrossChainDepositQuoteShape(params) {
22369
22596
  config: earnConfigSchema.optional()
22370
22597
  });
22371
22598
 
22599
+ /** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg.name);
22600
+
22372
22601
  // Auto-register this kit for user agent tracking
22373
22602
  registerKit(`${pkg.name}/${pkg.version}`);
22374
22603
 
package/swap.d.cts CHANGED
@@ -6808,10 +6808,21 @@ interface AppKitContext {
6808
6808
  * ```
6809
6809
  */
6810
6810
  actions: Record<'bridge' | 'earn', Record<string, ((payload: unknown) => void)[]>>;
6811
+ /**
6812
+ * Disable success analytics for the underlying EarnKit, SwapKit, and
6813
+ * UnifiedBalanceKit.
6814
+ *
6815
+ * When `true`, completed earn, swap, and unified balance operations will not
6816
+ * POST analytics events. This does not disable error reporting; use
6817
+ * {@link AppKitContext.disableErrorReporting} for that. Defaults to `false`.
6818
+ *
6819
+ * @defaultValue false
6820
+ */
6821
+ disableAnalytics?: boolean;
6811
6822
  /**
6812
6823
  * Disable error telemetry for all sub-kits.
6813
6824
  *
6814
- * When `true`, none of the underlying kits (BridgeKit, SwapKit,
6825
+ * When `true`, none of the underlying kits (BridgeKit, SwapKit, EarnKit,
6815
6826
  * UnifiedBalanceKit) will POST error details to the telemetry
6816
6827
  * endpoint when operations throw. Defaults to `false` (enabled).
6817
6828
  *
package/swap.d.mts CHANGED
@@ -6808,10 +6808,21 @@ interface AppKitContext {
6808
6808
  * ```
6809
6809
  */
6810
6810
  actions: Record<'bridge' | 'earn', Record<string, ((payload: unknown) => void)[]>>;
6811
+ /**
6812
+ * Disable success analytics for the underlying EarnKit, SwapKit, and
6813
+ * UnifiedBalanceKit.
6814
+ *
6815
+ * When `true`, completed earn, swap, and unified balance operations will not
6816
+ * POST analytics events. This does not disable error reporting; use
6817
+ * {@link AppKitContext.disableErrorReporting} for that. Defaults to `false`.
6818
+ *
6819
+ * @defaultValue false
6820
+ */
6821
+ disableAnalytics?: boolean;
6811
6822
  /**
6812
6823
  * Disable error telemetry for all sub-kits.
6813
6824
  *
6814
- * When `true`, none of the underlying kits (BridgeKit, SwapKit,
6825
+ * When `true`, none of the underlying kits (BridgeKit, SwapKit, EarnKit,
6815
6826
  * UnifiedBalanceKit) will POST error details to the telemetry
6816
6827
  * endpoint when operations throw. Defaults to `false` (enabled).
6817
6828
  *
package/swap.d.ts CHANGED
@@ -6808,10 +6808,21 @@ interface AppKitContext {
6808
6808
  * ```
6809
6809
  */
6810
6810
  actions: Record<'bridge' | 'earn', Record<string, ((payload: unknown) => void)[]>>;
6811
+ /**
6812
+ * Disable success analytics for the underlying EarnKit, SwapKit, and
6813
+ * UnifiedBalanceKit.
6814
+ *
6815
+ * When `true`, completed earn, swap, and unified balance operations will not
6816
+ * POST analytics events. This does not disable error reporting; use
6817
+ * {@link AppKitContext.disableErrorReporting} for that. Defaults to `false`.
6818
+ *
6819
+ * @defaultValue false
6820
+ */
6821
+ disableAnalytics?: boolean;
6811
6822
  /**
6812
6823
  * Disable error telemetry for all sub-kits.
6813
6824
  *
6814
- * When `true`, none of the underlying kits (BridgeKit, SwapKit,
6825
+ * When `true`, none of the underlying kits (BridgeKit, SwapKit, EarnKit,
6815
6826
  * UnifiedBalanceKit) will POST error details to the telemetry
6816
6827
  * endpoint when operations throw. Defaults to `false` (enabled).
6817
6828
  *