@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/earn.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';
21
32
  import '@ethersproject/bytes';
@@ -26,8 +37,8 @@ import 'bn.js';
26
37
  import '@coral-xyz/anchor';
27
38
  import 'bs58';
28
39
  import '@noble/curves/ed25519';
29
- import { keccak256 } from '@ethersproject/keccak256';
30
40
  import { formatUnits as formatUnits$1 } from '@ethersproject/units';
41
+ import { keccak256 } from '@ethersproject/keccak256';
31
42
 
32
43
  // Import global type declarations
33
44
  /**
@@ -44,6 +55,51 @@ import { formatUnits as formatUnits$1 } from '@ethersproject/units';
44
55
  * }
45
56
  * ```
46
57
  */ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
58
+ /**
59
+ * Check whether the current runtime exposes a browser DOM.
60
+ *
61
+ * @remarks
62
+ * This intentionally does not treat every non-Node runtime as a browser.
63
+ * Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
64
+ * Edge Functions do not expose Node globals but can safely use server
65
+ * credentials. A Node.js runtime remains server-side even when a test or SSR
66
+ * environment provides a DOM shim.
67
+ *
68
+ * @returns `true` when running in a browser window, `false` otherwise.
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * import { isBrowserEnvironment } from '@core/utils'
73
+ *
74
+ * if (isBrowserEnvironment()) {
75
+ * throw new Error('Server-only secrets must not be used in the browser')
76
+ * }
77
+ * ```
78
+ */ const isBrowserEnvironment = ()=>{
79
+ const browserWindow = globalThis.window;
80
+ return !isNodeEnvironment() && browserWindow?.document !== undefined;
81
+ };
82
+ /**
83
+ * Return the SDK User-Agent request header only when running in Node.js.
84
+ *
85
+ * Browsers forbid manually setting `User-Agent`, and a custom fallback header
86
+ * can trigger CORS preflight. Non-Node server runtimes also omit this optional
87
+ * attribution header because they cannot set it reliably.
88
+ *
89
+ * @returns A User-Agent header in Node.js, or an empty object otherwise.
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * import { getNodeUserAgentHeader } from '@core/utils'
94
+ *
95
+ * const headers = {
96
+ * 'Content-Type': 'application/json',
97
+ * ...getNodeUserAgentHeader(),
98
+ * }
99
+ * ```
100
+ */ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
101
+ 'User-Agent': getUserAgent()
102
+ } : {};
47
103
  /**
48
104
  * Detect the runtime environment and return a shortened identifier.
49
105
  *
@@ -6392,6 +6448,39 @@ const swapTokenEnumSchema = z.enum([
6392
6448
  throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
6393
6449
  }
6394
6450
 
6451
+ /**
6452
+ * Resolve a chain identifier to a plain chain-name string.
6453
+ *
6454
+ * Accept a string literal (`'Ethereum'`), a `ChainDefinition`-like
6455
+ * object (`{ chain: 'Ethereum' }`), or `null`/`undefined` and return
6456
+ * the chain name as a string. Return `undefined` when the value
6457
+ * cannot be resolved.
6458
+ *
6459
+ * @remarks
6460
+ * Unlike `resolveChainIdentifier` (which returns a full `ChainDefinition`
6461
+ * and throws on invalid input), this helper is intentionally lenient and
6462
+ * never throws — it is safe to call in error-handling and telemetry paths.
6463
+ *
6464
+ * @param value - A string, chain-definition object, or nullish value.
6465
+ * @returns The chain name string, or `undefined`.
6466
+ *
6467
+ * @example
6468
+ * ```typescript
6469
+ * import { resolveChainName } from '@core/chains'
6470
+ *
6471
+ * resolveChainName('Ethereum') // 'Ethereum'
6472
+ * resolveChainName({ chain: 'Ethereum' }) // 'Ethereum'
6473
+ * resolveChainName(undefined) // undefined
6474
+ * ```
6475
+ */ function resolveChainName(value) {
6476
+ if (value == null) return undefined;
6477
+ if (typeof value === 'string') return value;
6478
+ if (typeof value === 'object' && 'chain' in value && typeof value.chain === 'string') {
6479
+ return value.chain;
6480
+ }
6481
+ return undefined;
6482
+ }
6483
+
6395
6484
  /**
6396
6485
  * Extracts chain information including name, display name, and expected address format.
6397
6486
  *
@@ -6776,13 +6865,12 @@ const swapTokenEnumSchema = z.enum([
6776
6865
  headers: {
6777
6866
  ...DEFAULT_CONFIG$1.headers,
6778
6867
  ...config.headers ?? {},
6779
- // In browser environments, directly setting the 'User-Agent' or similar headers is restricted and may be ignored or cause errors.
6780
- // This is why we use the 'X-User-Agent' header instead.
6781
- ...typeof window === 'undefined' ? {
6782
- 'User-Agent': getUserAgent()
6783
- } : {
6784
- 'X-User-Agent': getUserAgent()
6785
- }
6868
+ // Browsers forbid setting a user-agent request header, and the custom
6869
+ // fallback header the SDK used instead trips CORS preflight against the
6870
+ // Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
6871
+ // blocking the request. So send the SDK user agent only in Node;
6872
+ // browsers omit it entirely.
6873
+ ...getNodeUserAgentHeader()
6786
6874
  }
6787
6875
  };
6788
6876
  let lastError;
@@ -8048,6 +8136,216 @@ const swapTokenEnumSchema = z.enum([
8048
8136
  * This prefix is right-padded to 24 bytes in the final hookData.
8049
8137
  */ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
8050
8138
 
8139
+ /**
8140
+ * Project an arbitrary payload onto the exact set of fields the telemetry
8141
+ * endpoint accepts.
8142
+ *
8143
+ * @remarks
8144
+ * Defense-in-depth before the last network hop: rather than
8145
+ * `JSON.stringify`-ing the caller's object verbatim, only the
8146
+ * allowlisted {@link ClientLogPayload} fields (and the allowlisted
8147
+ * sub-fields of `errorDetails` / `clientContext`) are copied across.
8148
+ * A regressing upstream mapper — or a plain-JS caller that bypasses the
8149
+ * type — therefore cannot exfiltrate stray properties (secrets, PII,
8150
+ * raw error stacks) through the analytics channel. Optional fields are
8151
+ * only included when present so the serialised shape matches the
8152
+ * server's strict schema.
8153
+ *
8154
+ * @internal
8155
+ */ function toSafePayload(payload) {
8156
+ const clientContext = {
8157
+ platform: payload.clientContext.platform,
8158
+ os: payload.clientContext.os,
8159
+ runtimeName: payload.clientContext.runtimeName
8160
+ };
8161
+ const safe = {
8162
+ sdkName: payload.sdkName,
8163
+ sdkVersion: payload.sdkVersion,
8164
+ eventType: payload.eventType,
8165
+ timestamp: payload.timestamp,
8166
+ clientContext
8167
+ };
8168
+ if (payload.sourceChain !== undefined) safe['sourceChain'] = payload.sourceChain;
8169
+ if (payload.destinationChain !== undefined) safe['destinationChain'] = payload.destinationChain;
8170
+ if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
8171
+ if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
8172
+ if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
8173
+ if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
8174
+ if (payload.errorDetails !== undefined) {
8175
+ const errorDetails = {
8176
+ ...payload.errorDetails.errorCode !== undefined && {
8177
+ errorCode: payload.errorDetails.errorCode
8178
+ },
8179
+ ...payload.errorDetails.errorType !== undefined && {
8180
+ errorType: payload.errorDetails.errorType
8181
+ }
8182
+ };
8183
+ safe['errorDetails'] = errorDetails;
8184
+ }
8185
+ return safe;
8186
+ }
8187
+ /**
8188
+ * Default telemetry endpoint.
8189
+ *
8190
+ * Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
8191
+ * (e.g. for staging or local development).
8192
+ *
8193
+ * @internal
8194
+ */ const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
8195
+ /**
8196
+ * Resolve the telemetry endpoint URL.
8197
+ *
8198
+ * @internal
8199
+ */ function getLogsUrl() {
8200
+ if (isNodeEnvironment() && typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' && process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
8201
+ return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
8202
+ }
8203
+ return DEFAULT_LOGS_URL;
8204
+ }
8205
+ /**
8206
+ * Send a telemetry event to the proxy service.
8207
+ *
8208
+ * @remarks
8209
+ * Fire-and-forget: the returned promise is intentionally not awaited
8210
+ * by the caller. A fetch failure (network error, non-2xx, timeout)
8211
+ * is silently swallowed so telemetry never blocks or fails user
8212
+ * operations.
8213
+ *
8214
+ * @param payload - The structured log payload matching the server schema.
8215
+ *
8216
+ * @example
8217
+ * ```typescript
8218
+ * import { emitAnalyticsLog } from '@core/utils'
8219
+ *
8220
+ * // Fire-and-forget — do not await
8221
+ * void emitAnalyticsLog(payload)
8222
+ * ```
8223
+ */ async function emitAnalyticsLog(payload) {
8224
+ // Hand-rolled timeout via `AbortController` + `setTimeout` rather than
8225
+ // `AbortSignal.timeout(...)` so we can `clearTimeout` the handle in a
8226
+ // `finally`. `AbortSignal.timeout` registers a timer that stays on the
8227
+ // event loop until it fires even if the fetch already settled, which
8228
+ // manifests as spurious `TimeoutError` unhandled rejections during
8229
+ // process teardown (notably between e2e test fork lifecycles). See
8230
+ // nodejs/node#48298 for the underlying issue.
8231
+ const controller = new AbortController();
8232
+ const timeoutHandle = setTimeout(()=>{
8233
+ controller.abort(new DOMException('Telemetry request timed out', 'TimeoutError'));
8234
+ }, 5_000);
8235
+ // Don't let the timer keep the Node event loop alive in short-lived
8236
+ // CLIs / test processes; telemetry is best-effort and must never
8237
+ // block clean process exit. `unref` only exists on Node's `Timeout`
8238
+ // object, not on the `number` returned by the browser's `setTimeout`,
8239
+ // so we feature-detect rather than call unconditionally.
8240
+ if (typeof timeoutHandle.unref === 'function') {
8241
+ timeoutHandle.unref();
8242
+ }
8243
+ try {
8244
+ await fetch(getLogsUrl(), {
8245
+ method: 'POST',
8246
+ headers: {
8247
+ 'Content-Type': 'application/json',
8248
+ // Browsers forbid setting a user-agent request header, and the custom
8249
+ // fallback header the SDK used instead trips CORS preflight (it isn't
8250
+ // in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
8251
+ // it only in Node; browsers omit it entirely.
8252
+ ...getNodeUserAgentHeader()
8253
+ },
8254
+ body: JSON.stringify(toSafePayload(payload)),
8255
+ signal: controller.signal
8256
+ });
8257
+ } catch {
8258
+ // Silently swallow — telemetry must never break user operations.
8259
+ } finally{
8260
+ clearTimeout(timeoutHandle);
8261
+ }
8262
+ }
8263
+
8264
+ /**
8265
+ * Build the `clientContext` object for telemetry payloads.
8266
+ *
8267
+ * @remarks
8268
+ * Use the exported `getRuntime()` and `isNodeEnvironment()` from
8269
+ * `@core/utils` to detect the runtime environment. The returned
8270
+ * string is parsed into the structured `ClientContext` fields
8271
+ * expected by the server schema.
8272
+ *
8273
+ * @returns A {@link ClientContext} with platform, OS, and runtime name
8274
+ * populated from the current environment.
8275
+ *
8276
+ * @example
8277
+ * ```typescript
8278
+ * import { buildClientContext } from '@core/utils'
8279
+ *
8280
+ * const ctx = buildClientContext()
8281
+ * // Node: { platform: 'node', os: 'darwin', runtimeName: null }
8282
+ * // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
8283
+ * ```
8284
+ */ function buildClientContext() {
8285
+ const runtime = getRuntime();
8286
+ if (runtime.startsWith('browser/')) {
8287
+ return {
8288
+ platform: 'browser',
8289
+ os: null,
8290
+ runtimeName: runtime.slice('browser/'.length).toLowerCase()
8291
+ };
8292
+ }
8293
+ if (runtime.startsWith('node/')) {
8294
+ return {
8295
+ platform: 'node',
8296
+ os: isNodeEnvironment() ? process.platform : null,
8297
+ runtimeName: null
8298
+ };
8299
+ }
8300
+ return {
8301
+ platform: 'node',
8302
+ os: null,
8303
+ runtimeName: null
8304
+ };
8305
+ }
8306
+
8307
+ /**
8308
+ * Extract structured error details from an unknown error value.
8309
+ *
8310
+ * @remarks
8311
+ * Handle three cases:
8312
+ * - `KitError` — extract `code` and `name`.
8313
+ * - `Error` — extract `name`.
8314
+ * - Anything else — return empty details.
8315
+ *
8316
+ * Only structured, bounded fields (`errorCode`, `errorType`) are
8317
+ * included. Free-text fields (`message`, `stack`) are intentionally
8318
+ * omitted to avoid leaking secrets or PII through vendor telemetry.
8319
+ *
8320
+ * @param error - The thrown value to extract details from.
8321
+ * @returns A {@link ErrorDetails} object suitable for telemetry payloads.
8322
+ *
8323
+ * @example
8324
+ * ```typescript
8325
+ * import { extractErrorDetails } from '@core/utils'
8326
+ *
8327
+ * try {
8328
+ * await riskyOperation()
8329
+ * } catch (error) {
8330
+ * const details = extractErrorDetails(error)
8331
+ * // { errorCode: '1001', errorType: 'INPUT_NETWORK_MISMATCH' }
8332
+ * }
8333
+ * ```
8334
+ */ function extractErrorDetails(error) {
8335
+ if (error instanceof KitError) {
8336
+ return {
8337
+ errorCode: String(error.code),
8338
+ errorType: error.name
8339
+ };
8340
+ }
8341
+ if (error instanceof Error) {
8342
+ return {
8343
+ errorType: error.name
8344
+ };
8345
+ }
8346
+ return {};
8347
+ }
8348
+
8051
8349
  /**
8052
8350
  * Strip the `@circle-fin/` scope from a kit package name to produce the
8053
8351
  * short SDK name used in telemetry payloads.
@@ -8066,8 +8364,154 @@ const swapTokenEnumSchema = z.enum([
8066
8364
  return pkgName.replace('@circle-fin/', '');
8067
8365
  }
8068
8366
 
8367
+ /**
8368
+ * Soft signal for the case where building or emitting a telemetry payload
8369
+ * threw — for example, a buggy `TelemetryContextResolver`, a regression in
8370
+ * `extractErrorDetails`, or a synchronous failure inside `emitAnalyticsLog`
8371
+ * before it could swallow the error itself. Logged with a stable prefix so
8372
+ * consumers can grep for it. We deliberately do not re-throw: the caller's
8373
+ * original operation error must always win.
8374
+ *
8375
+ * @internal
8376
+ */ function warnTelemetryDrop(eventType, cause) {
8377
+ try {
8378
+ // Pass `cause` as the second console.warn argument rather than
8379
+ // string-coercing it. `String(err)` (and `err.message` alone)
8380
+ // discards the stack trace, nested `cause`, and any custom Error
8381
+ // properties — exactly the context an on-call needs when a
8382
+ // resolver-closure regression triggers this path.
8383
+ console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
8384
+ } catch {
8385
+ // console.warn itself throwing is the user's environment; nothing more we
8386
+ // can do without risking the original operation error.
8387
+ }
8388
+ }
8389
+ /**
8390
+ * Build a telemetry payload from common fields.
8391
+ *
8392
+ * @internal
8393
+ */ function buildPayload(config, eventType, errorDetails, context) {
8394
+ return {
8395
+ sdkName: config.sdkName,
8396
+ sdkVersion: config.sdkVersion,
8397
+ eventType,
8398
+ timestamp: new Date().toISOString(),
8399
+ ...errorDetails !== undefined && {
8400
+ errorDetails
8401
+ },
8402
+ clientContext: buildClientContext(),
8403
+ ...context?.sourceChain != null && {
8404
+ sourceChain: context.sourceChain
8405
+ },
8406
+ ...context?.destinationChain != null && {
8407
+ destinationChain: context.destinationChain
8408
+ },
8409
+ ...context?.tokenIn != null && {
8410
+ tokenIn: context.tokenIn
8411
+ },
8412
+ ...context?.tokenOut != null && {
8413
+ tokenOut: context.tokenOut
8414
+ },
8415
+ ...context?.txHash != null && {
8416
+ txHash: context.txHash
8417
+ },
8418
+ ...context?.correlationId != null && {
8419
+ correlationId: context.correlationId
8420
+ }
8421
+ };
8422
+ }
8423
+ /**
8424
+ * Emit telemetry for a completed operation without affecting its caller.
8425
+ *
8426
+ * No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
8427
+ * failures while constructing or submitting the telemetry payload are reported
8428
+ * as a soft warning and never change a completed operation's result.
8429
+ *
8430
+ * @param eventType - The telemetry event type for the completed operation.
8431
+ * @param config - Per-kit SDK identity and disabled flag.
8432
+ * @param context - Optional chain, token, and transaction context.
8433
+ * @returns Nothing.
8434
+ * @throws Never — telemetry failures are reported as warnings.
8435
+ *
8436
+ * @example
8437
+ * ```typescript
8438
+ * import { emitSuccessTelemetry } from '@core/utils'
8439
+ *
8440
+ * emitSuccessTelemetry(
8441
+ * 'bridge_bridge',
8442
+ * { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
8443
+ * { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
8444
+ * )
8445
+ * ```
8446
+ */ function emitSuccessTelemetry(eventType, config, context) {
8447
+ if (config.disabled) {
8448
+ return;
8449
+ }
8450
+ try {
8451
+ void emitAnalyticsLog(buildPayload(config, eventType, undefined, context));
8452
+ } catch (telemetryError) {
8453
+ warnTelemetryDrop(eventType, telemetryError);
8454
+ }
8455
+ }
8456
+ /**
8457
+ * Wrap an async operation with error telemetry.
8458
+ *
8459
+ * Execute `fn` and, if it throws, emit an error telemetry payload
8460
+ * before re-throwing. No-ops when `config.disabled` is `true`.
8461
+ *
8462
+ * `context` may be a static {@link TelemetryContext} or a
8463
+ * {@link TelemetryContextResolver}. The resolver is invoked in the
8464
+ * catch branch, so it can read state — most importantly `txHash` —
8465
+ * that the wrapped operation set after a successful broadcast. The
8466
+ * resolver must close over per-call locals only; passing instance
8467
+ * state would break isolation between concurrent invocations.
8468
+ *
8469
+ * @param fn - The async operation to execute.
8470
+ * @param eventType - The telemetry event type for this operation.
8471
+ * @param config - Per-kit SDK identity and disabled flag.
8472
+ * @param context - Optional context, static or lazily resolved.
8473
+ * @returns The result of the operation.
8474
+ * @throws Re-throws any error after emitting telemetry.
8475
+ *
8476
+ * @example
8477
+ * ```typescript
8478
+ * import { withErrorTelemetry } from '@core/utils'
8479
+ *
8480
+ * let txHash: string | undefined
8481
+ * const result = await withErrorTelemetry(
8482
+ * () => provider.swap(params, h => { txHash = h }),
8483
+ * 'swap_swap',
8484
+ * { sdkName: 'swap-kit', sdkVersion: '1.0.0', disabled: false },
8485
+ * () => ({
8486
+ * sourceChain: 'Ethereum',
8487
+ * tokenIn: 'USDC',
8488
+ * tokenOut: 'EURC',
8489
+ * ...(txHash != null && { txHash }),
8490
+ * }),
8491
+ * )
8492
+ * ```
8493
+ */ async function withErrorTelemetry(fn, eventType, config, context) {
8494
+ try {
8495
+ return await fn();
8496
+ } catch (error) {
8497
+ if (!config.disabled) {
8498
+ try {
8499
+ const resolved = typeof context === 'function' ? context() : context;
8500
+ void emitAnalyticsLog(buildPayload(config, eventType, extractErrorDetails(error), resolved));
8501
+ } catch (telemetryError) {
8502
+ // Never let telemetry emission mask the original operation error.
8503
+ // But surface a soft signal so silent telemetry drops are
8504
+ // discoverable (e.g. a regression in a resolver closure or in
8505
+ // `extractErrorDetails`) instead of vanishing without any trace.
8506
+ warnTelemetryDrop(eventType, telemetryError);
8507
+ }
8508
+ }
8509
+ throw error;
8510
+ }
8511
+ }
8512
+
8069
8513
  var name$3 = "@circle-fin/bridge-kit";
8070
- var version$3 = "1.12.1";
8514
+ var version$3 = "1.12.2";
8071
8515
  var pkg$3 = {
8072
8516
  name: name$3,
8073
8517
  version: version$3};
@@ -8895,7 +9339,13 @@ var TransferSpeed;
8895
9339
  /**
8896
9340
  * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
8897
9341
  * hookData must start with.
8898
- */ Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
9342
+ *
9343
+ * Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
9344
+ * so this module-level constant does not reference the Node `Buffer` global at
9345
+ * import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
9346
+ * bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
9347
+ * that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
9348
+ */ Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
8899
9349
 
8900
9350
  /**
8901
9351
  * The minimum finality threshold for CCTPv2 transfers.
@@ -8929,7 +9379,7 @@ var TransferSpeed;
8929
9379
  registerKit(`${pkg$3.name}/${pkg$3.version}`);
8930
9380
 
8931
9381
  var name$2 = "@circle-fin/swap-kit";
8932
- var version$2 = "1.4.0";
9382
+ var version$2 = "1.5.0";
8933
9383
  var pkg$2 = {
8934
9384
  name: name$2,
8935
9385
  version: version$2};
@@ -9670,6 +10120,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
9670
10120
  required_error: 'estimatedAmount is required',
9671
10121
  invalid_type_error: 'estimatedAmount must be a string'
9672
10122
  }).min(1, 'estimatedAmount must be a non-empty string'),
10123
+ // Per-swap join key echoed back verbatim on success telemetry. Optional so a
10124
+ // not-yet-upgraded service (no field) still validates during rollout. A
10125
+ // malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
10126
+ // than throwing: this is a telemetry-only field (stripped from the developer
10127
+ // result, never used for control flow), so it must not be able to abort the
10128
+ // swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
10129
+ // never-throw contract of the rest of the telemetry stack. Implemented with
10130
+ // `preprocess` rather than Zod's `.catch()` because static analysis misreads
10131
+ // `.catch` on the schema chain as an unhandled Promise (S7785).
10132
+ correlationId: z.preprocess((value)=>z.string().uuid().safeParse(value).success ? value : undefined, z.string().optional()),
9673
10133
  config: createSwapRequestBaseSchema.shape.config.optional(),
9674
10134
  fees: createSwapFeesSchema.optional(),
9675
10135
  transaction: createSwapTransactionSchema
@@ -12228,7 +12688,7 @@ new Set(Object.values(Blockchain));
12228
12688
  registerKit(`${pkg$2.name}/${pkg$2.version}`);
12229
12689
 
12230
12690
  var name$1 = "@circle-fin/earn-kit";
12231
- var version$1 = "1.3.0";
12691
+ var version$1 = "1.4.0";
12232
12692
  var pkg$1 = {
12233
12693
  name: name$1,
12234
12694
  version: version$1};
@@ -14227,7 +14687,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
14227
14687
  }
14228
14688
 
14229
14689
  var name = "@circle-fin/provider-earn-service";
14230
- var version = "1.3.0";
14690
+ var version = "1.3.1";
14231
14691
  var pkg = {
14232
14692
  name: name,
14233
14693
  version: version};
@@ -14289,15 +14749,25 @@ var pkg = {
14289
14749
  *
14290
14750
  * @internal
14291
14751
  */ function buildConfig(serviceConfig) {
14752
+ // The kit key is a server-only secret. Reject it in the browser so it cannot
14753
+ // leak into a client bundle (no-op in Node.js). Keyless usage stays allowed.
14754
+ if (serviceConfig?.kitKey !== undefined && isBrowserEnvironment()) {
14755
+ throw createValidationFailedError('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');
14756
+ }
14292
14757
  const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
14293
- const sdkVersion = resolveSdkVersionHeader();
14758
+ // The API CORS policy does not allow this custom header. Keep the existing
14759
+ // per-request version attribution for Node callers, but omit it in browsers
14760
+ // so public EarnKit endpoints do not fail at CORS preflight.
14761
+ const sdkVersionHeader = isNodeEnvironment() ? {
14762
+ [SDK_VERSION_HEADER]: resolveSdkVersionHeader()
14763
+ } : {};
14294
14764
  if (serviceConfig?.kitKey === undefined) {
14295
14765
  return {
14296
14766
  pollingConfig: {
14297
14767
  ...DEFAULT_CONFIG,
14298
14768
  headers: {
14299
14769
  ...DEFAULT_CONFIG.headers,
14300
- [SDK_VERSION_HEADER]: sdkVersion
14770
+ ...sdkVersionHeader
14301
14771
  }
14302
14772
  },
14303
14773
  baseUrl
@@ -14315,7 +14785,7 @@ var pkg = {
14315
14785
  ...DEFAULT_CONFIG,
14316
14786
  headers: {
14317
14787
  ...DEFAULT_CONFIG.headers,
14318
- [SDK_VERSION_HEADER]: sdkVersion,
14788
+ ...sdkVersionHeader,
14319
14789
  Authorization: `Bearer ${serviceConfig.kitKey}`
14320
14790
  }
14321
14791
  },
@@ -16419,6 +16889,12 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
16419
16889
  if (config.providers !== undefined && !Array.isArray(config.providers)) {
16420
16890
  throw createValidationFailedError('config.providers', config.providers, 'providers must be an array of earn providers when provided');
16421
16891
  }
16892
+ if (config.disableAnalytics !== undefined && typeof config.disableAnalytics !== 'boolean') {
16893
+ throw createValidationFailedError('config.disableAnalytics', config.disableAnalytics, 'disableAnalytics must be a boolean when provided');
16894
+ }
16895
+ if (config.disableErrorReporting !== undefined && typeof config.disableErrorReporting !== 'boolean') {
16896
+ throw createValidationFailedError('config.disableErrorReporting', config.disableErrorReporting, 'disableErrorReporting must be a boolean when provided');
16897
+ }
16422
16898
  const defaultProviders = getDefaultProviders();
16423
16899
  const providers = [
16424
16900
  ...config.providers ?? [],
@@ -16430,6 +16906,31 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
16430
16906
  return context;
16431
16907
  }
16432
16908
 
16909
+ /**
16910
+ * Register Earn Kit telemetry event type strings with the shared registry so
16911
+ * error telemetry helpers remain compile-time checked.
16912
+ *
16913
+ * @internal
16914
+ */ /**
16915
+ * Telemetry event type identifiers for Earn Kit operations.
16916
+ *
16917
+ * @internal
16918
+ */ const EARN_EVENT_TYPES = {
16919
+ GET_VAULTS: 'earn_get_vaults',
16920
+ EXPLORE_VAULTS: 'earn_explore_vaults',
16921
+ GET_POSITION: 'earn_get_position',
16922
+ GET_CROSS_CHAIN_DEPOSIT_STATUS: 'earn_get_cross_chain_deposit_status',
16923
+ WAIT_FOR_CROSS_CHAIN_DEPOSIT: 'earn_wait_for_cross_chain_deposit',
16924
+ DEPOSIT: 'earn_deposit',
16925
+ CROSS_CHAIN_DEPOSIT: 'earn_cross_chain_deposit',
16926
+ WITHDRAW: 'earn_withdraw',
16927
+ CLAIM_REWARDS: 'earn_claim_rewards',
16928
+ GET_DEPOSIT_QUOTE: 'earn_get_deposit_quote',
16929
+ GET_WITHDRAWAL_QUOTE: 'earn_get_withdrawal_quote',
16930
+ GET_CLAIM_REWARDS_QUOTE: 'earn_get_claim_rewards_quote',
16931
+ RETRY: 'earn_retry'
16932
+ };
16933
+
16433
16934
  /**
16434
16935
  * Format a provider amount object as a human-readable decimal string.
16435
16936
  *
@@ -17945,6 +18446,14 @@ function hasCrossChainDestination(params) {
17945
18446
  return formatClaimRewardsQuoteInfo(result);
17946
18447
  }
17947
18448
 
18449
+ /** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg$1.name);
18450
+ /**
18451
+ * Determine whether deposit parameters target a destination chain.
18452
+ *
18453
+ * @internal
18454
+ */ function isCrossChainDeposit(params) {
18455
+ return 'to' in params && params.to !== undefined;
18456
+ }
17948
18457
  function formatRetryResult(operation, result) {
17949
18458
  switch(operation){
17950
18459
  case 'deposit':
@@ -17959,6 +18468,70 @@ function formatRetryResult(operation, result) {
17959
18468
  }
17960
18469
  }
17961
18470
  }
18471
+ /**
18472
+ * Emit the success event corresponding to a completed retry.
18473
+ *
18474
+ * @internal
18475
+ */ function emitRetrySuccessTelemetry(trace, result, config) {
18476
+ switch(trace.operation){
18477
+ case 'deposit':
18478
+ {
18479
+ const sourceChain = resolveChainName(trace.params.from.chain);
18480
+ if ('to' in trace.params && trace.params.to !== undefined) {
18481
+ const destinationChain = resolveChainName(trace.params.to.chain);
18482
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, config, {
18483
+ ...sourceChain != null && {
18484
+ sourceChain
18485
+ },
18486
+ ...destinationChain != null && {
18487
+ destinationChain
18488
+ }
18489
+ });
18490
+ return;
18491
+ }
18492
+ emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, config, {
18493
+ ...sourceChain != null && {
18494
+ sourceChain
18495
+ },
18496
+ ...'txHash' in result && {
18497
+ txHash: result.txHash
18498
+ }
18499
+ });
18500
+ return;
18501
+ }
18502
+ case 'withdraw':
18503
+ {
18504
+ const sourceChain = resolveChainName(trace.params.from.chain);
18505
+ emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, config, {
18506
+ ...sourceChain != null && {
18507
+ sourceChain
18508
+ },
18509
+ ...'txHash' in result && {
18510
+ txHash: result.txHash
18511
+ }
18512
+ });
18513
+ return;
18514
+ }
18515
+ case 'claimRewards':
18516
+ {
18517
+ if ('rewards' in result && result.status === 'claimed') {
18518
+ const sourceChain = resolveChainName(trace.params.from.chain);
18519
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, config, {
18520
+ ...sourceChain != null && {
18521
+ sourceChain
18522
+ },
18523
+ txHash: result.txHash
18524
+ });
18525
+ }
18526
+ return;
18527
+ }
18528
+ default:
18529
+ {
18530
+ const exhaustive = trace;
18531
+ throw createValidationFailedError('error.cause.trace', exhaustive, 'EarnKit.retry() does not support this earn operation');
18532
+ }
18533
+ }
18534
+ }
17962
18535
  /**
17963
18536
  * A high-level class-based interface for DeFi lending vault operations.
17964
18537
  *
@@ -18017,6 +18590,8 @@ function formatRetryResult(operation, result) {
18017
18590
  * ```
18018
18591
  */ class EarnKit {
18019
18592
  context;
18593
+ /** Per-kit identity and opt-out state for error telemetry. */ telemetryConfig;
18594
+ /** Per-kit identity and opt-out state for success telemetry. */ analyticsTelemetryConfig;
18020
18595
  /**
18021
18596
  * Event dispatcher for step-level events emitted during multi-phase earn
18022
18597
  * operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
@@ -18041,6 +18616,16 @@ function formatRetryResult(operation, result) {
18041
18616
  */ constructor(config = {}){
18042
18617
  this.context = createEarnKitContext(config);
18043
18618
  this.actionDispatcher = new Actionable();
18619
+ this.telemetryConfig = {
18620
+ sdkName: SDK_NAME,
18621
+ sdkVersion: pkg$1.version,
18622
+ disabled: config.disableErrorReporting === true
18623
+ };
18624
+ this.analyticsTelemetryConfig = {
18625
+ sdkName: SDK_NAME,
18626
+ sdkVersion: pkg$1.version,
18627
+ disabled: config.disableAnalytics === true
18628
+ };
18044
18629
  for (const provider of this.context.providers){
18045
18630
  provider.registerDispatcher(this.actionDispatcher);
18046
18631
  }
@@ -18101,29 +18686,36 @@ function formatRetryResult(operation, result) {
18101
18686
  * }
18102
18687
  * ```
18103
18688
  */ async retry(error) {
18104
- if (!isKitError(error)) {
18105
- throw createValidationFailedError('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
18106
- }
18107
- if (!isRetryableError$1(error)) {
18108
- throw createValidationFailedError('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
18109
- }
18110
- const trace = error.cause?.trace;
18111
- if (!isEarnErrorTrace(trace)) {
18112
- throw createValidationFailedError('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
18113
- }
18114
- const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
18115
- if (provider === undefined) {
18116
- throw createValidationFailedError('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
18117
- }
18118
- const result = await provider.retry(error);
18119
- // `provider.retry` returns a flat result union with no compile-time link to
18120
- // `trace.operation`, so narrow the operation here to select the matching
18121
- // overload. The result cast in each branch is sound: the provider always
18122
- // returns the result type corresponding to the resumed operation.
18123
- if (trace.operation === 'claimRewards') {
18689
+ const result = await withErrorTelemetry(async ()=>{
18690
+ if (!isKitError(error)) {
18691
+ throw createValidationFailedError('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
18692
+ }
18693
+ if (!isRetryableError$1(error)) {
18694
+ throw createValidationFailedError('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
18695
+ }
18696
+ const trace = error.cause?.trace;
18697
+ if (!isEarnErrorTrace(trace)) {
18698
+ throw createValidationFailedError('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
18699
+ }
18700
+ const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
18701
+ if (provider === undefined) {
18702
+ throw createValidationFailedError('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
18703
+ }
18704
+ const result = await provider.retry(error);
18705
+ // `provider.retry` returns a flat result union with no compile-time link to
18706
+ // `trace.operation`, so narrow the operation here to select the matching
18707
+ // overload. The result cast in each branch is sound: the provider always
18708
+ // returns the result type corresponding to the resumed operation.
18709
+ if (trace.operation === 'claimRewards') {
18710
+ return formatRetryResult(trace.operation, result);
18711
+ }
18124
18712
  return formatRetryResult(trace.operation, result);
18713
+ }, EARN_EVENT_TYPES.RETRY, this.telemetryConfig);
18714
+ const trace = isKitError(error) ? error.cause?.trace : undefined;
18715
+ if (isEarnErrorTrace(trace)) {
18716
+ emitRetrySuccessTelemetry(trace, result, this.analyticsTelemetryConfig);
18125
18717
  }
18126
- return formatRetryResult(trace.operation, result);
18718
+ return result;
18127
18719
  }
18128
18720
  /**
18129
18721
  * Return the chains supported by configured earn providers.
@@ -18157,7 +18749,9 @@ function formatRetryResult(operation, result) {
18157
18749
  * result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
18158
18750
  * ```
18159
18751
  */ async getVaults(params) {
18160
- return getVaults$1(this.context, params);
18752
+ const result = await withErrorTelemetry(async ()=>getVaults$1(this.context, params), EARN_EVENT_TYPES.GET_VAULTS, this.telemetryConfig);
18753
+ emitSuccessTelemetry(EARN_EVENT_TYPES.GET_VAULTS, this.analyticsTelemetryConfig, {});
18754
+ return result;
18161
18755
  }
18162
18756
  /**
18163
18757
  * Discover vaults available on a chain.
@@ -18182,7 +18776,12 @@ function formatRetryResult(operation, result) {
18182
18776
  * const guarded = result.vaults.filter(v => v.circleGuarded) // Circle-guarded vaults only
18183
18777
  * ```
18184
18778
  */ async exploreVaults(params) {
18185
- return exploreVaults$1(this.context, params);
18779
+ const context = {
18780
+ sourceChain: resolveChainName(params.chain)
18781
+ };
18782
+ const result = await withErrorTelemetry(async ()=>exploreVaults$1(this.context, params), EARN_EVENT_TYPES.EXPLORE_VAULTS, this.telemetryConfig, context);
18783
+ emitSuccessTelemetry(EARN_EVENT_TYPES.EXPLORE_VAULTS, this.analyticsTelemetryConfig, context);
18784
+ return result;
18186
18785
  }
18187
18786
  /**
18188
18787
  * Lazily iterate every vault available on a chain.
@@ -18229,7 +18828,9 @@ function formatRetryResult(operation, result) {
18229
18828
  * }
18230
18829
  * ```
18231
18830
  */ async getPosition(params) {
18232
- return getPosition$1(this.context, params);
18831
+ return withErrorTelemetry(async ()=>getPosition$1(this.context, params), EARN_EVENT_TYPES.GET_POSITION, this.telemetryConfig, {
18832
+ sourceChain: resolveChainName(params.from.chain)
18833
+ });
18233
18834
  }
18234
18835
  /**
18235
18836
  * Fetch the current status of a cross-chain deposit by execution ID.
@@ -18254,7 +18855,7 @@ function formatRetryResult(operation, result) {
18254
18855
  * console.log(`Bridge ${status.execId} is ${status.status}`)
18255
18856
  * ```
18256
18857
  */ async getCrossChainDepositStatus(params) {
18257
- return getCrossChainDepositStatus$1(this.context, params);
18858
+ return withErrorTelemetry(async ()=>getCrossChainDepositStatus$1(this.context, params), EARN_EVENT_TYPES.GET_CROSS_CHAIN_DEPOSIT_STATUS, this.telemetryConfig);
18258
18859
  }
18259
18860
  /**
18260
18861
  * Poll a cross-chain deposit until it reaches a terminal bridge state.
@@ -18281,10 +18882,30 @@ function formatRetryResult(operation, result) {
18281
18882
  * console.log(`Bridge ended as ${result.outcome}`)
18282
18883
  * ```
18283
18884
  */ async waitForCrossChainDeposit(params) {
18284
- return waitForCrossChainDeposit$1(this.context, params);
18885
+ return withErrorTelemetry(async ()=>waitForCrossChainDeposit$1(this.context, params), EARN_EVENT_TYPES.WAIT_FOR_CROSS_CHAIN_DEPOSIT, this.telemetryConfig);
18285
18886
  }
18286
18887
  async deposit(params) {
18287
- return deposit$1(this.context, params);
18888
+ const isCrossChain = isCrossChainDeposit(params);
18889
+ const context = {
18890
+ sourceChain: resolveChainName(params.from.chain),
18891
+ ...isCrossChain && {
18892
+ destinationChain: resolveChainName(params.to.chain)
18893
+ }
18894
+ };
18895
+ const eventType = isCrossChain ? EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT : EARN_EVENT_TYPES.DEPOSIT;
18896
+ const result = await withErrorTelemetry(async ()=>deposit$1(this.context, params), eventType, this.telemetryConfig, context);
18897
+ if (result.kind === 'cross-chain') {
18898
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, this.analyticsTelemetryConfig, {
18899
+ sourceChain: resolveChainName(result.sourceChain),
18900
+ destinationChain: resolveChainName(result.destinationChain)
18901
+ });
18902
+ } else {
18903
+ emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, this.analyticsTelemetryConfig, {
18904
+ ...context,
18905
+ txHash: result.txHash
18906
+ });
18907
+ }
18908
+ return result;
18288
18909
  }
18289
18910
  /**
18290
18911
  * Execute a withdrawal from a DeFi lending vault.
@@ -18310,7 +18931,15 @@ function formatRetryResult(operation, result) {
18310
18931
  * console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
18311
18932
  * ```
18312
18933
  */ async withdraw(params) {
18313
- return withdraw$1(this.context, params);
18934
+ const context = {
18935
+ sourceChain: resolveChainName(params.from.chain)
18936
+ };
18937
+ const result = await withErrorTelemetry(async ()=>withdraw$1(this.context, params), EARN_EVENT_TYPES.WITHDRAW, this.telemetryConfig, context);
18938
+ emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, this.analyticsTelemetryConfig, {
18939
+ ...context,
18940
+ txHash: result.txHash
18941
+ });
18942
+ return result;
18314
18943
  }
18315
18944
  /**
18316
18945
  * Claim rewards from earn vaults.
@@ -18337,7 +18966,17 @@ function formatRetryResult(operation, result) {
18337
18966
  *
18338
18967
  * @internal
18339
18968
  */ async claimRewards(params) {
18340
- return claimRewards$1(this.context, params);
18969
+ const context = {
18970
+ sourceChain: resolveChainName(params.from.chain)
18971
+ };
18972
+ const result = await withErrorTelemetry(async ()=>claimRewards$1(this.context, params), EARN_EVENT_TYPES.CLAIM_REWARDS, this.telemetryConfig, context);
18973
+ if (result.status === 'claimed') {
18974
+ emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, this.analyticsTelemetryConfig, {
18975
+ ...context,
18976
+ txHash: result.txHash
18977
+ });
18978
+ }
18979
+ return result;
18341
18980
  }
18342
18981
  /**
18343
18982
  * Get an informational quote for a deposit into a vault.
@@ -18359,7 +18998,9 @@ function formatRetryResult(operation, result) {
18359
18998
  * console.log(`Expected shares: ${quote.expectedShares.amount}`)
18360
18999
  * ```
18361
19000
  */ async getDepositQuote(params) {
18362
- return getDepositQuote$1(this.context, params);
19001
+ return withErrorTelemetry(async ()=>getDepositQuote$1(this.context, params), EARN_EVENT_TYPES.GET_DEPOSIT_QUOTE, this.telemetryConfig, {
19002
+ sourceChain: resolveChainName(params.from.chain)
19003
+ });
18363
19004
  }
18364
19005
  /**
18365
19006
  * Get an informational quote for a withdrawal from a vault.
@@ -18381,7 +19022,9 @@ function formatRetryResult(operation, result) {
18381
19022
  * console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
18382
19023
  * ```
18383
19024
  */ async getWithdrawalQuote(params) {
18384
- return getWithdrawalQuote$1(this.context, params);
19025
+ return withErrorTelemetry(async ()=>getWithdrawalQuote$1(this.context, params), EARN_EVENT_TYPES.GET_WITHDRAWAL_QUOTE, this.telemetryConfig, {
19026
+ sourceChain: resolveChainName(params.from.chain)
19027
+ });
18385
19028
  }
18386
19029
  /**
18387
19030
  * Get an informational quote for claiming rewards.
@@ -18403,7 +19046,9 @@ function formatRetryResult(operation, result) {
18403
19046
  *
18404
19047
  * @internal
18405
19048
  */ async getClaimRewardsQuote(params) {
18406
- return getClaimRewardsQuote$1(this.context, params);
19049
+ return withErrorTelemetry(async ()=>getClaimRewardsQuote$1(this.context, params), EARN_EVENT_TYPES.GET_CLAIM_REWARDS_QUOTE, this.telemetryConfig, {
19050
+ sourceChain: resolveChainName(params.from.chain)
19051
+ });
18407
19052
  }
18408
19053
  }
18409
19054
 
@@ -18492,7 +19137,14 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
18492
19137
  * const earnKit = createEarnKit(context)
18493
19138
  * ```
18494
19139
  */ const createEarnKit = (context)=>{
18495
- const kit = new EarnKit();
19140
+ const kit = new EarnKit({
19141
+ ...context.disableErrorReporting != null && {
19142
+ disableErrorReporting: context.disableErrorReporting
19143
+ },
19144
+ ...context.disableAnalytics != null && {
19145
+ disableAnalytics: context.disableAnalytics
19146
+ }
19147
+ });
18496
19148
  registerActionHandlers(kit, context.actions.earn, 'earn');
18497
19149
  return kit;
18498
19150
  };