@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/index.mjs CHANGED
@@ -16,6 +16,17 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
+ // Buffer polyfill setup - executes before any other code
20
+ // Ensures globalThis.Buffer is available for Solana libraries
21
+ import { Buffer } from 'buffer';
22
+ if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
23
+ globalThis.Buffer = Buffer;
24
+ }
25
+ if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
26
+ window.Buffer = Buffer;
27
+ }
28
+
29
+
19
30
  import { z } from 'zod';
20
31
  import pino from 'pino';
21
32
  import { parseUnits as parseUnits$1, formatUnits as formatUnits$1 } from '@ethersproject/units';
@@ -82,6 +93,51 @@ import { keccak256 } from '@ethersproject/keccak256';
82
93
  * }
83
94
  * ```
84
95
  */ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
96
+ /**
97
+ * Check whether the current runtime exposes a browser DOM.
98
+ *
99
+ * @remarks
100
+ * This intentionally does not treat every non-Node runtime as a browser.
101
+ * Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
102
+ * Edge Functions do not expose Node globals but can safely use server
103
+ * credentials. A Node.js runtime remains server-side even when a test or SSR
104
+ * environment provides a DOM shim.
105
+ *
106
+ * @returns `true` when running in a browser window, `false` otherwise.
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * import { isBrowserEnvironment } from '@core/utils'
111
+ *
112
+ * if (isBrowserEnvironment()) {
113
+ * throw new Error('Server-only secrets must not be used in the browser')
114
+ * }
115
+ * ```
116
+ */ const isBrowserEnvironment = ()=>{
117
+ const browserWindow = globalThis.window;
118
+ return !isNodeEnvironment() && browserWindow?.document !== undefined;
119
+ };
120
+ /**
121
+ * Return the SDK User-Agent request header only when running in Node.js.
122
+ *
123
+ * Browsers forbid manually setting `User-Agent`, and a custom fallback header
124
+ * can trigger CORS preflight. Non-Node server runtimes also omit this optional
125
+ * attribution header because they cannot set it reliably.
126
+ *
127
+ * @returns A User-Agent header in Node.js, or an empty object otherwise.
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * import { getNodeUserAgentHeader } from '@core/utils'
132
+ *
133
+ * const headers = {
134
+ * 'Content-Type': 'application/json',
135
+ * ...getNodeUserAgentHeader(),
136
+ * }
137
+ * ```
138
+ */ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
139
+ 'User-Agent': getUserAgent()
140
+ } : {};
85
141
  /**
86
142
  * Detect the runtime environment and return a shortened identifier.
87
143
  *
@@ -9282,13 +9338,12 @@ const swapTokenEnumSchema = z.enum([
9282
9338
  headers: {
9283
9339
  ...DEFAULT_CONFIG$3.headers,
9284
9340
  ...config.headers ?? {},
9285
- // In browser environments, directly setting the 'User-Agent' or similar headers is restricted and may be ignored or cause errors.
9286
- // This is why we use the 'X-User-Agent' header instead.
9287
- ...typeof window === 'undefined' ? {
9288
- 'User-Agent': getUserAgent()
9289
- } : {
9290
- 'X-User-Agent': getUserAgent()
9291
- }
9341
+ // Browsers forbid setting a user-agent request header, and the custom
9342
+ // fallback header the SDK used instead trips CORS preflight against the
9343
+ // Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
9344
+ // blocking the request. So send the SDK user agent only in Node;
9345
+ // browsers omit it entirely.
9346
+ ...getNodeUserAgentHeader()
9292
9347
  }
9293
9348
  };
9294
9349
  let lastError;
@@ -11219,6 +11274,7 @@ function resolveOptions(options) {
11219
11274
  if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
11220
11275
  if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
11221
11276
  if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
11277
+ if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
11222
11278
  if (payload.errorDetails !== undefined) {
11223
11279
  const errorDetails = {
11224
11280
  ...payload.errorDetails.errorCode !== undefined && {
@@ -11289,18 +11345,15 @@ function resolveOptions(options) {
11289
11345
  timeoutHandle.unref();
11290
11346
  }
11291
11347
  try {
11292
- const isNode = isNodeEnvironment();
11293
- const userAgent = getUserAgent();
11294
11348
  await fetch(getLogsUrl(), {
11295
11349
  method: 'POST',
11296
11350
  headers: {
11297
11351
  'Content-Type': 'application/json',
11298
- // Browser restricts setting User-Agent; use X-User-Agent instead.
11299
- ...isNode ? {
11300
- 'User-Agent': userAgent
11301
- } : {
11302
- 'X-User-Agent': userAgent
11303
- }
11352
+ // Browsers forbid setting a user-agent request header, and the custom
11353
+ // fallback header the SDK used instead trips CORS preflight (it isn't
11354
+ // in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
11355
+ // it only in Node; browsers omit it entirely.
11356
+ ...getNodeUserAgentHeader()
11304
11357
  },
11305
11358
  body: JSON.stringify(toSafePayload(payload)),
11306
11359
  signal: controller.signal
@@ -11513,7 +11566,7 @@ function resolveOptions(options) {
11513
11566
  // discards the stack trace, nested `cause`, and any custom Error
11514
11567
  // properties — exactly the context an on-call needs when a
11515
11568
  // resolver-closure regression triggers this path.
11516
- console.warn(`[stablecoin-kits telemetry] dropped error event '${eventType}':`, cause);
11569
+ console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
11517
11570
  } catch {
11518
11571
  // console.warn itself throwing is the user's environment; nothing more we
11519
11572
  // can do without risking the original operation error.
@@ -11529,7 +11582,9 @@ function resolveOptions(options) {
11529
11582
  sdkVersion: config.sdkVersion,
11530
11583
  eventType,
11531
11584
  timestamp: new Date().toISOString(),
11532
- errorDetails,
11585
+ ...errorDetails !== undefined && {
11586
+ errorDetails
11587
+ },
11533
11588
  clientContext: buildClientContext(),
11534
11589
  ...context?.sourceChain != null && {
11535
11590
  sourceChain: context.sourceChain
@@ -11545,9 +11600,45 @@ function resolveOptions(options) {
11545
11600
  },
11546
11601
  ...context?.txHash != null && {
11547
11602
  txHash: context.txHash
11603
+ },
11604
+ ...context?.correlationId != null && {
11605
+ correlationId: context.correlationId
11548
11606
  }
11549
11607
  };
11550
11608
  }
11609
+ /**
11610
+ * Emit telemetry for a completed operation without affecting its caller.
11611
+ *
11612
+ * No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
11613
+ * failures while constructing or submitting the telemetry payload are reported
11614
+ * as a soft warning and never change a completed operation's result.
11615
+ *
11616
+ * @param eventType - The telemetry event type for the completed operation.
11617
+ * @param config - Per-kit SDK identity and disabled flag.
11618
+ * @param context - Optional chain, token, and transaction context.
11619
+ * @returns Nothing.
11620
+ * @throws Never — telemetry failures are reported as warnings.
11621
+ *
11622
+ * @example
11623
+ * ```typescript
11624
+ * import { emitSuccessTelemetry } from '@core/utils'
11625
+ *
11626
+ * emitSuccessTelemetry(
11627
+ * 'bridge_bridge',
11628
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
11629
+ * { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
11630
+ * )
11631
+ * ```
11632
+ */ function emitSuccessTelemetry(eventType, config, context) {
11633
+ if (config.disabled) {
11634
+ return;
11635
+ }
11636
+ try {
11637
+ void emitAnalyticsLog(buildPayload$1(config, eventType, undefined, context));
11638
+ } catch (telemetryError) {
11639
+ warnTelemetryDrop(eventType, telemetryError);
11640
+ }
11641
+ }
11551
11642
  /**
11552
11643
  * Wrap an async operation with error telemetry.
11553
11644
  *
@@ -11658,7 +11749,7 @@ function resolveOptions(options) {
11658
11749
  }
11659
11750
 
11660
11751
  var name$4 = "@circle-fin/bridge-kit";
11661
- var version$5 = "1.12.1";
11752
+ var version$5 = "1.12.2";
11662
11753
  var pkg$5 = {
11663
11754
  name: name$4,
11664
11755
  version: version$5};
@@ -14294,7 +14385,13 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
14294
14385
  /**
14295
14386
  * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
14296
14387
  * hookData must start with.
14297
- */ const CCTP_FORWARD_MAGIC_HEX = Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
14388
+ *
14389
+ * Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
14390
+ * so this module-level constant does not reference the Node `Buffer` global at
14391
+ * import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
14392
+ * bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
14393
+ * that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
14394
+ */ const CCTP_FORWARD_MAGIC_HEX = Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
14298
14395
  /**
14299
14396
  * Determine whether a hookData blob begins with the `cctp-forward` envelope.
14300
14397
  *
@@ -16732,7 +16829,7 @@ const mockAttestationMessage = {
16732
16829
  return step;
16733
16830
  }
16734
16831
 
16735
- var version$4 = "1.10.0";
16832
+ var version$4 = "1.10.1";
16736
16833
  var pkg$4 = {
16737
16834
  version: version$4};
16738
16835
 
@@ -18611,7 +18708,7 @@ function assertCCTPV2Config(config) {
18611
18708
  ]
18612
18709
  ];
18613
18710
 
18614
- /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$5.name);
18711
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$3 = resolveKitSdkName(pkg$5.name);
18615
18712
  /**
18616
18713
  * Pick the most-relevant `txHash` to attach to an error telemetry payload.
18617
18714
  *
@@ -18753,7 +18850,7 @@ function assertCCTPV2Config(config) {
18753
18850
  this.actionDispatcher = new Actionable();
18754
18851
  this.disableErrorReporting = config.disableErrorReporting === true;
18755
18852
  this.telemetryConfig = {
18756
- sdkName: SDK_NAME$2,
18853
+ sdkName: SDK_NAME$3,
18757
18854
  sdkVersion: pkg$5.version,
18758
18855
  disabled: this.disableErrorReporting
18759
18856
  };
@@ -19346,7 +19443,7 @@ registerKit(`${pkg$5.name}/${pkg$5.version}`);
19346
19443
  };
19347
19444
 
19348
19445
  var name$3 = "@circle-fin/swap-kit";
19349
- var version$3 = "1.4.0";
19446
+ var version$3 = "1.5.0";
19350
19447
  var pkg$3 = {
19351
19448
  name: name$3,
19352
19449
  version: version$3};
@@ -20179,6 +20276,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
20179
20276
  required_error: 'estimatedAmount is required',
20180
20277
  invalid_type_error: 'estimatedAmount must be a string'
20181
20278
  }).min(1, 'estimatedAmount must be a non-empty string'),
20279
+ // Per-swap join key echoed back verbatim on success telemetry. Optional so a
20280
+ // not-yet-upgraded service (no field) still validates during rollout. A
20281
+ // malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
20282
+ // than throwing: this is a telemetry-only field (stripped from the developer
20283
+ // result, never used for control flow), so it must not be able to abort the
20284
+ // swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
20285
+ // never-throw contract of the rest of the telemetry stack. Implemented with
20286
+ // `preprocess` rather than Zod's `.catch()` because static analysis misreads
20287
+ // `.catch` on the schema chain as an unhandled Promise (S7785).
20288
+ correlationId: z.preprocess((value)=>z.string().uuid().safeParse(value).success ? value : undefined, z.string().optional()),
20182
20289
  config: createSwapRequestBaseSchema.shape.config.optional(),
20183
20290
  fees: createSwapFeesSchema.optional(),
20184
20291
  transaction: createSwapTransactionSchema
@@ -20304,6 +20411,45 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
20304
20411
  // Validate without trimming - any whitespace will cause validation to fail
20305
20412
  return apiKeyPattern.test(apiKey);
20306
20413
  };
20414
+ /**
20415
+ * Assert that a Stablecoin Service kit key is not being supplied from a browser.
20416
+ *
20417
+ * The kit key (`KIT_KEY:<id>:<secret>`) is a server-only secret. Every
20418
+ * Stablecoin Service request that attaches an `Authorization: Bearer` header
20419
+ * funnels through this package, so calling this guard before that header is
20420
+ * built prevents the secret from being sent from — and thus bundled into — a
20421
+ * client application. In Node.js the check is a no-op, preserving the
20422
+ * legitimate "hold the kit key on the server, forward the prepared transaction
20423
+ * to the client" flow. When no kit key is supplied the permissionless (keyless)
20424
+ * client path remains fully allowed.
20425
+ *
20426
+ * @param apiKey - The inline kit key for the request, or `undefined` when none
20427
+ * was supplied (permissionless mode).
20428
+ * @returns Nothing.
20429
+ * @throws KitError with VALIDATION_FAILED when a kit key is supplied while
20430
+ * running in a browser environment. The secret value is never echoed.
20431
+ *
20432
+ * @example
20433
+ * ```typescript
20434
+ * import { assertBrowserSafeApiKey } from '@core/service-client'
20435
+ *
20436
+ * // Server (Node.js): no-op, request proceeds with the Authorization header.
20437
+ * assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
20438
+ *
20439
+ * // Browser: throws to stop the secret from leaking into the client bundle.
20440
+ * assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
20441
+ *
20442
+ * // Browser, permissionless: allowed.
20443
+ * assertBrowserSafeApiKey(undefined)
20444
+ * ```
20445
+ */ const assertBrowserSafeApiKey = (apiKey)=>{
20446
+ if (apiKey === undefined) {
20447
+ return;
20448
+ }
20449
+ if (isBrowserEnvironment()) {
20450
+ 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');
20451
+ }
20452
+ };
20307
20453
 
20308
20454
  /**
20309
20455
  * Create a cross-chain bridge and swap transaction through the Stablecoin Service.
@@ -20361,6 +20507,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
20361
20507
  const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
20362
20508
  // Remove the API key from the request body
20363
20509
  const { apiKey, ...requestBody } = validatedParams;
20510
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
20511
+ assertBrowserSafeApiKey(apiKey);
20364
20512
  const effectiveConfig = {
20365
20513
  ...DEFAULT_CONFIG$1,
20366
20514
  headers: {
@@ -20515,6 +20663,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
20515
20663
  }
20516
20664
  // Use validated data
20517
20665
  const validatedParams = result.data;
20666
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
20667
+ assertBrowserSafeApiKey(validatedParams.apiKey);
20518
20668
  // Build the API URL
20519
20669
  const url = buildQuoteUrl(validatedParams);
20520
20670
  // Merge default config with Authorization header
@@ -20587,6 +20737,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
20587
20737
  toChain: result.data.toChain
20588
20738
  }
20589
20739
  };
20740
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
20741
+ assertBrowserSafeApiKey(validatedParams.apiKey);
20590
20742
  const url = buildSwapStatusUrl(validatedParams);
20591
20743
  const effectiveConfig = {
20592
20744
  ...DEFAULT_CONFIG$1,
@@ -20691,6 +20843,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
20691
20843
  addresses: result.data.addresses
20692
20844
  }
20693
20845
  };
20846
+ // Never let a server-only kit key leave a browser (no-op in Node.js).
20847
+ assertBrowserSafeApiKey(validatedParams.apiKey);
20694
20848
  const url = buildTokenRatesUrl(validatedParams);
20695
20849
  const effectiveConfig = {
20696
20850
  ...DEFAULT_CONFIG$1,
@@ -27462,6 +27616,11 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
27462
27616
  apiKey: serviceParams.apiKey
27463
27617
  }
27464
27618
  });
27619
+ // Per-swap correlation id returned by the service as a top-level response
27620
+ // field for every chain (EVM + Solana). Attached to success telemetry so a
27621
+ // swap can be correlated across records; never used for control flow.
27622
+ // Undefined only against a not-yet-upgraded service that omits it.
27623
+ const correlationId = serviceResponse.correlationId;
27465
27624
  // Build and return SwapResult
27466
27625
  return {
27467
27626
  tokenIn,
@@ -27471,6 +27630,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
27471
27630
  fromAddress: serviceParams.fromAddress,
27472
27631
  toAddress: serviceParams.toAddress,
27473
27632
  txHash,
27633
+ ...correlationId !== undefined && {
27634
+ correlationId
27635
+ },
27474
27636
  executedTransactions,
27475
27637
  ...config !== undefined && {
27476
27638
  config
@@ -30370,7 +30532,13 @@ async function resolveTokenDecimals(context, resolvedParams) {
30370
30532
  * amountIn: '50.00'
30371
30533
  * })
30372
30534
  * ```
30373
- */ async function swap$1(context, params, /** @internal */ onBroadcast) {
30535
+ */ async function swap$1(context, params, /**
30536
+ * @internal
30537
+ * Invoked after a successful broadcast with the on-chain `txHash` and the
30538
+ * service-issued `correlationId` (join key for success telemetry). The
30539
+ * service returns `correlationId` for every chain (EVM + Solana); it is
30540
+ * undefined only against a not-yet-upgraded service that omits the field.
30541
+ */ onBroadcast) {
30374
30542
  // Step 1: Validate parameters using schema
30375
30543
  assertSwapParams(params, swapParamsSchema);
30376
30544
  // Step 2: Resolve parameters (chain definitions, wallet addresses, token aliases preserved)
@@ -30390,13 +30558,16 @@ async function resolveTokenDecimals(context, resolvedParams) {
30390
30558
  // Step 5: Execute swap via provider
30391
30559
  const swapParams = buildServiceSwapParams(resolvedParams, paramsWithFees);
30392
30560
  const providerResult = await provider.swap(swapParams);
30561
+ // `correlationId` is an internal telemetry join key, not part of the public
30562
+ // SwapResult — strip it here so it never leaks into the formatted result.
30563
+ const { correlationId, ...providerResultPublic } = providerResult;
30393
30564
  // A throwing `onBroadcast` must never strand the caller after a
30394
30565
  // successful swap broadcast — the chain has moved. `safeInvokeCallback`
30395
30566
  // swallows the error and surfaces a `console.warn` prefixed with
30396
30567
  // `[stablecoin-kits swap-kit] callback threw and was swallowed:` so a
30397
30568
  // kit-side closure bug stays debuggable rather than vanishing.
30398
30569
  safeInvokeCallback('swap-kit', ()=>{
30399
- onBroadcast?.(providerResult.txHash);
30570
+ onBroadcast?.(providerResultPublic.txHash, correlationId);
30400
30571
  });
30401
30572
  const { tokenInDecimals, tokenOutDecimals } = await resolveTokenDecimals(context, resolvedParams);
30402
30573
  // Step 6: Compose chain identity (owned by the kit, derived from the
@@ -30405,10 +30576,10 @@ async function resolveTokenDecimals(context, resolvedParams) {
30405
30576
  // resolves `progress` — the provider's snapshot, or a terminal `'DONE'` for
30406
30577
  // a provider that omits it (a synchronous same-chain completion).
30407
30578
  const composedResult = {
30408
- ...providerResult,
30579
+ ...providerResultPublic,
30409
30580
  chainIn: resolvedParams.from.chain,
30410
30581
  chainOut: resolvedParams.toChain ?? resolvedParams.from.chain,
30411
- progress: providerResult.progress ?? {
30582
+ progress: providerResultPublic.progress ?? {
30412
30583
  status: 'DONE'
30413
30584
  }
30414
30585
  };
@@ -31189,7 +31360,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
31189
31360
  ESTIMATE: 'swap_estimate'
31190
31361
  };
31191
31362
 
31192
- /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$3.name);
31363
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$3.name);
31193
31364
  /**
31194
31365
  * A high-level class-based interface for same-chain and cross-chain token swap operations.
31195
31366
  *
@@ -31261,7 +31432,13 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
31261
31432
  */ class SwapKit {
31262
31433
  context;
31263
31434
  /** Whether error telemetry is disabled. */ disableErrorReporting;
31264
- /** Per-kit telemetry identity for shared helpers. */ telemetryConfig;
31435
+ /** Per-kit telemetry identity for error reporting. */ telemetryConfig;
31436
+ /**
31437
+ * Per-kit telemetry identity for success/analytics events. Gated by
31438
+ * `disableAnalytics` (independent of `disableErrorReporting`) so a developer
31439
+ * can opt out of volume analytics without also silencing error reports —
31440
+ * mirrors EarnKit.
31441
+ */ analyticsTelemetryConfig;
31265
31442
  /**
31266
31443
  * Create a new SwapKit instance.
31267
31444
  *
@@ -31311,10 +31488,15 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
31311
31488
  this.context = createSwapKitContext(config);
31312
31489
  this.disableErrorReporting = config.disableErrorReporting === true;
31313
31490
  this.telemetryConfig = {
31314
- sdkName: SDK_NAME$1,
31491
+ sdkName: SDK_NAME$2,
31315
31492
  sdkVersion: pkg$3.version,
31316
31493
  disabled: this.disableErrorReporting
31317
31494
  };
31495
+ this.analyticsTelemetryConfig = {
31496
+ sdkName: SDK_NAME$2,
31497
+ sdkVersion: pkg$3.version,
31498
+ disabled: config.disableAnalytics === true
31499
+ };
31318
31500
  }
31319
31501
  /**
31320
31502
  * Estimate the output amount and fees for a swap operation.
@@ -31355,8 +31537,12 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
31355
31537
  * console.log(`Fees:`, quote.fees)
31356
31538
  * ```
31357
31539
  */ async estimate(params) {
31540
+ const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
31358
31541
  return withErrorTelemetry(async ()=>estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
31359
31542
  sourceChain: resolveChainName(params.from.chain),
31543
+ ...destinationChain != null && {
31544
+ destinationChain
31545
+ },
31360
31546
  tokenIn: params.tokenIn,
31361
31547
  tokenOut: params.tokenOut
31362
31548
  });
@@ -31415,16 +31601,54 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
31415
31601
  * ```
31416
31602
  */ async swap(params) {
31417
31603
  let txHash;
31418
- return withErrorTelemetry(async ()=>swap$1(this.context, params, (h)=>{
31419
- txHash = h;
31420
- }), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, ()=>({
31604
+ let correlationId;
31605
+ // Shared context builder so the error resolver and the success emit stay in
31606
+ // lockstep — a field added here reaches both call sites. Reads the per-call
31607
+ // locals lazily, so txHash/correlationId (set during the swap) are captured
31608
+ // whenever it is invoked.
31609
+ // Destination chain is the primary attribution dimension for cross-chain
31610
+ // swaps; resolved once from the (static) params. Omitted for same-chain
31611
+ // swaps that leave `to.chain` unset (destination == source).
31612
+ const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
31613
+ const buildTelemetryContext = ()=>({
31421
31614
  sourceChain: resolveChainName(params.from.chain),
31615
+ ...destinationChain != null && {
31616
+ destinationChain
31617
+ },
31422
31618
  tokenIn: params.tokenIn,
31423
31619
  tokenOut: params.tokenOut,
31424
31620
  ...txHash != null && {
31425
31621
  txHash
31622
+ },
31623
+ ...correlationId != null && {
31624
+ correlationId
31426
31625
  }
31427
- }));
31626
+ });
31627
+ const result = await withErrorTelemetry(async ()=>swap$1(this.context, params, (h, cId)=>{
31628
+ txHash = h;
31629
+ correlationId = cId;
31630
+ }), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, buildTelemetryContext);
31631
+ // withErrorTelemetry only emits on failure. Record the successful swap here
31632
+ // so the backend can attribute swap volume to a developer: the client event
31633
+ // carries the (burn) txHash + correlationId, which joins to the
31634
+ // server-emitted event carrying entity_id. Best-effort; never throws.
31635
+ //
31636
+ // Emit whenever a broadcast happened, i.e. we have the source/burn txHash.
31637
+ // For a cross-chain swap that is the source-chain burn (progress is still
31638
+ // PENDING while the destination mint settles) — we intentionally attribute
31639
+ // at broadcast using the burn txHash rather than tracking the destination
31640
+ // leg, which keeps the capture simple and self-contained in swap().
31641
+ //
31642
+ // Guard against a terminal-failure result: the EVM provider throws on
31643
+ // revert today, but the kit is provider-agnostic, so a provider that
31644
+ // returns a FAILED/NOT_FOUND result without throwing must not be recorded
31645
+ // as a successful swap. Routed through analyticsTelemetryConfig so it is
31646
+ // gated by disableAnalytics, independent of error reporting.
31647
+ const status = result.progress?.status;
31648
+ if (txHash != null && status !== 'FAILED' && status !== 'NOT_FOUND') {
31649
+ emitSuccessTelemetry(SWAP_EVENT_TYPES.SWAP, this.analyticsTelemetryConfig, buildTelemetryContext());
31650
+ }
31651
+ return result;
31428
31652
  }
31429
31653
  /**
31430
31654
  * Fetch the current status of a swap from the Stablecoin Service.
@@ -31803,6 +32027,9 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
31803
32027
  const kit = new SwapKit({
31804
32028
  ...context.disableErrorReporting != null && {
31805
32029
  disableErrorReporting: context.disableErrorReporting
32030
+ },
32031
+ ...context.disableAnalytics != null && {
32032
+ disableAnalytics: context.disableAnalytics
31806
32033
  }
31807
32034
  });
31808
32035
  if (hasBoth) {
@@ -31864,7 +32091,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
31864
32091
  };
31865
32092
 
31866
32093
  var name$2 = "@circle-fin/earn-kit";
31867
- var version$2 = "1.3.0";
32094
+ var version$2 = "1.4.0";
31868
32095
  var pkg$2 = {
31869
32096
  name: name$2,
31870
32097
  version: version$2};
@@ -33863,7 +34090,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
33863
34090
  }
33864
34091
 
33865
34092
  var name$1 = "@circle-fin/provider-earn-service";
33866
- var version$1 = "1.3.0";
34093
+ var version$1 = "1.3.1";
33867
34094
  var pkg$1 = {
33868
34095
  name: name$1,
33869
34096
  version: version$1};
@@ -33925,15 +34152,25 @@ var pkg$1 = {
33925
34152
  *
33926
34153
  * @internal
33927
34154
  */ function buildConfig(serviceConfig) {
34155
+ // The kit key is a server-only secret. Reject it in the browser so it cannot
34156
+ // leak into a client bundle (no-op in Node.js). Keyless usage stays allowed.
34157
+ if (serviceConfig?.kitKey !== undefined && isBrowserEnvironment()) {
34158
+ throw createValidationFailedError$1('kitKey', '[redacted]', 'kitKey must not be provided in a browser environment — it is a server-only secret. Run EarnKit operations that use a kit key on your server, or omit kitKey to use the permissionless (keyless) client path');
34159
+ }
33928
34160
  const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
33929
- const sdkVersion = resolveSdkVersionHeader();
34161
+ // The API CORS policy does not allow this custom header. Keep the existing
34162
+ // per-request version attribution for Node callers, but omit it in browsers
34163
+ // so public EarnKit endpoints do not fail at CORS preflight.
34164
+ const sdkVersionHeader = isNodeEnvironment() ? {
34165
+ [SDK_VERSION_HEADER]: resolveSdkVersionHeader()
34166
+ } : {};
33930
34167
  if (serviceConfig?.kitKey === undefined) {
33931
34168
  return {
33932
34169
  pollingConfig: {
33933
34170
  ...DEFAULT_CONFIG,
33934
34171
  headers: {
33935
34172
  ...DEFAULT_CONFIG.headers,
33936
- [SDK_VERSION_HEADER]: sdkVersion
34173
+ ...sdkVersionHeader
33937
34174
  }
33938
34175
  },
33939
34176
  baseUrl
@@ -33951,7 +34188,7 @@ var pkg$1 = {
33951
34188
  ...DEFAULT_CONFIG,
33952
34189
  headers: {
33953
34190
  ...DEFAULT_CONFIG.headers,
33954
- [SDK_VERSION_HEADER]: sdkVersion,
34191
+ ...sdkVersionHeader,
33955
34192
  Authorization: `Bearer ${serviceConfig.kitKey}`
33956
34193
  }
33957
34194
  },
@@ -36065,6 +36302,12 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
36065
36302
  if (config.providers !== undefined && !Array.isArray(config.providers)) {
36066
36303
  throw createValidationFailedError$1('config.providers', config.providers, 'providers must be an array of earn providers when provided');
36067
36304
  }
36305
+ if (config.disableAnalytics !== undefined && typeof config.disableAnalytics !== 'boolean') {
36306
+ throw createValidationFailedError$1('config.disableAnalytics', config.disableAnalytics, 'disableAnalytics must be a boolean when provided');
36307
+ }
36308
+ if (config.disableErrorReporting !== undefined && typeof config.disableErrorReporting !== 'boolean') {
36309
+ throw createValidationFailedError$1('config.disableErrorReporting', config.disableErrorReporting, 'disableErrorReporting must be a boolean when provided');
36310
+ }
36068
36311
  const defaultProviders = getDefaultProviders$1();
36069
36312
  const providers = [
36070
36313
  ...config.providers ?? [],
@@ -36076,6 +36319,31 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
36076
36319
  return context;
36077
36320
  }
36078
36321
 
36322
+ /**
36323
+ * Register Earn Kit telemetry event type strings with the shared registry so
36324
+ * error telemetry helpers remain compile-time checked.
36325
+ *
36326
+ * @internal
36327
+ */ /**
36328
+ * Telemetry event type identifiers for Earn Kit operations.
36329
+ *
36330
+ * @internal
36331
+ */ const EARN_EVENT_TYPES = {
36332
+ GET_VAULTS: 'earn_get_vaults',
36333
+ EXPLORE_VAULTS: 'earn_explore_vaults',
36334
+ GET_POSITION: 'earn_get_position',
36335
+ GET_CROSS_CHAIN_DEPOSIT_STATUS: 'earn_get_cross_chain_deposit_status',
36336
+ WAIT_FOR_CROSS_CHAIN_DEPOSIT: 'earn_wait_for_cross_chain_deposit',
36337
+ DEPOSIT: 'earn_deposit',
36338
+ CROSS_CHAIN_DEPOSIT: 'earn_cross_chain_deposit',
36339
+ WITHDRAW: 'earn_withdraw',
36340
+ CLAIM_REWARDS: 'earn_claim_rewards',
36341
+ GET_DEPOSIT_QUOTE: 'earn_get_deposit_quote',
36342
+ GET_WITHDRAWAL_QUOTE: 'earn_get_withdrawal_quote',
36343
+ GET_CLAIM_REWARDS_QUOTE: 'earn_get_claim_rewards_quote',
36344
+ RETRY: 'earn_retry'
36345
+ };
36346
+
36079
36347
  /**
36080
36348
  * Format a provider amount object as a human-readable decimal string.
36081
36349
  *
@@ -37591,6 +37859,14 @@ function hasCrossChainDestination(params) {
37591
37859
  return formatClaimRewardsQuoteInfo(result);
37592
37860
  }
37593
37861
 
37862
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
37863
+ /**
37864
+ * Determine whether deposit parameters target a destination chain.
37865
+ *
37866
+ * @internal
37867
+ */ function isCrossChainDeposit(params) {
37868
+ return 'to' in params && params.to !== undefined;
37869
+ }
37594
37870
  function formatRetryResult(operation, result) {
37595
37871
  switch(operation){
37596
37872
  case 'deposit':
@@ -37605,6 +37881,70 @@ function formatRetryResult(operation, result) {
37605
37881
  }
37606
37882
  }
37607
37883
  }
37884
+ /**
37885
+ * Emit the success event corresponding to a completed retry.
37886
+ *
37887
+ * @internal
37888
+ */ function emitRetrySuccessTelemetry(trace, result, config) {
37889
+ switch(trace.operation){
37890
+ case 'deposit':
37891
+ {
37892
+ const sourceChain = resolveChainName(trace.params.from.chain);
37893
+ if ('to' in trace.params && trace.params.to !== undefined) {
37894
+ const destinationChain = resolveChainName(trace.params.to.chain);
37895
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, config, {
37896
+ ...sourceChain != null && {
37897
+ sourceChain
37898
+ },
37899
+ ...destinationChain != null && {
37900
+ destinationChain
37901
+ }
37902
+ });
37903
+ return;
37904
+ }
37905
+ emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, config, {
37906
+ ...sourceChain != null && {
37907
+ sourceChain
37908
+ },
37909
+ ...'txHash' in result && {
37910
+ txHash: result.txHash
37911
+ }
37912
+ });
37913
+ return;
37914
+ }
37915
+ case 'withdraw':
37916
+ {
37917
+ const sourceChain = resolveChainName(trace.params.from.chain);
37918
+ emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, config, {
37919
+ ...sourceChain != null && {
37920
+ sourceChain
37921
+ },
37922
+ ...'txHash' in result && {
37923
+ txHash: result.txHash
37924
+ }
37925
+ });
37926
+ return;
37927
+ }
37928
+ case 'claimRewards':
37929
+ {
37930
+ if ('rewards' in result && result.status === 'claimed') {
37931
+ const sourceChain = resolveChainName(trace.params.from.chain);
37932
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, config, {
37933
+ ...sourceChain != null && {
37934
+ sourceChain
37935
+ },
37936
+ txHash: result.txHash
37937
+ });
37938
+ }
37939
+ return;
37940
+ }
37941
+ default:
37942
+ {
37943
+ const exhaustive = trace;
37944
+ throw createValidationFailedError$1('error.cause.trace', exhaustive, 'EarnKit.retry() does not support this earn operation');
37945
+ }
37946
+ }
37947
+ }
37608
37948
  /**
37609
37949
  * A high-level class-based interface for DeFi lending vault operations.
37610
37950
  *
@@ -37663,6 +38003,8 @@ function formatRetryResult(operation, result) {
37663
38003
  * ```
37664
38004
  */ class EarnKit {
37665
38005
  context;
38006
+ /** Per-kit identity and opt-out state for error telemetry. */ telemetryConfig;
38007
+ /** Per-kit identity and opt-out state for success telemetry. */ analyticsTelemetryConfig;
37666
38008
  /**
37667
38009
  * Event dispatcher for step-level events emitted during multi-phase earn
37668
38010
  * operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
@@ -37687,6 +38029,16 @@ function formatRetryResult(operation, result) {
37687
38029
  */ constructor(config = {}){
37688
38030
  this.context = createEarnKitContext(config);
37689
38031
  this.actionDispatcher = new Actionable();
38032
+ this.telemetryConfig = {
38033
+ sdkName: SDK_NAME$1,
38034
+ sdkVersion: pkg$2.version,
38035
+ disabled: config.disableErrorReporting === true
38036
+ };
38037
+ this.analyticsTelemetryConfig = {
38038
+ sdkName: SDK_NAME$1,
38039
+ sdkVersion: pkg$2.version,
38040
+ disabled: config.disableAnalytics === true
38041
+ };
37690
38042
  for (const provider of this.context.providers){
37691
38043
  provider.registerDispatcher(this.actionDispatcher);
37692
38044
  }
@@ -37747,29 +38099,36 @@ function formatRetryResult(operation, result) {
37747
38099
  * }
37748
38100
  * ```
37749
38101
  */ async retry(error) {
37750
- if (!isKitError(error)) {
37751
- throw createValidationFailedError$1('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
37752
- }
37753
- if (!isRetryableError$1(error)) {
37754
- throw createValidationFailedError$1('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
37755
- }
37756
- const trace = error.cause?.trace;
37757
- if (!isEarnErrorTrace(trace)) {
37758
- throw createValidationFailedError$1('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
37759
- }
37760
- const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
37761
- if (provider === undefined) {
37762
- throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
37763
- }
37764
- const result = await provider.retry(error);
37765
- // `provider.retry` returns a flat result union with no compile-time link to
37766
- // `trace.operation`, so narrow the operation here to select the matching
37767
- // overload. The result cast in each branch is sound: the provider always
37768
- // returns the result type corresponding to the resumed operation.
37769
- if (trace.operation === 'claimRewards') {
38102
+ const result = await withErrorTelemetry(async ()=>{
38103
+ if (!isKitError(error)) {
38104
+ throw createValidationFailedError$1('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
38105
+ }
38106
+ if (!isRetryableError$1(error)) {
38107
+ throw createValidationFailedError$1('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
38108
+ }
38109
+ const trace = error.cause?.trace;
38110
+ if (!isEarnErrorTrace(trace)) {
38111
+ throw createValidationFailedError$1('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
38112
+ }
38113
+ const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
38114
+ if (provider === undefined) {
38115
+ throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
38116
+ }
38117
+ const result = await provider.retry(error);
38118
+ // `provider.retry` returns a flat result union with no compile-time link to
38119
+ // `trace.operation`, so narrow the operation here to select the matching
38120
+ // overload. The result cast in each branch is sound: the provider always
38121
+ // returns the result type corresponding to the resumed operation.
38122
+ if (trace.operation === 'claimRewards') {
38123
+ return formatRetryResult(trace.operation, result);
38124
+ }
37770
38125
  return formatRetryResult(trace.operation, result);
38126
+ }, EARN_EVENT_TYPES.RETRY, this.telemetryConfig);
38127
+ const trace = isKitError(error) ? error.cause?.trace : undefined;
38128
+ if (isEarnErrorTrace(trace)) {
38129
+ emitRetrySuccessTelemetry(trace, result, this.analyticsTelemetryConfig);
37771
38130
  }
37772
- return formatRetryResult(trace.operation, result);
38131
+ return result;
37773
38132
  }
37774
38133
  /**
37775
38134
  * Return the chains supported by configured earn providers.
@@ -37803,7 +38162,9 @@ function formatRetryResult(operation, result) {
37803
38162
  * result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
37804
38163
  * ```
37805
38164
  */ async getVaults(params) {
37806
- return getVaults$1(this.context, params);
38165
+ const result = await withErrorTelemetry(async ()=>getVaults$1(this.context, params), EARN_EVENT_TYPES.GET_VAULTS, this.telemetryConfig);
38166
+ emitSuccessTelemetry(EARN_EVENT_TYPES.GET_VAULTS, this.analyticsTelemetryConfig, {});
38167
+ return result;
37807
38168
  }
37808
38169
  /**
37809
38170
  * Discover vaults available on a chain.
@@ -37828,7 +38189,12 @@ function formatRetryResult(operation, result) {
37828
38189
  * const guarded = result.vaults.filter(v => v.circleGuarded) // Circle-guarded vaults only
37829
38190
  * ```
37830
38191
  */ async exploreVaults(params) {
37831
- return exploreVaults$1(this.context, params);
38192
+ const context = {
38193
+ sourceChain: resolveChainName(params.chain)
38194
+ };
38195
+ const result = await withErrorTelemetry(async ()=>exploreVaults$1(this.context, params), EARN_EVENT_TYPES.EXPLORE_VAULTS, this.telemetryConfig, context);
38196
+ emitSuccessTelemetry(EARN_EVENT_TYPES.EXPLORE_VAULTS, this.analyticsTelemetryConfig, context);
38197
+ return result;
37832
38198
  }
37833
38199
  /**
37834
38200
  * Lazily iterate every vault available on a chain.
@@ -37875,7 +38241,9 @@ function formatRetryResult(operation, result) {
37875
38241
  * }
37876
38242
  * ```
37877
38243
  */ async getPosition(params) {
37878
- return getPosition$1(this.context, params);
38244
+ return withErrorTelemetry(async ()=>getPosition$1(this.context, params), EARN_EVENT_TYPES.GET_POSITION, this.telemetryConfig, {
38245
+ sourceChain: resolveChainName(params.from.chain)
38246
+ });
37879
38247
  }
37880
38248
  /**
37881
38249
  * Fetch the current status of a cross-chain deposit by execution ID.
@@ -37900,7 +38268,7 @@ function formatRetryResult(operation, result) {
37900
38268
  * console.log(`Bridge ${status.execId} is ${status.status}`)
37901
38269
  * ```
37902
38270
  */ async getCrossChainDepositStatus(params) {
37903
- return getCrossChainDepositStatus$1(this.context, params);
38271
+ return withErrorTelemetry(async ()=>getCrossChainDepositStatus$1(this.context, params), EARN_EVENT_TYPES.GET_CROSS_CHAIN_DEPOSIT_STATUS, this.telemetryConfig);
37904
38272
  }
37905
38273
  /**
37906
38274
  * Poll a cross-chain deposit until it reaches a terminal bridge state.
@@ -37927,10 +38295,30 @@ function formatRetryResult(operation, result) {
37927
38295
  * console.log(`Bridge ended as ${result.outcome}`)
37928
38296
  * ```
37929
38297
  */ async waitForCrossChainDeposit(params) {
37930
- return waitForCrossChainDeposit$1(this.context, params);
38298
+ return withErrorTelemetry(async ()=>waitForCrossChainDeposit$1(this.context, params), EARN_EVENT_TYPES.WAIT_FOR_CROSS_CHAIN_DEPOSIT, this.telemetryConfig);
37931
38299
  }
37932
38300
  async deposit(params) {
37933
- return deposit$3(this.context, params);
38301
+ const isCrossChain = isCrossChainDeposit(params);
38302
+ const context = {
38303
+ sourceChain: resolveChainName(params.from.chain),
38304
+ ...isCrossChain && {
38305
+ destinationChain: resolveChainName(params.to.chain)
38306
+ }
38307
+ };
38308
+ const eventType = isCrossChain ? EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT : EARN_EVENT_TYPES.DEPOSIT;
38309
+ const result = await withErrorTelemetry(async ()=>deposit$3(this.context, params), eventType, this.telemetryConfig, context);
38310
+ if (result.kind === 'cross-chain') {
38311
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, this.analyticsTelemetryConfig, {
38312
+ sourceChain: resolveChainName(result.sourceChain),
38313
+ destinationChain: resolveChainName(result.destinationChain)
38314
+ });
38315
+ } else {
38316
+ emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, this.analyticsTelemetryConfig, {
38317
+ ...context,
38318
+ txHash: result.txHash
38319
+ });
38320
+ }
38321
+ return result;
37934
38322
  }
37935
38323
  /**
37936
38324
  * Execute a withdrawal from a DeFi lending vault.
@@ -37956,7 +38344,15 @@ function formatRetryResult(operation, result) {
37956
38344
  * console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
37957
38345
  * ```
37958
38346
  */ async withdraw(params) {
37959
- return withdraw$1(this.context, params);
38347
+ const context = {
38348
+ sourceChain: resolveChainName(params.from.chain)
38349
+ };
38350
+ const result = await withErrorTelemetry(async ()=>withdraw$1(this.context, params), EARN_EVENT_TYPES.WITHDRAW, this.telemetryConfig, context);
38351
+ emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, this.analyticsTelemetryConfig, {
38352
+ ...context,
38353
+ txHash: result.txHash
38354
+ });
38355
+ return result;
37960
38356
  }
37961
38357
  /**
37962
38358
  * Claim rewards from earn vaults.
@@ -37983,7 +38379,17 @@ function formatRetryResult(operation, result) {
37983
38379
  *
37984
38380
  * @internal
37985
38381
  */ async claimRewards(params) {
37986
- return claimRewards$1(this.context, params);
38382
+ const context = {
38383
+ sourceChain: resolveChainName(params.from.chain)
38384
+ };
38385
+ const result = await withErrorTelemetry(async ()=>claimRewards$1(this.context, params), EARN_EVENT_TYPES.CLAIM_REWARDS, this.telemetryConfig, context);
38386
+ if (result.status === 'claimed') {
38387
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, this.analyticsTelemetryConfig, {
38388
+ ...context,
38389
+ txHash: result.txHash
38390
+ });
38391
+ }
38392
+ return result;
37987
38393
  }
37988
38394
  /**
37989
38395
  * Get an informational quote for a deposit into a vault.
@@ -38005,7 +38411,9 @@ function formatRetryResult(operation, result) {
38005
38411
  * console.log(`Expected shares: ${quote.expectedShares.amount}`)
38006
38412
  * ```
38007
38413
  */ async getDepositQuote(params) {
38008
- return getDepositQuote$1(this.context, params);
38414
+ return withErrorTelemetry(async ()=>getDepositQuote$1(this.context, params), EARN_EVENT_TYPES.GET_DEPOSIT_QUOTE, this.telemetryConfig, {
38415
+ sourceChain: resolveChainName(params.from.chain)
38416
+ });
38009
38417
  }
38010
38418
  /**
38011
38419
  * Get an informational quote for a withdrawal from a vault.
@@ -38027,7 +38435,9 @@ function formatRetryResult(operation, result) {
38027
38435
  * console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
38028
38436
  * ```
38029
38437
  */ async getWithdrawalQuote(params) {
38030
- return getWithdrawalQuote$1(this.context, params);
38438
+ return withErrorTelemetry(async ()=>getWithdrawalQuote$1(this.context, params), EARN_EVENT_TYPES.GET_WITHDRAWAL_QUOTE, this.telemetryConfig, {
38439
+ sourceChain: resolveChainName(params.from.chain)
38440
+ });
38031
38441
  }
38032
38442
  /**
38033
38443
  * Get an informational quote for claiming rewards.
@@ -38049,7 +38459,9 @@ function formatRetryResult(operation, result) {
38049
38459
  *
38050
38460
  * @internal
38051
38461
  */ async getClaimRewardsQuote(params) {
38052
- return getClaimRewardsQuote$1(this.context, params);
38462
+ return withErrorTelemetry(async ()=>getClaimRewardsQuote$1(this.context, params), EARN_EVENT_TYPES.GET_CLAIM_REWARDS_QUOTE, this.telemetryConfig, {
38463
+ sourceChain: resolveChainName(params.from.chain)
38464
+ });
38053
38465
  }
38054
38466
  }
38055
38467
 
@@ -38138,7 +38550,14 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
38138
38550
  * const earnKit = createEarnKit(context)
38139
38551
  * ```
38140
38552
  */ const createEarnKit = (context)=>{
38141
- const kit = new EarnKit();
38553
+ const kit = new EarnKit({
38554
+ ...context.disableErrorReporting != null && {
38555
+ disableErrorReporting: context.disableErrorReporting
38556
+ },
38557
+ ...context.disableAnalytics != null && {
38558
+ disableAnalytics: context.disableAnalytics
38559
+ }
38560
+ });
38142
38561
  registerActionHandlers(kit, context.actions.earn, 'earn');
38143
38562
  return kit;
38144
38563
  };
@@ -39447,7 +39866,7 @@ async function deposit$2(context, params) {
39447
39866
  }
39448
39867
 
39449
39868
  var name = "@circle-fin/unified-balance-kit";
39450
- var version = "1.3.0";
39869
+ var version = "1.3.1";
39451
39870
  var pkg = {
39452
39871
  name: name,
39453
39872
  version: version};
@@ -45957,7 +46376,11 @@ const removeFundParamsSchema = z.object({
45957
46376
  // Remove Fund Operations
45958
46377
  // ---------------------------------------------------------------------------
45959
46378
  /**
45960
- * Kick off a delayed fund removal from an account.
46379
+ * Kick off a delayed recovery fund removal from an account.
46380
+ *
46381
+ * Use `initiateRemoveFund` only as a trustless fallback when the normal spend
46382
+ * flow is unavailable. For day-to-day movement out of a Unified Balance, use
46383
+ * `spend`.
45961
46384
  *
45962
46385
  * Validates `from` and `amount`, resolves the chain and token via
45963
46386
  * {@link resolveRemoveFundParams}, selects the matching provider, then calls
@@ -45994,7 +46417,10 @@ const removeFundParamsSchema = z.object({
45994
46417
  return provider.initiateRemoveFund(resolved);
45995
46418
  }
45996
46419
  /**
45997
- * Complete a fund removal once the 7-day activation period has passed.
46420
+ * Complete a recovery fund removal once the 7-day withdrawal delay has passed.
46421
+ *
46422
+ * Use `removeFund` only as a trustless fallback when the normal spend flow is
46423
+ * unavailable. For day-to-day movement out of a Unified Balance, use `spend`.
45998
46424
  *
45999
46425
  * Validates `from`, resolves the chain and token via
46000
46426
  * {@link resolveRemoveFundParams}, selects the matching provider, then calls
@@ -46083,13 +46509,18 @@ const removeFundParamsSchema = z.object({
46083
46509
  /** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg.name);
46084
46510
  /**
46085
46511
  * A high-level class-based interface for cross-chain USDC deposits,
46086
- * spending, balance queries, delegation management, and withdrawals.
46512
+ * spending, balance queries, delegation management, and recovery fund removals.
46087
46513
  *
46088
46514
  * UnifiedBalanceKit provides a familiar class-based API for developers who
46089
46515
  * prefer traditional object-oriented patterns. The class maintains an
46090
46516
  * internal context and provides methods that delegate to the standalone
46091
46517
  * operation functions exported by this package.
46092
46518
  *
46519
+ * Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
46520
+ * trustless recovery path for situations where the normal spend flow is
46521
+ * unavailable, and it requires a 7-day withdrawal delay before funds can be
46522
+ * removed.
46523
+ *
46093
46524
  * @remarks
46094
46525
  * For functional usage, import and use the operations directly:
46095
46526
  * ```typescript
@@ -46310,7 +46741,11 @@ const removeFundParamsSchema = z.object({
46310
46741
  });
46311
46742
  }
46312
46743
  /**
46313
- * Kick off a delayed fund removal from an account.
46744
+ * Kick off a delayed recovery fund removal from an account.
46745
+ *
46746
+ * Use this only as a trustless fallback when the normal spend flow is
46747
+ * unavailable. For day-to-day movement out of a Unified Balance, use
46748
+ * `spend`.
46314
46749
  *
46315
46750
  * @param params - The account owner's adapter context, amount, and
46316
46751
  * optional token type.
@@ -46324,7 +46759,12 @@ const removeFundParamsSchema = z.object({
46324
46759
  });
46325
46760
  }
46326
46761
  /**
46327
- * Complete a fund removal once the activation period has passed.
46762
+ * Complete a recovery fund removal once the 7-day withdrawal delay has
46763
+ * passed.
46764
+ *
46765
+ * Use this only as a trustless fallback when the normal spend flow is
46766
+ * unavailable. For day-to-day movement out of a Unified Balance, use
46767
+ * `spend`.
46328
46768
  *
46329
46769
  * @param params - The account owner context matching the original
46330
46770
  * fund removal initiation.
@@ -46451,6 +46891,11 @@ registerKit(`${pkg.name}/${pkg.version}`);
46451
46891
  * Internally holds a persistent {@link UnifiedBalanceKit} instance so that
46452
46892
  * event dispatchers and custom fee policies are preserved across calls.
46453
46893
  *
46894
+ * Use {@link AppKitUnifiedBalance.spend} for normal movement out of a Unified
46895
+ * Balance. {@link AppKitUnifiedBalance.removeFund} is a trustless recovery path
46896
+ * for situations where the normal spend flow is unavailable, and it requires a
46897
+ * 7-day withdrawal delay after {@link AppKitUnifiedBalance.initiateRemoveFund}.
46898
+ *
46454
46899
  * @example
46455
46900
  * ```typescript
46456
46901
  * import { AppKit } from '@circle-fin/app-kit'
@@ -46662,7 +47107,12 @@ registerKit(`${pkg.name}/${pkg.version}`);
46662
47107
  return this.kit.removeDelegate(params);
46663
47108
  }
46664
47109
  /**
46665
- * Kick off a delayed fund removal from an account.
47110
+ * Initiate a trustless recovery removal from an account.
47111
+ *
47112
+ * Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
47113
+ * recovery path for situations where the normal spend flow is unavailable.
47114
+ * Calling this method starts the 7-day withdrawal delay before the removal can
47115
+ * be completed.
46666
47116
  *
46667
47117
  * @param params - The account owner's adapter context, amount, and token.
46668
47118
  * @returns Promise resolving to the initiation details.
@@ -46681,11 +47131,16 @@ registerKit(`${pkg.name}/${pkg.version}`);
46681
47131
  return this.kit.initiateRemoveFund(params);
46682
47132
  }
46683
47133
  /**
46684
- * Complete a fund removal once the activation period has passed.
47134
+ * Complete a trustless recovery removal after the withdrawal delay.
47135
+ *
47136
+ * Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
47137
+ * recovery path for situations where the normal spend flow is unavailable.
47138
+ * Both EVM and Solana removals require a 7-day withdrawal delay after
47139
+ * `initiateRemoveFund` before funds can be removed.
46685
47140
  *
46686
47141
  * @param params - The account owner context matching the original initiation.
46687
47142
  * @returns Promise resolving to the fund removal details.
46688
- * @throws {KitError} If the activation period has not elapsed or the
47143
+ * @throws {KitError} If the withdrawal delay has not elapsed or the
46689
47144
  * on-chain transaction fails.
46690
47145
  *
46691
47146
  * @example
@@ -46934,6 +47389,9 @@ registerKit(`${pkg.name}/${pkg.version}`);
46934
47389
  ...config.unifiedBalance,
46935
47390
  ...config.disableErrorReporting != null && {
46936
47391
  disableErrorReporting: config.disableErrorReporting
47392
+ },
47393
+ ...config.disableAnalytics != null && {
47394
+ disableAnalytics: config.disableAnalytics
46937
47395
  }
46938
47396
  });
46939
47397
  this.earn = {