@circle-fin/app-kit 1.5.1 → 1.6.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
@@ -1067,6 +1067,35 @@ function createUnsupportedSwapRouteError(tokenIn, tokenOut, chain) {
1067
1067
  };
1068
1068
  return new KitError(errorDetails);
1069
1069
  }
1070
+ /**
1071
+ * Creates error for unsupported earn route.
1072
+ *
1073
+ * This error is thrown when no available earn provider supports the requested
1074
+ * operation on the specified chain.
1075
+ *
1076
+ * @param operation - Earn operation name
1077
+ * @param chain - Chain name where the earn operation was attempted
1078
+ * @returns KitError with specific earn route details
1079
+ *
1080
+ * @example
1081
+ * ```typescript
1082
+ * import { createUnsupportedEarnRouteError } from '@core/errors'
1083
+ *
1084
+ * throw createUnsupportedEarnRouteError('deposit', 'Ethereum')
1085
+ * // Message: "Earn deposit on Ethereum is not supported by any available provider."
1086
+ * ```
1087
+ */
1088
+ function createUnsupportedEarnRouteError(operation, chain) {
1089
+ const errorDetails = {
1090
+ ...InputError.UNSUPPORTED_ROUTE,
1091
+ recoverability: 'FATAL',
1092
+ message: `Earn ${operation} on ${chain} is not supported by any available provider.`,
1093
+ cause: {
1094
+ trace: { operation, chain },
1095
+ },
1096
+ };
1097
+ return new KitError(errorDetails);
1098
+ }
1070
1099
  /**
1071
1100
  * Creates error for unsupported token on chain.
1072
1101
  *
@@ -3210,7 +3239,7 @@ function handleTimeoutError(serviceName, operation, error) {
3210
3239
  * @returns `true` when `error.responseBody` is a non-null object
3211
3240
  * @internal
3212
3241
  */
3213
- function hasResponseBody(error) {
3242
+ function hasResponseBody$1(error) {
3214
3243
  return (typeof error === 'object' &&
3215
3244
  error !== null &&
3216
3245
  'responseBody' in error &&
@@ -3223,10 +3252,12 @@ function hasResponseBody(error) {
3223
3252
  *
3224
3253
  * @param error - The raw error from the HTTP layer
3225
3254
  * @returns The parsed body cast to {@link ApiErrorResponseBody}, or undefined
3255
+ * @throws Never. Returns `undefined` when the error does not carry a valid
3256
+ * `responseBody`.
3226
3257
  * @internal
3227
3258
  */
3228
3259
  function extractResponseBody(error) {
3229
- if (!hasResponseBody(error)) {
3260
+ if (!hasResponseBody$1(error)) {
3230
3261
  return undefined;
3231
3262
  }
3232
3263
  return error.responseBody;
@@ -3358,6 +3389,224 @@ function extractHttpStatusCode(msg) {
3358
3389
  return null;
3359
3390
  }
3360
3391
 
3392
+ /**
3393
+ * Standardized error definitions for Earn/Zenith operations.
3394
+ *
3395
+ * These error codes provide fine-grained categorization of failures
3396
+ * from the Zenith earn service, enabling SDK consumers to distinguish
3397
+ * between input errors (fix your request) and service errors (retry later).
3398
+ *
3399
+ * Error code ranges:
3400
+ * - 1100-1104: INPUT errors — invalid inputs, unsupported configurations
3401
+ * - 8100-8103: SERVICE errors — retryable backend/provider failures
3402
+ *
3403
+ * @example
3404
+ * ```typescript
3405
+ * import { EarnError, isInputError, isRetryableError } from '@circle-fin/earn-kit'
3406
+ *
3407
+ * try {
3408
+ * await earnKit.deposit(params)
3409
+ * } catch (error) {
3410
+ * if (isInputError(error)) {
3411
+ * // Fix the request: vault doesn't exist, chain not supported, etc.
3412
+ * }
3413
+ * if (isRetryableError(error)) {
3414
+ * // Try again: signing timed out, provider temporarily unavailable, etc.
3415
+ * }
3416
+ * }
3417
+ * ```
3418
+ */
3419
+ const EarnError = {
3420
+ /** The requested vault does not exist for the provided chain. */
3421
+ VAULT_NOT_FOUND: {
3422
+ code: 1100,
3423
+ name: 'EARN_VAULT_NOT_FOUND',
3424
+ type: 'INPUT',
3425
+ },
3426
+ /** The specified blockchain is not supported for earn operations. */
3427
+ UNSUPPORTED_CHAIN: {
3428
+ code: 1101,
3429
+ name: 'EARN_UNSUPPORTED_CHAIN',
3430
+ type: 'INPUT',
3431
+ },
3432
+ /** The vault's underlying asset is not supported. */
3433
+ UNSUPPORTED_VAULT: {
3434
+ code: 1102,
3435
+ name: 'EARN_UNSUPPORTED_VAULT',
3436
+ type: 'INPUT',
3437
+ },
3438
+ /** The submitted signature could not be verified. */
3439
+ SIGNATURE_REJECTED: {
3440
+ code: 1103,
3441
+ name: 'EARN_SIGNATURE_REJECTED',
3442
+ type: 'INPUT',
3443
+ },
3444
+ /** General input validation failure (invalid ID, batch size, etc.). */
3445
+ INVALID_INPUT: {
3446
+ code: 1104,
3447
+ name: 'EARN_INVALID_INPUT',
3448
+ type: 'INPUT',
3449
+ },
3450
+ /** The proxy signing call failed — retryable. */
3451
+ SIGNING_FAILED: {
3452
+ code: 8100,
3453
+ name: 'EARN_SIGNING_FAILED',
3454
+ type: 'SERVICE',
3455
+ },
3456
+ /** An external provider API request failed — retryable. */
3457
+ PROVIDER_ERROR: {
3458
+ code: 8101,
3459
+ name: 'EARN_PROVIDER_ERROR',
3460
+ type: 'SERVICE',
3461
+ },
3462
+ /** Failed to fetch reward data — retryable. */
3463
+ REWARDS_FETCH_FAILED: {
3464
+ code: 8102,
3465
+ name: 'EARN_REWARDS_FETCH_FAILED',
3466
+ type: 'SERVICE',
3467
+ },
3468
+ /** An internal earn service error occurred — retryable. */
3469
+ INTERNAL_ERROR: {
3470
+ code: 8103,
3471
+ name: 'EARN_INTERNAL_ERROR',
3472
+ type: 'SERVICE',
3473
+ },
3474
+ };
3475
+
3476
+ function hasResponseBody(error) {
3477
+ return (typeof error === 'object' &&
3478
+ error !== null &&
3479
+ 'responseBody' in error &&
3480
+ typeof error['responseBody'] === 'object' &&
3481
+ error['responseBody'] !== null);
3482
+ }
3483
+ function extractEarnResponseBody(error) {
3484
+ if (!hasResponseBody(error)) {
3485
+ return undefined;
3486
+ }
3487
+ return error.responseBody;
3488
+ }
3489
+ function getOptionalString(value) {
3490
+ return typeof value === 'string' ? value : undefined;
3491
+ }
3492
+ /**
3493
+ * Maps earn service backend error codes (380xxx) to typed EarnError constants
3494
+ * with correct recoverability.
3495
+ *
3496
+ * INPUT errors (FATAL) — fix your request:
3497
+ * - vault-not-found, unsupported-chain, unsupported-vault,
3498
+ * signature-rejected, invalid-id, invalid-batch-size, position-not-registered,
3499
+ * withdrawal-max-exceeded, insufficient-balance
3500
+ *
3501
+ * SERVICE errors (RETRYABLE) — try again later:
3502
+ * - signing-failed, provider-error, rewards-fetch-failed,
3503
+ * internal-error, vault-refresh-busy, bridge failures
3504
+ *
3505
+ * Unrecognized codes fall through to `parseApiError` for HTTP-status-based
3506
+ * handling.
3507
+ *
3508
+ * @internal
3509
+ */
3510
+ const EARN_CODE_MAP = new Map([
3511
+ // Common (380_0XX)
3512
+ [380001, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3513
+ [
3514
+ 380002,
3515
+ {
3516
+ errorDef: RateLimitError.RATE_LIMIT_EXCEEDED,
3517
+ recoverability: 'RETRYABLE',
3518
+ },
3519
+ ],
3520
+ // Shared (380_1XX)
3521
+ [380100, { errorDef: EarnError.VAULT_NOT_FOUND, recoverability: 'FATAL' }],
3522
+ [380101, { errorDef: EarnError.SIGNATURE_REJECTED, recoverability: 'FATAL' }],
3523
+ [380102, { errorDef: EarnError.UNSUPPORTED_CHAIN, recoverability: 'FATAL' }],
3524
+ // Vault Discovery (380_3XX)
3525
+ [380300, { errorDef: EarnError.VAULT_NOT_FOUND, recoverability: 'FATAL' }],
3526
+ [380301, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3527
+ [380302, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3528
+ [380303, { errorDef: EarnError.UNSUPPORTED_VAULT, recoverability: 'FATAL' }],
3529
+ // EarnKit (380_4XX)
3530
+ [380400, { errorDef: EarnError.UNSUPPORTED_CHAIN, recoverability: 'FATAL' }],
3531
+ [380401, { errorDef: EarnError.SIGNING_FAILED, recoverability: 'RETRYABLE' }],
3532
+ [
3533
+ 380402,
3534
+ { errorDef: EarnError.REWARDS_FETCH_FAILED, recoverability: 'RETRYABLE' },
3535
+ ],
3536
+ [380403, { errorDef: EarnError.INTERNAL_ERROR, recoverability: 'RETRYABLE' }],
3537
+ [380404, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3538
+ [380405, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3539
+ [380406, { errorDef: EarnError.INTERNAL_ERROR, recoverability: 'RETRYABLE' }],
3540
+ [380407, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3541
+ [380408, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3542
+ [380409, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3543
+ // Bridge (380_5XX)
3544
+ [380500, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3545
+ [380501, { errorDef: EarnError.SIGNING_FAILED, recoverability: 'RETRYABLE' }],
3546
+ [380502, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3547
+ ]);
3548
+ /**
3549
+ * Parses Earn Service API errors into typed KitError instances using
3550
+ * earn service backend error codes for fine-grained categorization.
3551
+ *
3552
+ * When the response body contains a recognized earn service backend code
3553
+ * (380xxx), the error is mapped to a specific EarnError constant with the
3554
+ * correct recoverability. The original backend error code is preserved in
3555
+ * `error.cause.trace.earnServiceCode`.
3556
+ *
3557
+ * When the code is unrecognized or the response body is unavailable,
3558
+ * falls through to the generic `parseApiError` for HTTP-status-based handling.
3559
+ *
3560
+ * @param error - The raw error from the API call
3561
+ * @param context - Context information (operation name)
3562
+ * @returns A structured KitError instance
3563
+ * @throws Never — all error paths are caught and returned as a `KitError`
3564
+ * instance
3565
+ *
3566
+ * @example
3567
+ * ```typescript
3568
+ * try {
3569
+ * await earnServiceApi.deposit(params)
3570
+ * } catch (error) {
3571
+ * throw parseEarnApiError(error, { operation: 'deposit' })
3572
+ * }
3573
+ * ```
3574
+ */
3575
+ function parseEarnApiError(error, context) {
3576
+ // If it's already a KitError, return it as-is
3577
+ if (error instanceof KitError) {
3578
+ return error;
3579
+ }
3580
+ const responseBody = extractEarnResponseBody(error);
3581
+ const earnServiceCode = typeof responseBody?.['code'] === 'number'
3582
+ ? responseBody['code']
3583
+ : undefined;
3584
+ if (earnServiceCode !== undefined) {
3585
+ const mapping = EARN_CODE_MAP.get(earnServiceCode);
3586
+ if (mapping !== undefined) {
3587
+ const message = getOptionalString(responseBody?.['externalMessage']) ??
3588
+ getOptionalString(responseBody?.['message']) ??
3589
+ `Earn Service ${context.operation ?? 'API'} failed`;
3590
+ return new KitError({
3591
+ ...mapping.errorDef,
3592
+ recoverability: mapping.recoverability,
3593
+ message,
3594
+ cause: {
3595
+ trace: {
3596
+ earnServiceCode,
3597
+ originalError: error,
3598
+ },
3599
+ },
3600
+ });
3601
+ }
3602
+ }
3603
+ // Fallthrough: unrecognized code or no response body — use HTTP-status-based parsing
3604
+ return parseApiError(error, {
3605
+ ...context,
3606
+ service: context.service ?? 'Earn Service',
3607
+ });
3608
+ }
3609
+
3361
3610
  // -----------------------------------------------------------------------------
3362
3611
  // Blockchain Enum
3363
3612
  // -----------------------------------------------------------------------------
@@ -3717,23 +3966,21 @@ var UnifiedBalanceChain;
3717
3966
  * Enumeration of blockchains that support earn (vault deposit/withdraw)
3718
3967
  * operations through the Earn Kit.
3719
3968
  *
3720
- * Currently only Ethereum mainnet is supported. Additional chains
3721
- * will be added as vault protocol support expands.
3722
- *
3723
3969
  * @example
3724
3970
  * ```typescript
3725
3971
  * import { EarnChain } from '@core/chains'
3726
3972
  *
3727
3973
  * const result = await earnKit.deposit({
3728
- * from: { adapter, chain: EarnChain.Ethereum },
3974
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
3729
3975
  * vaultAddress: '0x...',
3730
3976
  * amount: '100',
3731
3977
  * })
3732
3978
  * ```
3733
3979
  */
3980
+ // Values must match Blockchain enum members exactly.
3734
3981
  var EarnChain;
3735
3982
  (function (EarnChain) {
3736
- EarnChain["Ethereum"] = "Ethereum";
3983
+ EarnChain["Arc_Testnet"] = "Arc_Testnet";
3737
3984
  })(EarnChain || (EarnChain = {}));
3738
3985
 
3739
3986
  /**
@@ -7474,7 +7721,7 @@ const bridgeChainIdentifierSchema = z.union([
7474
7721
  * Zod schema for validating earn-specific chain identifiers.
7475
7722
  *
7476
7723
  * Validate that the provided chain is supported for earn (vault
7477
- * deposit/withdraw) operations. Currently only Ethereum is
7724
+ * deposit/withdraw) operations. Currently only Arc Testnet is
7478
7725
  * supported.
7479
7726
  *
7480
7727
  * Accept an EarnChain enum value, a matching string literal, or
@@ -7483,18 +7730,18 @@ const bridgeChainIdentifierSchema = z.union([
7483
7730
  * @example
7484
7731
  * ```typescript
7485
7732
  * import { earnChainIdentifierSchema } from '@core/chains'
7486
- * import { EarnChain, Ethereum } from '@core/chains'
7733
+ * import { ArcTestnet, EarnChain } from '@core/chains'
7487
7734
  *
7488
7735
  * // Valid
7489
- * earnChainIdentifierSchema.parse(EarnChain.Ethereum)
7490
- * earnChainIdentifierSchema.parse('Ethereum')
7491
- * earnChainIdentifierSchema.parse(Ethereum)
7736
+ * earnChainIdentifierSchema.parse(EarnChain.Arc_Testnet)
7737
+ * earnChainIdentifierSchema.parse('Arc_Testnet')
7738
+ * earnChainIdentifierSchema.parse(ArcTestnet)
7492
7739
  *
7493
7740
  * // Invalid (throws ZodError)
7494
7741
  * earnChainIdentifierSchema.parse('Solana')
7495
7742
  * ```
7496
7743
  */
7497
- z.union([
7744
+ const earnChainIdentifierSchema = z.union([
7498
7745
  z.string().refine((val) => val in EarnChain, (val) => ({
7499
7746
  message: `"${val}" is not a supported earn chain. ` +
7500
7747
  `Supported chains: ${Object.values(EarnChain).join(', ')}`,
@@ -8352,7 +8599,7 @@ function handleAddressError(path, _code, _message, paramsObj) {
8352
8599
  * Default configuration values for the API polling utility.
8353
8600
  * @internal
8354
8601
  */
8355
- const DEFAULT_CONFIG$2 = {
8602
+ const DEFAULT_CONFIG$3 = {
8356
8603
  timeout: 2_000,
8357
8604
  maxRetries: 10,
8358
8605
  retryDelay: 200,
@@ -8561,10 +8808,10 @@ const isRetryableError = (error) => {
8561
8808
  const pollApiWithValidation = async (url, method, isValidType, config = {}, body) => {
8562
8809
  // Merge headers so that 'User-Agent' is always present and not overwritten
8563
8810
  const effectiveConfig = {
8564
- ...DEFAULT_CONFIG$2,
8811
+ ...DEFAULT_CONFIG$3,
8565
8812
  ...config,
8566
8813
  headers: {
8567
- ...DEFAULT_CONFIG$2.headers,
8814
+ ...DEFAULT_CONFIG$3.headers,
8568
8815
  ...(config.headers ?? {}),
8569
8816
  // In browser environments, directly setting the 'User-Agent' or similar headers is restricted and may be ignored or cause errors.
8570
8817
  // This is why we use the 'X-User-Agent' header instead.
@@ -10071,7 +10318,7 @@ z.custom((value) => {
10071
10318
  * formatAmount({ value: '3141592000000000000', token: 'NATIVE', chain: Ethereum }) // "3.141592"
10072
10319
  * ```
10073
10320
  */
10074
- const formatAmount$1 = (params) => {
10321
+ const formatAmount$2 = (params) => {
10075
10322
  const { value, token, tokens } = params;
10076
10323
  // Handle NATIVE token first (chain-specific decimals)
10077
10324
  if (token === 'NATIVE') {
@@ -10791,11 +11038,11 @@ function emitResultStepErrorTelemetry(failedStep, stepEventMap, fallbackEventTyp
10791
11038
  void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
10792
11039
  }
10793
11040
 
10794
- var name$2 = "@circle-fin/bridge-kit";
10795
- var version$3 = "1.10.0";
10796
- var pkg$3 = {
10797
- name: name$2,
10798
- version: version$3};
11041
+ var name$3 = "@circle-fin/bridge-kit";
11042
+ var version$4 = "1.10.1";
11043
+ var pkg$4 = {
11044
+ name: name$3,
11045
+ version: version$4};
10799
11046
 
10800
11047
  const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
10801
11048
  /**
@@ -11091,7 +11338,9 @@ const hexStringSchema = z
11091
11338
  * console.log(result.success) // true
11092
11339
  * ```
11093
11340
  */
11094
- const evmAddressSchema = hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
11341
+ const evmAddressSchema = hexStringSchema
11342
+ .refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)')
11343
+ .transform((value) => value);
11095
11344
  /**
11096
11345
  * Schema for validating transaction hashes.
11097
11346
  *
@@ -11112,6 +11361,29 @@ const evmAddressSchema = hexStringSchema.refine((value) => value.length === 42,
11112
11361
  * ```
11113
11362
  */
11114
11363
  hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
11364
+ /**
11365
+ * Schema for validating EVM signatures.
11366
+ *
11367
+ * This schema validates that a string is a properly formatted 65-byte EVM
11368
+ * signature:
11369
+ * - Must be a valid hex string with '0x' prefix
11370
+ * - Must be exactly 132 characters long (0x + 130 hex characters)
11371
+ *
11372
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11373
+ *
11374
+ * @example
11375
+ * ```typescript
11376
+ * import { evmSignatureSchema } from '@core/adapter'
11377
+ *
11378
+ * const validSignature = `0x${'ab'.repeat(65)}`
11379
+ *
11380
+ * const result = evmSignatureSchema.safeParse(validSignature)
11381
+ * console.log(result.success) // true
11382
+ * ```
11383
+ */
11384
+ const evmSignatureSchema = hexStringSchema
11385
+ .refine((value) => value.length === 132, 'EVM signature must be exactly 132 characters long (0x + 130 hex characters)')
11386
+ .transform((value) => value);
11115
11387
  /**
11116
11388
  * Schema for validating base58-encoded strings.
11117
11389
  *
@@ -11766,7 +12038,7 @@ function createRecipientAddressValidator() {
11766
12038
  *
11767
12039
  * Optionally includes address for developer-controlled adapters.
11768
12040
  */
11769
- const adapterContextSchema$6 = z.object({
12041
+ const adapterContextSchema$7 = z.object({
11770
12042
  adapter: adapterSchema$1,
11771
12043
  chain: bridgeChainIdentifierSchema,
11772
12044
  address: z.string().optional(),
@@ -11776,7 +12048,7 @@ const adapterContextSchema$6 = z.object({
11776
12048
  * Contains an explicit recipientAddress along with adapter and chain.
11777
12049
  * The address format is validated based on the chain type (EVM or Solana).
11778
12050
  */
11779
- const bridgeDestinationWithAddressSchema = adapterContextSchema$6
12051
+ const bridgeDestinationWithAddressSchema = adapterContextSchema$7
11780
12052
  .extend({
11781
12053
  recipientAddress: z.string().min(1, 'Recipient address is required'),
11782
12054
  useForwarder: z.boolean().optional(),
@@ -11786,7 +12058,7 @@ const bridgeDestinationWithAddressSchema = adapterContextSchema$6
11786
12058
  * Schema for validating AdapterContext with optional useForwarder.
11787
12059
  * Extends adapterContextSchema with the useForwarder flag.
11788
12060
  */
11789
- const adapterContextWithForwarderSchema = adapterContextSchema$6.extend({
12061
+ const adapterContextWithForwarderSchema = adapterContextSchema$7.extend({
11790
12062
  useForwarder: z.boolean().optional(),
11791
12063
  });
11792
12064
  /**
@@ -11867,7 +12139,7 @@ const bridgeDestinationSchema = z.union([
11867
12139
  * ```
11868
12140
  */
11869
12141
  const bridgeParamsWithChainIdentifierSchema = z.object({
11870
- from: adapterContextSchema$6.strict(),
12142
+ from: adapterContextSchema$7.strict(),
11871
12143
  to: bridgeDestinationSchema,
11872
12144
  amount: z
11873
12145
  .string()
@@ -12933,7 +13205,7 @@ function resolveBridgeInvocation$1(invocationMeta) {
12933
13205
  const bridgeKitCaller = {
12934
13206
  type: 'kit',
12935
13207
  name: 'BridgeKit',
12936
- version: pkg$3.version,
13208
+ version: pkg$4.version,
12937
13209
  };
12938
13210
  // Create default runtime and tokens for invocation context resolution
12939
13211
  const defaults = {
@@ -13203,7 +13475,7 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
13203
13475
  }
13204
13476
  // Fetch the fee tiers from the API
13205
13477
  const url = buildFastBurnFeeUrl(sourceDomain, destinationDomain, isTestnet);
13206
- const response = await pollApiGet(url, isFastBurnFeeResponse, DEFAULT_CONFIG$2);
13478
+ const response = await pollApiGet(url, isFastBurnFeeResponse, DEFAULT_CONFIG$3);
13207
13479
  // Validate that we have at least one fee tier
13208
13480
  if (response.length === 0) {
13209
13481
  throw new KitError({
@@ -13552,7 +13824,7 @@ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, fi
13552
13824
  }
13553
13825
  // Fetch the fee tiers from the API with forward=true
13554
13826
  const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet);
13555
- const response = await pollApiGet(url, isForwardingFeeResponse, DEFAULT_CONFIG$2);
13827
+ const response = await pollApiGet(url, isForwardingFeeResponse, DEFAULT_CONFIG$3);
13556
13828
  // Validate that we have at least one fee tier
13557
13829
  if (response.length === 0) {
13558
13830
  throw new KitError({
@@ -13619,7 +13891,7 @@ const CCTPv2MinFinalityThreshold = {
13619
13891
  * Default configuration values for the attestation fetcher.
13620
13892
  * @internal
13621
13893
  */
13622
- const DEFAULT_CONFIG$1 = {
13894
+ const DEFAULT_CONFIG$2 = {
13623
13895
  timeout: 2_000, // 2 seconds
13624
13896
  maxRetries: 30 * 20, // 30 * 20 * 2 seconds (from the retry delay) = 20 minutes (to account for slow transfers maximum time based on confirmations)
13625
13897
  retryDelay: 2_000, // 2 seconds
@@ -13790,7 +14062,7 @@ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet) => {
13790
14062
  */
13791
14063
  const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {}) => {
13792
14064
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13793
- const effectiveConfig = { ...DEFAULT_CONFIG$1, ...config };
14065
+ const effectiveConfig = { ...DEFAULT_CONFIG$2, ...config };
13794
14066
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
13795
14067
  };
13796
14068
  /**
@@ -13836,7 +14108,7 @@ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHas
13836
14108
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13837
14109
  // Use minimal retries since we're just fetching existing data
13838
14110
  const effectiveConfig = {
13839
- ...DEFAULT_CONFIG$1,
14111
+ ...DEFAULT_CONFIG$2,
13840
14112
  maxRetries: 3,
13841
14113
  ...config,
13842
14114
  };
@@ -13901,7 +14173,7 @@ const isReAttestedAttestationResponse = (obj) => {
13901
14173
  */
13902
14174
  const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {}) => {
13903
14175
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13904
- const effectiveConfig = { ...DEFAULT_CONFIG$1, ...config };
14176
+ const effectiveConfig = { ...DEFAULT_CONFIG$2, ...config };
13905
14177
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
13906
14178
  };
13907
14179
  /**
@@ -13976,7 +14248,7 @@ const requestReAttestation = async (nonce, isTestnet, config = {}) => {
13976
14248
  const url = buildReAttestUrl(nonce, isTestnet);
13977
14249
  // Use minimal retries since we're just submitting a request, not polling for state
13978
14250
  const effectiveConfig = {
13979
- ...DEFAULT_CONFIG$1,
14251
+ ...DEFAULT_CONFIG$2,
13980
14252
  maxRetries: 3,
13981
14253
  ...config,
13982
14254
  };
@@ -15231,7 +15503,7 @@ const isRelayerMintConfirmed = (obj) => {
15231
15503
  */
15232
15504
  const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {}) => {
15233
15505
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
15234
- const effectiveConfig = { ...DEFAULT_CONFIG$1, ...config };
15506
+ const effectiveConfig = { ...DEFAULT_CONFIG$2, ...config };
15235
15507
  let response;
15236
15508
  try {
15237
15509
  response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
@@ -15743,9 +16015,9 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCo
15743
16015
  return step;
15744
16016
  }
15745
16017
 
15746
- var version$2 = "1.8.1";
15747
- var pkg$2 = {
15748
- version: version$2};
16018
+ var version$3 = "1.8.2";
16019
+ var pkg$3 = {
16020
+ version: version$3};
15749
16021
 
15750
16022
  /**
15751
16023
  * Provider caller component for bridge operations.
@@ -15753,7 +16025,7 @@ var pkg$2 = {
15753
16025
  const BRIDGE_CALLER = {
15754
16026
  type: 'provider',
15755
16027
  name: 'CCTPV2BridgingProvider.bridge',
15756
- version: pkg$2.version,
16028
+ version: pkg$3.version,
15757
16029
  };
15758
16030
  /**
15759
16031
  * Resolve invocation context for bridge operations.
@@ -16133,7 +16405,7 @@ async function bridgeReAttest({ params, provider, }, burnTxHash) {
16133
16405
  const RETRY_CALLER = {
16134
16406
  type: 'provider',
16135
16407
  name: 'CCTPV2BridgingProvider.retry',
16136
- version: pkg$2.version,
16408
+ version: pkg$3.version,
16137
16409
  };
16138
16410
  /**
16139
16411
  * Resolve invocation context for retry operations.
@@ -17339,7 +17611,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
17339
17611
  * The default providers that will be used in addition to the providers provided
17340
17612
  * to the BridgeKit constructor.
17341
17613
  */
17342
- const getDefaultProviders$2 = () => [new CCTPV2BridgingProvider()];
17614
+ const getDefaultProviders$3 = () => [new CCTPV2BridgingProvider()];
17343
17615
 
17344
17616
  /**
17345
17617
  * A helper function to get a function that transforms an amount into a human-readable string or a bigint string.
@@ -17347,7 +17619,7 @@ const getDefaultProviders$2 = () => [new CCTPV2BridgingProvider()];
17347
17619
  * @returns A function that transforms an amount into a human-readable string or a bigint string.
17348
17620
  */
17349
17621
  const getAmountTransformer = (formatDirection) => formatDirection === 'to-human-readable'
17350
- ? (params) => formatAmount$1(params)
17622
+ ? (params) => formatAmount$2(params)
17351
17623
  : (params) => parseAmount(params).toString();
17352
17624
  /**
17353
17625
  * Format the bridge result into human-readable string values for the user or bigint string values for internal use.
@@ -17437,14 +17709,14 @@ const BRIDGE_STEP_EVENT_MAP = [
17437
17709
  ];
17438
17710
 
17439
17711
  /** SDK name used in telemetry payloads. */
17440
- const SDK_NAME$2 = resolveKitSdkName(pkg$3.name);
17712
+ const SDK_NAME$2 = resolveKitSdkName(pkg$4.name);
17441
17713
  /**
17442
17714
  * BridgeKit caller component for retry and estimate operations.
17443
17715
  */
17444
17716
  const BRIDGE_KIT_CALLER = {
17445
17717
  type: 'kit',
17446
17718
  name: 'BridgeKit',
17447
- version: pkg$3.version,
17719
+ version: pkg$4.version,
17448
17720
  };
17449
17721
  /**
17450
17722
  * Merge BridgeKit's caller into the invocation metadata for retry operations.
@@ -17539,13 +17811,13 @@ class BridgeKit {
17539
17811
  */
17540
17812
  constructor(config = {}) {
17541
17813
  // Handle provider configuration
17542
- const defaultProviders = getDefaultProviders$2();
17814
+ const defaultProviders = getDefaultProviders$3();
17543
17815
  this.providers = [...defaultProviders, ...(config.providers ?? [])];
17544
17816
  this.actionDispatcher = new Actionable();
17545
17817
  this.disableErrorReporting = config.disableErrorReporting === true;
17546
17818
  this.telemetryConfig = {
17547
17819
  sdkName: SDK_NAME$2,
17548
- sdkVersion: pkg$3.version,
17820
+ sdkVersion: pkg$4.version,
17549
17821
  disabled: this.disableErrorReporting,
17550
17822
  };
17551
17823
  for (const provider of this.providers) {
@@ -18074,7 +18346,7 @@ class BridgeKit {
18074
18346
  * @packageDocumentation
18075
18347
  */
18076
18348
  // Auto-register this kit for user agent tracking
18077
- registerKit(`${pkg$3.name}/${pkg$3.version}`);
18349
+ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18078
18350
 
18079
18351
  /**
18080
18352
  * Create a BridgeKit instance with optional developer fee configuration.
@@ -18141,11 +18413,11 @@ const createBridgeKit = (context) => {
18141
18413
  return kit;
18142
18414
  };
18143
18415
 
18144
- var name$1 = "@circle-fin/swap-kit";
18145
- var version$1 = "1.2.1";
18146
- var pkg$1 = {
18147
- name: name$1,
18148
- version: version$1};
18416
+ var name$2 = "@circle-fin/swap-kit";
18417
+ var version$2 = "1.2.2";
18418
+ var pkg$2 = {
18419
+ name: name$2,
18420
+ version: version$2};
18149
18421
 
18150
18422
  /**
18151
18423
  * @packageDocumentation
@@ -18242,7 +18514,7 @@ const serviceSwapConfigSchema = z.object({
18242
18514
  * @remarks
18243
18515
  * Optionally includes address for developer-controlled adapters.
18244
18516
  */
18245
- const adapterContextSchema$5 = z.object({
18517
+ const adapterContextSchema$6 = z.object({
18246
18518
  adapter: adapterSchema$1,
18247
18519
  chain: chainIdentifierSchema,
18248
18520
  address: z.string().optional(),
@@ -18264,7 +18536,7 @@ const adapterContextSchema$5 = z.object({
18264
18536
  * ```
18265
18537
  */
18266
18538
  const serviceSwapParamsSchema = z.object({
18267
- from: adapterContextSchema$5,
18539
+ from: adapterContextSchema$6,
18268
18540
  tokenIn: z
18269
18541
  .string({
18270
18542
  required_error: 'tokenIn is required',
@@ -18479,7 +18751,7 @@ const validateServiceSwapParams = (params) => {
18479
18751
  *
18480
18752
  * @internal
18481
18753
  */
18482
- const DEFAULT_CONFIG = {
18754
+ const DEFAULT_CONFIG$1 = {
18483
18755
  timeout: 30_000, // 30 seconds - matches load balancer timeout
18484
18756
  maxRetries: 3, // 3 retries as per requirements
18485
18757
  retryDelay: 200, // 200ms between retries
@@ -19144,6 +19416,46 @@ const parseCreateSwapResponse = (obj) => createSwapResponseSchema.parse(obj);
19144
19416
  * ```
19145
19417
  */
19146
19418
  const isGetSwapStatusResponse = (obj) => getSwapStatusResponseSchema.safeParse(obj).success;
19419
+ /**
19420
+ * Validates that an API key is properly formatted for Circle Stablecoin Service.
19421
+ *
19422
+ * This function performs validation on API keys to ensure they conform to the
19423
+ * expected format before making API requests. The key must follow the structure:
19424
+ * `KIT_KEY:keyId:keySecret`
19425
+ *
19426
+ * @param apiKey - The API key to validate (can be any type for graceful handling).
19427
+ * @returns True if the API key is valid, false otherwise.
19428
+ *
19429
+ * @remarks
19430
+ * This function handles invalid inputs gracefully by returning false rather than
19431
+ * throwing errors. It validates:
19432
+ * - The key starts with the `KIT_KEY:` prefix
19433
+ * - Contains exactly three colon-separated parts
19434
+ * - keyId and keySecret contain only valid characters (alphanumeric, `-`, `_`, `.`)
19435
+ * - keyId and keySecret are non-empty
19436
+ * - No whitespace anywhere in the API key (including leading, trailing, or internal)
19437
+ *
19438
+ * Valid characters in keyId and keySecret: `a-z`, `A-Z`, `0-9`, `-`, `_`, `.`
19439
+ *
19440
+ * @example
19441
+ * ```typescript
19442
+ * import { isValidApiKey } from '@core/service-client'
19443
+ *
19444
+ * // Valid API key
19445
+ * const isValid = isValidApiKey('KIT_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
19446
+ * console.log(isValid) // true
19447
+ *
19448
+ * ```
19449
+ */
19450
+ const isValidApiKey = (apiKey) => {
19451
+ // Handle invalid input types
19452
+ if (typeof apiKey !== 'string') {
19453
+ return false;
19454
+ }
19455
+ const apiKeyPattern = /^KIT_KEY:[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$/;
19456
+ // Validate without trimming - any whitespace will cause validation to fail
19457
+ return apiKeyPattern.test(apiKey);
19458
+ };
19147
19459
 
19148
19460
  /**
19149
19461
  * @packageDocumentation
@@ -19214,9 +19526,9 @@ const createSwap = async (params) => {
19214
19526
  // Remove the API key from the request body
19215
19527
  const { apiKey, ...requestBody } = validatedParams;
19216
19528
  const effectiveConfig = {
19217
- ...DEFAULT_CONFIG,
19529
+ ...DEFAULT_CONFIG$1,
19218
19530
  headers: {
19219
- ...DEFAULT_CONFIG.headers,
19531
+ ...DEFAULT_CONFIG$1.headers,
19220
19532
  Authorization: `Bearer ${apiKey}`,
19221
19533
  },
19222
19534
  };
@@ -19369,9 +19681,9 @@ const getQuote = async (params) => {
19369
19681
  const url = buildQuoteUrl(validatedParams);
19370
19682
  // Merge default config with Authorization header
19371
19683
  const effectiveConfig = {
19372
- ...DEFAULT_CONFIG,
19684
+ ...DEFAULT_CONFIG$1,
19373
19685
  headers: {
19374
- ...DEFAULT_CONFIG.headers,
19686
+ ...DEFAULT_CONFIG$1.headers,
19375
19687
  Authorization: `Bearer ${validatedParams.apiKey}`,
19376
19688
  },
19377
19689
  };
@@ -19424,9 +19736,9 @@ const getSwapStatus = async (params) => {
19424
19736
  }
19425
19737
  const url = buildSwapStatusUrl(result.data);
19426
19738
  const effectiveConfig = {
19427
- ...DEFAULT_CONFIG,
19739
+ ...DEFAULT_CONFIG$1,
19428
19740
  headers: {
19429
- ...DEFAULT_CONFIG.headers,
19741
+ ...DEFAULT_CONFIG$1.headers,
19430
19742
  Authorization: `Bearer ${result.data.apiKey}`,
19431
19743
  },
19432
19744
  };
@@ -22301,6 +22613,27 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
22301
22613
  */
22302
22614
  const NATIVE_TOKEN_ADDRESS$1 = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
22303
22615
 
22616
+ const hexBytesSchema$1 = z
22617
+ .string()
22618
+ .regex(/^0x([0-9a-fA-F]{2})*$/, {
22619
+ message: 'value must be a 0x-prefixed even-length hex string',
22620
+ })
22621
+ .transform((value) => value);
22622
+ z
22623
+ .object({
22624
+ executeParams: z.object({}).passthrough(),
22625
+ signature: evmSignatureSchema,
22626
+ tokenInputs: z.array(z.object({
22627
+ permitType: z.nativeEnum(PermitType),
22628
+ token: evmAddressSchema,
22629
+ amount: z.bigint().refine((value) => value >= 0n, {
22630
+ message: 'amount must be a non-negative bigint',
22631
+ }),
22632
+ permitCalldata: hexBytesSchema$1,
22633
+ })),
22634
+ })
22635
+ .passthrough();
22636
+
22304
22637
  /**
22305
22638
  * Solana mainnet genesis hash
22306
22639
  */
@@ -24511,21 +24844,21 @@ function buildInsufficientSwapBalanceError(tokenSymbol, walletAddress, currentBa
24511
24844
  const displaySymbol = resolvedSymbol ?? tokenSymbol;
24512
24845
  // Format human-readable amounts if we can resolve the token, otherwise use base units only
24513
24846
  const currentFormatted = canFormat
24514
- ? formatAmount$1({
24847
+ ? formatAmount$2({
24515
24848
  value: currentBalance.toString(),
24516
24849
  token: resolvedSymbol,
24517
24850
  chain,
24518
24851
  })
24519
24852
  : null;
24520
24853
  const requiredFormatted = canFormat
24521
- ? formatAmount$1({
24854
+ ? formatAmount$2({
24522
24855
  value: requiredAmount.toString(),
24523
24856
  token: resolvedSymbol,
24524
24857
  chain,
24525
24858
  })
24526
24859
  : null;
24527
24860
  const shortfallFormatted = canFormat
24528
- ? formatAmount$1({
24861
+ ? formatAmount$2({
24529
24862
  value: shortfall.toString(),
24530
24863
  token: resolvedSymbol,
24531
24864
  chain,
@@ -24611,7 +24944,7 @@ async function formatTokenValue(value, token, chain, adapter) {
24611
24944
  if (resolvedSymbol) {
24612
24945
  if (isSwapToken(resolvedSymbol)) {
24613
24946
  return {
24614
- amount: formatAmount$1({
24947
+ amount: formatAmount$2({
24615
24948
  value,
24616
24949
  token: resolvedSymbol,
24617
24950
  chain,
@@ -24652,7 +24985,7 @@ async function formatTokenValue(value, token, chain, adapter) {
24652
24985
  * i.e. 1 + 0.2 + 0.1 = 1.3. Use the greater of (local estimate × this multiplier)
24653
24986
  * or the proxy's gasLimit.
24654
24987
  */
24655
- const GAS_SAFETY_MULTIPLIER = 1.3;
24988
+ const GAS_SAFETY_MULTIPLIER$1 = 1.3;
24656
24989
  const TOKEN_REGISTRY = createTokenRegistry();
24657
24990
  /**
24658
24991
  * Enhances a KitError with transaction details (txHash and explorerUrl).
@@ -25085,7 +25418,7 @@ class StablecoinServiceSwapProvider {
25085
25418
  const gasEstimate = await params.from.adapter.calculateTransactionFee(gasLimitUnits, undefined, // Use default 5% buffer
25086
25419
  chain);
25087
25420
  // Format the fee from wei to human-readable native currency amount
25088
- estimatedGasFee = formatAmount$1({
25421
+ estimatedGasFee = formatAmount$2({
25089
25422
  value: gasEstimate.fee,
25090
25423
  token: 'NATIVE',
25091
25424
  chain,
@@ -25098,7 +25431,7 @@ class StablecoinServiceSwapProvider {
25098
25431
  const gasEstimate = await params.from.adapter.calculateTransactionFee(300000n, undefined, // Use default buffer
25099
25432
  chain);
25100
25433
  // Format the fee from lamports to human-readable SOL amount
25101
- estimatedGasFee = formatAmount$1({
25434
+ estimatedGasFee = formatAmount$2({
25102
25435
  value: gasEstimate.fee,
25103
25436
  token: 'NATIVE',
25104
25437
  chain,
@@ -25632,7 +25965,7 @@ class StablecoinServiceSwapProvider {
25632
25965
  const localEstimate = await preparedAction.estimate();
25633
25966
  const localGas = Number(localEstimate?.gas);
25634
25967
  if (Number.isFinite(localGas) && localGas > 0) {
25635
- const localWithBuffer = Math.ceil(localGas * GAS_SAFETY_MULTIPLIER);
25968
+ const localWithBuffer = Math.ceil(localGas * GAS_SAFETY_MULTIPLIER$1);
25636
25969
  if (Number.isFinite(localWithBuffer) && localWithBuffer > 0) {
25637
25970
  effectiveGasLimit = Math.max(localWithBuffer, proxyGasLimit);
25638
25971
  }
@@ -25728,7 +26061,7 @@ class StablecoinServiceSwapProvider {
25728
26061
  * Must always contain both adapter and chain explicitly.
25729
26062
  * Optionally includes address for developer-controlled adapters.
25730
26063
  */
25731
- const adapterContextSchema$4 = z.object({
26064
+ const adapterContextSchema$5 = z.object({
25732
26065
  adapter: adapterSchema$1,
25733
26066
  chain: swapChainIdentifierSchema,
25734
26067
  address: z.string().optional(),
@@ -25944,7 +26277,7 @@ const amountInSchema = z
25944
26277
  * ```
25945
26278
  */
25946
26279
  const swapParamsSchema = z.object({
25947
- from: adapterContextSchema$4,
26280
+ from: adapterContextSchema$5,
25948
26281
  tokenIn: swapTokenSchema,
25949
26282
  tokenOut: swapTokenSchema,
25950
26283
  amountIn: amountInSchema,
@@ -28475,7 +28808,7 @@ async function swap$1(context, params) {
28475
28808
  * )
28476
28809
  * ```
28477
28810
  */
28478
- function getSupportedChains$3(context) {
28811
+ function getSupportedChains$4(context) {
28479
28812
  const swapChains = new Set(Object.values(SwapChain));
28480
28813
  // Collect all supported chains from all providers
28481
28814
  // Use optional chaining to gracefully handle providers without supportedChains
@@ -28662,7 +28995,7 @@ function removeCustomFeePolicy(context) {
28662
28995
  * @returns An array containing the default StablecoinServiceSwapProvider
28663
28996
  * @internal
28664
28997
  */
28665
- const getDefaultProviders$1 = () => [new StablecoinServiceSwapProvider()];
28998
+ const getDefaultProviders$2 = () => [new StablecoinServiceSwapProvider()];
28666
28999
  /**
28667
29000
  * Create a SwapKit context with validated configuration.
28668
29001
  *
@@ -28722,7 +29055,7 @@ const getDefaultProviders$1 = () => [new StablecoinServiceSwapProvider()];
28722
29055
  */
28723
29056
  function createSwapKitContext(config = {}) {
28724
29057
  // Initialize default providers
28725
- const defaultProviders = getDefaultProviders$1();
29058
+ const defaultProviders = getDefaultProviders$2();
28726
29059
  // Merge default and custom providers
28727
29060
  const providers = [...defaultProviders, ...(config.providers ?? [])];
28728
29061
  // Build base context without fee policy
@@ -28750,7 +29083,7 @@ const SWAP_EVENT_TYPES = {
28750
29083
  };
28751
29084
 
28752
29085
  /** SDK name used in telemetry payloads. */
28753
- const SDK_NAME$1 = resolveKitSdkName(pkg$1.name);
29086
+ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
28754
29087
  /**
28755
29088
  * A high-level class-based interface for single-chain token swap operations.
28756
29089
  *
@@ -28877,7 +29210,7 @@ class SwapKit {
28877
29210
  this.disableErrorReporting = config.disableErrorReporting === true;
28878
29211
  this.telemetryConfig = {
28879
29212
  sdkName: SDK_NAME$1,
28880
- sdkVersion: pkg$1.version,
29213
+ sdkVersion: pkg$2.version,
28881
29214
  disabled: this.disableErrorReporting,
28882
29215
  };
28883
29216
  }
@@ -29021,7 +29354,7 @@ class SwapKit {
29021
29354
  * ```
29022
29355
  */
29023
29356
  getSupportedChains() {
29024
- return getSupportedChains$3(this.context);
29357
+ return getSupportedChains$4(this.context);
29025
29358
  }
29026
29359
  /**
29027
29360
  * Set a custom fee policy for all swap operations.
@@ -29145,7 +29478,7 @@ class SwapKit {
29145
29478
  * @packageDocumentation
29146
29479
  */
29147
29480
  // Auto-register this kit for user agent tracking
29148
- registerKit(`${pkg$1.name}/${pkg$1.version}`);
29481
+ registerKit(`${pkg$2.name}/${pkg$2.version}`);
29149
29482
 
29150
29483
  /**
29151
29484
  * Create a SwapKit instance with optional developer fee configuration.
@@ -29243,288 +29576,3138 @@ const createSwapKit = (context) => {
29243
29576
  return kit;
29244
29577
  };
29245
29578
 
29579
+ var name$1 = "@circle-fin/earn-kit";
29580
+ var version$1 = "1.0.0";
29581
+ var pkg$1 = {
29582
+ name: name$1,
29583
+ version: version$1};
29584
+
29246
29585
  /**
29247
- * Register event handlers from a context actions map to a kit instance.
29248
- *
29249
- * This utility function registers event handlers stored in a context actions map
29250
- * with a kit instance that supports event handling via an `on` method. It handles
29251
- * wildcard handlers ('*') and prefixed action handlers, stripping the prefix
29252
- * before registration.
29586
+ * Default base URL for the Earn Service API.
29253
29587
  *
29254
- * The function is designed to be reusable across different operation types
29255
- * (bridge, swap, stake, etc.) by accepting a configurable prefix parameter.
29256
- *
29257
- * @param kit - The kit instance to register handlers with (must have an `on` method)
29258
- * @param handlers - Map of action names to arrays of handler functions
29259
- * @param prefix - Optional prefix to strip from action names (e.g., 'bridge.')
29588
+ * @internal
29589
+ */
29590
+ const EARN_SERVICE_BASE_URL = 'https://api.circle.com';
29591
+ /**
29592
+ * API path prefix for all EarnKit endpoints.
29260
29593
  *
29261
- * @example
29262
- * ```typescript
29263
- * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
29264
- * import { BridgeKit } from '@circle-fin/bridge-kit'
29594
+ * @internal
29595
+ */
29596
+ const EARN_KIT_API_PREFIX = '/v1/earnKit';
29597
+ /**
29598
+ * Map SDK chain identifiers to Zenith API chain strings.
29265
29599
  *
29266
- * const kit = new BridgeKit()
29267
- * const handlers = {
29268
- * '*': [(payload) => console.log('All actions:', payload)],
29269
- * 'bridge.approve': [(payload) => console.log('Approved:', payload)],
29270
- * 'bridge.burn': [(payload) => console.log('Burned:', payload)],
29271
- * }
29600
+ * The Zenith backend uses short-form blockchain identifiers (e.g., 'ARC-TESTNET')
29601
+ * while the SDK uses the {@link Blockchain} enum (e.g., 'Arc_Testnet').
29602
+ * Adding a new {@link EarnSupportedBlockchain} without a corresponding
29603
+ * entry here produces a compile error.
29272
29604
  *
29273
- * registerActionHandlers(kit, handlers, 'bridge.')
29274
- * ```
29605
+ * @internal
29606
+ */
29607
+ const CHAIN_TO_API = {
29608
+ [Blockchain.Arc_Testnet]: 'ARC-TESTNET',
29609
+ };
29610
+ /**
29611
+ * Display symbol for vault share tokens.
29275
29612
  *
29276
- * @example
29277
- * ```typescript
29278
- * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
29279
- * import { SwapKit } from '@circle-fin/swap-kit'
29613
+ * @internal
29614
+ */
29615
+ const VAULT_SHARE_SYMBOL = 'shares';
29616
+ /**
29617
+ * Default polling configuration for Earn Service API calls.
29280
29618
  *
29281
- * const kit = new SwapKit()
29282
- * const handlers = {
29283
- * 'swap.initiate': [(payload) => console.log('Swap initiated:', payload)],
29284
- * }
29619
+ * Match the load-balancer timeout (30 s) with retry settings consistent
29620
+ * with `@core/service-client` defaults.
29285
29621
  *
29286
- * registerActionHandlers(kit, handlers, 'swap.')
29287
- * ```
29622
+ * @internal
29288
29623
  */
29289
- const registerActionHandlers = (kit, handlers, prefix = '') => {
29290
- for (const [action, handlerArray] of Object.entries(handlers)) {
29291
- // Register all handlers for this action
29292
- for (const handler of handlerArray) {
29293
- if (action === '*') {
29294
- // Wildcard handlers are registered as-is
29295
- kit.on('*', handler);
29296
- }
29297
- else if (prefix && action.startsWith(prefix)) {
29298
- // Remove prefix to get the actual kit action name
29299
- const kitAction = action.split('.').at(1);
29300
- if (kitAction) {
29301
- kit.on(kitAction, handler);
29302
- }
29303
- }
29304
- else if (!prefix) {
29305
- // No prefix configured, register action as-is
29306
- kit.on(action, handler);
29307
- }
29308
- // Actions that don't match the prefix are silently ignored
29309
- }
29310
- }
29624
+ const DEFAULT_CONFIG = {
29625
+ timeout: 30_000,
29626
+ maxRetries: 3,
29627
+ retryDelay: 200,
29628
+ headers: {
29629
+ 'Content-Type': 'application/json',
29630
+ },
29311
29631
  };
29312
29632
 
29633
+ const API_TO_CHAIN = Object.fromEntries(Object.entries(CHAIN_TO_API).map(([sdkChain, apiChain]) => [
29634
+ apiChain,
29635
+ sdkChain,
29636
+ ]));
29637
+ function toApiChain(chain) {
29638
+ if (!Object.hasOwn(CHAIN_TO_API, chain)) {
29639
+ return undefined;
29640
+ }
29641
+ return CHAIN_TO_API[chain];
29642
+ }
29643
+ function toSdkChain(chain) {
29644
+ if (Object.hasOwn(CHAIN_TO_API, chain)) {
29645
+ return chain;
29646
+ }
29647
+ if (!Object.hasOwn(API_TO_CHAIN, chain)) {
29648
+ return undefined;
29649
+ }
29650
+ return API_TO_CHAIN[chain];
29651
+ }
29652
+
29313
29653
  /**
29314
- * List of all supported token aliases for App Kit send operations.
29654
+ * Extract address and chain string from an adapter context.
29315
29655
  *
29316
- * This array is used for runtime validation to check if a token string
29317
- * is a known alias rather than a custom address.
29656
+ * Handle both user-controlled adapters (address resolved via
29657
+ * `adapter.getAddress()`) and developer-controlled adapters
29658
+ * (address provided explicitly in the context).
29318
29659
  *
29319
- * @remarks
29320
- * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
29321
- * with the TokenAlias type definition - TypeScript will enforce any changes.
29660
+ * @param from - Adapter context containing adapter, chain, and optional address
29661
+ * @returns Resolved wallet address, API chain string, and chain definition
29662
+ * @throws {@link KitError} If the chain is unsupported by the Earn Service provider.
29663
+ * @throws Propagates errors from `adapter.getAddress(chain)` when
29664
+ * `from.address` is not supplied.
29322
29665
  *
29323
- * For swap operations, additional tokens (EURC, DAI, USDE, PYUSD) are supported
29324
- * via SwapKit's SupportedToken type.
29666
+ * @example
29667
+ * ```typescript
29668
+ * const { address, chain, chainDefinition } =
29669
+ * await resolveAdapterContext(params.from)
29670
+ * ```
29671
+ *
29672
+ * @internal
29325
29673
  */
29326
- const TOKEN_ALIASES = ['USDC', 'USDT', 'NATIVE'];
29674
+ async function resolveAdapterContext(from) {
29675
+ const chainDef = resolveChainIdentifier(from.chain);
29676
+ const apiChain = toApiChain(chainDef.chain);
29677
+ if (apiChain === undefined) {
29678
+ throw createInvalidChainError(chainDef.chain, 'Chain is not supported by the Earn Service provider');
29679
+ }
29680
+ const hasAddress = 'address' in from &&
29681
+ typeof from.address === 'string' &&
29682
+ from.address.length > 0;
29683
+ const address = hasAddress
29684
+ ? from.address
29685
+ : await from.adapter.getAddress(chainDef);
29686
+ return { address, chain: apiChain, chainDefinition: chainDef };
29687
+ }
29688
+
29327
29689
  /**
29328
- * Check if a token string is a known alias.
29329
- *
29330
- * This function performs case-sensitive matching against the list of known
29331
- * token aliases. Known aliases take precedence over custom addresses in the
29332
- * send operation logic.
29690
+ * Assert that a value is a 0x-prefixed 20-byte hex address.
29333
29691
  *
29334
- * @param token - The token identifier to check
29335
- * @returns True if the token is a known alias, false otherwise
29692
+ * @param field - Name of the field being validated.
29693
+ * @param value - Runtime value to validate.
29694
+ * @param message - Optional message describing the expected address.
29695
+ * @returns The validated value narrowed to a 0x-prefixed string.
29696
+ * @throws {@link KitError} If the value is not an EVM address.
29336
29697
  *
29337
29698
  * @example
29338
29699
  * ```typescript
29339
- * import { isTokenAlias } from '@circle-fin/app-kit'
29340
- *
29341
- * console.log(isTokenAlias('USDC')) // true
29342
- * console.log(isTokenAlias('NATIVE')) // true
29343
- * console.log(isTokenAlias('0x6B175474E89094C44Da98b954EedeAC495271d0F')) // false
29700
+ * const token = assertHexAddress(
29701
+ * 'chain.usdcAddress',
29702
+ * '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
29703
+ * )
29344
29704
  * ```
29705
+ *
29706
+ * @internal
29345
29707
  */
29346
- function isTokenAlias(token) {
29347
- return TOKEN_ALIASES.includes(token);
29708
+ function assertHexAddress(field, value, message = `${field} must be a 0x-prefixed 20-byte hex address`) {
29709
+ const result = evmAddressSchema.safeParse(value);
29710
+ if (!result.success) {
29711
+ throw createValidationFailedError$1(field, value, message);
29712
+ }
29713
+ return result.data;
29348
29714
  }
29715
+
29349
29716
  /**
29350
- * Type guard to check if a string is a valid token address for a chain.
29351
- *
29352
- * This function verifies that a token string is:
29353
- * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
29354
- * 2. A valid address format for the specified chain
29355
- *
29356
- * Use this to narrow the type to `TokenAddress` in TypeScript.
29717
+ * Return the adapter contract address configured for the chain, or throw a
29718
+ * fatal KitError if unavailable. Earn operations require an adapter contract
29719
+ * to forward service-signed payloads.
29357
29720
  *
29358
- * @param token - The token string to validate
29359
- * @param chain - The chain definition for address validation
29360
- * @returns True if the token is a valid address (not an alias)
29721
+ * @param chain - Resolved chain definition.
29722
+ * @returns Adapter contract address as a 0x-prefixed hex string.
29723
+ * @throws {@link KitError} If `chain.kitContracts.adapter` is undefined.
29724
+ * @throws {@link KitError} If `chain.kitContracts.adapter` is malformed.
29361
29725
  *
29362
29726
  * @example
29363
29727
  * ```typescript
29364
- * import { isTokenAddress } from '@circle-fin/app-kit'
29365
- * import { Ethereum } from '@core/chains'
29366
- *
29367
- * const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
29368
- * if (isTokenAddress(daiAddress, Ethereum)) {
29369
- * // TypeScript knows daiAddress is TokenAddress here
29370
- * console.log('Valid token address:', daiAddress)
29371
- * }
29372
- *
29373
- * console.log(isTokenAddress('USDC', Ethereum)) // false - it's an alias
29374
- * console.log(isTokenAddress('invalid', Ethereum)) // false - invalid format
29728
+ * const adapterContract = requireAdapterContract(chain)
29375
29729
  * ```
29730
+ *
29731
+ * @internal
29376
29732
  */
29377
- function isTokenAddress(token, chain) {
29378
- return !isTokenAlias(token) && isValidAddressForChain(token, chain);
29733
+ function requireAdapterContract(chain) {
29734
+ const adapterContractAddress = chain.kitContracts?.adapter;
29735
+ if (adapterContractAddress === undefined) {
29736
+ throw createValidationFailedError$1('chain.kitContracts.adapter', adapterContractAddress, `Adapter contract not configured for chain ${chain.name}. Earn operations require an adapter contract.`);
29737
+ }
29738
+ return assertHexAddress('chain.kitContracts.adapter', adapterContractAddress, `Adapter contract for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
29379
29739
  }
29740
+
29380
29741
  /**
29381
- * Validate and classify a token identifier.
29382
- *
29383
- * This function determines whether a token string is:
29384
- * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
29385
- * 2. A valid token address for the given chain
29386
- * 3. An invalid/unrecognized token identifier
29742
+ * Safety multiplier applied to locally estimated gas for earn transactions.
29387
29743
  *
29388
- * Known aliases always take precedence. If the token is not an alias,
29389
- * it's validated as an address for the given chain.
29744
+ * Earn transactions can include nested vault and token contract calls.
29745
+ * Local gas estimation has undercounted those execution paths in production.
29746
+ * A 1.3x buffer matching swap-kit reduces out-of-gas risk while preserving
29747
+ * the adapter fallback path when local estimation fails.
29390
29748
  *
29391
- * @param token - The token identifier to validate
29392
- * @param chain - The chain definition for address validation
29393
- * @returns An object containing validation results
29749
+ * @internal
29750
+ */
29751
+ const GAS_SAFETY_MULTIPLIER = 1.3;
29752
+ /**
29753
+ * Estimate gas for a prepared chain request and apply {@link GAS_SAFETY_MULTIPLIER}.
29394
29754
  *
29395
- * @example
29396
- * ```typescript
29397
- * import { validateToken } from '@circle-fin/app-kit'
29398
- * import { Ethereum } from '@core/chains'
29755
+ * Returns the buffered gas limit, or `undefined` if estimation fails or
29756
+ * produces an invalid value. Callers should fall through to the adapter's
29757
+ * default gas handling when `undefined` is returned.
29399
29758
  *
29400
- * // Known alias
29401
- * const usdc = validateToken('USDC', Ethereum)
29402
- * console.log(usdc) // { isAlias: true, isAddress: false, isValid: true }
29759
+ * @internal
29760
+ */
29761
+ async function estimateBufferedGasLimit(prepared) {
29762
+ try {
29763
+ const estimate = await prepared.estimate();
29764
+ const estimatedGas = Number(estimate.gas);
29765
+ if (Number.isFinite(estimatedGas) && estimatedGas > 0) {
29766
+ const buffered = Math.ceil(estimatedGas * GAS_SAFETY_MULTIPLIER);
29767
+ if (Number.isFinite(buffered) && buffered > 0) {
29768
+ return buffered;
29769
+ }
29770
+ }
29771
+ }
29772
+ catch {
29773
+ // If estimation fails, fall through and let the adapter handle gas internally
29774
+ }
29775
+ return undefined;
29776
+ }
29777
+
29778
+ /**
29779
+ * Maximum `uint256` value. The approval amount used when this helper needs
29780
+ * to send a token approval.
29403
29781
  *
29404
- * // Custom token address
29405
- * const dai = validateToken('0x6B175474E89094C44Da98b954EedeAC495271d0F', Ethereum)
29406
- * console.log(dai) // { isAlias: false, isAddress: true, isValid: true }
29782
+ * @internal
29783
+ */
29784
+ const MAX_UINT256$1 = 2n ** 256n - 1n;
29785
+ /**
29786
+ * Allowance threshold above which a prior max-approve is assumed to still
29787
+ * cover any earn operation. Detects prior max-approvals even when the token
29788
+ * decrements allowance on `transferFrom`, since that decay is negligible
29789
+ * relative to 2^255.
29407
29790
  *
29408
- * // Invalid token
29409
- * const invalid = validateToken('invalid', Ethereum)
29410
- * console.log(invalid) // { isAlias: false, isAddress: false, isValid: false }
29411
- * ```
29791
+ * @internal
29412
29792
  */
29413
- function validateToken(token, chain) {
29414
- // Check if it's a known alias first (aliases take precedence)
29415
- const alias = isTokenAlias(token);
29416
- if (alias) {
29417
- return {
29418
- isAlias: true,
29419
- isAddress: false,
29420
- isValid: true,
29421
- };
29793
+ const ALLOWANCE_THRESHOLD = MAX_UINT256$1 / 2n;
29794
+ function parseAllowanceResponse(allowanceRaw) {
29795
+ if (allowanceRaw === undefined || allowanceRaw === null) {
29796
+ return 0n;
29422
29797
  }
29423
- // Not an alias - check if it's a valid address for the chain
29424
- const address = isTokenAddress(token, chain);
29425
- return {
29426
- isAlias: false,
29427
- isAddress: address,
29428
- isValid: address,
29429
- };
29798
+ let allowance;
29799
+ if (typeof allowanceRaw === 'bigint') {
29800
+ allowance = allowanceRaw;
29801
+ }
29802
+ else if (typeof allowanceRaw === 'string') {
29803
+ try {
29804
+ allowance = BigInt(allowanceRaw);
29805
+ }
29806
+ catch {
29807
+ allowance = undefined;
29808
+ }
29809
+ }
29810
+ if (allowance === undefined || allowance < 0n) {
29811
+ throw createValidationFailedError$1('token.allowance', allowanceRaw, 'token.allowance response must be a non-negative bigint-compatible string or bigint');
29812
+ }
29813
+ return allowance;
29430
29814
  }
29431
-
29432
29815
  /**
29433
- * Estimate the costs and details of a cross-chain bridge transfer using the AppKit context.
29816
+ * Read the current ERC-20 allowance and, if below threshold, send a
29817
+ * max-approval transaction and wait for confirmation.
29434
29818
  *
29435
- * This function provides cost estimation for bridge operations within the AppKit
29436
- * ecosystem. It delegates to the underlying BridgeKit infrastructure while maintaining
29437
- * consistency with the AppKit patterns and context-based architecture.
29819
+ * Uses the generalised `token.allowance` + `token.approve` adapter actions so
29820
+ * the kit stays chain-agnostic. Assumes the target token supports
29821
+ * non-zero-to-non-zero approve (USDC, ERC-4626 share tokens). Tokens with
29822
+ * USDT-style semantics (require allowance == 0 before non-zero approve) need
29823
+ * a different pattern.
29438
29824
  *
29439
- * The function validates parameters, applies context-specific configurations,
29440
- * and returns comprehensive estimation details including fees, gas costs, and
29441
- * transaction parameters without executing the actual transfer.
29825
+ * Approval status is checked explicitly because `waitForTransaction` returns
29826
+ * a receipt with `status: 'reverted'` rather than throwing on revert.
29442
29827
  *
29443
- * @param context - AppKit context containing fee calculation and configuration hooks
29444
- * @param params - Bridge parameters containing source, destination, amount, and token
29445
- * @returns Promise resolving to the estimate result with comprehensive fee and cost information
29446
- * @throws If bridge parameters are invalid
29447
- * @throws If the bridge route is not supported
29448
- * @throws If estimation fails due to network or configuration issues
29828
+ * @returns The approval tx hash when an approval was sent, or `undefined` if
29829
+ * the existing allowance was already above threshold.
29830
+ * @throws {@link KitError} If the allowance response is malformed.
29831
+ * @throws {@link KitError} If the approval transaction reverts on-chain.
29449
29832
  *
29450
29833
  * @example
29451
29834
  * ```typescript
29452
- * import { estimateBridge } from '@circle-fin/app-kit/bridge'
29453
- * import { createContext } from '@circle-fin/app-kit/context'
29454
- *
29455
- * const context = createContext({
29456
- * getFee: async (type, params) => '1000000', // 1 USDC fee
29457
- * getFeeRecipient: async (type, info) => '0xfee-recipient-address'
29458
- * })
29835
+ * const delegate = requireAdapterContract(chain)
29459
29836
  *
29460
- * const estimate = await estimateBridge(context, {
29461
- * from: sourceAdapter,
29462
- * to: { adapter: destAdapter, chain: 'Polygon' },
29463
- * amount: '100.50',
29464
- * token: 'USDC'
29837
+ * const approvalTxHash = await approveMaxIfNeeded({
29838
+ * adapter,
29839
+ * chain,
29840
+ * tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
29841
+ * delegate,
29842
+ * address,
29843
+ * revertMessage: 'USDC approval reverted on-chain',
29465
29844
  * })
29466
- *
29467
- * console.log('Estimated total fee:', estimate.fee)
29468
- * console.log('Estimated gas cost:', estimate.gasEstimate)
29469
29845
  * ```
29846
+ *
29847
+ * @internal
29470
29848
  */
29471
- const estimateBridge = async (context, params) => {
29472
- const kit = createBridgeKit(context);
29473
- // Delegate to the BridgeKit for actual estimation
29474
- return kit.estimate(params);
29475
- };
29849
+ async function approveMaxIfNeeded(params) {
29850
+ const { adapter, chain, tokenAddress, delegate, address, revertMessage } = params;
29851
+ const allowancePrepared = await adapter.prepareAction('token.allowance', { tokenAddress, delegate }, { chain, address });
29852
+ const allowanceRaw = await allowancePrepared.execute();
29853
+ const currentAllowance = parseAllowanceResponse(allowanceRaw);
29854
+ if (currentAllowance >= ALLOWANCE_THRESHOLD) {
29855
+ return undefined;
29856
+ }
29857
+ const approvalPrepared = await adapter.prepareAction('token.approve', { tokenAddress, delegate, amount: MAX_UINT256$1 }, { chain, address });
29858
+ const gasLimitOverride = await estimateBufferedGasLimit(approvalPrepared);
29859
+ const approvalTxHash = approvalPrepared.type === 'evm' && gasLimitOverride !== undefined
29860
+ ? await approvalPrepared.execute({ gasLimit: gasLimitOverride })
29861
+ : await approvalPrepared.execute();
29862
+ const approvalReceipt = await adapter.waitForTransaction(approvalTxHash, { confirmations: 1 }, chain);
29863
+ if (approvalReceipt.status === 'reverted') {
29864
+ throw createTransactionRevertedError(chain.name, revertMessage, undefined, approvalTxHash);
29865
+ }
29866
+ return approvalTxHash;
29867
+ }
29476
29868
 
29477
- const assertSendParamsSymbol = Symbol('assertSendParams');
29478
29869
  /**
29479
- * Matches numeric amount strings with optional thousands separators and decimal part.
29870
+ * Build the `tokenInputs` array forwarded to the adapter contract's
29871
+ * `execute(executeParams, tokenInputs, signature)` call.
29480
29872
  *
29481
- * Accepts both US (e.g. "1,234.56") and EU (e.g. "1.234,56") styles by allowing
29482
- * either "," or "." as the grouping and decimal separators. Also matches integers
29483
- * like "10" and simple decimals like "10.5" or "10,5".
29484
- */
29485
- const amountNumericStringWithSeparatorsRegex = /^\d+(?:[.,]\d{3})*(?:[.,]\d+)?$/;
29486
- /**
29487
- * Schema describing the minimal adapter surface required by the kit.
29873
+ * The adapter contract pulls ERC-20 tokens from `msg.sender` using the
29874
+ * entries in `tokenInputs`, combined either with a pre-existing allowance
29875
+ * ({@link PermitType.NONE}) or a permit signature ({@link PermitType.EIP2612}).
29876
+ * Without an entry, the contract never moves the user's tokens and the
29877
+ * nested instruction reverts with "ERC20: transfer amount exceeds balance"
29878
+ * when the adapter tries to forward tokens it does not hold.
29488
29879
  *
29489
- * The adapter must implement two functions:
29490
- * - `prepare` build the transaction or instruction payload.
29491
- * - `waitForTransaction` await finality/confirmation for a submitted transaction.
29880
+ * Earn deposit and withdraw responses are schema-validated to contain at
29881
+ * least one instruction. This helper emits one `PermitType.NONE` entry per
29882
+ * instruction with a positive `amountToApprove`. Return an empty array when
29883
+ * the service did not request any token pulls.
29492
29884
  *
29493
- * @returns Zod schema validating an object with `prepare` and `waitForTransaction` functions.
29885
+ * @param executionParams - Service-signed `ExecutionParams` forwarded to the adapter.
29886
+ * @param approvedToken - Token approved for adapter spending on this chain.
29887
+ * @returns `TokenInput[]` entries when pulls are required, `[]` otherwise.
29888
+ * @throws {@link KitError} If `tokenIn` differs from the approved token.
29494
29889
  *
29495
29890
  * @example
29496
29891
  * ```typescript
29497
- * import { adapterSchema } from '@circle-fin/app-kit'
29892
+ * const approvedToken = assertHexAddress(
29893
+ * 'chain.usdcAddress',
29894
+ * chain.usdcAddress,
29895
+ * )
29498
29896
  *
29499
- * console.log(adapterSchema.safeParse(ethereumAdapter).success) // true
29897
+ * const tokenInputs = buildEarnTokenInputs(
29898
+ * executionPayload.executionParams,
29899
+ * approvedToken,
29900
+ * )
29500
29901
  * ```
29902
+ *
29903
+ * @internal
29501
29904
  */
29502
- const adapterSchema = z.object({
29503
- prepare: z.function().returns(z.any()),
29504
- waitForTransaction: z.function().returns(z.any()),
29505
- });
29905
+ function buildEarnTokenInputs(executionParams, approvedToken) {
29906
+ const tokenInputs = [];
29907
+ executionParams.instructions.forEach((instruction, index) => {
29908
+ const amount = BigInt(instruction.amountToApprove);
29909
+ if (amount <= 0n) {
29910
+ return;
29911
+ }
29912
+ const { tokenIn } = instruction;
29913
+ if (tokenIn.toLowerCase() !== approvedToken.toLowerCase()) {
29914
+ throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
29915
+ }
29916
+ tokenInputs.push({
29917
+ permitType: PermitType.NONE,
29918
+ token: tokenIn,
29919
+ amount,
29920
+ permitCalldata: '0x',
29921
+ });
29922
+ });
29923
+ return tokenInputs;
29924
+ }
29925
+
29506
29926
  /**
29507
- * Schema for validating adapter contexts.
29508
- * Ensure required fields are present and properly typed. An adapter context must include:
29509
- * - A valid adapter with `prepare` and `waitForTransaction` methods.
29510
- * - A valid chain identifier (string literal or chain object recognized by the kit).
29927
+ * Prepare an earn adapter action, execute it, wait for confirmation, and
29928
+ * throw a structured revert error if the receipt status is `'reverted'`.
29511
29929
  *
29512
- * @returns Zod schema validating an adapter context object.
29513
- * @throws ZodError If validation fails, with details about which properties failed.
29930
+ * Wraps the prepareAction, execute, waitForTransaction, and status check
29931
+ * sequence so the provider can dispatch any `earn.*` action key with
29932
+ * consistent revert handling.
29933
+ *
29934
+ * Gas estimation: after preparing the action the helper calls
29935
+ * {@link estimateBufferedGasLimit}, which applies a safety buffer to the
29936
+ * returned gas value. If estimation fails, execution falls through to the
29937
+ * adapter's default gas handling.
29938
+ *
29939
+ * @typeParam TActionKey - Earn action key being executed.
29940
+ * @param params - Adapter action, execution context, and revert message.
29941
+ * @returns The confirmed on-chain transaction hash and explorer URL.
29942
+ * @throws {@link KitError} If the transaction reverts on-chain.
29514
29943
  *
29515
29944
  * @example
29516
29945
  * ```typescript
29517
- * import { adapterContextSchema } from '@circle-fin/app-kit'
29518
- *
29519
- * const result = adapterContextSchema.safeParse(context)
29520
- * console.log(result.success) // true
29946
+ * const { txHash, explorerUrl } = await executeEarnAction({
29947
+ * adapter,
29948
+ * chain,
29949
+ * address,
29950
+ * actionKey: 'earn.deposit',
29951
+ * actionParams: { executeParams, tokenInputs, signature },
29952
+ * revertMessage: 'Earn deposit reverted on-chain',
29953
+ * })
29521
29954
  * ```
29955
+ *
29956
+ * @internal
29522
29957
  */
29523
- const adapterContextSchema$3 = z.object({
29524
- adapter: adapterSchema,
29525
- /**
29526
- * Note: We cast chainIdentifierSchema to 'never' here to work around a TypeScript
29527
- * limitation (TS2589: Type instantiation is excessively deep and possibly infinite)
29958
+ async function executeEarnAction(params) {
29959
+ const { adapter, chain, address, actionKey, actionParams, revertMessage } = params;
29960
+ const prepared = await adapter.prepareAction(actionKey, actionParams, {
29961
+ chain,
29962
+ address,
29963
+ });
29964
+ const gasLimitOverride = await estimateBufferedGasLimit(prepared);
29965
+ const txHash = prepared.type === 'evm' && gasLimitOverride !== undefined
29966
+ ? await prepared.execute({ gasLimit: gasLimitOverride })
29967
+ : await prepared.execute();
29968
+ const explorerUrl = buildExplorerUrl(chain, txHash);
29969
+ const receipt = await adapter.waitForTransaction(txHash, { confirmations: 1 }, chain);
29970
+ if (receipt.status === 'reverted') {
29971
+ throw createTransactionRevertedError(chain.name, revertMessage, undefined, txHash, explorerUrl);
29972
+ }
29973
+ return { txHash, explorerUrl };
29974
+ }
29975
+
29976
+ /**
29977
+ * Validate that a service-signed execution payload has not expired before
29978
+ * the SDK asks the wallet to broadcast a transaction.
29979
+ *
29980
+ * @internal
29981
+ */
29982
+ function validateExecutionDeadline(executionParams) {
29983
+ const deadline = BigInt(executionParams.deadline);
29984
+ const now = BigInt(Math.floor(Date.now() / 1000));
29985
+ if (deadline <= now) {
29986
+ throw createValidationFailedError$1('executionParams.deadline', executionParams.deadline, 'Earn execution deadline has expired');
29987
+ }
29988
+ }
29989
+
29990
+ // ---------------------------------------------------------------------------
29991
+ // Shared primitives
29992
+ // ---------------------------------------------------------------------------
29993
+ function isBigIntLike(value) {
29994
+ try {
29995
+ BigInt(value);
29996
+ return true;
29997
+ }
29998
+ catch {
29999
+ return false;
30000
+ }
30001
+ }
30002
+ function isNonNegativeBigIntLike(value) {
30003
+ try {
30004
+ return BigInt(value) >= 0n;
30005
+ }
30006
+ catch {
30007
+ return false;
30008
+ }
30009
+ }
30010
+ const hexSignatureSchema = evmSignatureSchema;
30011
+ const hexAddressSchema = evmAddressSchema;
30012
+ /**
30013
+ * Zod schema for a non-negative uint256-like value.
30014
+ *
30015
+ * The service emits uint256 fields as decimal strings in JSON, while tests and
30016
+ * lower-level SDK callers may already hold bigint values. Keep the parsed value
30017
+ * unchanged so signed execution params can be forwarded verbatim.
30018
+ *
30019
+ * @internal
30020
+ */
30021
+ const uint256LikeSchema = z
30022
+ .union([z.string(), z.bigint()])
30023
+ .refine(isNonNegativeBigIntLike, {
30024
+ message: 'value must be a non-negative bigint-compatible string or bigint',
30025
+ });
30026
+ /**
30027
+ * Zod schema for typed amount objects emitted by the API.
30028
+ *
30029
+ * @internal
30030
+ */
30031
+ const amountJsonSchema = z.object({
30032
+ raw: z.string().refine(isBigIntLike, {
30033
+ message: 'raw must be a bigint-compatible string',
30034
+ }),
30035
+ decimals: z.number().int().nonnegative(),
30036
+ });
30037
+ /** @internal */
30038
+ const hexBytesSchema = z.string().regex(/^0x([0-9a-fA-F]{2})*$/, {
30039
+ message: 'value must be a 0x-prefixed even-length hex string',
30040
+ });
30041
+ // ---------------------------------------------------------------------------
30042
+ // Vault response schemas
30043
+ // ---------------------------------------------------------------------------
30044
+ /**
30045
+ * Zod schema for a vault reward token in the API response.
30046
+ *
30047
+ * @internal
30048
+ */
30049
+ const vaultRewardSchema = z.object({
30050
+ token: z.string(),
30051
+ tokenAddress: z.string(),
30052
+ apy: z.number(),
30053
+ });
30054
+ /**
30055
+ * Zod schema for a vault collateral market in the API response.
30056
+ *
30057
+ * @internal
30058
+ */
30059
+ const collateralSchema = z.object({
30060
+ asset: z.string(),
30061
+ assetAddress: z.string(),
30062
+ lltv: z.number(),
30063
+ supplyUsd: z.number(),
30064
+ });
30065
+ /**
30066
+ * Zod schema for a Morpho vault warning in the API response.
30067
+ *
30068
+ * @internal
30069
+ */
30070
+ const vaultWarningSchema = z.object({
30071
+ type: z.string(),
30072
+ level: z.enum(['YELLOW', 'RED']),
30073
+ });
30074
+ /**
30075
+ * Zod schema for a single vault info object in the API response.
30076
+ *
30077
+ * @internal
30078
+ */
30079
+ const vaultInfoResponseSchema = z.object({
30080
+ vaultAddress: z.string(),
30081
+ chain: z.string(),
30082
+ name: z.string(),
30083
+ protocol: z.string(),
30084
+ asset: z.string(),
30085
+ assetAddress: z.string(),
30086
+ currentApy: z.number(),
30087
+ nativeApy: z.number(),
30088
+ vaultFee: z.number(),
30089
+ rewards: z.array(vaultRewardSchema),
30090
+ collateral: z.array(collateralSchema),
30091
+ totalDeposits: amountJsonSchema,
30092
+ liquidity: amountJsonSchema,
30093
+ status: z.enum(['active', 'low_liquidity']),
30094
+ warnings: z.array(vaultWarningSchema).optional(),
30095
+ earnKitWarnings: z.array(z.string()).optional(),
30096
+ });
30097
+ // ---------------------------------------------------------------------------
30098
+ // Position response schema
30099
+ // ---------------------------------------------------------------------------
30100
+ /**
30101
+ * Zod schema for an accrued reward in the position API response.
30102
+ *
30103
+ * @internal
30104
+ */
30105
+ const accruedRewardSchema = z.object({
30106
+ token: z.string(),
30107
+ symbol: z.string(),
30108
+ amount: amountJsonSchema,
30109
+ });
30110
+ const positionPnlSchema = z.discriminatedUnion('status', [
30111
+ z.object({
30112
+ status: z.literal('available'),
30113
+ principalDeposited: amountJsonSchema,
30114
+ totalYieldEarned: amountJsonSchema,
30115
+ }),
30116
+ z.object({
30117
+ status: z.literal('pending'),
30118
+ }),
30119
+ z.object({
30120
+ status: z.literal('unavailable'),
30121
+ reason: z.string(),
30122
+ }),
30123
+ ]);
30124
+ /**
30125
+ * Zod schema for the inner position payload.
30126
+ *
30127
+ * @internal
30128
+ */
30129
+ const positionPayloadSchema = z.object({
30130
+ wallet: z.string(),
30131
+ chain: z.string(),
30132
+ vaultAddress: z.string(),
30133
+ vaultName: z.string(),
30134
+ asset: z.string(),
30135
+ currentBalance: amountJsonSchema,
30136
+ currentApy: z.number(),
30137
+ shares: amountJsonSchema,
30138
+ pnl: positionPnlSchema,
30139
+ accruedRewards: z.array(accruedRewardSchema).optional(),
30140
+ rewardsUnavailableReason: z.string().optional(),
30141
+ });
30142
+ /**
30143
+ * Zod schema for the `GET /v1/earnKit/position/{address}` API response.
30144
+ *
30145
+ * The Earn Service API wraps the position payload in a `data` envelope.
30146
+ *
30147
+ * @internal
30148
+ */
30149
+ const positionResponseSchema = z.object({
30150
+ data: positionPayloadSchema,
30151
+ });
30152
+ // ---------------------------------------------------------------------------
30153
+ // Deposit response schema
30154
+ // ---------------------------------------------------------------------------
30155
+ /**
30156
+ * Zod schema for the deposit instruction fields used by the provider.
30157
+ *
30158
+ * The canonical instruction shape includes more fields, but deposit
30159
+ * orchestration only depends on `tokenIn` and `amountToApprove` to build the
30160
+ * token input and approval flow. Preserve additional signed fields with
30161
+ * `passthrough()` so the adapter receives the full service payload.
30162
+ *
30163
+ * @internal
30164
+ */
30165
+ const earnInstructionSchema = z
30166
+ .object({
30167
+ tokenIn: hexAddressSchema,
30168
+ amountToApprove: uint256LikeSchema,
30169
+ })
30170
+ .passthrough();
30171
+ /**
30172
+ * Zod schema for earn execution params returned by the earn service.
30173
+ *
30174
+ * Require at least one instruction so the provider can derive token pulls
30175
+ * before it submits the signed params on-chain. Claim rewards has its own
30176
+ * schema because it does not run approval preflight.
30177
+ *
30178
+ * @internal
30179
+ */
30180
+ const earnExecutionParamsSchema = z
30181
+ .object({
30182
+ instructions: z.tuple([earnInstructionSchema]).rest(earnInstructionSchema),
30183
+ deadline: uint256LikeSchema,
30184
+ })
30185
+ .passthrough();
30186
+ /** @internal */
30187
+ const depositExecutionParamsSchema = earnExecutionParamsSchema;
30188
+ /** @internal */
30189
+ const withdrawExecutionParamsSchema = earnExecutionParamsSchema;
30190
+ /**
30191
+ * Zod schema for the claim rewards instruction fields that must be safe to
30192
+ * ABI-encode.
30193
+ *
30194
+ * Claim rewards has no approval preflight, so this validates the forwarded
30195
+ * signed payload at the provider boundary. Additional instruction fields are
30196
+ * accepted and preserved.
30197
+ *
30198
+ * @internal
30199
+ */
30200
+ const claimRewardsInstructionSchema = z
30201
+ .object({
30202
+ target: hexAddressSchema,
30203
+ data: hexBytesSchema,
30204
+ value: uint256LikeSchema,
30205
+ tokenIn: hexAddressSchema,
30206
+ amountToApprove: uint256LikeSchema,
30207
+ tokenOut: hexAddressSchema,
30208
+ minTokenOut: uint256LikeSchema,
30209
+ })
30210
+ .passthrough();
30211
+ /** @internal */
30212
+ const claimRewardsTokenSchema = z
30213
+ .object({
30214
+ token: hexAddressSchema,
30215
+ beneficiary: hexAddressSchema,
30216
+ })
30217
+ .passthrough();
30218
+ /**
30219
+ * Zod schema for signed claim rewards execution params.
30220
+ *
30221
+ * This enforces the required envelope and preserves additive fields so the
30222
+ * adapter receives the signed params verbatim.
30223
+ *
30224
+ * @internal
30225
+ */
30226
+ const claimRewardsExecutionParamsSchema = z
30227
+ .object({
30228
+ instructions: z
30229
+ .tuple([claimRewardsInstructionSchema])
30230
+ .rest(claimRewardsInstructionSchema),
30231
+ tokens: z.tuple([claimRewardsTokenSchema]).rest(claimRewardsTokenSchema),
30232
+ execId: uint256LikeSchema,
30233
+ deadline: uint256LikeSchema,
30234
+ metadata: hexBytesSchema,
30235
+ })
30236
+ .passthrough();
30237
+ /**
30238
+ * Zod schema for the deposit payload inside the API `data` envelope.
30239
+ *
30240
+ * @internal
30241
+ */
30242
+ const depositPayloadSchema = z.object({
30243
+ executionParams: depositExecutionParamsSchema,
30244
+ signature: hexSignatureSchema,
30245
+ });
30246
+ /**
30247
+ * Zod schema for the `POST /v1/earnKit/deposit` API response.
30248
+ *
30249
+ * The API wraps the deposit payload in a `data` envelope.
30250
+ *
30251
+ * @internal
30252
+ */
30253
+ const depositResponseSchema = z.object({
30254
+ data: depositPayloadSchema,
30255
+ });
30256
+ // ---------------------------------------------------------------------------
30257
+ // Withdraw response schema
30258
+ // ---------------------------------------------------------------------------
30259
+ /**
30260
+ * Zod schema for the withdraw payload inside the API `data` envelope.
30261
+ *
30262
+ * @internal
30263
+ */
30264
+ const withdrawPayloadSchema = z.object({
30265
+ executionParams: withdrawExecutionParamsSchema,
30266
+ signature: hexSignatureSchema,
30267
+ });
30268
+ /**
30269
+ * Zod schema for the `POST /v1/earnKit/withdraw` API response.
30270
+ *
30271
+ * The Earn Service API wraps the withdraw payload in a `data` envelope.
30272
+ *
30273
+ * @internal
30274
+ */
30275
+ const withdrawResponseSchema = z.object({
30276
+ data: withdrawPayloadSchema,
30277
+ });
30278
+ // ---------------------------------------------------------------------------
30279
+ // Claim rewards response schema
30280
+ // ---------------------------------------------------------------------------
30281
+ /**
30282
+ * Zod schema for a claimed reward amount in the API response.
30283
+ *
30284
+ * @internal
30285
+ */
30286
+ const claimedAmountSchema = z.object({
30287
+ token: hexAddressSchema,
30288
+ symbol: z.string(),
30289
+ amount: amountJsonSchema,
30290
+ });
30291
+ /**
30292
+ * Zod schema for the claim rewards payload inside the API `data` envelope.
30293
+ *
30294
+ * Enforce that `executionParams` and `signature` are either both
30295
+ * present (claimable rewards) or both absent (nothing to claim).
30296
+ *
30297
+ * @internal
30298
+ */
30299
+ const claimRewardsPayloadSchema = z
30300
+ .object({
30301
+ rewards: z.array(claimedAmountSchema),
30302
+ executionParams: claimRewardsExecutionParamsSchema.optional(),
30303
+ signature: hexSignatureSchema.optional(),
30304
+ })
30305
+ .refine((data) => {
30306
+ const signedFieldCount = (data.executionParams === undefined ? 0 : 1) +
30307
+ (data.signature === undefined ? 0 : 1);
30308
+ if (data.rewards.length > 0) {
30309
+ return signedFieldCount === 2;
30310
+ }
30311
+ return signedFieldCount === 0;
30312
+ }, {
30313
+ message: 'claim rewards response must include executionParams and signature only when rewards are claimable',
30314
+ });
30315
+ /**
30316
+ * Zod schema for the `POST /v1/earnKit/claimRewards` API response.
30317
+ *
30318
+ * The Earn Service API wraps the claim rewards payload in a `data` envelope.
30319
+ *
30320
+ * @internal
30321
+ */
30322
+ const claimRewardsResponseSchema = z.object({
30323
+ data: claimRewardsPayloadSchema,
30324
+ });
30325
+ // ---------------------------------------------------------------------------
30326
+ // Deposit quote response schema
30327
+ // ---------------------------------------------------------------------------
30328
+ /**
30329
+ * Zod schema for the inner deposit quote payload.
30330
+ *
30331
+ * @internal
30332
+ */
30333
+ const depositQuotePayloadSchema = z.object({
30334
+ vaultAddress: z.string(),
30335
+ vaultName: z.string(),
30336
+ asset: z.string(),
30337
+ depositAmount: amountJsonSchema,
30338
+ expectedShares: amountJsonSchema,
30339
+ sharePrice: z.string(),
30340
+ currentApy: z.number(),
30341
+ });
30342
+ /**
30343
+ * Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
30344
+ *
30345
+ * The Zenith API wraps the payload in a `data` envelope.
30346
+ *
30347
+ * @internal
30348
+ */
30349
+ const depositQuoteResponseSchema = z.object({
30350
+ data: depositQuotePayloadSchema,
30351
+ });
30352
+ // ---------------------------------------------------------------------------
30353
+ // Withdrawal quote response schema
30354
+ // ---------------------------------------------------------------------------
30355
+ /**
30356
+ * Zod schema for a withdrawal quote fee in the API response.
30357
+ *
30358
+ * @internal
30359
+ */
30360
+ const withdrawalQuoteFeeSchema = z.object({
30361
+ token: z.string(),
30362
+ amount: amountJsonSchema,
30363
+ });
30364
+ /**
30365
+ * Zod schema for the inner withdrawal quote payload.
30366
+ *
30367
+ * @internal
30368
+ */
30369
+ const withdrawalQuotePayloadSchema = z.object({
30370
+ vaultAddress: z.string(),
30371
+ vaultName: z.string(),
30372
+ asset: z.string(),
30373
+ withdrawAmount: amountJsonSchema,
30374
+ sharesToRedeem: amountJsonSchema,
30375
+ sharePrice: z.string(),
30376
+ maxWithdrawable: amountJsonSchema,
30377
+ fees: z.array(withdrawalQuoteFeeSchema),
30378
+ warnings: z.array(z.string()).optional(),
30379
+ });
30380
+ /**
30381
+ * Zod schema for the `POST /v1/earnKit/withdrawal/quote` API response.
30382
+ *
30383
+ * The Zenith API wraps the payload in a `data` envelope.
30384
+ *
30385
+ * @internal
30386
+ */
30387
+ const withdrawalQuoteResponseSchema = z.object({
30388
+ data: withdrawalQuotePayloadSchema,
30389
+ });
30390
+ // ---------------------------------------------------------------------------
30391
+ // Claim rewards quote response schema
30392
+ // ---------------------------------------------------------------------------
30393
+ /**
30394
+ * Zod schema for a reward in the claim rewards quote response.
30395
+ *
30396
+ * @internal
30397
+ */
30398
+ const claimRewardsQuoteRewardSchema = z.object({
30399
+ token: z.string(),
30400
+ symbol: z.string(),
30401
+ amount: amountJsonSchema,
30402
+ });
30403
+ /**
30404
+ * Zod schema for the inner claim rewards quote payload.
30405
+ *
30406
+ * @internal
30407
+ */
30408
+ const claimRewardsQuotePayloadSchema = z.object({
30409
+ rewards: z.array(claimRewardsQuoteRewardSchema),
30410
+ });
30411
+ /**
30412
+ * Zod schema for the `POST /v1/earnKit/claimRewards/quote` API response.
30413
+ *
30414
+ * The Zenith API wraps the payload in a `data` envelope.
30415
+ *
30416
+ * @internal
30417
+ */
30418
+ const claimRewardsQuoteResponseSchema = z.object({
30419
+ data: claimRewardsQuotePayloadSchema,
30420
+ });
30421
+ // ---------------------------------------------------------------------------
30422
+ // Batch vault response schema
30423
+ // ---------------------------------------------------------------------------
30424
+ /**
30425
+ * Zod schema for a per-vault error in the batch vault API response.
30426
+ *
30427
+ * @internal
30428
+ */
30429
+ const vaultErrorSchema = z.object({
30430
+ chain: z.string(),
30431
+ vaultAddress: z.string(),
30432
+ code: z.number(),
30433
+ message: z.string(),
30434
+ });
30435
+ /**
30436
+ * Zod schema for the inner batch vault payload.
30437
+ *
30438
+ * @internal
30439
+ */
30440
+ const getVaultsPayloadSchema = z.object({
30441
+ vaults: z.array(vaultInfoResponseSchema),
30442
+ errors: z.array(vaultErrorSchema),
30443
+ });
30444
+ /**
30445
+ * Zod schema for the `GET /v1/earnKit/vaults` batch API response.
30446
+ *
30447
+ * The Earn Service API wraps the vault payload in a `data` envelope.
30448
+ *
30449
+ * @internal
30450
+ */
30451
+ const getVaultsResponseSchema = z.object({
30452
+ data: getVaultsPayloadSchema,
30453
+ });
30454
+
30455
+ /**
30456
+ * Type guard for the batch vault lookup API response.
30457
+ *
30458
+ * @param value - Unknown response value to validate
30459
+ * @returns True when the value matches the batch vault response shape
30460
+ *
30461
+ * @internal
30462
+ */
30463
+ function isGetVaultsResponse(value) {
30464
+ return getVaultsResponseSchema.safeParse(value).success;
30465
+ }
30466
+ /**
30467
+ * Type guard for the position API response.
30468
+ *
30469
+ * @param value - Unknown response value to validate
30470
+ * @returns True when the value matches the position response shape
30471
+ *
30472
+ * @internal
30473
+ */
30474
+ function isPositionResponse(value) {
30475
+ return positionResponseSchema.safeParse(value).success;
30476
+ }
30477
+ /**
30478
+ * Type guard for the deposit API response.
30479
+ *
30480
+ * @param value - Unknown response value to validate
30481
+ * @returns True when the value matches the deposit response shape
30482
+ *
30483
+ * @internal
30484
+ */
30485
+ function isDepositResponse(value) {
30486
+ return depositResponseSchema.safeParse(value).success;
30487
+ }
30488
+ /**
30489
+ * Type guard for the withdraw API response.
30490
+ *
30491
+ * @param value - Unknown response value to validate
30492
+ * @returns True when the value matches the withdraw response shape
30493
+ *
30494
+ * @internal
30495
+ */
30496
+ function isWithdrawResponse(value) {
30497
+ return withdrawResponseSchema.safeParse(value).success;
30498
+ }
30499
+ /**
30500
+ * Type guard for the claim rewards API response.
30501
+ *
30502
+ * @param value - Unknown response value to validate
30503
+ * @returns True when the value matches the claim rewards response shape
30504
+ *
30505
+ * @internal
30506
+ */
30507
+ function isClaimRewardsResponse(value) {
30508
+ return claimRewardsResponseSchema.safeParse(value).success;
30509
+ }
30510
+ /**
30511
+ * Type guard for the deposit quote API response.
30512
+ *
30513
+ * @param value - Unknown response value to validate
30514
+ * @returns True when the value matches the deposit quote response shape
30515
+ *
30516
+ * @internal
30517
+ */
30518
+ function isDepositQuoteResponse(value) {
30519
+ return depositQuoteResponseSchema.safeParse(value).success;
30520
+ }
30521
+ /**
30522
+ * Type guard for the withdrawal quote API response.
30523
+ *
30524
+ * @param value - Unknown response value to validate
30525
+ * @returns True when the value matches the withdrawal quote response shape
30526
+ *
30527
+ * @internal
30528
+ */
30529
+ function isWithdrawalQuoteResponse(value) {
30530
+ return withdrawalQuoteResponseSchema.safeParse(value).success;
30531
+ }
30532
+ /**
30533
+ * Type guard for the claim rewards quote API response.
30534
+ *
30535
+ * @param value - Unknown response value to validate
30536
+ * @returns True when the value matches the claim rewards quote response shape
30537
+ *
30538
+ * @internal
30539
+ */
30540
+ function isClaimRewardsQuoteResponse(value) {
30541
+ return claimRewardsQuoteResponseSchema.safeParse(value).success;
30542
+ }
30543
+
30544
+ /**
30545
+ * Build an API polling config with optional authorization header,
30546
+ * and resolve the base URL (configurable for testing).
30547
+ *
30548
+ * @param serviceConfig - Optional earn service configuration
30549
+ * @returns Resolved polling config and base URL
30550
+ *
30551
+ * @internal
30552
+ */
30553
+ function buildConfig(serviceConfig) {
30554
+ const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
30555
+ if (serviceConfig?.kitKey === undefined) {
30556
+ return { pollingConfig: DEFAULT_CONFIG, baseUrl };
30557
+ }
30558
+ if (!isValidApiKey(serviceConfig.kitKey)) {
30559
+ throw new KitError({
30560
+ ...InputError.VALIDATION_FAILED,
30561
+ recoverability: 'FATAL',
30562
+ message: 'Invalid kitKey format. Expected KIT_KEY:<keyId>:<keySecret>.',
30563
+ });
30564
+ }
30565
+ return {
30566
+ pollingConfig: {
30567
+ ...DEFAULT_CONFIG,
30568
+ headers: {
30569
+ ...DEFAULT_CONFIG.headers,
30570
+ Authorization: `Bearer ${serviceConfig.kitKey}`,
30571
+ },
30572
+ },
30573
+ baseUrl,
30574
+ };
30575
+ }
30576
+
30577
+ function toVaultInfo(data) {
30578
+ const { totalDeposits, liquidity, ...vault } = data;
30579
+ const chain = toSdkChain(vault.chain);
30580
+ if (chain === undefined) {
30581
+ throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
30582
+ }
30583
+ return {
30584
+ ...vault,
30585
+ chain,
30586
+ totalDeposits: Amount.fromJSON(totalDeposits),
30587
+ liquidity: Amount.fromJSON(liquidity),
30588
+ };
30589
+ }
30590
+ function toVaultError(error) {
30591
+ const chain = toSdkChain(error.chain);
30592
+ if (chain === undefined) {
30593
+ throw createInvalidChainError(error.chain, 'Chain returned by the Earn Service is not supported by the SDK');
30594
+ }
30595
+ return { ...error, chain };
30596
+ }
30597
+ function getVaultQueryChainLabel(chain) {
30598
+ return typeof chain === 'string' ? chain : 'unknown';
30599
+ }
30600
+ function resolveVaultQueryChain(chain) {
30601
+ try {
30602
+ return resolveChainIdentifier(chain);
30603
+ }
30604
+ catch {
30605
+ throw createInvalidChainError(getVaultQueryChainLabel(chain), 'Chain is not supported by the Earn Service provider');
30606
+ }
30607
+ }
30608
+ /**
30609
+ * Fetch vault information from the Earn Service API.
30610
+ *
30611
+ * Call `GET /v1/earnKit/vaults` with repeated chain and vaultAddress
30612
+ * query parameters for batch vault lookup.
30613
+ *
30614
+ * @param params - Vault query parameters
30615
+ * @returns Batch result with vaults and per-vault errors
30616
+ * @throws {@link KitError} When the API call fails
30617
+ *
30618
+ * @internal
30619
+ */
30620
+ async function fetchVaults(params) {
30621
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30622
+ const url = new URL(`${EARN_KIT_API_PREFIX}/vaults`, baseUrl);
30623
+ // The API pairs chain[i] with vaultAddress[i] by insertion order.
30624
+ for (const vault of params.vaults) {
30625
+ const chainDefinition = resolveVaultQueryChain(vault.chain);
30626
+ const apiChain = toApiChain(chainDefinition.chain);
30627
+ if (apiChain === undefined) {
30628
+ throw createInvalidChainError(chainDefinition.chain, 'Chain is not supported by the Earn Service provider');
30629
+ }
30630
+ url.searchParams.append('chain', apiChain);
30631
+ url.searchParams.append('vaultAddress', vault.vaultAddress);
30632
+ }
30633
+ try {
30634
+ const response = await pollApiGet(url.toString(), isGetVaultsResponse, pollingConfig);
30635
+ return {
30636
+ vaults: response.data.vaults.map(toVaultInfo),
30637
+ errors: response.data.errors.map(toVaultError),
30638
+ };
30639
+ }
30640
+ catch (error) {
30641
+ throw parseEarnApiError(error, { operation: 'getVaults' });
30642
+ }
30643
+ }
30644
+
30645
+ function toAccruedReward(reward) {
30646
+ return {
30647
+ tokenAddress: reward.token,
30648
+ symbol: reward.symbol,
30649
+ amount: Amount.fromJSON(reward.amount),
30650
+ };
30651
+ }
30652
+ function assertNever(value) {
30653
+ throw new KitError({
30654
+ ...EarnError.INTERNAL_ERROR,
30655
+ recoverability: 'FATAL',
30656
+ message: `Unhandled PnL status: ${JSON.stringify(value)}`,
30657
+ cause: {
30658
+ trace: value,
30659
+ },
30660
+ });
30661
+ }
30662
+ function toPositionPnL(data) {
30663
+ switch (data.status) {
30664
+ case 'available':
30665
+ return {
30666
+ status: 'available',
30667
+ principalDeposited: Amount.fromJSON(data.principalDeposited),
30668
+ totalYieldEarned: Amount.fromJSON(data.totalYieldEarned),
30669
+ };
30670
+ case 'pending':
30671
+ return { status: 'pending' };
30672
+ case 'unavailable':
30673
+ return { status: 'unavailable', reason: data.reason };
30674
+ default:
30675
+ return assertNever(data);
30676
+ }
30677
+ }
30678
+ function toPositionInfo(data) {
30679
+ const { currentBalance, shares, pnl, accruedRewards = [], ...position } = data;
30680
+ const chain = toSdkChain(position.chain);
30681
+ if (chain === undefined) {
30682
+ throw new KitError({
30683
+ ...EarnError.INTERNAL_ERROR,
30684
+ recoverability: 'FATAL',
30685
+ message: `Unsupported Earn Service chain: ${position.chain}`,
30686
+ cause: {
30687
+ trace: {
30688
+ chain: position.chain,
30689
+ },
30690
+ },
30691
+ });
30692
+ }
30693
+ return {
30694
+ ...position,
30695
+ chain,
30696
+ currentBalance: Amount.fromJSON(currentBalance),
30697
+ shares: Amount.fromJSON(shares),
30698
+ pnl: toPositionPnL(pnl),
30699
+ accruedRewards: accruedRewards.map(toAccruedReward),
30700
+ };
30701
+ }
30702
+ /**
30703
+ * Fetch a user's vault position from the Earn Service API.
30704
+ *
30705
+ * Call `GET /v1/earnKit/position/{address}` with the provided chain
30706
+ * and vault address as query parameters.
30707
+ *
30708
+ * @param params - Position query parameters
30709
+ * @returns The user's position information
30710
+ * @throws {@link KitError} When the API call fails
30711
+ *
30712
+ * @internal
30713
+ */
30714
+ async function fetchPosition(params) {
30715
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30716
+ const url = new URL(`${EARN_KIT_API_PREFIX}/position/${encodeURIComponent(params.address)}`, baseUrl);
30717
+ url.searchParams.set('chain', params.chain);
30718
+ url.searchParams.set('vaultAddress', params.vaultAddress);
30719
+ try {
30720
+ const response = await pollApiGet(url.toString(), isPositionResponse, pollingConfig);
30721
+ return toPositionInfo(response.data);
30722
+ }
30723
+ catch (error) {
30724
+ throw parseEarnApiError(error, { operation: 'getPosition' });
30725
+ }
30726
+ }
30727
+
30728
+ /**
30729
+ * Build signed deposit instructions via the Earn Service API.
30730
+ *
30731
+ * Call `POST /v1/earnKit/deposit` with the vault address, amount,
30732
+ * wallet address, and chain. Return EIP-712 signed execution
30733
+ * parameters for on-chain submission.
30734
+ *
30735
+ * @param params - Deposit parameters
30736
+ * @returns Signed execution parameters for the deposit
30737
+ * @throws {@link KitError} When the API call fails
30738
+ *
30739
+ * @internal
30740
+ */
30741
+ async function fetchDeposit(params) {
30742
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30743
+ const url = new URL(`${EARN_KIT_API_PREFIX}/deposit`, baseUrl);
30744
+ const requestBody = {
30745
+ vaultAddress: params.vaultAddress,
30746
+ amount: params.amount,
30747
+ address: params.address,
30748
+ chain: params.chain,
30749
+ };
30750
+ try {
30751
+ const response = await pollApiPost(url.toString(), requestBody, isDepositResponse, pollingConfig);
30752
+ return response.data;
30753
+ }
30754
+ catch (error) {
30755
+ throw parseEarnApiError(error, { operation: 'deposit' });
30756
+ }
30757
+ }
30758
+
30759
+ /**
30760
+ * Build signed withdrawal instructions via the Earn Service API.
30761
+ *
30762
+ * Call `POST /v1/earnKit/withdraw` with the vault address, amount,
30763
+ * wallet address, and chain. Return EIP-712 signed execution
30764
+ * parameters for on-chain submission.
30765
+ *
30766
+ * @param params - Withdrawal parameters
30767
+ * @returns Signed execution parameters for the withdrawal
30768
+ * @throws {@link KitError} When the API call fails
30769
+ *
30770
+ * @internal
30771
+ */
30772
+ async function fetchWithdraw(params) {
30773
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30774
+ const url = new URL(`${EARN_KIT_API_PREFIX}/withdraw`, baseUrl);
30775
+ const requestBody = {
30776
+ vaultAddress: params.vaultAddress,
30777
+ amount: params.amount,
30778
+ address: params.address,
30779
+ chain: params.chain,
30780
+ };
30781
+ try {
30782
+ const response = await pollApiPost(url.toString(), requestBody, isWithdrawResponse, pollingConfig);
30783
+ return response.data;
30784
+ }
30785
+ catch (error) {
30786
+ throw parseEarnApiError(error, { operation: 'withdraw' });
30787
+ }
30788
+ }
30789
+
30790
+ function toClaimedAmount(reward) {
30791
+ return {
30792
+ address: reward.token,
30793
+ symbol: reward.symbol,
30794
+ amount: Amount.fromJSON(reward.amount),
30795
+ };
30796
+ }
30797
+ /**
30798
+ * Build signed claim rewards instructions via the Earn Service API.
30799
+ *
30800
+ * Call `POST /v1/earnKit/claimRewards` with the wallet address, chain, and
30801
+ * vault address. Return EIP-712 signed execution parameters for on-chain
30802
+ * submission. When no rewards are claimable, return an empty rewards array
30803
+ * without execution parameters.
30804
+ *
30805
+ * @param params - Claim rewards parameters
30806
+ * @returns Signed execution parameters and reward details
30807
+ * @throws {@link KitError} When the API call fails
30808
+ *
30809
+ * @internal
30810
+ */
30811
+ async function fetchClaimRewards(params) {
30812
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30813
+ const url = new URL(`${EARN_KIT_API_PREFIX}/claimRewards`, baseUrl);
30814
+ const requestBody = {
30815
+ address: params.address,
30816
+ chain: params.chain,
30817
+ vaultAddress: params.vaultAddress,
30818
+ };
30819
+ try {
30820
+ const response = await pollApiPost(url.toString(), requestBody, isClaimRewardsResponse, pollingConfig);
30821
+ const { rewards, executionParams, signature } = response.data;
30822
+ return {
30823
+ rewards: rewards.map(toClaimedAmount),
30824
+ ...(executionParams === undefined ? {} : { executionParams }),
30825
+ ...(signature === undefined ? {} : { signature }),
30826
+ };
30827
+ }
30828
+ catch (error) {
30829
+ throw parseEarnApiError(error, { operation: 'claimRewards' });
30830
+ }
30831
+ }
30832
+
30833
+ function toDepositQuoteInfo(data) {
30834
+ return {
30835
+ vaultAddress: data.vaultAddress,
30836
+ vaultName: data.vaultName,
30837
+ deposit: {
30838
+ symbol: data.asset,
30839
+ amount: Amount.fromJSON(data.depositAmount),
30840
+ },
30841
+ expectedShares: {
30842
+ symbol: VAULT_SHARE_SYMBOL,
30843
+ address: data.vaultAddress,
30844
+ amount: Amount.fromJSON(data.expectedShares),
30845
+ },
30846
+ sharePrice: data.sharePrice,
30847
+ currentApy: data.currentApy,
30848
+ fees: [],
30849
+ };
30850
+ }
30851
+ /**
30852
+ * Fetch a deposit quote from the Earn Service API.
30853
+ *
30854
+ * Call `POST /v1/earnKit/deposit/quote` with the vault address, amount,
30855
+ * wallet address, and chain. Return informational quote data including
30856
+ * expected shares, share price, and APY.
30857
+ *
30858
+ * @param params - Deposit quote parameters
30859
+ * @returns Deposit quote information
30860
+ * @throws {@link KitError} When the API call fails
30861
+ *
30862
+ * @internal
30863
+ */
30864
+ async function fetchDepositQuote(params) {
30865
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30866
+ const url = new URL(`${EARN_KIT_API_PREFIX}/deposit/quote`, baseUrl);
30867
+ const requestBody = {
30868
+ vaultAddress: params.vaultAddress,
30869
+ amount: params.amount,
30870
+ address: params.address,
30871
+ chain: params.chain,
30872
+ };
30873
+ try {
30874
+ const response = await pollApiPost(url.toString(), requestBody, isDepositQuoteResponse, pollingConfig);
30875
+ return toDepositQuoteInfo(response.data);
30876
+ }
30877
+ catch (error) {
30878
+ throw parseEarnApiError(error, { operation: 'getDepositQuote' });
30879
+ }
30880
+ }
30881
+
30882
+ function toWithdrawalQuoteInfo(data) {
30883
+ return {
30884
+ vaultAddress: data.vaultAddress,
30885
+ vaultName: data.vaultName,
30886
+ withdrawal: {
30887
+ symbol: data.asset,
30888
+ amount: Amount.fromJSON(data.withdrawAmount),
30889
+ },
30890
+ sharesToRedeem: {
30891
+ symbol: VAULT_SHARE_SYMBOL,
30892
+ address: data.vaultAddress,
30893
+ amount: Amount.fromJSON(data.sharesToRedeem),
30894
+ },
30895
+ sharePrice: data.sharePrice,
30896
+ maxWithdrawable: {
30897
+ symbol: data.asset,
30898
+ amount: Amount.fromJSON(data.maxWithdrawable),
30899
+ },
30900
+ fees: data.fees.map((fee) => ({
30901
+ symbol: fee.token,
30902
+ amount: Amount.fromJSON(fee.amount),
30903
+ })),
30904
+ // Wire format uses `warnings`, but the SDK surface uses
30905
+ // `earnKitWarnings` to match the precedent set by `VaultInfo` —
30906
+ // `warnings` is reserved for the structured `VaultWarning` shape.
30907
+ ...(data.warnings !== undefined && { earnKitWarnings: data.warnings }),
30908
+ };
30909
+ }
30910
+ /**
30911
+ * Fetch a withdrawal quote from the Earn Service API.
30912
+ *
30913
+ * Call `POST /v1/earnKit/withdrawal/quote` with the vault address, amount,
30914
+ * wallet address, and chain. Return informational quote data including
30915
+ * shares to redeem, max withdrawable, and fees.
30916
+ *
30917
+ * @param params - Withdrawal quote parameters
30918
+ * @returns Withdrawal quote information
30919
+ * @throws {@link KitError} When the API call fails
30920
+ *
30921
+ * @internal
30922
+ */
30923
+ async function fetchWithdrawalQuote(params) {
30924
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30925
+ const url = new URL(`${EARN_KIT_API_PREFIX}/withdrawal/quote`, baseUrl);
30926
+ const requestBody = {
30927
+ vaultAddress: params.vaultAddress,
30928
+ amount: params.amount,
30929
+ address: params.address,
30930
+ chain: params.chain,
30931
+ };
30932
+ try {
30933
+ const response = await pollApiPost(url.toString(), requestBody, isWithdrawalQuoteResponse, pollingConfig);
30934
+ return toWithdrawalQuoteInfo(response.data);
30935
+ }
30936
+ catch (error) {
30937
+ throw parseEarnApiError(error, { operation: 'getWithdrawalQuote' });
30938
+ }
30939
+ }
30940
+
30941
+ /**
30942
+ * Fetch a claim rewards quote from the Earn Service API.
30943
+ *
30944
+ * Call `POST /v1/earnKit/claimRewards/quote` with the wallet address,
30945
+ * chain, and vault address. Return informational quote data including
30946
+ * claimable reward details.
30947
+ *
30948
+ * @param params - Claim rewards quote parameters
30949
+ * @returns Claim rewards quote information
30950
+ * @throws {@link KitError} When the API call fails
30951
+ *
30952
+ * @internal
30953
+ */
30954
+ async function fetchClaimRewardsQuote(params) {
30955
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30956
+ const url = new URL(`${EARN_KIT_API_PREFIX}/claimRewards/quote`, baseUrl);
30957
+ const requestBody = {
30958
+ vaultAddress: params.vaultAddress,
30959
+ address: params.address,
30960
+ chain: params.chain,
30961
+ };
30962
+ try {
30963
+ const response = await pollApiPost(url.toString(), requestBody, isClaimRewardsQuoteResponse, pollingConfig);
30964
+ return {
30965
+ rewards: response.data.rewards.map((r) => ({
30966
+ symbol: r.symbol,
30967
+ amount: Amount.fromJSON(r.amount),
30968
+ address: r.token,
30969
+ })),
30970
+ };
30971
+ }
30972
+ catch (error) {
30973
+ throw parseEarnApiError(error, { operation: 'getClaimRewardsQuote' });
30974
+ }
30975
+ }
30976
+
30977
+ /**
30978
+ * Earn Service provider for the Circle earn service API.
30979
+ *
30980
+ * Implement the {@link EarningProvider} interface for yield-bearing vault
30981
+ * operations including vault discovery, position queries, deposits,
30982
+ * withdrawals, and reward claiming.
30983
+ *
30984
+ * @example
30985
+ * ```typescript
30986
+ * import { EarnServiceProvider } from '@circle-fin/provider-earn-service'
30987
+ *
30988
+ * // Permissionless mode
30989
+ * const provider = new EarnServiceProvider()
30990
+ *
30991
+ * // Permissioned mode with Kit Key
30992
+ * const provider = new EarnServiceProvider({
30993
+ * kitKey: 'KIT_KEY:keyId:keySecret',
30994
+ * })
30995
+ *
30996
+ * // Custom base URL for testing
30997
+ * const provider = new EarnServiceProvider({
30998
+ * baseUrl: 'https://api-staging.circle.com',
30999
+ * })
31000
+ *
31001
+ * const result = await provider.getVaults({
31002
+ * vaults: [
31003
+ * { chain: 'Arc_Testnet', vaultAddress: '0x8eB67...' },
31004
+ * ],
31005
+ * })
31006
+ * ```
31007
+ */
31008
+ class EarnServiceProvider {
31009
+ /** {@inheritdoc} */
31010
+ name = 'EarnService';
31011
+ /** {@inheritdoc} */
31012
+ supportedChains = Object.keys(CHAIN_TO_API).map((chain) => resolveChainIdentifier(chain));
31013
+ defaultConfig;
31014
+ /**
31015
+ * Create a new EarnServiceProvider.
31016
+ *
31017
+ * @param config - Optional default configuration applied to all operations.
31018
+ * Per-operation config takes precedence when provided.
31019
+ */
31020
+ constructor(config) {
31021
+ this.defaultConfig = config;
31022
+ }
31023
+ resolveConfig(config) {
31024
+ const resolvedConfig = this.defaultConfig === undefined
31025
+ ? config
31026
+ : { ...this.defaultConfig, ...config };
31027
+ return resolvedConfig;
31028
+ }
31029
+ /** {@inheritdoc} */
31030
+ async getVaults(params) {
31031
+ const config = this.resolveConfig(params.config);
31032
+ return fetchVaults({
31033
+ vaults: params.vaults,
31034
+ config,
31035
+ });
31036
+ }
31037
+ /** {@inheritdoc} */
31038
+ async getPosition(params) {
31039
+ const config = this.resolveConfig(params.config);
31040
+ const { address, chain } = await resolveAdapterContext(params.from);
31041
+ return fetchPosition({
31042
+ address,
31043
+ chain,
31044
+ vaultAddress: params.vaultAddress,
31045
+ config,
31046
+ });
31047
+ }
31048
+ /** {@inheritdoc} */
31049
+ async deposit(params) {
31050
+ const config = this.resolveConfig(params.config);
31051
+ const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
31052
+ const adapterContractAddress = requireAdapterContract(chain);
31053
+ const rawUsdcAddress = chain.usdcAddress;
31054
+ if (rawUsdcAddress === null) {
31055
+ throw createUnsupportedTokenError('USDC', chain.name);
31056
+ }
31057
+ const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
31058
+ const { executionParams, signature } = await fetchDeposit({
31059
+ vaultAddress: params.vaultAddress,
31060
+ amount: params.amount,
31061
+ address,
31062
+ chain: apiChain,
31063
+ config,
31064
+ });
31065
+ validateExecutionDeadline(executionParams);
31066
+ const { adapter } = params.from;
31067
+ const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
31068
+ const approvalToken = tokenInputs[0]?.token;
31069
+ if (approvalToken !== undefined) {
31070
+ await approveMaxIfNeeded({
31071
+ adapter,
31072
+ chain,
31073
+ tokenAddress: approvalToken,
31074
+ delegate: adapterContractAddress,
31075
+ address,
31076
+ revertMessage: 'USDC approval reverted on-chain',
31077
+ });
31078
+ }
31079
+ const { txHash, explorerUrl } = await executeEarnAction({
31080
+ adapter,
31081
+ chain,
31082
+ address,
31083
+ actionKey: 'earn.deposit',
31084
+ actionParams: {
31085
+ executeParams: executionParams,
31086
+ tokenInputs,
31087
+ signature,
31088
+ },
31089
+ revertMessage: 'Earn deposit reverted on-chain',
31090
+ });
31091
+ return {
31092
+ txHash,
31093
+ explorerUrl,
31094
+ vaultAddress: params.vaultAddress,
31095
+ amount: params.amount,
31096
+ };
31097
+ }
31098
+ /** {@inheritdoc} */
31099
+ async withdraw(params) {
31100
+ const config = this.resolveConfig(params.config);
31101
+ const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
31102
+ const adapterContractAddress = requireAdapterContract(chain);
31103
+ const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
31104
+ const { executionParams, signature } = await fetchWithdraw({
31105
+ vaultAddress,
31106
+ amount: params.amount,
31107
+ address,
31108
+ chain: apiChain,
31109
+ config,
31110
+ });
31111
+ validateExecutionDeadline(executionParams);
31112
+ const { adapter } = params.from;
31113
+ const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
31114
+ const approvalToken = tokenInputs[0]?.token;
31115
+ if (approvalToken !== undefined) {
31116
+ await approveMaxIfNeeded({
31117
+ adapter,
31118
+ chain,
31119
+ tokenAddress: approvalToken,
31120
+ delegate: adapterContractAddress,
31121
+ address,
31122
+ revertMessage: 'Vault share token approval reverted on-chain',
31123
+ });
31124
+ }
31125
+ const { txHash, explorerUrl } = await executeEarnAction({
31126
+ adapter,
31127
+ chain,
31128
+ address,
31129
+ actionKey: 'earn.withdraw',
31130
+ actionParams: {
31131
+ executeParams: executionParams,
31132
+ tokenInputs,
31133
+ signature,
31134
+ },
31135
+ revertMessage: 'Earn withdraw reverted on-chain',
31136
+ });
31137
+ return {
31138
+ txHash,
31139
+ explorerUrl,
31140
+ vaultAddress,
31141
+ amount: params.amount,
31142
+ };
31143
+ }
31144
+ /** {@inheritdoc} */
31145
+ async claimRewards(params) {
31146
+ const config = this.resolveConfig(params.config);
31147
+ const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
31148
+ // Claim rewards has no approval step, but still requires adapter support.
31149
+ requireAdapterContract(chain);
31150
+ const { rewards, executionParams, signature } = await fetchClaimRewards({
31151
+ address,
31152
+ chain: apiChain,
31153
+ vaultAddress: params.vaultAddress,
31154
+ config,
31155
+ });
31156
+ const nothingToClaim = rewards.length === 0;
31157
+ if (nothingToClaim) {
31158
+ return { status: 'no_rewards', rewards: [] };
31159
+ }
31160
+ const missingExecutionParams = executionParams === undefined;
31161
+ const missingSignature = signature === undefined;
31162
+ if (missingExecutionParams || missingSignature) {
31163
+ throw new KitError({
31164
+ ...EarnError.INTERNAL_ERROR,
31165
+ recoverability: 'RETRYABLE',
31166
+ message: 'Claim rewards response must include executionParams and signature when rewards are claimable',
31167
+ cause: {
31168
+ trace: {
31169
+ rewardsCount: rewards.length,
31170
+ missingExecutionParams,
31171
+ missingSignature,
31172
+ },
31173
+ },
31174
+ });
31175
+ }
31176
+ validateExecutionDeadline(executionParams);
31177
+ const { txHash, explorerUrl } = await executeEarnAction({
31178
+ adapter: params.from.adapter,
31179
+ chain,
31180
+ address,
31181
+ actionKey: 'earn.claimRewards',
31182
+ actionParams: {
31183
+ executeParams: executionParams,
31184
+ tokenInputs: [],
31185
+ signature,
31186
+ },
31187
+ revertMessage: 'Earn claim rewards reverted on-chain',
31188
+ });
31189
+ return { status: 'claimed', rewards, txHash, explorerUrl };
31190
+ }
31191
+ /** {@inheritdoc} */
31192
+ async getDepositQuote(params) {
31193
+ const config = this.resolveConfig(params.config);
31194
+ const { address, chain } = await resolveAdapterContext(params.from);
31195
+ return fetchDepositQuote({
31196
+ vaultAddress: params.vaultAddress,
31197
+ amount: params.amount,
31198
+ address,
31199
+ chain,
31200
+ config,
31201
+ });
31202
+ }
31203
+ /** {@inheritdoc} */
31204
+ async getWithdrawalQuote(params) {
31205
+ const config = this.resolveConfig(params.config);
31206
+ const { address, chain } = await resolveAdapterContext(params.from);
31207
+ return fetchWithdrawalQuote({
31208
+ vaultAddress: params.vaultAddress,
31209
+ amount: params.amount,
31210
+ address,
31211
+ chain,
31212
+ config,
31213
+ });
31214
+ }
31215
+ /** {@inheritdoc} */
31216
+ async getClaimRewardsQuote(params) {
31217
+ const config = this.resolveConfig(params.config);
31218
+ const { address, chain } = await resolveAdapterContext(params.from);
31219
+ return fetchClaimRewardsQuote({
31220
+ vaultAddress: params.vaultAddress,
31221
+ address,
31222
+ chain,
31223
+ config,
31224
+ });
31225
+ }
31226
+ }
31227
+
31228
+ /**
31229
+ * The default providers used when no custom providers are specified.
31230
+ *
31231
+ * @returns An array containing the default EarnServiceProvider
31232
+ * @internal
31233
+ */
31234
+ const getDefaultProviders$1 = () => [new EarnServiceProvider()];
31235
+ /**
31236
+ * Create an EarnKit context with validated configuration.
31237
+ *
31238
+ * Initialize an EarnKitContext with default providers and optional
31239
+ * custom configuration. Custom and default providers are merged,
31240
+ * preserving their exact types for type safety.
31241
+ *
31242
+ * @typeParam TExtraProviders - Array type of additional earn providers
31243
+ * @param config - Optional configuration for the EarnKit context
31244
+ * @returns A fully initialized EarnKitContext ready for earn operations
31245
+ *
31246
+ * @example
31247
+ * ```typescript
31248
+ * import { createEarnKitContext } from '@circle-fin/earn-kit'
31249
+ *
31250
+ * // Create context with defaults
31251
+ * const context = createEarnKitContext()
31252
+ * ```
31253
+ *
31254
+ * @example
31255
+ * ```typescript
31256
+ * import { createEarnKitContext } from '@circle-fin/earn-kit'
31257
+ *
31258
+ * // Create context with custom providers
31259
+ * const context = createEarnKitContext({
31260
+ * providers: [myCustomEarnProvider],
31261
+ * })
31262
+ * ```
31263
+ */
31264
+ function createEarnKitContext(config = {}) {
31265
+ const defaultProviders = getDefaultProviders$1();
31266
+ const providers = [...(config.providers ?? []), ...defaultProviders];
31267
+ const context = {
31268
+ providers,
31269
+ };
31270
+ return context;
31271
+ }
31272
+
31273
+ /**
31274
+ * Symbol used to track that assertEarnParams has validated an object.
31275
+ * @internal
31276
+ */
31277
+ const ASSERT_EARN_PARAMS_SYMBOL = Symbol('assertEarnParams');
31278
+ /**
31279
+ * Assert that the provided value conforms to the given earn params schema.
31280
+ *
31281
+ * Validate earn parameters using the provided Zod schema and track
31282
+ * validation state to avoid duplicate checks. Throw a structured
31283
+ * error with detailed validation messages if any parameter is invalid.
31284
+ *
31285
+ * @typeParam T - The expected type after validation
31286
+ * @param params - The earn parameters to validate
31287
+ * @param schema - The Zod schema to validate against
31288
+ * @throws {@link KitError} If the parameters fail validation
31289
+ *
31290
+ * @example
31291
+ * ```typescript
31292
+ * import { assertEarnParams, depositParamsSchema } from '@circle-fin/earn-kit'
31293
+ *
31294
+ * assertEarnParams(params, depositParamsSchema)
31295
+ * ```
31296
+ */
31297
+ function assertEarnParams(params, schema) {
31298
+ validateWithStateTracking(params, schema, 'earn parameters', ASSERT_EARN_PARAMS_SYMBOL);
31299
+ }
31300
+
31301
+ function findProvider(context, chain, operation = 'earn') {
31302
+ let fallback;
31303
+ for (const provider of context.providers) {
31304
+ fallback ??= provider;
31305
+ if (provider.supportedChains.length === 0)
31306
+ continue;
31307
+ if (chain === undefined ||
31308
+ provider.supportedChains.some((c) => c.chain === chain.chain)) {
31309
+ return provider;
31310
+ }
31311
+ }
31312
+ if (fallback === undefined) {
31313
+ throw createValidationFailedError$1('context.providers', [], 'No earn providers configured');
31314
+ }
31315
+ if (chain !== undefined) {
31316
+ throw createUnsupportedEarnRouteError(operation, chain.name);
31317
+ }
31318
+ return fallback;
31319
+ }
31320
+
31321
+ /**
31322
+ * Format a provider amount object as a human-readable decimal string.
31323
+ *
31324
+ * The kit result surface intentionally exposes only the decimal string value,
31325
+ * so nested provider amount metadata is not retained at this boundary.
31326
+ *
31327
+ * @param amount - Provider amount object to format
31328
+ * @returns Human-readable decimal amount string
31329
+ */
31330
+ function formatAmount$1(amount) {
31331
+ return formatUnits(amount.raw.toString(), amount.decimals);
31332
+ }
31333
+ function formatAssetAmount(assetAmount) {
31334
+ const { amount, ...rest } = assetAmount;
31335
+ return {
31336
+ ...rest,
31337
+ amount: formatAmount$1(amount),
31338
+ };
31339
+ }
31340
+ function formatClaimedAmount(claimedAmount) {
31341
+ const { amount, ...rest } = claimedAmount;
31342
+ return {
31343
+ ...rest,
31344
+ amount: formatAmount$1(amount),
31345
+ };
31346
+ }
31347
+ function formatAccruedReward(reward) {
31348
+ const { amount, ...rest } = reward;
31349
+ return {
31350
+ ...rest,
31351
+ amount: formatAmount$1(amount),
31352
+ };
31353
+ }
31354
+ function formatPositionPnL(pnl) {
31355
+ if (pnl.status !== 'available') {
31356
+ return pnl;
31357
+ }
31358
+ return {
31359
+ ...pnl,
31360
+ principalDeposited: formatAmount$1(pnl.principalDeposited),
31361
+ totalYieldEarned: formatAmount$1(pnl.totalYieldEarned),
31362
+ };
31363
+ }
31364
+ /**
31365
+ * Convert provider vault info into an EarnKit vault result.
31366
+ *
31367
+ * @param vault - Provider vault info with raw amount objects
31368
+ * @returns Vault info with total deposits and liquidity formatted as strings
31369
+ */
31370
+ function formatVaultInfo(vault) {
31371
+ const { totalDeposits, liquidity, ...rest } = vault;
31372
+ return {
31373
+ ...rest,
31374
+ totalDeposits: formatAmount$1(totalDeposits),
31375
+ liquidity: formatAmount$1(liquidity),
31376
+ };
31377
+ }
31378
+ /**
31379
+ * Convert a provider vault lookup result into an EarnKit result.
31380
+ *
31381
+ * @param result - Provider vault lookup result
31382
+ * @returns Vault lookup result with each vault amount formatted as strings
31383
+ */
31384
+ function formatGetVaultsResult(result) {
31385
+ return {
31386
+ ...result,
31387
+ vaults: result.vaults.map(formatVaultInfo),
31388
+ };
31389
+ }
31390
+ /**
31391
+ * Convert provider position info into an EarnKit position result.
31392
+ *
31393
+ * @param position - Provider position info with raw amount objects
31394
+ * @returns Position info with balances, shares, PnL, and rewards formatted as strings
31395
+ */
31396
+ function formatPositionInfo(position) {
31397
+ const { currentBalance, shares, pnl, accruedRewards, ...rest } = position;
31398
+ return {
31399
+ ...rest,
31400
+ currentBalance: formatAmount$1(currentBalance),
31401
+ shares: formatAmount$1(shares),
31402
+ pnl: formatPositionPnL(pnl),
31403
+ accruedRewards: accruedRewards.map(formatAccruedReward),
31404
+ };
31405
+ }
31406
+ /**
31407
+ * Convert a provider deposit quote into an EarnKit deposit quote result.
31408
+ *
31409
+ * @param quote - Provider deposit quote with raw amount objects
31410
+ * @returns Deposit quote with deposit, shares, and fees formatted as strings
31411
+ */
31412
+ function formatDepositQuoteInfo(quote) {
31413
+ return {
31414
+ ...quote,
31415
+ deposit: formatAssetAmount(quote.deposit),
31416
+ expectedShares: formatAssetAmount(quote.expectedShares),
31417
+ fees: quote.fees.map(formatAssetAmount),
31418
+ };
31419
+ }
31420
+ /**
31421
+ * Convert a provider withdrawal quote into an EarnKit withdrawal quote result.
31422
+ *
31423
+ * @param quote - Provider withdrawal quote with raw amount objects
31424
+ * @returns Withdrawal quote with withdrawal, shares, max, and fees formatted as strings
31425
+ */
31426
+ function formatWithdrawalQuoteInfo(quote) {
31427
+ return {
31428
+ ...quote,
31429
+ withdrawal: formatAssetAmount(quote.withdrawal),
31430
+ sharesToRedeem: formatAssetAmount(quote.sharesToRedeem),
31431
+ maxWithdrawable: formatAssetAmount(quote.maxWithdrawable),
31432
+ fees: quote.fees.map(formatAssetAmount),
31433
+ };
31434
+ }
31435
+ /**
31436
+ * Convert a provider claim-rewards quote into an EarnKit quote result.
31437
+ *
31438
+ * @param quote - Provider claim-rewards quote with raw reward amount objects
31439
+ * @returns Claim-rewards quote with rewards formatted as strings
31440
+ */
31441
+ function formatClaimRewardsQuoteInfo(quote) {
31442
+ return {
31443
+ ...quote,
31444
+ rewards: quote.rewards.map(formatAssetAmount),
31445
+ };
31446
+ }
31447
+ /**
31448
+ * Convert a provider claim-rewards result into an EarnKit result.
31449
+ *
31450
+ * @param result - Provider claim-rewards result
31451
+ * @returns Claim-rewards result with claimed reward amounts formatted as strings
31452
+ */
31453
+ function formatClaimRewardsResult(result) {
31454
+ if (result.status === 'no_rewards') {
31455
+ return result;
31456
+ }
31457
+ return {
31458
+ ...result,
31459
+ rewards: result.rewards.map(formatClaimedAmount),
31460
+ };
31461
+ }
31462
+
31463
+ /**
31464
+ * Schema for the adapter context within earn operations.
31465
+ *
31466
+ * Validate that a properly-shaped adapter instance and a valid chain
31467
+ * identifier are provided. The address field is optional (resolved from
31468
+ * the adapter at runtime).
31469
+ *
31470
+ * @internal
31471
+ */
31472
+ const adapterContextSchema$4 = z.object({
31473
+ adapter: adapterSchema$1,
31474
+ // AdapterContext accepts a wider chain field, but runtime validation must
31475
+ // stay constrained to EarnChain identifiers.
31476
+ chain: earnChainIdentifierSchema,
31477
+ address: z.string().optional(),
31478
+ });
31479
+ /**
31480
+ * Schema for the EarnConfig options.
31481
+ *
31482
+ * Validate the optional Kit Key field using the standard `apiKeySchema`
31483
+ * format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
31484
+ * operates in permissionless mode.
31485
+ *
31486
+ * @internal
31487
+ */
31488
+ const earnConfigSchema = z.object({
31489
+ kitKey: apiKeySchema.optional(),
31490
+ });
31491
+ /**
31492
+ * Schema for validating human-readable decimal amount strings.
31493
+ *
31494
+ * Accept positive decimal strings like '100', '100.50', '0.001'.
31495
+ * Reject zero, negative, and non-numeric strings. Support up to 18
31496
+ * decimal places for maximum token compatibility.
31497
+ *
31498
+ * @internal
31499
+ */
31500
+ const amountSchema$1 = z
31501
+ .string({ required_error: 'amount is required' })
31502
+ .min(1, 'amount is required')
31503
+ .pipe(createDecimalStringValidator({
31504
+ allowZero: false,
31505
+ regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
31506
+ attributeName: 'amount',
31507
+ maxDecimals: 18,
31508
+ })(z.string()));
31509
+ /**
31510
+ * Schema for vault address validation.
31511
+ *
31512
+ * Validate that the vault address is a valid EVM address (0x + 40 hex
31513
+ * chars). EarnKit currently supports EVM vault addresses on Arc Testnet.
31514
+ *
31515
+ * @internal
31516
+ */
31517
+ const vaultAddressSchema = evmAddressSchema;
31518
+ /**
31519
+ * Validation schema for VaultQuery.
31520
+ *
31521
+ * @example
31522
+ * ```typescript
31523
+ * import { vaultQuerySchema } from '@circle-fin/earn-kit'
31524
+ *
31525
+ * const result = vaultQuerySchema.safeParse({
31526
+ * chain: 'Arc_Testnet',
31527
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31528
+ * })
31529
+ * ```
31530
+ */
31531
+ const vaultQuerySchema = z.object({
31532
+ chain: earnChainIdentifierSchema,
31533
+ vaultAddress: vaultAddressSchema,
31534
+ });
31535
+ /**
31536
+ * Validation schema for GetVaultsParams.
31537
+ *
31538
+ * @example
31539
+ * ```typescript
31540
+ * import { getVaultsParamsSchema } from '@circle-fin/earn-kit'
31541
+ *
31542
+ * const result = getVaultsParamsSchema.safeParse({
31543
+ * vaults: [
31544
+ * {
31545
+ * chain: 'Arc_Testnet',
31546
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31547
+ * },
31548
+ * ],
31549
+ * })
31550
+ * ```
31551
+ */
31552
+ const getVaultsParamsSchema = z.object({
31553
+ vaults: z
31554
+ .array(vaultQuerySchema)
31555
+ .min(1, 'at least one vault query is required')
31556
+ .max(20, 'maximum 20 vault queries per request'),
31557
+ config: earnConfigSchema.optional(),
31558
+ });
31559
+ /**
31560
+ * Validation schema for GetPositionParams.
31561
+ *
31562
+ * @example
31563
+ * ```typescript
31564
+ * import { getPositionParamsSchema } from '@circle-fin/earn-kit'
31565
+ *
31566
+ * const result = getPositionParamsSchema.safeParse({
31567
+ * from: { adapter, chain: 'Arc_Testnet' },
31568
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31569
+ * })
31570
+ * ```
31571
+ */
31572
+ const getPositionParamsSchema = z.object({
31573
+ from: adapterContextSchema$4,
31574
+ vaultAddress: vaultAddressSchema,
31575
+ config: earnConfigSchema.optional(),
31576
+ });
31577
+ /**
31578
+ * Validation schema for DepositParams.
31579
+ *
31580
+ * @example
31581
+ * ```typescript
31582
+ * import { depositParamsSchema } from '@circle-fin/earn-kit'
31583
+ *
31584
+ * const result = depositParamsSchema.safeParse({
31585
+ * from: { adapter, chain: 'Arc_Testnet' },
31586
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31587
+ * amount: '100.50',
31588
+ * })
31589
+ * ```
31590
+ */
31591
+ const depositParamsSchema$1 = z.object({
31592
+ from: adapterContextSchema$4,
31593
+ vaultAddress: vaultAddressSchema,
31594
+ amount: amountSchema$1,
31595
+ config: earnConfigSchema.optional(),
31596
+ });
31597
+ /**
31598
+ * Validation schema for WithdrawParams.
31599
+ *
31600
+ * @example
31601
+ * ```typescript
31602
+ * import { withdrawParamsSchema } from '@circle-fin/earn-kit'
31603
+ *
31604
+ * const result = withdrawParamsSchema.safeParse({
31605
+ * from: { adapter, chain: 'Arc_Testnet' },
31606
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31607
+ * amount: '50.00',
31608
+ * })
31609
+ * ```
31610
+ */
31611
+ const withdrawParamsSchema = z.object({
31612
+ from: adapterContextSchema$4,
31613
+ vaultAddress: vaultAddressSchema,
31614
+ amount: amountSchema$1,
31615
+ config: earnConfigSchema.optional(),
31616
+ });
31617
+ /**
31618
+ * Validation schema for ClaimRewardsParams.
31619
+ *
31620
+ * @example
31621
+ * ```typescript
31622
+ * import { claimRewardsParamsSchema } from '@circle-fin/earn-kit'
31623
+ *
31624
+ * const result = claimRewardsParamsSchema.safeParse({
31625
+ * from: { adapter, chain: 'Arc_Testnet' },
31626
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31627
+ * })
31628
+ * ```
31629
+ */
31630
+ const claimRewardsParamsSchema = z.object({
31631
+ from: adapterContextSchema$4,
31632
+ vaultAddress: vaultAddressSchema,
31633
+ config: earnConfigSchema.optional(),
31634
+ });
31635
+ /**
31636
+ * Validation schema for GetDepositQuoteParams.
31637
+ *
31638
+ * @example
31639
+ * ```typescript
31640
+ * import { getDepositQuoteParamsSchema } from '@circle-fin/earn-kit'
31641
+ *
31642
+ * const result = getDepositQuoteParamsSchema.safeParse({
31643
+ * from: { adapter, chain: 'Arc_Testnet' },
31644
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31645
+ * amount: '100.50',
31646
+ * })
31647
+ * ```
31648
+ */
31649
+ const getDepositQuoteParamsSchema = z.object({
31650
+ from: adapterContextSchema$4,
31651
+ vaultAddress: vaultAddressSchema,
31652
+ amount: amountSchema$1,
31653
+ config: earnConfigSchema.optional(),
31654
+ });
31655
+ /**
31656
+ * Validation schema for GetWithdrawalQuoteParams.
31657
+ *
31658
+ * @example
31659
+ * ```typescript
31660
+ * import { getWithdrawalQuoteParamsSchema } from '@circle-fin/earn-kit'
31661
+ *
31662
+ * const result = getWithdrawalQuoteParamsSchema.safeParse({
31663
+ * from: { adapter, chain: 'Arc_Testnet' },
31664
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31665
+ * amount: '50.00',
31666
+ * })
31667
+ * ```
31668
+ */
31669
+ const getWithdrawalQuoteParamsSchema = z.object({
31670
+ from: adapterContextSchema$4,
31671
+ vaultAddress: vaultAddressSchema,
31672
+ amount: amountSchema$1,
31673
+ config: earnConfigSchema.optional(),
31674
+ });
31675
+ /**
31676
+ * Validation schema for GetClaimRewardsQuoteParams.
31677
+ *
31678
+ * @example
31679
+ * ```typescript
31680
+ * import { getClaimRewardsQuoteParamsSchema } from '@circle-fin/earn-kit'
31681
+ *
31682
+ * const result = getClaimRewardsQuoteParamsSchema.safeParse({
31683
+ * from: { adapter, chain: 'Arc_Testnet' },
31684
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31685
+ * })
31686
+ * ```
31687
+ */
31688
+ const getClaimRewardsQuoteParamsSchema = z.object({
31689
+ from: adapterContextSchema$4,
31690
+ vaultAddress: vaultAddressSchema,
31691
+ config: earnConfigSchema.optional(),
31692
+ });
31693
+
31694
+ /**
31695
+ * Fetch vault information from the earn service.
31696
+ *
31697
+ * Query the configured earn providers to get information about one
31698
+ * or more DeFi lending vaults. Accept batch queries as chain +
31699
+ * vaultAddress pairs.
31700
+ *
31701
+ * @param context - The EarnKit context containing providers
31702
+ * @param params - Vault query parameters
31703
+ * @returns A promise resolving to the vault lookup results
31704
+ * @throws {@link KitError} If validation fails or no provider is configured
31705
+ *
31706
+ * @example
31707
+ * ```typescript
31708
+ * import { createEarnKitContext, getVaults } from '@circle-fin/earn-kit'
31709
+ *
31710
+ * const context = createEarnKitContext()
31711
+ * const result = await getVaults(context, {
31712
+ * vaults: [{ chain: 'Arc_Testnet', vaultAddress: '0x...' }],
31713
+ * })
31714
+ * console.log(`Found ${result.vaults.length} vaults`)
31715
+ * ```
31716
+ */
31717
+ async function getVaults$1(context, params) {
31718
+ assertEarnParams(params, getVaultsParamsSchema);
31719
+ const provider = findProvider(context);
31720
+ const result = await provider.getVaults({
31721
+ vaults: params.vaults,
31722
+ ...(params.config !== undefined && { config: params.config }),
31723
+ });
31724
+ return formatGetVaultsResult(result);
31725
+ }
31726
+
31727
+ /**
31728
+ * Resolve the wallet address to use for on-chain operations.
31729
+ *
31730
+ * For developer-controlled adapters, callers pass `address` explicitly and we
31731
+ * return it unchanged. For user-controlled adapters, we fall back to
31732
+ * `adapter.getAddress(chain)`.
31733
+ *
31734
+ * @param from - Adapter context containing the adapter and optional explicit address.
31735
+ * @param chain - Resolved chain definition.
31736
+ * @returns The wallet address.
31737
+ * @throws Propagates errors from `adapter.getAddress(chain)` when
31738
+ * `from.address` is not supplied.
31739
+ *
31740
+ * @example
31741
+ * ```typescript
31742
+ * const address = await resolveAdapterAddress(params.from, chain)
31743
+ * ```
31744
+ *
31745
+ * @internal
31746
+ */
31747
+ async function resolveAdapterAddress(from, chain) {
31748
+ if (from.address !== undefined && from.address !== '') {
31749
+ return from.address;
31750
+ }
31751
+ return from.adapter.getAddress(chain);
31752
+ }
31753
+
31754
+ /**
31755
+ * Fetch P&L position data for a wallet.
31756
+ *
31757
+ * Query the configured earn providers to get balance, principal,
31758
+ * earnings, and shares data for the wallet specified in the adapter context.
31759
+ *
31760
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31761
+ * @param context - The EarnKit context containing providers
31762
+ * @param params - Position query parameters with adapter context
31763
+ * @returns A promise resolving to the wallet's position info
31764
+ * @throws {@link KitError} If validation fails or no provider is configured
31765
+ *
31766
+ * @example
31767
+ * ```typescript
31768
+ * import {
31769
+ * createEarnKitContext,
31770
+ * getPosition,
31771
+ * EarnChain,
31772
+ * } from '@circle-fin/earn-kit'
31773
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31774
+ *
31775
+ * const context = createEarnKitContext()
31776
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31777
+ *
31778
+ * const position = await getPosition(context, {
31779
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31780
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31781
+ * })
31782
+ * console.log(`Balance: ${position.currentBalance}`)
31783
+ * ```
31784
+ */
31785
+ async function getPosition$1(context, params) {
31786
+ assertEarnParams(params, getPositionParamsSchema);
31787
+ const chain = resolveChainIdentifier(params.from.chain);
31788
+ params.from.adapter.validateChainSupport(chain);
31789
+ const provider = findProvider(context, chain, 'getPosition');
31790
+ const address = await resolveAdapterAddress(params.from, chain);
31791
+ const result = await provider.getPosition({
31792
+ from: { ...params.from, chain, address },
31793
+ vaultAddress: params.vaultAddress,
31794
+ ...(params.config !== undefined && { config: params.config }),
31795
+ });
31796
+ return formatPositionInfo(result);
31797
+ }
31798
+
31799
+ /**
31800
+ * Execute a deposit into a DeFi lending vault.
31801
+ *
31802
+ * Validate user params, resolve the chain and wallet address, then delegate
31803
+ * the full on-chain flow (fetch signed instructions, issue a max USDC ERC-20
31804
+ * approval when needed, submit the deposit, wait for confirmation) to the
31805
+ * selected earn provider.
31806
+ *
31807
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31808
+ * @param context - The EarnKit context containing providers
31809
+ * @param params - Deposit parameters including vault address, amount, and adapter
31810
+ * @returns A promise resolving to the deposit result with transaction details
31811
+ * @throws {@link KitError} If validation fails or no provider is configured
31812
+ * @throws {@link KitError} If the chain has no adapter contract configured
31813
+ * @throws {@link KitError} If the chain does not support USDC
31814
+ * @throws {@link KitError} If the USDC approval transaction reverts on-chain
31815
+ * @throws {@link KitError} If the deposit transaction reverts on-chain
31816
+ *
31817
+ * @example
31818
+ * ```typescript
31819
+ * import {
31820
+ * createEarnKitContext,
31821
+ * deposit,
31822
+ * EarnChain,
31823
+ * } from '@circle-fin/earn-kit'
31824
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31825
+ *
31826
+ * const context = createEarnKitContext()
31827
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31828
+ *
31829
+ * const result = await deposit(context, {
31830
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31831
+ * vaultAddress: '0x...',
31832
+ * amount: '100.50',
31833
+ * })
31834
+ * console.log(`Deposited ${result.amount} into ${result.vaultAddress}, tx: ${result.txHash}`)
31835
+ * ```
31836
+ */
31837
+ async function deposit$3(context, params) {
31838
+ assertEarnParams(params, depositParamsSchema$1);
31839
+ const chain = resolveChainIdentifier(params.from.chain);
31840
+ params.from.adapter.validateChainSupport(chain);
31841
+ const provider = findProvider(context, chain, 'deposit');
31842
+ const address = await resolveAdapterAddress(params.from, chain);
31843
+ return provider.deposit({
31844
+ from: { ...params.from, chain, address },
31845
+ vaultAddress: params.vaultAddress,
31846
+ amount: params.amount,
31847
+ ...(params.config !== undefined && { config: params.config }),
31848
+ });
31849
+ }
31850
+
31851
+ /**
31852
+ * Execute a withdrawal from a DeFi lending vault.
31853
+ *
31854
+ * Validate user params, resolve the chain and wallet address, then delegate
31855
+ * the full on-chain flow (fetch signed instructions, issue a max vault-share
31856
+ * ERC-20 approval when needed, submit the withdrawal, wait for confirmation)
31857
+ * to the selected earn provider.
31858
+ *
31859
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31860
+ * @param context - The EarnKit context containing providers
31861
+ * @param params - Withdrawal parameters including vault address, amount, and adapter
31862
+ * @returns A promise resolving to the withdrawal result with transaction details
31863
+ * @throws {@link KitError} If validation fails or no provider is configured
31864
+ * @throws {@link KitError} If the chain has no adapter contract configured
31865
+ * @throws {@link KitError} If the vault share token approval transaction reverts on-chain
31866
+ * @throws {@link KitError} If the withdrawal transaction reverts on-chain
31867
+ *
31868
+ * @example
31869
+ * ```typescript
31870
+ * import {
31871
+ * createEarnKitContext,
31872
+ * withdraw,
31873
+ * EarnChain,
31874
+ * } from '@circle-fin/earn-kit'
31875
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31876
+ *
31877
+ * const context = createEarnKitContext()
31878
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31879
+ *
31880
+ * const result = await withdraw(context, {
31881
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31882
+ * vaultAddress: '0x...',
31883
+ * amount: '50.00',
31884
+ * })
31885
+ * console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
31886
+ * ```
31887
+ */
31888
+ async function withdraw$1(context, params) {
31889
+ assertEarnParams(params, withdrawParamsSchema);
31890
+ const chain = resolveChainIdentifier(params.from.chain);
31891
+ params.from.adapter.validateChainSupport(chain);
31892
+ const provider = findProvider(context, chain, 'withdraw');
31893
+ const address = await resolveAdapterAddress(params.from, chain);
31894
+ return provider.withdraw({
31895
+ from: { ...params.from, chain, address },
31896
+ vaultAddress: params.vaultAddress,
31897
+ amount: params.amount,
31898
+ ...(params.config !== undefined && { config: params.config }),
31899
+ });
31900
+ }
31901
+
31902
+ /**
31903
+ * Claim rewards from earn vaults.
31904
+ *
31905
+ * Validate user params, resolve the chain and wallet address, then delegate
31906
+ * the full on-chain flow (fetch signed instructions, submit the claim, wait
31907
+ * for confirmation) to the selected earn provider. When no rewards are
31908
+ * claimable, the provider returns rewards without submitting a transaction.
31909
+ *
31910
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31911
+ * @param context - The EarnKit context containing providers
31912
+ * @param params - Claim parameters including adapter context and vault address
31913
+ * @returns A promise resolving to the claim result with reward details
31914
+ * @throws {@link KitError} If validation fails or no provider is configured
31915
+ * @throws {@link KitError} If the chain has no adapter contract configured
31916
+ * @throws {@link KitError} If the earn service API request or response fails
31917
+ * @throws {@link KitError} If the claim transaction reverts on-chain
31918
+ *
31919
+ * @example
31920
+ * ```typescript
31921
+ * import {
31922
+ * createEarnKitContext,
31923
+ * claimRewards,
31924
+ * EarnChain,
31925
+ * } from '@circle-fin/earn-kit'
31926
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31927
+ *
31928
+ * const context = createEarnKitContext()
31929
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31930
+ *
31931
+ * const result = await claimRewards(context, {
31932
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31933
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31934
+ * })
31935
+ * if (result.status === 'no_rewards') {
31936
+ * console.log('No rewards to claim')
31937
+ * } else {
31938
+ * console.log(`Claimed ${result.rewards.length} reward(s), tx: ${result.txHash}`)
31939
+ * }
31940
+ * ```
31941
+ */
31942
+ async function claimRewards$1(context, params) {
31943
+ assertEarnParams(params, claimRewardsParamsSchema);
31944
+ const chain = resolveChainIdentifier(params.from.chain);
31945
+ params.from.adapter.validateChainSupport(chain);
31946
+ const provider = findProvider(context, chain, 'claimRewards');
31947
+ const address = await resolveAdapterAddress(params.from, chain);
31948
+ const result = await provider.claimRewards({
31949
+ from: { ...params.from, chain, address },
31950
+ vaultAddress: params.vaultAddress,
31951
+ ...(params.config === undefined ? {} : { config: params.config }),
31952
+ });
31953
+ return formatClaimRewardsResult(result);
31954
+ }
31955
+
31956
+ /**
31957
+ * Get an informational quote for a deposit into a vault.
31958
+ *
31959
+ * Query expected shares, share price, and APY for the specified
31960
+ * deposit amount without executing any transaction.
31961
+ *
31962
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31963
+ * @param context - The EarnKit context containing providers
31964
+ * @param params - Deposit quote parameters including vault address and amount
31965
+ * @returns A promise resolving to the deposit quote information
31966
+ * @throws {@link KitError} If validation fails or no provider is configured
31967
+ *
31968
+ * @example
31969
+ * ```typescript
31970
+ * import {
31971
+ * createEarnKitContext,
31972
+ * getDepositQuote,
31973
+ * EarnChain,
31974
+ * } from '@circle-fin/earn-kit'
31975
+ *
31976
+ * const context = createEarnKitContext()
31977
+ * const quote = await getDepositQuote(context, {
31978
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31979
+ * vaultAddress: '0x...',
31980
+ * amount: '100.50',
31981
+ * })
31982
+ * console.log(`Expected shares: ${quote.expectedShares.amount}`)
31983
+ * ```
31984
+ */
31985
+ async function getDepositQuote$1(context, params) {
31986
+ assertEarnParams(params, getDepositQuoteParamsSchema);
31987
+ const chain = resolveChainIdentifier(params.from.chain);
31988
+ params.from.adapter.validateChainSupport(chain);
31989
+ const provider = findProvider(context, chain, 'getDepositQuote');
31990
+ const address = await resolveAdapterAddress(params.from, chain);
31991
+ const result = await provider.getDepositQuote({
31992
+ from: { ...params.from, chain, address },
31993
+ vaultAddress: params.vaultAddress,
31994
+ amount: params.amount,
31995
+ ...(params.config !== undefined && { config: params.config }),
31996
+ });
31997
+ return formatDepositQuoteInfo(result);
31998
+ }
31999
+
32000
+ /**
32001
+ * Get an informational quote for a withdrawal from a vault.
32002
+ *
32003
+ * Query shares to redeem, max withdrawable, and fees for the specified
32004
+ * withdrawal amount without executing any transaction.
32005
+ *
32006
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
32007
+ * @param context - The EarnKit context containing providers
32008
+ * @param params - Withdrawal quote parameters including vault address and amount
32009
+ * @returns A promise resolving to the withdrawal quote information
32010
+ * @throws {@link KitError} If validation fails or no provider is configured
32011
+ *
32012
+ * @example
32013
+ * ```typescript
32014
+ * import {
32015
+ * createEarnKitContext,
32016
+ * getWithdrawalQuote,
32017
+ * EarnChain,
32018
+ * } from '@circle-fin/earn-kit'
32019
+ *
32020
+ * const context = createEarnKitContext()
32021
+ * const quote = await getWithdrawalQuote(context, {
32022
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
32023
+ * vaultAddress: '0x...',
32024
+ * amount: '50.00',
32025
+ * })
32026
+ * console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
32027
+ * ```
32028
+ */
32029
+ async function getWithdrawalQuote$1(context, params) {
32030
+ assertEarnParams(params, getWithdrawalQuoteParamsSchema);
32031
+ const chain = resolveChainIdentifier(params.from.chain);
32032
+ params.from.adapter.validateChainSupport(chain);
32033
+ const provider = findProvider(context, chain, 'getWithdrawalQuote');
32034
+ const address = await resolveAdapterAddress(params.from, chain);
32035
+ const result = await provider.getWithdrawalQuote({
32036
+ from: { ...params.from, chain, address },
32037
+ vaultAddress: params.vaultAddress,
32038
+ amount: params.amount,
32039
+ ...(params.config !== undefined && { config: params.config }),
32040
+ });
32041
+ return formatWithdrawalQuoteInfo(result);
32042
+ }
32043
+
32044
+ /**
32045
+ * Return all chains supported by configured earn providers.
32046
+ *
32047
+ * @param context - The EarnKit context containing providers
32048
+ * @returns Deduplicated supported chain definitions
32049
+ *
32050
+ * @example
32051
+ * ```typescript
32052
+ * import { createEarnKitContext, getSupportedChains } from '@circle-fin/earn-kit'
32053
+ *
32054
+ * const context = createEarnKitContext()
32055
+ * const chains = getSupportedChains(context)
32056
+ * ```
32057
+ */
32058
+ function getSupportedChains$3(context) {
32059
+ const earnChains = new Set(Object.values(EarnChain));
32060
+ const chainsById = new Map();
32061
+ for (const provider of context.providers) {
32062
+ for (const chain of provider.supportedChains) {
32063
+ if (earnChains.has(chain.chain)) {
32064
+ chainsById.set(chain.chain, chain);
32065
+ }
32066
+ }
32067
+ }
32068
+ return [...chainsById.values()];
32069
+ }
32070
+
32071
+ /**
32072
+ * Get an informational quote for claiming rewards.
32073
+ *
32074
+ * Query claimable reward details without executing any transaction.
32075
+ *
32076
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
32077
+ * @param context - The EarnKit context containing providers
32078
+ * @param params - Claim rewards quote parameters including vault address
32079
+ * @returns A promise resolving to the claim rewards quote information
32080
+ * @throws {@link KitError} If validation fails or no provider is configured
32081
+ *
32082
+ * @example
32083
+ * ```typescript
32084
+ * import {
32085
+ * createEarnKitContext,
32086
+ * getClaimRewardsQuote,
32087
+ * EarnChain,
32088
+ * } from '@circle-fin/earn-kit'
32089
+ *
32090
+ * const context = createEarnKitContext()
32091
+ * const quote = await getClaimRewardsQuote(context, {
32092
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
32093
+ * vaultAddress: '0x...',
32094
+ * })
32095
+ * console.log(`Claimable rewards: ${quote.rewards.length}`)
32096
+ * ```
32097
+ */
32098
+ async function getClaimRewardsQuote$1(context, params) {
32099
+ assertEarnParams(params, getClaimRewardsQuoteParamsSchema);
32100
+ const chain = resolveChainIdentifier(params.from.chain);
32101
+ params.from.adapter.validateChainSupport(chain);
32102
+ const provider = findProvider(context, chain, 'getClaimRewardsQuote');
32103
+ const address = await resolveAdapterAddress(params.from, chain);
32104
+ const result = await provider.getClaimRewardsQuote({
32105
+ from: { ...params.from, chain, address },
32106
+ vaultAddress: params.vaultAddress,
32107
+ ...(params.config !== undefined && { config: params.config }),
32108
+ });
32109
+ return formatClaimRewardsQuoteInfo(result);
32110
+ }
32111
+
32112
+ /**
32113
+ * A high-level class-based interface for DeFi lending vault operations.
32114
+ *
32115
+ * EarnKit provides a familiar class-based API for developers who prefer
32116
+ * traditional object-oriented patterns. The class maintains an internal
32117
+ * context and provides methods for depositing, withdrawing, claiming
32118
+ * rewards, and querying vault and position data.
32119
+ *
32120
+ * Key features:
32121
+ * - Strongly typed parameters with comprehensive Zod validation
32122
+ * - Supported earn vault deposits and withdrawals
32123
+ * - Deposit quote queries (expected shares, share price, APY without executing a transaction)
32124
+ * - Withdrawal quote queries (shares to redeem, max withdrawable, fees without executing a transaction)
32125
+ * - Claim rewards quote queries (claimable reward details without executing a transaction)
32126
+ * - Reward claiming
32127
+ * - P&L position tracking
32128
+ * - Dual-mode authentication (permissioned and permissionless)
32129
+ * - Full TypeScript support with IntelliSense
32130
+ *
32131
+ * @example
32132
+ * ```typescript
32133
+ * import { EarnChain, EarnKit } from '@circle-fin/earn-kit'
32134
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
32135
+ *
32136
+ * const kit = new EarnKit()
32137
+ * const adapter = createViemAdapterFromPrivateKey({
32138
+ * privateKey: process.env.PRIVATE_KEY,
32139
+ * })
32140
+ *
32141
+ * // Fetch vault info
32142
+ * const { vaults } = await kit.getVaults({
32143
+ * vaults: [{ chain: EarnChain.Arc_Testnet, vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458' }],
32144
+ * })
32145
+ * const [vault] = vaults
32146
+ * if (vault === undefined) {
32147
+ * throw new Error('No earn vaults available')
32148
+ * }
32149
+ *
32150
+ * // Deposit into a vault
32151
+ * const result = await kit.deposit({
32152
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
32153
+ * vaultAddress: vault.vaultAddress,
32154
+ * amount: '100.50',
32155
+ * })
32156
+ * console.log(`Deposited ${result.amount} into ${result.vaultAddress}, tx: ${result.txHash}`)
32157
+ * ```
32158
+ *
32159
+ * @remarks
32160
+ * For functional usage, import and use the operations directly:
32161
+ * ```typescript
32162
+ * import { createEarnKitContext, deposit } from '@circle-fin/earn-kit'
32163
+ * const context = createEarnKitContext()
32164
+ * await deposit(context, params)
32165
+ * ```
32166
+ */
32167
+ class EarnKit {
32168
+ context;
32169
+ /**
32170
+ * Create a new EarnKit instance.
32171
+ *
32172
+ * Accept an optional configuration object to customize the kit's
32173
+ * behavior. If no configuration is provided, the kit is initialized
32174
+ * with default settings using the built-in EarnServiceProvider.
32175
+ *
32176
+ * @param config - Optional configuration for the EarnKit instance
32177
+ *
32178
+ * @example
32179
+ * ```typescript
32180
+ * import { EarnKit } from '@circle-fin/earn-kit'
32181
+ *
32182
+ * // Create with default configuration
32183
+ * const kit = new EarnKit()
32184
+ * ```
32185
+ */
32186
+ constructor(config = {}) {
32187
+ this.context = createEarnKitContext(config);
32188
+ }
32189
+ /**
32190
+ * Return the chains supported by configured earn providers.
32191
+ *
32192
+ * @returns Deduplicated supported chain definitions
32193
+ *
32194
+ * @example
32195
+ * ```typescript
32196
+ * const kit = new EarnKit()
32197
+ * const chains = kit.getSupportedChains()
32198
+ * ```
32199
+ */
32200
+ getSupportedChains() {
32201
+ return getSupportedChains$3(this.context);
32202
+ }
32203
+ /**
32204
+ * Fetch available vault information.
32205
+ *
32206
+ * Query the configured earn providers to get a list of available
32207
+ * DeFi lending vaults with APY, TVL, and reward information.
32208
+ *
32209
+ * @param params - Query parameters with vault address and optional chain filter
32210
+ * @returns A promise resolving to the list of available vaults
32211
+ * @throws {@link KitError} If no provider is configured
32212
+ *
32213
+ * @example
32214
+ * ```typescript
32215
+ * const kit = new EarnKit()
32216
+ * const result = await kit.getVaults({
32217
+ * vaults: [{ chain: 'Arc_Testnet', vaultAddress: '0x8eB67...' }],
32218
+ * })
32219
+ * result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
32220
+ * ```
32221
+ */
32222
+ async getVaults(params) {
32223
+ return getVaults$1(this.context, params);
32224
+ }
32225
+ /**
32226
+ * Fetch P&L position data for the connected wallet.
32227
+ *
32228
+ * Query balance, principal, earnings, and shares data for the wallet
32229
+ * specified in the adapter context.
32230
+ *
32231
+ * @param params - Position query parameters with adapter context
32232
+ * @returns A promise resolving to the wallet's position info
32233
+ * @throws {@link KitError} If validation fails or no provider is configured
32234
+ *
32235
+ * @example
32236
+ * ```typescript
32237
+ * const position = await kit.getPosition({
32238
+ * from: { adapter, chain: 'Arc_Testnet' },
32239
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
32240
+ * })
32241
+ * if (position.pnl.status === 'available') {
32242
+ * console.log(`Yield: ${position.pnl.totalYieldEarned}`)
32243
+ * }
32244
+ * ```
32245
+ */
32246
+ async getPosition(params) {
32247
+ return getPosition$1(this.context, params);
32248
+ }
32249
+ /**
32250
+ * Execute a deposit into a DeFi lending vault.
32251
+ *
32252
+ * Build signed deposit instructions via the earn service, issue a max USDC
32253
+ * ERC-20 approval for the adapter contract when the current allowance is
32254
+ * below the SDK threshold, then submit the deposit on-chain. Return the
32255
+ * confirmed transaction hash together with the deposited amount and target
32256
+ * vault.
32257
+ *
32258
+ * @param params - Deposit parameters including vault address and amount
32259
+ * @returns A promise resolving to the deposit result with transaction details
32260
+ * @throws {@link KitError} If validation fails, no provider is configured, the
32261
+ * chain has no adapter contract configured, the approval transaction
32262
+ * reverts on-chain, or the deposit transaction reverts on-chain
32263
+ *
32264
+ * @example
32265
+ * ```typescript
32266
+ * const result = await kit.deposit({
32267
+ * from: { adapter, chain: 'Arc_Testnet' },
32268
+ * vaultAddress: '0x...',
32269
+ * amount: '100.50',
32270
+ * })
32271
+ * console.log(`Deposited ${result.amount} into ${result.vaultAddress}, tx: ${result.txHash}`)
32272
+ * ```
32273
+ */
32274
+ async deposit(params) {
32275
+ return deposit$3(this.context, params);
32276
+ }
32277
+ /**
32278
+ * Execute a withdrawal from a DeFi lending vault.
32279
+ *
32280
+ * Build signed withdrawal instructions via the earn service, approve the
32281
+ * adapter contract to spend vault share tokens if needed, then submit the
32282
+ * withdrawal on-chain. Return the withdrawal result including the on-chain
32283
+ * transaction hash, withdrawn amount, and target vault.
32284
+ *
32285
+ * @param params - Withdrawal parameters including vault address and amount
32286
+ * @returns A promise resolving to the withdrawal result with transaction details
32287
+ * @throws {@link KitError} If validation fails, no provider is configured,
32288
+ * the chain has no adapter contract configured, the share-token approval
32289
+ * reverts on-chain, or the withdrawal transaction reverts on-chain.
32290
+ *
32291
+ * @example
32292
+ * ```typescript
32293
+ * const result = await kit.withdraw({
32294
+ * from: { adapter, chain: 'Arc_Testnet' },
32295
+ * vaultAddress: '0x...',
32296
+ * amount: '50.00',
32297
+ * })
32298
+ * console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
32299
+ * ```
32300
+ */
32301
+ async withdraw(params) {
32302
+ return withdraw$1(this.context, params);
32303
+ }
32304
+ /**
32305
+ * Claim rewards from earn vaults.
32306
+ *
32307
+ * Build signed claim rewards instructions via the earn service, submit
32308
+ * them on-chain through the adapter, and return the confirmed transaction
32309
+ * hash. When no rewards are claimable, no transaction is submitted.
32310
+ *
32311
+ * @param params - Claim parameters including adapter context
32312
+ * @returns A promise resolving to the claim result with reward details
32313
+ * @throws {@link KitError} If validation fails or no provider is configured
32314
+ *
32315
+ * @example
32316
+ * ```typescript
32317
+ * const result = await kit.claimRewards({
32318
+ * from: { adapter, chain: 'Arc_Testnet' },
32319
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
32320
+ * })
32321
+ *
32322
+ * if (result.status === 'claimed') {
32323
+ * console.log(`Claimed ${result.rewards.length} reward token(s), tx: ${result.txHash}`)
32324
+ * }
32325
+ * ```
32326
+ */
32327
+ async claimRewards(params) {
32328
+ return claimRewards$1(this.context, params);
32329
+ }
32330
+ /**
32331
+ * Get an informational quote for a deposit into a vault.
32332
+ *
32333
+ * Query expected shares, share price, and APY for the specified
32334
+ * deposit amount without executing any transaction.
32335
+ *
32336
+ * @param params - Deposit quote parameters including vault address and amount
32337
+ * @returns A promise resolving to the deposit quote information
32338
+ * @throws {@link KitError} If validation fails or no provider is configured
32339
+ *
32340
+ * @example
32341
+ * ```typescript
32342
+ * const quote = await kit.getDepositQuote({
32343
+ * from: { adapter, chain: 'Arc_Testnet' },
32344
+ * vaultAddress: '0x...',
32345
+ * amount: '100.50',
32346
+ * })
32347
+ * console.log(`Expected shares: ${quote.expectedShares.amount}`)
32348
+ * ```
32349
+ */
32350
+ async getDepositQuote(params) {
32351
+ return getDepositQuote$1(this.context, params);
32352
+ }
32353
+ /**
32354
+ * Get an informational quote for a withdrawal from a vault.
32355
+ *
32356
+ * Query shares to redeem, max withdrawable, and fees for the specified
32357
+ * withdrawal amount without executing any transaction.
32358
+ *
32359
+ * @param params - Withdrawal quote parameters including vault address and amount
32360
+ * @returns A promise resolving to the withdrawal quote information
32361
+ * @throws {@link KitError} If validation fails or no provider is configured
32362
+ *
32363
+ * @example
32364
+ * ```typescript
32365
+ * const quote = await kit.getWithdrawalQuote({
32366
+ * from: { adapter, chain: 'Arc_Testnet' },
32367
+ * vaultAddress: '0x...',
32368
+ * amount: '50.00',
32369
+ * })
32370
+ * console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
32371
+ * ```
32372
+ */
32373
+ async getWithdrawalQuote(params) {
32374
+ return getWithdrawalQuote$1(this.context, params);
32375
+ }
32376
+ /**
32377
+ * Get an informational quote for claiming rewards.
32378
+ *
32379
+ * Query claimable reward details without executing any transaction.
32380
+ *
32381
+ * @param params - Claim rewards quote parameters including vault address
32382
+ * @returns A promise resolving to the claim rewards quote information
32383
+ * @throws {@link KitError} If validation fails or no provider is configured
32384
+ *
32385
+ * @example
32386
+ * ```typescript
32387
+ * const quote = await kit.getClaimRewardsQuote({
32388
+ * from: { adapter, chain: 'Arc_Testnet' },
32389
+ * vaultAddress: '0x...',
32390
+ * })
32391
+ * console.log(`Claimable rewards: ${quote.rewards.length}`)
32392
+ * ```
32393
+ */
32394
+ async getClaimRewardsQuote(params) {
32395
+ return getClaimRewardsQuote$1(this.context, params);
32396
+ }
32397
+ }
32398
+
32399
+ /**
32400
+ *
32401
+ * Earn Kit
32402
+ *
32403
+ * A strongly-typed SDK for DeFi lending vault deposits, withdrawals, and rewards
32404
+ * @packageDocumentation
32405
+ */
32406
+ // Auto-register this kit for user agent tracking
32407
+ registerKit(`${pkg$1.name}/${pkg$1.version}`);
32408
+
32409
+ /**
32410
+ * Create an EarnKit instance for AppKit earn operations.
32411
+ *
32412
+ * @remarks The context parameter is reserved for future EarnKit wiring.
32413
+ * EarnKit does not currently support AppKit developer fee hooks, so the
32414
+ * factory does not read fee callbacks from the context. Earn custom fees remain
32415
+ * reserved until EarnKit fee support ships.
32416
+ *
32417
+ * When EarnKit supports developer fee hooks, this factory can wire AppKit context through.
32418
+ *
32419
+ * @param context - AppKit context reserved for future EarnKit wiring
32420
+ * @returns A new EarnKit instance
32421
+ *
32422
+ * @example
32423
+ * ```typescript
32424
+ * const earnKit = createEarnKit(context)
32425
+ * ```
32426
+ */
32427
+ const createEarnKit = () => new EarnKit();
32428
+
32429
+ /**
32430
+ * Register event handlers from a context actions map to a kit instance.
32431
+ *
32432
+ * This utility function registers event handlers stored in a context actions map
32433
+ * with a kit instance that supports event handling via an `on` method. It handles
32434
+ * wildcard handlers ('*') and prefixed action handlers, stripping the prefix
32435
+ * before registration.
32436
+ *
32437
+ * The function is designed to be reusable across different operation types
32438
+ * (bridge, swap, stake, etc.) by accepting a configurable prefix parameter.
32439
+ *
32440
+ * @param kit - The kit instance to register handlers with (must have an `on` method)
32441
+ * @param handlers - Map of action names to arrays of handler functions
32442
+ * @param prefix - Optional prefix to strip from action names (e.g., 'bridge.')
32443
+ *
32444
+ * @example
32445
+ * ```typescript
32446
+ * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
32447
+ * import { BridgeKit } from '@circle-fin/bridge-kit'
32448
+ *
32449
+ * const kit = new BridgeKit()
32450
+ * const handlers = {
32451
+ * '*': [(payload) => console.log('All actions:', payload)],
32452
+ * 'bridge.approve': [(payload) => console.log('Approved:', payload)],
32453
+ * 'bridge.burn': [(payload) => console.log('Burned:', payload)],
32454
+ * }
32455
+ *
32456
+ * registerActionHandlers(kit, handlers, 'bridge.')
32457
+ * ```
32458
+ *
32459
+ * @example
32460
+ * ```typescript
32461
+ * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
32462
+ * import { SwapKit } from '@circle-fin/swap-kit'
32463
+ *
32464
+ * const kit = new SwapKit()
32465
+ * const handlers = {
32466
+ * 'swap.initiate': [(payload) => console.log('Swap initiated:', payload)],
32467
+ * }
32468
+ *
32469
+ * registerActionHandlers(kit, handlers, 'swap.')
32470
+ * ```
32471
+ */
32472
+ const registerActionHandlers = (kit, handlers, prefix = '') => {
32473
+ for (const [action, handlerArray] of Object.entries(handlers)) {
32474
+ // Register all handlers for this action
32475
+ for (const handler of handlerArray) {
32476
+ if (action === '*') {
32477
+ // Wildcard handlers are registered as-is
32478
+ kit.on('*', handler);
32479
+ }
32480
+ else if (prefix && action.startsWith(prefix)) {
32481
+ // Remove prefix to get the actual kit action name
32482
+ const kitAction = action.split('.').at(1);
32483
+ if (kitAction) {
32484
+ kit.on(kitAction, handler);
32485
+ }
32486
+ }
32487
+ else if (!prefix) {
32488
+ // No prefix configured, register action as-is
32489
+ kit.on(action, handler);
32490
+ }
32491
+ // Actions that don't match the prefix are silently ignored
32492
+ }
32493
+ }
32494
+ };
32495
+
32496
+ /**
32497
+ * List of all supported token aliases for App Kit send operations.
32498
+ *
32499
+ * This array is used for runtime validation to check if a token string
32500
+ * is a known alias rather than a custom address.
32501
+ *
32502
+ * @remarks
32503
+ * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
32504
+ * with the TokenAlias type definition - TypeScript will enforce any changes.
32505
+ *
32506
+ * For swap operations, additional tokens (EURC, DAI, USDE, PYUSD) are supported
32507
+ * via SwapKit's SupportedToken type.
32508
+ */
32509
+ const TOKEN_ALIASES = ['USDC', 'USDT', 'NATIVE'];
32510
+ /**
32511
+ * Check if a token string is a known alias.
32512
+ *
32513
+ * This function performs case-sensitive matching against the list of known
32514
+ * token aliases. Known aliases take precedence over custom addresses in the
32515
+ * send operation logic.
32516
+ *
32517
+ * @param token - The token identifier to check
32518
+ * @returns True if the token is a known alias, false otherwise
32519
+ *
32520
+ * @example
32521
+ * ```typescript
32522
+ * import { isTokenAlias } from '@circle-fin/app-kit'
32523
+ *
32524
+ * console.log(isTokenAlias('USDC')) // true
32525
+ * console.log(isTokenAlias('NATIVE')) // true
32526
+ * console.log(isTokenAlias('0x6B175474E89094C44Da98b954EedeAC495271d0F')) // false
32527
+ * ```
32528
+ */
32529
+ function isTokenAlias(token) {
32530
+ return TOKEN_ALIASES.includes(token);
32531
+ }
32532
+ /**
32533
+ * Type guard to check if a string is a valid token address for a chain.
32534
+ *
32535
+ * This function verifies that a token string is:
32536
+ * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
32537
+ * 2. A valid address format for the specified chain
32538
+ *
32539
+ * Use this to narrow the type to `TokenAddress` in TypeScript.
32540
+ *
32541
+ * @param token - The token string to validate
32542
+ * @param chain - The chain definition for address validation
32543
+ * @returns True if the token is a valid address (not an alias)
32544
+ *
32545
+ * @example
32546
+ * ```typescript
32547
+ * import { isTokenAddress } from '@circle-fin/app-kit'
32548
+ * import { Ethereum } from '@core/chains'
32549
+ *
32550
+ * const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
32551
+ * if (isTokenAddress(daiAddress, Ethereum)) {
32552
+ * // TypeScript knows daiAddress is TokenAddress here
32553
+ * console.log('Valid token address:', daiAddress)
32554
+ * }
32555
+ *
32556
+ * console.log(isTokenAddress('USDC', Ethereum)) // false - it's an alias
32557
+ * console.log(isTokenAddress('invalid', Ethereum)) // false - invalid format
32558
+ * ```
32559
+ */
32560
+ function isTokenAddress(token, chain) {
32561
+ return !isTokenAlias(token) && isValidAddressForChain(token, chain);
32562
+ }
32563
+ /**
32564
+ * Validate and classify a token identifier.
32565
+ *
32566
+ * This function determines whether a token string is:
32567
+ * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
32568
+ * 2. A valid token address for the given chain
32569
+ * 3. An invalid/unrecognized token identifier
32570
+ *
32571
+ * Known aliases always take precedence. If the token is not an alias,
32572
+ * it's validated as an address for the given chain.
32573
+ *
32574
+ * @param token - The token identifier to validate
32575
+ * @param chain - The chain definition for address validation
32576
+ * @returns An object containing validation results
32577
+ *
32578
+ * @example
32579
+ * ```typescript
32580
+ * import { validateToken } from '@circle-fin/app-kit'
32581
+ * import { Ethereum } from '@core/chains'
32582
+ *
32583
+ * // Known alias
32584
+ * const usdc = validateToken('USDC', Ethereum)
32585
+ * console.log(usdc) // { isAlias: true, isAddress: false, isValid: true }
32586
+ *
32587
+ * // Custom token address
32588
+ * const dai = validateToken('0x6B175474E89094C44Da98b954EedeAC495271d0F', Ethereum)
32589
+ * console.log(dai) // { isAlias: false, isAddress: true, isValid: true }
32590
+ *
32591
+ * // Invalid token
32592
+ * const invalid = validateToken('invalid', Ethereum)
32593
+ * console.log(invalid) // { isAlias: false, isAddress: false, isValid: false }
32594
+ * ```
32595
+ */
32596
+ function validateToken(token, chain) {
32597
+ // Check if it's a known alias first (aliases take precedence)
32598
+ const alias = isTokenAlias(token);
32599
+ if (alias) {
32600
+ return {
32601
+ isAlias: true,
32602
+ isAddress: false,
32603
+ isValid: true,
32604
+ };
32605
+ }
32606
+ // Not an alias - check if it's a valid address for the chain
32607
+ const address = isTokenAddress(token, chain);
32608
+ return {
32609
+ isAlias: false,
32610
+ isAddress: address,
32611
+ isValid: address,
32612
+ };
32613
+ }
32614
+
32615
+ /**
32616
+ * Estimate the costs and details of a cross-chain bridge transfer using the AppKit context.
32617
+ *
32618
+ * This function provides cost estimation for bridge operations within the AppKit
32619
+ * ecosystem. It delegates to the underlying BridgeKit infrastructure while maintaining
32620
+ * consistency with the AppKit patterns and context-based architecture.
32621
+ *
32622
+ * The function validates parameters, applies context-specific configurations,
32623
+ * and returns comprehensive estimation details including fees, gas costs, and
32624
+ * transaction parameters without executing the actual transfer.
32625
+ *
32626
+ * @param context - AppKit context containing fee calculation and configuration hooks
32627
+ * @param params - Bridge parameters containing source, destination, amount, and token
32628
+ * @returns Promise resolving to the estimate result with comprehensive fee and cost information
32629
+ * @throws If bridge parameters are invalid
32630
+ * @throws If the bridge route is not supported
32631
+ * @throws If estimation fails due to network or configuration issues
32632
+ *
32633
+ * @example
32634
+ * ```typescript
32635
+ * import { estimateBridge } from '@circle-fin/app-kit/bridge'
32636
+ * import { createContext } from '@circle-fin/app-kit/context'
32637
+ *
32638
+ * const context = createContext({
32639
+ * getFee: async (type, params) => '1000000', // 1 USDC fee
32640
+ * getFeeRecipient: async (type, info) => '0xfee-recipient-address'
32641
+ * })
32642
+ *
32643
+ * const estimate = await estimateBridge(context, {
32644
+ * from: sourceAdapter,
32645
+ * to: { adapter: destAdapter, chain: 'Polygon' },
32646
+ * amount: '100.50',
32647
+ * token: 'USDC'
32648
+ * })
32649
+ *
32650
+ * console.log('Estimated total fee:', estimate.fee)
32651
+ * console.log('Estimated gas cost:', estimate.gasEstimate)
32652
+ * ```
32653
+ */
32654
+ const estimateBridge = async (context, params) => {
32655
+ const kit = createBridgeKit(context);
32656
+ // Delegate to the BridgeKit for actual estimation
32657
+ return kit.estimate(params);
32658
+ };
32659
+
32660
+ const assertSendParamsSymbol = Symbol('assertSendParams');
32661
+ /**
32662
+ * Matches numeric amount strings with optional thousands separators and decimal part.
32663
+ *
32664
+ * Accepts both US (e.g. "1,234.56") and EU (e.g. "1.234,56") styles by allowing
32665
+ * either "," or "." as the grouping and decimal separators. Also matches integers
32666
+ * like "10" and simple decimals like "10.5" or "10,5".
32667
+ */
32668
+ const amountNumericStringWithSeparatorsRegex = /^\d+(?:[.,]\d{3})*(?:[.,]\d+)?$/;
32669
+ /**
32670
+ * Schema describing the minimal adapter surface required by the kit.
32671
+ *
32672
+ * The adapter must implement two functions:
32673
+ * - `prepare` — build the transaction or instruction payload.
32674
+ * - `waitForTransaction` — await finality/confirmation for a submitted transaction.
32675
+ *
32676
+ * @returns Zod schema validating an object with `prepare` and `waitForTransaction` functions.
32677
+ *
32678
+ * @example
32679
+ * ```typescript
32680
+ * import { adapterSchema } from '@circle-fin/app-kit'
32681
+ *
32682
+ * console.log(adapterSchema.safeParse(ethereumAdapter).success) // true
32683
+ * ```
32684
+ */
32685
+ const adapterSchema = z.object({
32686
+ prepare: z.function().returns(z.any()),
32687
+ waitForTransaction: z.function().returns(z.any()),
32688
+ });
32689
+ /**
32690
+ * Schema for validating adapter contexts.
32691
+ * Ensure required fields are present and properly typed. An adapter context must include:
32692
+ * - A valid adapter with `prepare` and `waitForTransaction` methods.
32693
+ * - A valid chain identifier (string literal or chain object recognized by the kit).
32694
+ *
32695
+ * @returns Zod schema validating an adapter context object.
32696
+ * @throws ZodError If validation fails, with details about which properties failed.
32697
+ *
32698
+ * @example
32699
+ * ```typescript
32700
+ * import { adapterContextSchema } from '@circle-fin/app-kit'
32701
+ *
32702
+ * const result = adapterContextSchema.safeParse(context)
32703
+ * console.log(result.success) // true
32704
+ * ```
32705
+ */
32706
+ const adapterContextSchema$3 = z.object({
32707
+ adapter: adapterSchema,
32708
+ /**
32709
+ * Note: We cast chainIdentifierSchema to 'never' here to work around a TypeScript
32710
+ * limitation (TS2589: Type instantiation is excessively deep and possibly infinite)
29528
32711
  * that can occur with complex Zod unions (e.g., string literals + enums).
29529
32712
  * This cast is safe in this context because runtime validation is correct,
29530
32713
  * and the type is enforced at the API boundary.
@@ -30051,14 +33234,15 @@ const estimateSwap = async (context, params) => {
30051
33234
  * Get chains supported by AppKit operations.
30052
33235
  *
30053
33236
  * This helper returns the set of chains that can be used for a given
30054
- * operation type (bridge, swap, or both). When no operation type is specified,
30055
- * it returns all chains known to support any AppKit operation.
33237
+ * operation type (bridge, swap, earn, unified balance, or all). When no
33238
+ * operation type is specified, it returns all chains known to support any
33239
+ * AppKit operation.
30056
33240
  *
30057
33241
  * The result list contains unique chains and is safe to pass directly into
30058
33242
  * higher-level flows such as chain pickers or routing logic.
30059
33243
  *
30060
33244
  * @param context - AppKit context containing fee configuration.
30061
- * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'unifiedBalance').
33245
+ * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance').
30062
33246
  * @param unifiedBalance - Persistent AppKitUnifiedBalance namespace instance.
30063
33247
  * @returns Array of unique chain definitions supporting the specified operation(s)
30064
33248
  *
@@ -30084,6 +33268,12 @@ const estimateSwap = async (context, params) => {
30084
33268
  * console.log('Chains supporting swap:', swapChains.map(c => c.name))
30085
33269
  * ```
30086
33270
  *
33271
+ * @example Get earn-specific chains
33272
+ * ```typescript
33273
+ * const earnChains = kit.getSupportedChains('earn')
33274
+ * console.log('Chains supporting earn:', earnChains.map(c => c.name))
33275
+ * ```
33276
+ *
30087
33277
  * @example Check if specific chain supports an operation
30088
33278
  * ```typescript
30089
33279
  * import { Ethereum } from '@core/chains'
@@ -30098,11 +33288,12 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
30098
33288
  if (operationType !== undefined &&
30099
33289
  operationType !== 'bridge' &&
30100
33290
  operationType !== 'swap' &&
33291
+ operationType !== 'earn' &&
30101
33292
  operationType !== 'unifiedBalance') {
30102
33293
  throw new KitError({
30103
33294
  ...InputError.VALIDATION_FAILED,
30104
33295
  recoverability: 'FATAL',
30105
- message: `Invalid operationType: "${String(operationType)}". Expected 'bridge', 'swap', or 'unifiedBalance'.`,
33296
+ message: `Invalid operationType: "${String(operationType)}". Expected 'bridge', 'swap', 'earn', or 'unifiedBalance'.`,
30106
33297
  });
30107
33298
  }
30108
33299
  // Return bridge-specific chains
@@ -30115,23 +33306,228 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
30115
33306
  const swapKit = createSwapKit(context);
30116
33307
  return swapKit.getSupportedChains();
30117
33308
  }
33309
+ // Return earn-specific chains
33310
+ if (operationType === 'earn') {
33311
+ const earnKit = createEarnKit();
33312
+ return earnKit.getSupportedChains();
33313
+ }
30118
33314
  // Return unified balance chains
30119
33315
  if (operationType === 'unifiedBalance') {
30120
33316
  return unifiedBalance.getSupportedChains();
30121
33317
  }
30122
33318
  // No operation type specified - return union of all chains
33319
+ // Reuse kit instances here if provider constructors become expensive or stateful.
30123
33320
  const bridgeKit = createBridgeKit(context);
30124
33321
  const swapKit = createSwapKit(context);
33322
+ const earnKit = createEarnKit();
30125
33323
  const bridgeChains = bridgeKit.getSupportedChains();
30126
33324
  const swapChains = swapKit.getSupportedChains();
33325
+ const earnChains = earnKit.getSupportedChains();
30127
33326
  const ubChains = unifiedBalance.getSupportedChains();
30128
- const allChains = [...bridgeChains, ...swapChains, ...ubChains];
30129
- // Deduplicate by chain identifier
30130
- return Object.values(Object.fromEntries(allChains.map((chain) => [chain.chain, chain])));
33327
+ // Combine chains from all operations
33328
+ const allChains = [...bridgeChains, ...swapChains, ...earnChains, ...ubChains];
33329
+ // Deduplicate by chain identifier. Later duplicates override earlier ones.
33330
+ return [...new Map(allChains.map((chain) => [chain.chain, chain])).values()];
30131
33331
  };
30132
33332
 
33333
+ /**
33334
+ * Execute an earn deposit operation.
33335
+ *
33336
+ * @param context - AppKit context
33337
+ * @param params - Deposit parameters including source adapter, vault address, and amount
33338
+ * @returns Promise resolving to the earn deposit result
33339
+ * @throws If EarnKit validation fails, no provider is configured, or the deposit transaction fails
33340
+ *
33341
+ * @example
33342
+ * ```typescript
33343
+ * import { EarnChain } from '@circle-fin/app-kit'
33344
+ * import { createContext } from '@circle-fin/app-kit/context'
33345
+ * import { deposit } from '@circle-fin/app-kit/earn'
33346
+ *
33347
+ * const context = createContext()
33348
+ * const result = await deposit(context, {
33349
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33350
+ * vaultAddress: '0x...',
33351
+ * amount: '100.50',
33352
+ * })
33353
+ * ```
33354
+ */
33355
+ async function deposit$2(context, params) {
33356
+ return createEarnKit().deposit(params);
33357
+ }
33358
+ /**
33359
+ * Execute an earn withdrawal operation.
33360
+ *
33361
+ * @param context - AppKit context
33362
+ * @param params - Withdrawal parameters including source adapter, vault address, and amount
33363
+ * @returns Promise resolving to the earn withdrawal result
33364
+ * @throws If EarnKit validation fails, no provider is configured, or the withdrawal transaction fails
33365
+ *
33366
+ * @example
33367
+ * ```typescript
33368
+ * import { EarnChain } from '@circle-fin/app-kit'
33369
+ * import { createContext } from '@circle-fin/app-kit/context'
33370
+ * import { withdraw } from '@circle-fin/app-kit/earn'
33371
+ *
33372
+ * const context = createContext()
33373
+ * const result = await withdraw(context, {
33374
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33375
+ * vaultAddress: '0x...',
33376
+ * amount: '50.00',
33377
+ * })
33378
+ * ```
33379
+ */
33380
+ async function withdraw(context, params) {
33381
+ return createEarnKit().withdraw(params);
33382
+ }
33383
+ /**
33384
+ * Claim earn rewards.
33385
+ *
33386
+ * @param context - AppKit context
33387
+ * @param params - Claim rewards parameters including source adapter and vault address
33388
+ * @returns Promise resolving to the earn claim rewards result
33389
+ * @throws If EarnKit validation fails, no provider is configured, or the claim transaction fails
33390
+ *
33391
+ * @example
33392
+ * ```typescript
33393
+ * import { EarnChain } from '@circle-fin/app-kit'
33394
+ * import { createContext } from '@circle-fin/app-kit/context'
33395
+ * import { claimRewards } from '@circle-fin/app-kit/earn'
33396
+ *
33397
+ * const context = createContext()
33398
+ * const result = await claimRewards(context, {
33399
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33400
+ * vaultAddress: '0x...',
33401
+ * })
33402
+ * ```
33403
+ */
33404
+ async function claimRewards(context, params) {
33405
+ return createEarnKit().claimRewards(params);
33406
+ }
33407
+ /**
33408
+ * Fetch vault information.
33409
+ *
33410
+ * @param context - AppKit context
33411
+ * @param params - Vault query parameters. `vaults` selects the chain and vault address pairs to query
33412
+ * @returns Promise resolving to vault data and per-vault errors
33413
+ * @throws If EarnKit validation fails or no provider is configured
33414
+ *
33415
+ * @example
33416
+ * ```typescript
33417
+ * import { EarnChain } from '@circle-fin/app-kit'
33418
+ * import { createContext } from '@circle-fin/app-kit/context'
33419
+ * import { getVaults } from '@circle-fin/app-kit/earn'
33420
+ *
33421
+ * const context = createContext()
33422
+ * const result = await getVaults(context, {
33423
+ * vaults: [{ chain: EarnChain.Arc_Testnet, vaultAddress: '0x...' }],
33424
+ * })
33425
+ * ```
33426
+ */
33427
+ async function getVaults(context, params) {
33428
+ return createEarnKit().getVaults(params);
33429
+ }
33430
+ /**
33431
+ * Fetch a wallet position in a vault.
33432
+ *
33433
+ * @param context - AppKit context
33434
+ * @param params - Position parameters including source adapter and vault address
33435
+ * @returns Promise resolving to wallet position information
33436
+ * @throws If EarnKit validation fails or no provider is configured
33437
+ *
33438
+ * @example
33439
+ * ```typescript
33440
+ * import { EarnChain } from '@circle-fin/app-kit'
33441
+ * import { createContext } from '@circle-fin/app-kit/context'
33442
+ * import { getPosition } from '@circle-fin/app-kit/earn'
33443
+ *
33444
+ * const context = createContext()
33445
+ * const position = await getPosition(context, {
33446
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33447
+ * vaultAddress: '0x...',
33448
+ * })
33449
+ * ```
33450
+ */
33451
+ async function getPosition(context, params) {
33452
+ return createEarnKit().getPosition(params);
33453
+ }
33454
+ /**
33455
+ * Fetch a deposit quote.
33456
+ *
33457
+ * @param context - AppKit context
33458
+ * @param params - Deposit quote parameters including source adapter, vault address, and amount
33459
+ * @returns Promise resolving to deposit quote information
33460
+ * @throws If EarnKit validation fails or no provider is configured
33461
+ *
33462
+ * @example
33463
+ * ```typescript
33464
+ * import { EarnChain } from '@circle-fin/app-kit'
33465
+ * import { createContext } from '@circle-fin/app-kit/context'
33466
+ * import { getDepositQuote } from '@circle-fin/app-kit/earn'
33467
+ *
33468
+ * const context = createContext()
33469
+ * const quote = await getDepositQuote(context, {
33470
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33471
+ * vaultAddress: '0x...',
33472
+ * amount: '100.50',
33473
+ * })
33474
+ * ```
33475
+ */
33476
+ async function getDepositQuote(context, params) {
33477
+ return createEarnKit().getDepositQuote(params);
33478
+ }
33479
+ /**
33480
+ * Fetch a withdrawal quote.
33481
+ *
33482
+ * @param context - AppKit context
33483
+ * @param params - Withdrawal quote parameters including source adapter, vault address, and amount
33484
+ * @returns Promise resolving to withdrawal quote information
33485
+ * @throws If EarnKit validation fails or no provider is configured
33486
+ *
33487
+ * @example
33488
+ * ```typescript
33489
+ * import { EarnChain } from '@circle-fin/app-kit'
33490
+ * import { createContext } from '@circle-fin/app-kit/context'
33491
+ * import { getWithdrawalQuote } from '@circle-fin/app-kit/earn'
33492
+ *
33493
+ * const context = createContext()
33494
+ * const quote = await getWithdrawalQuote(context, {
33495
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33496
+ * vaultAddress: '0x...',
33497
+ * amount: '50.00',
33498
+ * })
33499
+ * ```
33500
+ */
33501
+ async function getWithdrawalQuote(context, params) {
33502
+ return createEarnKit().getWithdrawalQuote(params);
33503
+ }
33504
+ /**
33505
+ * Fetch a claim rewards quote.
33506
+ *
33507
+ * @param context - AppKit context
33508
+ * @param params - Claim rewards quote parameters including source adapter and vault address
33509
+ * @returns Promise resolving to claim rewards quote information
33510
+ * @throws If EarnKit validation fails or no provider is configured
33511
+ *
33512
+ * @example
33513
+ * ```typescript
33514
+ * import { EarnChain } from '@circle-fin/app-kit'
33515
+ * import { createContext } from '@circle-fin/app-kit/context'
33516
+ * import { getClaimRewardsQuote } from '@circle-fin/app-kit/earn'
33517
+ *
33518
+ * const context = createContext()
33519
+ * const quote = await getClaimRewardsQuote(context, {
33520
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33521
+ * vaultAddress: '0x...',
33522
+ * })
33523
+ * ```
33524
+ */
33525
+ async function getClaimRewardsQuote(context, params) {
33526
+ return createEarnKit().getClaimRewardsQuote(params);
33527
+ }
33528
+
30133
33529
  var name = "@circle-fin/unified-balance-kit";
30134
- var version = "1.1.1";
33530
+ var version = "1.1.2";
30135
33531
  var pkg = {
30136
33532
  name: name,
30137
33533
  version: version};
@@ -37050,7 +40446,7 @@ class AppKitUnifiedBalance {
37050
40446
  }
37051
40447
 
37052
40448
  /**
37053
- * A high-level SDK for stablecoin operations, including bridging and swapping.
40449
+ * A high-level SDK for stablecoin operations, including bridging, swapping, and earn.
37054
40450
  *
37055
40451
  * The AppKit provides a unified interface for various stablecoin operations
37056
40452
  * with strongly typed parameters and comprehensive fee estimation capabilities.
@@ -37061,6 +40457,7 @@ class AppKitUnifiedBalance {
37061
40457
  * - Strongly typed fee estimation with operation-specific parameters
37062
40458
  * - Cross-chain USDC bridging through CCTPv2
37063
40459
  * - Same-chain token swapping between USDC, USDT, and native tokens
40460
+ * - Earn vault deposits, withdrawals, rewards, and quote queries
37064
40461
  * - Token sending operations
37065
40462
  * - Comprehensive error handling and validation
37066
40463
  * - Full TypeScript support with IntelliSense
@@ -37093,6 +40490,13 @@ class AppKitUnifiedBalance {
37093
40490
  * amountIn: '50.00'
37094
40491
  * })
37095
40492
  *
40493
+ * // Query an earn vault
40494
+ * const quote = await kit.earn.getDepositQuote({
40495
+ * from: { adapter, chain: 'Arc_Testnet' },
40496
+ * vaultAddress: '0x...',
40497
+ * amount: '100.50',
40498
+ * })
40499
+ *
37096
40500
  * // Query unified balances across chains
37097
40501
  * const balances = await kit.unifiedBalance.getBalances({
37098
40502
  * token: 'USDC',
@@ -37107,6 +40511,25 @@ class AppKitUnifiedBalance {
37107
40511
  */
37108
40512
  class AppKit {
37109
40513
  context;
40514
+ /**
40515
+ * Earn operations exposed under `kit.earn`.
40516
+ *
40517
+ * @see {@link AppKitEarnOperations}
40518
+ *
40519
+ * @example
40520
+ * ```typescript
40521
+ * import { AppKit, EarnChain } from '@circle-fin/app-kit'
40522
+ *
40523
+ * const kit = new AppKit()
40524
+ *
40525
+ * const result = await kit.earn.deposit({
40526
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
40527
+ * vaultAddress: '0x...',
40528
+ * amount: '100.50',
40529
+ * })
40530
+ * ```
40531
+ */
40532
+ earn;
37110
40533
  /**
37111
40534
  * Namespace object for unified balance operations (deposit, spend,
37112
40535
  * balance queries, delegation, fund removal).
@@ -37160,6 +40583,16 @@ class AppKit {
37160
40583
  disableErrorReporting: config.disableErrorReporting,
37161
40584
  }),
37162
40585
  });
40586
+ this.earn = {
40587
+ deposit: async (params) => deposit$2(this.context, params),
40588
+ withdraw: async (params) => withdraw(this.context, params),
40589
+ claimRewards: async (params) => claimRewards(this.context, params),
40590
+ getVaults: async (params) => getVaults(this.context, params),
40591
+ getPosition: async (params) => getPosition(this.context, params),
40592
+ getDepositQuote: async (params) => getDepositQuote(this.context, params),
40593
+ getWithdrawalQuote: async (params) => getWithdrawalQuote(this.context, params),
40594
+ getClaimRewardsQuote: async (params) => getClaimRewardsQuote(this.context, params),
40595
+ };
37163
40596
  }
37164
40597
  /**
37165
40598
  * Execute a cross-chain USDC bridge transfer.
@@ -37417,9 +40850,9 @@ class AppKit {
37417
40850
  *
37418
40851
  * Returns blockchain networks that support specific stablecoin operations.
37419
40852
  * When no operation type is specified, returns all chains supporting any
37420
- * operation (bridge, swap, or unified balance).
40853
+ * operation (bridge, swap, earn, or unified balance).
37421
40854
  *
37422
- * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'unifiedBalance')
40855
+ * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance')
37423
40856
  * @returns Array of unique chain definitions supporting the specified operation(s)
37424
40857
  *
37425
40858
  * @example Get all supported chains
@@ -37480,5 +40913,5 @@ class AppKit {
37480
40913
  }
37481
40914
  }
37482
40915
 
37483
- export { AppKit, BalanceError, Blockchain, BridgeChain, InputError, KitError, NetworkError, OnchainError, RateLimitError, RpcError, ServiceError, SwapChain, TOKEN_ALIASES, TransferSpeed, UnifiedBalanceChain, getErrorCode, getErrorMessage, getTokenDecimals, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRateLimitError, isRetryableError$1 as isRetryableError, isRpcError, isServiceError, isTokenAddress, isTokenAlias, isUserCancellationError, setExternalPrefix, validateToken };
40916
+ export { AppKit, BalanceError, Blockchain, BridgeChain, EarnChain, EarnError, EarnKit, InputError, KitError, NetworkError, OnchainError, RateLimitError, RpcError, ServiceError, SwapChain, TOKEN_ALIASES, TransferSpeed, UnifiedBalanceChain, claimRewardsParamsSchema, createEarnKitContext, depositParamsSchema$1 as depositParamsSchema, claimRewards$1 as earnClaimRewards, deposit$3 as earnDeposit, getClaimRewardsQuote$1 as earnGetClaimRewardsQuote, getDepositQuote$1 as earnGetDepositQuote, getPosition$1 as earnGetPosition, getSupportedChains$3 as earnGetSupportedChains, getVaults$1 as earnGetVaults, getWithdrawalQuote$1 as earnGetWithdrawalQuote, withdraw$1 as earnWithdraw, getClaimRewardsQuoteParamsSchema, getDepositQuoteParamsSchema, getErrorCode, getErrorMessage, getPositionParamsSchema, getTokenDecimals, getVaultsParamsSchema, getWithdrawalQuoteParamsSchema, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRateLimitError, isRetryableError$1 as isRetryableError, isRpcError, isServiceError, isTokenAddress, isTokenAlias, isUserCancellationError, setExternalPrefix, validateToken, withdrawParamsSchema };
37484
40917
  //# sourceMappingURL=index.mjs.map