@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.cjs CHANGED
@@ -1074,6 +1074,35 @@ function createUnsupportedSwapRouteError(tokenIn, tokenOut, chain) {
1074
1074
  };
1075
1075
  return new KitError(errorDetails);
1076
1076
  }
1077
+ /**
1078
+ * Creates error for unsupported earn route.
1079
+ *
1080
+ * This error is thrown when no available earn provider supports the requested
1081
+ * operation on the specified chain.
1082
+ *
1083
+ * @param operation - Earn operation name
1084
+ * @param chain - Chain name where the earn operation was attempted
1085
+ * @returns KitError with specific earn route details
1086
+ *
1087
+ * @example
1088
+ * ```typescript
1089
+ * import { createUnsupportedEarnRouteError } from '@core/errors'
1090
+ *
1091
+ * throw createUnsupportedEarnRouteError('deposit', 'Ethereum')
1092
+ * // Message: "Earn deposit on Ethereum is not supported by any available provider."
1093
+ * ```
1094
+ */
1095
+ function createUnsupportedEarnRouteError(operation, chain) {
1096
+ const errorDetails = {
1097
+ ...InputError.UNSUPPORTED_ROUTE,
1098
+ recoverability: 'FATAL',
1099
+ message: `Earn ${operation} on ${chain} is not supported by any available provider.`,
1100
+ cause: {
1101
+ trace: { operation, chain },
1102
+ },
1103
+ };
1104
+ return new KitError(errorDetails);
1105
+ }
1077
1106
  /**
1078
1107
  * Creates error for unsupported token on chain.
1079
1108
  *
@@ -3217,7 +3246,7 @@ function handleTimeoutError(serviceName, operation, error) {
3217
3246
  * @returns `true` when `error.responseBody` is a non-null object
3218
3247
  * @internal
3219
3248
  */
3220
- function hasResponseBody(error) {
3249
+ function hasResponseBody$1(error) {
3221
3250
  return (typeof error === 'object' &&
3222
3251
  error !== null &&
3223
3252
  'responseBody' in error &&
@@ -3230,10 +3259,12 @@ function hasResponseBody(error) {
3230
3259
  *
3231
3260
  * @param error - The raw error from the HTTP layer
3232
3261
  * @returns The parsed body cast to {@link ApiErrorResponseBody}, or undefined
3262
+ * @throws Never. Returns `undefined` when the error does not carry a valid
3263
+ * `responseBody`.
3233
3264
  * @internal
3234
3265
  */
3235
3266
  function extractResponseBody(error) {
3236
- if (!hasResponseBody(error)) {
3267
+ if (!hasResponseBody$1(error)) {
3237
3268
  return undefined;
3238
3269
  }
3239
3270
  return error.responseBody;
@@ -3365,6 +3396,224 @@ function extractHttpStatusCode(msg) {
3365
3396
  return null;
3366
3397
  }
3367
3398
 
3399
+ /**
3400
+ * Standardized error definitions for Earn/Zenith operations.
3401
+ *
3402
+ * These error codes provide fine-grained categorization of failures
3403
+ * from the Zenith earn service, enabling SDK consumers to distinguish
3404
+ * between input errors (fix your request) and service errors (retry later).
3405
+ *
3406
+ * Error code ranges:
3407
+ * - 1100-1104: INPUT errors — invalid inputs, unsupported configurations
3408
+ * - 8100-8103: SERVICE errors — retryable backend/provider failures
3409
+ *
3410
+ * @example
3411
+ * ```typescript
3412
+ * import { EarnError, isInputError, isRetryableError } from '@circle-fin/earn-kit'
3413
+ *
3414
+ * try {
3415
+ * await earnKit.deposit(params)
3416
+ * } catch (error) {
3417
+ * if (isInputError(error)) {
3418
+ * // Fix the request: vault doesn't exist, chain not supported, etc.
3419
+ * }
3420
+ * if (isRetryableError(error)) {
3421
+ * // Try again: signing timed out, provider temporarily unavailable, etc.
3422
+ * }
3423
+ * }
3424
+ * ```
3425
+ */
3426
+ const EarnError = {
3427
+ /** The requested vault does not exist for the provided chain. */
3428
+ VAULT_NOT_FOUND: {
3429
+ code: 1100,
3430
+ name: 'EARN_VAULT_NOT_FOUND',
3431
+ type: 'INPUT',
3432
+ },
3433
+ /** The specified blockchain is not supported for earn operations. */
3434
+ UNSUPPORTED_CHAIN: {
3435
+ code: 1101,
3436
+ name: 'EARN_UNSUPPORTED_CHAIN',
3437
+ type: 'INPUT',
3438
+ },
3439
+ /** The vault's underlying asset is not supported. */
3440
+ UNSUPPORTED_VAULT: {
3441
+ code: 1102,
3442
+ name: 'EARN_UNSUPPORTED_VAULT',
3443
+ type: 'INPUT',
3444
+ },
3445
+ /** The submitted signature could not be verified. */
3446
+ SIGNATURE_REJECTED: {
3447
+ code: 1103,
3448
+ name: 'EARN_SIGNATURE_REJECTED',
3449
+ type: 'INPUT',
3450
+ },
3451
+ /** General input validation failure (invalid ID, batch size, etc.). */
3452
+ INVALID_INPUT: {
3453
+ code: 1104,
3454
+ name: 'EARN_INVALID_INPUT',
3455
+ type: 'INPUT',
3456
+ },
3457
+ /** The proxy signing call failed — retryable. */
3458
+ SIGNING_FAILED: {
3459
+ code: 8100,
3460
+ name: 'EARN_SIGNING_FAILED',
3461
+ type: 'SERVICE',
3462
+ },
3463
+ /** An external provider API request failed — retryable. */
3464
+ PROVIDER_ERROR: {
3465
+ code: 8101,
3466
+ name: 'EARN_PROVIDER_ERROR',
3467
+ type: 'SERVICE',
3468
+ },
3469
+ /** Failed to fetch reward data — retryable. */
3470
+ REWARDS_FETCH_FAILED: {
3471
+ code: 8102,
3472
+ name: 'EARN_REWARDS_FETCH_FAILED',
3473
+ type: 'SERVICE',
3474
+ },
3475
+ /** An internal earn service error occurred — retryable. */
3476
+ INTERNAL_ERROR: {
3477
+ code: 8103,
3478
+ name: 'EARN_INTERNAL_ERROR',
3479
+ type: 'SERVICE',
3480
+ },
3481
+ };
3482
+
3483
+ function hasResponseBody(error) {
3484
+ return (typeof error === 'object' &&
3485
+ error !== null &&
3486
+ 'responseBody' in error &&
3487
+ typeof error['responseBody'] === 'object' &&
3488
+ error['responseBody'] !== null);
3489
+ }
3490
+ function extractEarnResponseBody(error) {
3491
+ if (!hasResponseBody(error)) {
3492
+ return undefined;
3493
+ }
3494
+ return error.responseBody;
3495
+ }
3496
+ function getOptionalString(value) {
3497
+ return typeof value === 'string' ? value : undefined;
3498
+ }
3499
+ /**
3500
+ * Maps earn service backend error codes (380xxx) to typed EarnError constants
3501
+ * with correct recoverability.
3502
+ *
3503
+ * INPUT errors (FATAL) — fix your request:
3504
+ * - vault-not-found, unsupported-chain, unsupported-vault,
3505
+ * signature-rejected, invalid-id, invalid-batch-size, position-not-registered,
3506
+ * withdrawal-max-exceeded, insufficient-balance
3507
+ *
3508
+ * SERVICE errors (RETRYABLE) — try again later:
3509
+ * - signing-failed, provider-error, rewards-fetch-failed,
3510
+ * internal-error, vault-refresh-busy, bridge failures
3511
+ *
3512
+ * Unrecognized codes fall through to `parseApiError` for HTTP-status-based
3513
+ * handling.
3514
+ *
3515
+ * @internal
3516
+ */
3517
+ const EARN_CODE_MAP = new Map([
3518
+ // Common (380_0XX)
3519
+ [380001, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3520
+ [
3521
+ 380002,
3522
+ {
3523
+ errorDef: RateLimitError.RATE_LIMIT_EXCEEDED,
3524
+ recoverability: 'RETRYABLE',
3525
+ },
3526
+ ],
3527
+ // Shared (380_1XX)
3528
+ [380100, { errorDef: EarnError.VAULT_NOT_FOUND, recoverability: 'FATAL' }],
3529
+ [380101, { errorDef: EarnError.SIGNATURE_REJECTED, recoverability: 'FATAL' }],
3530
+ [380102, { errorDef: EarnError.UNSUPPORTED_CHAIN, recoverability: 'FATAL' }],
3531
+ // Vault Discovery (380_3XX)
3532
+ [380300, { errorDef: EarnError.VAULT_NOT_FOUND, recoverability: 'FATAL' }],
3533
+ [380301, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3534
+ [380302, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3535
+ [380303, { errorDef: EarnError.UNSUPPORTED_VAULT, recoverability: 'FATAL' }],
3536
+ // EarnKit (380_4XX)
3537
+ [380400, { errorDef: EarnError.UNSUPPORTED_CHAIN, recoverability: 'FATAL' }],
3538
+ [380401, { errorDef: EarnError.SIGNING_FAILED, recoverability: 'RETRYABLE' }],
3539
+ [
3540
+ 380402,
3541
+ { errorDef: EarnError.REWARDS_FETCH_FAILED, recoverability: 'RETRYABLE' },
3542
+ ],
3543
+ [380403, { errorDef: EarnError.INTERNAL_ERROR, recoverability: 'RETRYABLE' }],
3544
+ [380404, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3545
+ [380405, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3546
+ [380406, { errorDef: EarnError.INTERNAL_ERROR, recoverability: 'RETRYABLE' }],
3547
+ [380407, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3548
+ [380408, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3549
+ [380409, { errorDef: EarnError.INVALID_INPUT, recoverability: 'FATAL' }],
3550
+ // Bridge (380_5XX)
3551
+ [380500, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3552
+ [380501, { errorDef: EarnError.SIGNING_FAILED, recoverability: 'RETRYABLE' }],
3553
+ [380502, { errorDef: EarnError.PROVIDER_ERROR, recoverability: 'RETRYABLE' }],
3554
+ ]);
3555
+ /**
3556
+ * Parses Earn Service API errors into typed KitError instances using
3557
+ * earn service backend error codes for fine-grained categorization.
3558
+ *
3559
+ * When the response body contains a recognized earn service backend code
3560
+ * (380xxx), the error is mapped to a specific EarnError constant with the
3561
+ * correct recoverability. The original backend error code is preserved in
3562
+ * `error.cause.trace.earnServiceCode`.
3563
+ *
3564
+ * When the code is unrecognized or the response body is unavailable,
3565
+ * falls through to the generic `parseApiError` for HTTP-status-based handling.
3566
+ *
3567
+ * @param error - The raw error from the API call
3568
+ * @param context - Context information (operation name)
3569
+ * @returns A structured KitError instance
3570
+ * @throws Never — all error paths are caught and returned as a `KitError`
3571
+ * instance
3572
+ *
3573
+ * @example
3574
+ * ```typescript
3575
+ * try {
3576
+ * await earnServiceApi.deposit(params)
3577
+ * } catch (error) {
3578
+ * throw parseEarnApiError(error, { operation: 'deposit' })
3579
+ * }
3580
+ * ```
3581
+ */
3582
+ function parseEarnApiError(error, context) {
3583
+ // If it's already a KitError, return it as-is
3584
+ if (error instanceof KitError) {
3585
+ return error;
3586
+ }
3587
+ const responseBody = extractEarnResponseBody(error);
3588
+ const earnServiceCode = typeof responseBody?.['code'] === 'number'
3589
+ ? responseBody['code']
3590
+ : undefined;
3591
+ if (earnServiceCode !== undefined) {
3592
+ const mapping = EARN_CODE_MAP.get(earnServiceCode);
3593
+ if (mapping !== undefined) {
3594
+ const message = getOptionalString(responseBody?.['externalMessage']) ??
3595
+ getOptionalString(responseBody?.['message']) ??
3596
+ `Earn Service ${context.operation ?? 'API'} failed`;
3597
+ return new KitError({
3598
+ ...mapping.errorDef,
3599
+ recoverability: mapping.recoverability,
3600
+ message,
3601
+ cause: {
3602
+ trace: {
3603
+ earnServiceCode,
3604
+ originalError: error,
3605
+ },
3606
+ },
3607
+ });
3608
+ }
3609
+ }
3610
+ // Fallthrough: unrecognized code or no response body — use HTTP-status-based parsing
3611
+ return parseApiError(error, {
3612
+ ...context,
3613
+ service: context.service ?? 'Earn Service',
3614
+ });
3615
+ }
3616
+
3368
3617
  // -----------------------------------------------------------------------------
3369
3618
  // Blockchain Enum
3370
3619
  // -----------------------------------------------------------------------------
@@ -3724,24 +3973,22 @@ exports.UnifiedBalanceChain = void 0;
3724
3973
  * Enumeration of blockchains that support earn (vault deposit/withdraw)
3725
3974
  * operations through the Earn Kit.
3726
3975
  *
3727
- * Currently only Ethereum mainnet is supported. Additional chains
3728
- * will be added as vault protocol support expands.
3729
- *
3730
3976
  * @example
3731
3977
  * ```typescript
3732
3978
  * import { EarnChain } from '@core/chains'
3733
3979
  *
3734
3980
  * const result = await earnKit.deposit({
3735
- * from: { adapter, chain: EarnChain.Ethereum },
3981
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
3736
3982
  * vaultAddress: '0x...',
3737
3983
  * amount: '100',
3738
3984
  * })
3739
3985
  * ```
3740
3986
  */
3741
- var EarnChain;
3987
+ // Values must match Blockchain enum members exactly.
3988
+ exports.EarnChain = void 0;
3742
3989
  (function (EarnChain) {
3743
- EarnChain["Ethereum"] = "Ethereum";
3744
- })(EarnChain || (EarnChain = {}));
3990
+ EarnChain["Arc_Testnet"] = "Arc_Testnet";
3991
+ })(exports.EarnChain || (exports.EarnChain = {}));
3745
3992
 
3746
3993
  /**
3747
3994
  * Helper function to define a chain with proper TypeScript typing.
@@ -7481,7 +7728,7 @@ const bridgeChainIdentifierSchema = zod.z.union([
7481
7728
  * Zod schema for validating earn-specific chain identifiers.
7482
7729
  *
7483
7730
  * Validate that the provided chain is supported for earn (vault
7484
- * deposit/withdraw) operations. Currently only Ethereum is
7731
+ * deposit/withdraw) operations. Currently only Arc Testnet is
7485
7732
  * supported.
7486
7733
  *
7487
7734
  * Accept an EarnChain enum value, a matching string literal, or
@@ -7490,26 +7737,26 @@ const bridgeChainIdentifierSchema = zod.z.union([
7490
7737
  * @example
7491
7738
  * ```typescript
7492
7739
  * import { earnChainIdentifierSchema } from '@core/chains'
7493
- * import { EarnChain, Ethereum } from '@core/chains'
7740
+ * import { ArcTestnet, EarnChain } from '@core/chains'
7494
7741
  *
7495
7742
  * // Valid
7496
- * earnChainIdentifierSchema.parse(EarnChain.Ethereum)
7497
- * earnChainIdentifierSchema.parse('Ethereum')
7498
- * earnChainIdentifierSchema.parse(Ethereum)
7743
+ * earnChainIdentifierSchema.parse(EarnChain.Arc_Testnet)
7744
+ * earnChainIdentifierSchema.parse('Arc_Testnet')
7745
+ * earnChainIdentifierSchema.parse(ArcTestnet)
7499
7746
  *
7500
7747
  * // Invalid (throws ZodError)
7501
7748
  * earnChainIdentifierSchema.parse('Solana')
7502
7749
  * ```
7503
7750
  */
7504
- zod.z.union([
7505
- zod.z.string().refine((val) => val in EarnChain, (val) => ({
7751
+ const earnChainIdentifierSchema = zod.z.union([
7752
+ zod.z.string().refine((val) => val in exports.EarnChain, (val) => ({
7506
7753
  message: `"${val}" is not a supported earn chain. ` +
7507
- `Supported chains: ${Object.values(EarnChain).join(', ')}`,
7754
+ `Supported chains: ${Object.values(exports.EarnChain).join(', ')}`,
7508
7755
  })),
7509
- zod.z.nativeEnum(EarnChain),
7510
- chainDefinitionSchema$2.refine((chain) => chain.chain in EarnChain, (chain) => ({
7756
+ zod.z.nativeEnum(exports.EarnChain),
7757
+ chainDefinitionSchema$2.refine((chain) => chain.chain in exports.EarnChain, (chain) => ({
7511
7758
  message: `"${chain.chain}" is not a supported earn chain. ` +
7512
- `Supported chains: ${Object.values(EarnChain).join(', ')}`,
7759
+ `Supported chains: ${Object.values(exports.EarnChain).join(', ')}`,
7513
7760
  })),
7514
7761
  ]);
7515
7762
  /**
@@ -8359,7 +8606,7 @@ function handleAddressError(path, _code, _message, paramsObj) {
8359
8606
  * Default configuration values for the API polling utility.
8360
8607
  * @internal
8361
8608
  */
8362
- const DEFAULT_CONFIG$2 = {
8609
+ const DEFAULT_CONFIG$3 = {
8363
8610
  timeout: 2_000,
8364
8611
  maxRetries: 10,
8365
8612
  retryDelay: 200,
@@ -8568,10 +8815,10 @@ const isRetryableError = (error) => {
8568
8815
  const pollApiWithValidation = async (url, method, isValidType, config = {}, body) => {
8569
8816
  // Merge headers so that 'User-Agent' is always present and not overwritten
8570
8817
  const effectiveConfig = {
8571
- ...DEFAULT_CONFIG$2,
8818
+ ...DEFAULT_CONFIG$3,
8572
8819
  ...config,
8573
8820
  headers: {
8574
- ...DEFAULT_CONFIG$2.headers,
8821
+ ...DEFAULT_CONFIG$3.headers,
8575
8822
  ...(config.headers ?? {}),
8576
8823
  // In browser environments, directly setting the 'User-Agent' or similar headers is restricted and may be ignored or cause errors.
8577
8824
  // This is why we use the 'X-User-Agent' header instead.
@@ -10078,7 +10325,7 @@ zod.z.custom((value) => {
10078
10325
  * formatAmount({ value: '3141592000000000000', token: 'NATIVE', chain: Ethereum }) // "3.141592"
10079
10326
  * ```
10080
10327
  */
10081
- const formatAmount$1 = (params) => {
10328
+ const formatAmount$2 = (params) => {
10082
10329
  const { value, token, tokens } = params;
10083
10330
  // Handle NATIVE token first (chain-specific decimals)
10084
10331
  if (token === 'NATIVE') {
@@ -10798,11 +11045,11 @@ function emitResultStepErrorTelemetry(failedStep, stepEventMap, fallbackEventTyp
10798
11045
  void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
10799
11046
  }
10800
11047
 
10801
- var name$2 = "@circle-fin/bridge-kit";
10802
- var version$3 = "1.10.0";
10803
- var pkg$3 = {
10804
- name: name$2,
10805
- version: version$3};
11048
+ var name$3 = "@circle-fin/bridge-kit";
11049
+ var version$4 = "1.10.1";
11050
+ var pkg$4 = {
11051
+ name: name$3,
11052
+ version: version$4};
10806
11053
 
10807
11054
  const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
10808
11055
  /**
@@ -11098,7 +11345,9 @@ const hexStringSchema = zod.z
11098
11345
  * console.log(result.success) // true
11099
11346
  * ```
11100
11347
  */
11101
- const evmAddressSchema = hexStringSchema.refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)');
11348
+ const evmAddressSchema = hexStringSchema
11349
+ .refine((value) => value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)')
11350
+ .transform((value) => value);
11102
11351
  /**
11103
11352
  * Schema for validating transaction hashes.
11104
11353
  *
@@ -11119,6 +11368,29 @@ const evmAddressSchema = hexStringSchema.refine((value) => value.length === 42,
11119
11368
  * ```
11120
11369
  */
11121
11370
  hexStringSchema.refine((value) => value.length === 66, 'Transaction hash must be exactly 66 characters long (0x + 64 hex characters)');
11371
+ /**
11372
+ * Schema for validating EVM signatures.
11373
+ *
11374
+ * This schema validates that a string is a properly formatted 65-byte EVM
11375
+ * signature:
11376
+ * - Must be a valid hex string with '0x' prefix
11377
+ * - Must be exactly 132 characters long (0x + 130 hex characters)
11378
+ *
11379
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
11380
+ *
11381
+ * @example
11382
+ * ```typescript
11383
+ * import { evmSignatureSchema } from '@core/adapter'
11384
+ *
11385
+ * const validSignature = `0x${'ab'.repeat(65)}`
11386
+ *
11387
+ * const result = evmSignatureSchema.safeParse(validSignature)
11388
+ * console.log(result.success) // true
11389
+ * ```
11390
+ */
11391
+ const evmSignatureSchema = hexStringSchema
11392
+ .refine((value) => value.length === 132, 'EVM signature must be exactly 132 characters long (0x + 130 hex characters)')
11393
+ .transform((value) => value);
11122
11394
  /**
11123
11395
  * Schema for validating base58-encoded strings.
11124
11396
  *
@@ -11773,7 +12045,7 @@ function createRecipientAddressValidator() {
11773
12045
  *
11774
12046
  * Optionally includes address for developer-controlled adapters.
11775
12047
  */
11776
- const adapterContextSchema$6 = zod.z.object({
12048
+ const adapterContextSchema$7 = zod.z.object({
11777
12049
  adapter: adapterSchema$1,
11778
12050
  chain: bridgeChainIdentifierSchema,
11779
12051
  address: zod.z.string().optional(),
@@ -11783,7 +12055,7 @@ const adapterContextSchema$6 = zod.z.object({
11783
12055
  * Contains an explicit recipientAddress along with adapter and chain.
11784
12056
  * The address format is validated based on the chain type (EVM or Solana).
11785
12057
  */
11786
- const bridgeDestinationWithAddressSchema = adapterContextSchema$6
12058
+ const bridgeDestinationWithAddressSchema = adapterContextSchema$7
11787
12059
  .extend({
11788
12060
  recipientAddress: zod.z.string().min(1, 'Recipient address is required'),
11789
12061
  useForwarder: zod.z.boolean().optional(),
@@ -11793,7 +12065,7 @@ const bridgeDestinationWithAddressSchema = adapterContextSchema$6
11793
12065
  * Schema for validating AdapterContext with optional useForwarder.
11794
12066
  * Extends adapterContextSchema with the useForwarder flag.
11795
12067
  */
11796
- const adapterContextWithForwarderSchema = adapterContextSchema$6.extend({
12068
+ const adapterContextWithForwarderSchema = adapterContextSchema$7.extend({
11797
12069
  useForwarder: zod.z.boolean().optional(),
11798
12070
  });
11799
12071
  /**
@@ -11874,7 +12146,7 @@ const bridgeDestinationSchema = zod.z.union([
11874
12146
  * ```
11875
12147
  */
11876
12148
  const bridgeParamsWithChainIdentifierSchema = zod.z.object({
11877
- from: adapterContextSchema$6.strict(),
12149
+ from: adapterContextSchema$7.strict(),
11878
12150
  to: bridgeDestinationSchema,
11879
12151
  amount: zod.z
11880
12152
  .string()
@@ -12940,7 +13212,7 @@ function resolveBridgeInvocation$1(invocationMeta) {
12940
13212
  const bridgeKitCaller = {
12941
13213
  type: 'kit',
12942
13214
  name: 'BridgeKit',
12943
- version: pkg$3.version,
13215
+ version: pkg$4.version,
12944
13216
  };
12945
13217
  // Create default runtime and tokens for invocation context resolution
12946
13218
  const defaults = {
@@ -13210,7 +13482,7 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
13210
13482
  }
13211
13483
  // Fetch the fee tiers from the API
13212
13484
  const url = buildFastBurnFeeUrl(sourceDomain, destinationDomain, isTestnet);
13213
- const response = await pollApiGet(url, isFastBurnFeeResponse, DEFAULT_CONFIG$2);
13485
+ const response = await pollApiGet(url, isFastBurnFeeResponse, DEFAULT_CONFIG$3);
13214
13486
  // Validate that we have at least one fee tier
13215
13487
  if (response.length === 0) {
13216
13488
  throw new KitError({
@@ -13559,7 +13831,7 @@ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, fi
13559
13831
  }
13560
13832
  // Fetch the fee tiers from the API with forward=true
13561
13833
  const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet);
13562
- const response = await pollApiGet(url, isForwardingFeeResponse, DEFAULT_CONFIG$2);
13834
+ const response = await pollApiGet(url, isForwardingFeeResponse, DEFAULT_CONFIG$3);
13563
13835
  // Validate that we have at least one fee tier
13564
13836
  if (response.length === 0) {
13565
13837
  throw new KitError({
@@ -13626,7 +13898,7 @@ const CCTPv2MinFinalityThreshold = {
13626
13898
  * Default configuration values for the attestation fetcher.
13627
13899
  * @internal
13628
13900
  */
13629
- const DEFAULT_CONFIG$1 = {
13901
+ const DEFAULT_CONFIG$2 = {
13630
13902
  timeout: 2_000, // 2 seconds
13631
13903
  maxRetries: 30 * 20, // 30 * 20 * 2 seconds (from the retry delay) = 20 minutes (to account for slow transfers maximum time based on confirmations)
13632
13904
  retryDelay: 2_000, // 2 seconds
@@ -13797,7 +14069,7 @@ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet) => {
13797
14069
  */
13798
14070
  const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {}) => {
13799
14071
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13800
- const effectiveConfig = { ...DEFAULT_CONFIG$1, ...config };
14072
+ const effectiveConfig = { ...DEFAULT_CONFIG$2, ...config };
13801
14073
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
13802
14074
  };
13803
14075
  /**
@@ -13843,7 +14115,7 @@ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHas
13843
14115
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13844
14116
  // Use minimal retries since we're just fetching existing data
13845
14117
  const effectiveConfig = {
13846
- ...DEFAULT_CONFIG$1,
14118
+ ...DEFAULT_CONFIG$2,
13847
14119
  maxRetries: 3,
13848
14120
  ...config,
13849
14121
  };
@@ -13908,7 +14180,7 @@ const isReAttestedAttestationResponse = (obj) => {
13908
14180
  */
13909
14181
  const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {}) => {
13910
14182
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13911
- const effectiveConfig = { ...DEFAULT_CONFIG$1, ...config };
14183
+ const effectiveConfig = { ...DEFAULT_CONFIG$2, ...config };
13912
14184
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
13913
14185
  };
13914
14186
  /**
@@ -13983,7 +14255,7 @@ const requestReAttestation = async (nonce, isTestnet, config = {}) => {
13983
14255
  const url = buildReAttestUrl(nonce, isTestnet);
13984
14256
  // Use minimal retries since we're just submitting a request, not polling for state
13985
14257
  const effectiveConfig = {
13986
- ...DEFAULT_CONFIG$1,
14258
+ ...DEFAULT_CONFIG$2,
13987
14259
  maxRetries: 3,
13988
14260
  ...config,
13989
14261
  };
@@ -15238,7 +15510,7 @@ const isRelayerMintConfirmed = (obj) => {
15238
15510
  */
15239
15511
  const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {}) => {
15240
15512
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
15241
- const effectiveConfig = { ...DEFAULT_CONFIG$1, ...config };
15513
+ const effectiveConfig = { ...DEFAULT_CONFIG$2, ...config };
15242
15514
  let response;
15243
15515
  try {
15244
15516
  response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
@@ -15750,9 +16022,9 @@ async function buildBatchedStep(name, receipt, batchId, adapter, chain, statusCo
15750
16022
  return step;
15751
16023
  }
15752
16024
 
15753
- var version$2 = "1.8.1";
15754
- var pkg$2 = {
15755
- version: version$2};
16025
+ var version$3 = "1.8.2";
16026
+ var pkg$3 = {
16027
+ version: version$3};
15756
16028
 
15757
16029
  /**
15758
16030
  * Provider caller component for bridge operations.
@@ -15760,7 +16032,7 @@ var pkg$2 = {
15760
16032
  const BRIDGE_CALLER = {
15761
16033
  type: 'provider',
15762
16034
  name: 'CCTPV2BridgingProvider.bridge',
15763
- version: pkg$2.version,
16035
+ version: pkg$3.version,
15764
16036
  };
15765
16037
  /**
15766
16038
  * Resolve invocation context for bridge operations.
@@ -16140,7 +16412,7 @@ async function bridgeReAttest({ params, provider, }, burnTxHash) {
16140
16412
  const RETRY_CALLER = {
16141
16413
  type: 'provider',
16142
16414
  name: 'CCTPV2BridgingProvider.retry',
16143
- version: pkg$2.version,
16415
+ version: pkg$3.version,
16144
16416
  };
16145
16417
  /**
16146
16418
  * Resolve invocation context for retry operations.
@@ -17346,7 +17618,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
17346
17618
  * The default providers that will be used in addition to the providers provided
17347
17619
  * to the BridgeKit constructor.
17348
17620
  */
17349
- const getDefaultProviders$2 = () => [new CCTPV2BridgingProvider()];
17621
+ const getDefaultProviders$3 = () => [new CCTPV2BridgingProvider()];
17350
17622
 
17351
17623
  /**
17352
17624
  * A helper function to get a function that transforms an amount into a human-readable string or a bigint string.
@@ -17354,7 +17626,7 @@ const getDefaultProviders$2 = () => [new CCTPV2BridgingProvider()];
17354
17626
  * @returns A function that transforms an amount into a human-readable string or a bigint string.
17355
17627
  */
17356
17628
  const getAmountTransformer = (formatDirection) => formatDirection === 'to-human-readable'
17357
- ? (params) => formatAmount$1(params)
17629
+ ? (params) => formatAmount$2(params)
17358
17630
  : (params) => parseAmount(params).toString();
17359
17631
  /**
17360
17632
  * Format the bridge result into human-readable string values for the user or bigint string values for internal use.
@@ -17444,14 +17716,14 @@ const BRIDGE_STEP_EVENT_MAP = [
17444
17716
  ];
17445
17717
 
17446
17718
  /** SDK name used in telemetry payloads. */
17447
- const SDK_NAME$2 = resolveKitSdkName(pkg$3.name);
17719
+ const SDK_NAME$2 = resolveKitSdkName(pkg$4.name);
17448
17720
  /**
17449
17721
  * BridgeKit caller component for retry and estimate operations.
17450
17722
  */
17451
17723
  const BRIDGE_KIT_CALLER = {
17452
17724
  type: 'kit',
17453
17725
  name: 'BridgeKit',
17454
- version: pkg$3.version,
17726
+ version: pkg$4.version,
17455
17727
  };
17456
17728
  /**
17457
17729
  * Merge BridgeKit's caller into the invocation metadata for retry operations.
@@ -17546,13 +17818,13 @@ class BridgeKit {
17546
17818
  */
17547
17819
  constructor(config = {}) {
17548
17820
  // Handle provider configuration
17549
- const defaultProviders = getDefaultProviders$2();
17821
+ const defaultProviders = getDefaultProviders$3();
17550
17822
  this.providers = [...defaultProviders, ...(config.providers ?? [])];
17551
17823
  this.actionDispatcher = new Actionable();
17552
17824
  this.disableErrorReporting = config.disableErrorReporting === true;
17553
17825
  this.telemetryConfig = {
17554
17826
  sdkName: SDK_NAME$2,
17555
- sdkVersion: pkg$3.version,
17827
+ sdkVersion: pkg$4.version,
17556
17828
  disabled: this.disableErrorReporting,
17557
17829
  };
17558
17830
  for (const provider of this.providers) {
@@ -18081,7 +18353,7 @@ class BridgeKit {
18081
18353
  * @packageDocumentation
18082
18354
  */
18083
18355
  // Auto-register this kit for user agent tracking
18084
- registerKit(`${pkg$3.name}/${pkg$3.version}`);
18356
+ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18085
18357
 
18086
18358
  /**
18087
18359
  * Create a BridgeKit instance with optional developer fee configuration.
@@ -18148,11 +18420,11 @@ const createBridgeKit = (context) => {
18148
18420
  return kit;
18149
18421
  };
18150
18422
 
18151
- var name$1 = "@circle-fin/swap-kit";
18152
- var version$1 = "1.2.1";
18153
- var pkg$1 = {
18154
- name: name$1,
18155
- version: version$1};
18423
+ var name$2 = "@circle-fin/swap-kit";
18424
+ var version$2 = "1.2.2";
18425
+ var pkg$2 = {
18426
+ name: name$2,
18427
+ version: version$2};
18156
18428
 
18157
18429
  /**
18158
18430
  * @packageDocumentation
@@ -18249,7 +18521,7 @@ const serviceSwapConfigSchema = zod.z.object({
18249
18521
  * @remarks
18250
18522
  * Optionally includes address for developer-controlled adapters.
18251
18523
  */
18252
- const adapterContextSchema$5 = zod.z.object({
18524
+ const adapterContextSchema$6 = zod.z.object({
18253
18525
  adapter: adapterSchema$1,
18254
18526
  chain: chainIdentifierSchema,
18255
18527
  address: zod.z.string().optional(),
@@ -18271,7 +18543,7 @@ const adapterContextSchema$5 = zod.z.object({
18271
18543
  * ```
18272
18544
  */
18273
18545
  const serviceSwapParamsSchema = zod.z.object({
18274
- from: adapterContextSchema$5,
18546
+ from: adapterContextSchema$6,
18275
18547
  tokenIn: zod.z
18276
18548
  .string({
18277
18549
  required_error: 'tokenIn is required',
@@ -18486,7 +18758,7 @@ const validateServiceSwapParams = (params) => {
18486
18758
  *
18487
18759
  * @internal
18488
18760
  */
18489
- const DEFAULT_CONFIG = {
18761
+ const DEFAULT_CONFIG$1 = {
18490
18762
  timeout: 30_000, // 30 seconds - matches load balancer timeout
18491
18763
  maxRetries: 3, // 3 retries as per requirements
18492
18764
  retryDelay: 200, // 200ms between retries
@@ -19151,6 +19423,46 @@ const parseCreateSwapResponse = (obj) => createSwapResponseSchema.parse(obj);
19151
19423
  * ```
19152
19424
  */
19153
19425
  const isGetSwapStatusResponse = (obj) => getSwapStatusResponseSchema.safeParse(obj).success;
19426
+ /**
19427
+ * Validates that an API key is properly formatted for Circle Stablecoin Service.
19428
+ *
19429
+ * This function performs validation on API keys to ensure they conform to the
19430
+ * expected format before making API requests. The key must follow the structure:
19431
+ * `KIT_KEY:keyId:keySecret`
19432
+ *
19433
+ * @param apiKey - The API key to validate (can be any type for graceful handling).
19434
+ * @returns True if the API key is valid, false otherwise.
19435
+ *
19436
+ * @remarks
19437
+ * This function handles invalid inputs gracefully by returning false rather than
19438
+ * throwing errors. It validates:
19439
+ * - The key starts with the `KIT_KEY:` prefix
19440
+ * - Contains exactly three colon-separated parts
19441
+ * - keyId and keySecret contain only valid characters (alphanumeric, `-`, `_`, `.`)
19442
+ * - keyId and keySecret are non-empty
19443
+ * - No whitespace anywhere in the API key (including leading, trailing, or internal)
19444
+ *
19445
+ * Valid characters in keyId and keySecret: `a-z`, `A-Z`, `0-9`, `-`, `_`, `.`
19446
+ *
19447
+ * @example
19448
+ * ```typescript
19449
+ * import { isValidApiKey } from '@core/service-client'
19450
+ *
19451
+ * // Valid API key
19452
+ * const isValid = isValidApiKey('KIT_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
19453
+ * console.log(isValid) // true
19454
+ *
19455
+ * ```
19456
+ */
19457
+ const isValidApiKey = (apiKey) => {
19458
+ // Handle invalid input types
19459
+ if (typeof apiKey !== 'string') {
19460
+ return false;
19461
+ }
19462
+ const apiKeyPattern = /^KIT_KEY:[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$/;
19463
+ // Validate without trimming - any whitespace will cause validation to fail
19464
+ return apiKeyPattern.test(apiKey);
19465
+ };
19154
19466
 
19155
19467
  /**
19156
19468
  * @packageDocumentation
@@ -19221,9 +19533,9 @@ const createSwap = async (params) => {
19221
19533
  // Remove the API key from the request body
19222
19534
  const { apiKey, ...requestBody } = validatedParams;
19223
19535
  const effectiveConfig = {
19224
- ...DEFAULT_CONFIG,
19536
+ ...DEFAULT_CONFIG$1,
19225
19537
  headers: {
19226
- ...DEFAULT_CONFIG.headers,
19538
+ ...DEFAULT_CONFIG$1.headers,
19227
19539
  Authorization: `Bearer ${apiKey}`,
19228
19540
  },
19229
19541
  };
@@ -19376,9 +19688,9 @@ const getQuote = async (params) => {
19376
19688
  const url = buildQuoteUrl(validatedParams);
19377
19689
  // Merge default config with Authorization header
19378
19690
  const effectiveConfig = {
19379
- ...DEFAULT_CONFIG,
19691
+ ...DEFAULT_CONFIG$1,
19380
19692
  headers: {
19381
- ...DEFAULT_CONFIG.headers,
19693
+ ...DEFAULT_CONFIG$1.headers,
19382
19694
  Authorization: `Bearer ${validatedParams.apiKey}`,
19383
19695
  },
19384
19696
  };
@@ -19431,9 +19743,9 @@ const getSwapStatus = async (params) => {
19431
19743
  }
19432
19744
  const url = buildSwapStatusUrl(result.data);
19433
19745
  const effectiveConfig = {
19434
- ...DEFAULT_CONFIG,
19746
+ ...DEFAULT_CONFIG$1,
19435
19747
  headers: {
19436
- ...DEFAULT_CONFIG.headers,
19748
+ ...DEFAULT_CONFIG$1.headers,
19437
19749
  Authorization: `Bearer ${result.data.apiKey}`,
19438
19750
  },
19439
19751
  };
@@ -22308,6 +22620,27 @@ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
22308
22620
  */
22309
22621
  const NATIVE_TOKEN_ADDRESS$1 = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
22310
22622
 
22623
+ const hexBytesSchema$1 = zod.z
22624
+ .string()
22625
+ .regex(/^0x([0-9a-fA-F]{2})*$/, {
22626
+ message: 'value must be a 0x-prefixed even-length hex string',
22627
+ })
22628
+ .transform((value) => value);
22629
+ zod.z
22630
+ .object({
22631
+ executeParams: zod.z.object({}).passthrough(),
22632
+ signature: evmSignatureSchema,
22633
+ tokenInputs: zod.z.array(zod.z.object({
22634
+ permitType: zod.z.nativeEnum(PermitType),
22635
+ token: evmAddressSchema,
22636
+ amount: zod.z.bigint().refine((value) => value >= 0n, {
22637
+ message: 'amount must be a non-negative bigint',
22638
+ }),
22639
+ permitCalldata: hexBytesSchema$1,
22640
+ })),
22641
+ })
22642
+ .passthrough();
22643
+
22311
22644
  /**
22312
22645
  * Solana mainnet genesis hash
22313
22646
  */
@@ -24518,21 +24851,21 @@ function buildInsufficientSwapBalanceError(tokenSymbol, walletAddress, currentBa
24518
24851
  const displaySymbol = resolvedSymbol ?? tokenSymbol;
24519
24852
  // Format human-readable amounts if we can resolve the token, otherwise use base units only
24520
24853
  const currentFormatted = canFormat
24521
- ? formatAmount$1({
24854
+ ? formatAmount$2({
24522
24855
  value: currentBalance.toString(),
24523
24856
  token: resolvedSymbol,
24524
24857
  chain,
24525
24858
  })
24526
24859
  : null;
24527
24860
  const requiredFormatted = canFormat
24528
- ? formatAmount$1({
24861
+ ? formatAmount$2({
24529
24862
  value: requiredAmount.toString(),
24530
24863
  token: resolvedSymbol,
24531
24864
  chain,
24532
24865
  })
24533
24866
  : null;
24534
24867
  const shortfallFormatted = canFormat
24535
- ? formatAmount$1({
24868
+ ? formatAmount$2({
24536
24869
  value: shortfall.toString(),
24537
24870
  token: resolvedSymbol,
24538
24871
  chain,
@@ -24618,7 +24951,7 @@ async function formatTokenValue(value, token, chain, adapter) {
24618
24951
  if (resolvedSymbol) {
24619
24952
  if (isSwapToken(resolvedSymbol)) {
24620
24953
  return {
24621
- amount: formatAmount$1({
24954
+ amount: formatAmount$2({
24622
24955
  value,
24623
24956
  token: resolvedSymbol,
24624
24957
  chain,
@@ -24659,7 +24992,7 @@ async function formatTokenValue(value, token, chain, adapter) {
24659
24992
  * i.e. 1 + 0.2 + 0.1 = 1.3. Use the greater of (local estimate × this multiplier)
24660
24993
  * or the proxy's gasLimit.
24661
24994
  */
24662
- const GAS_SAFETY_MULTIPLIER = 1.3;
24995
+ const GAS_SAFETY_MULTIPLIER$1 = 1.3;
24663
24996
  const TOKEN_REGISTRY = createTokenRegistry();
24664
24997
  /**
24665
24998
  * Enhances a KitError with transaction details (txHash and explorerUrl).
@@ -25092,7 +25425,7 @@ class StablecoinServiceSwapProvider {
25092
25425
  const gasEstimate = await params.from.adapter.calculateTransactionFee(gasLimitUnits, undefined, // Use default 5% buffer
25093
25426
  chain);
25094
25427
  // Format the fee from wei to human-readable native currency amount
25095
- estimatedGasFee = formatAmount$1({
25428
+ estimatedGasFee = formatAmount$2({
25096
25429
  value: gasEstimate.fee,
25097
25430
  token: 'NATIVE',
25098
25431
  chain,
@@ -25105,7 +25438,7 @@ class StablecoinServiceSwapProvider {
25105
25438
  const gasEstimate = await params.from.adapter.calculateTransactionFee(300000n, undefined, // Use default buffer
25106
25439
  chain);
25107
25440
  // Format the fee from lamports to human-readable SOL amount
25108
- estimatedGasFee = formatAmount$1({
25441
+ estimatedGasFee = formatAmount$2({
25109
25442
  value: gasEstimate.fee,
25110
25443
  token: 'NATIVE',
25111
25444
  chain,
@@ -25639,7 +25972,7 @@ class StablecoinServiceSwapProvider {
25639
25972
  const localEstimate = await preparedAction.estimate();
25640
25973
  const localGas = Number(localEstimate?.gas);
25641
25974
  if (Number.isFinite(localGas) && localGas > 0) {
25642
- const localWithBuffer = Math.ceil(localGas * GAS_SAFETY_MULTIPLIER);
25975
+ const localWithBuffer = Math.ceil(localGas * GAS_SAFETY_MULTIPLIER$1);
25643
25976
  if (Number.isFinite(localWithBuffer) && localWithBuffer > 0) {
25644
25977
  effectiveGasLimit = Math.max(localWithBuffer, proxyGasLimit);
25645
25978
  }
@@ -25735,7 +26068,7 @@ class StablecoinServiceSwapProvider {
25735
26068
  * Must always contain both adapter and chain explicitly.
25736
26069
  * Optionally includes address for developer-controlled adapters.
25737
26070
  */
25738
- const adapterContextSchema$4 = zod.z.object({
26071
+ const adapterContextSchema$5 = zod.z.object({
25739
26072
  adapter: adapterSchema$1,
25740
26073
  chain: swapChainIdentifierSchema,
25741
26074
  address: zod.z.string().optional(),
@@ -25951,7 +26284,7 @@ const amountInSchema = zod.z
25951
26284
  * ```
25952
26285
  */
25953
26286
  const swapParamsSchema = zod.z.object({
25954
- from: adapterContextSchema$4,
26287
+ from: adapterContextSchema$5,
25955
26288
  tokenIn: swapTokenSchema,
25956
26289
  tokenOut: swapTokenSchema,
25957
26290
  amountIn: amountInSchema,
@@ -28482,7 +28815,7 @@ async function swap$1(context, params) {
28482
28815
  * )
28483
28816
  * ```
28484
28817
  */
28485
- function getSupportedChains$3(context) {
28818
+ function getSupportedChains$4(context) {
28486
28819
  const swapChains = new Set(Object.values(exports.SwapChain));
28487
28820
  // Collect all supported chains from all providers
28488
28821
  // Use optional chaining to gracefully handle providers without supportedChains
@@ -28669,7 +29002,7 @@ function removeCustomFeePolicy(context) {
28669
29002
  * @returns An array containing the default StablecoinServiceSwapProvider
28670
29003
  * @internal
28671
29004
  */
28672
- const getDefaultProviders$1 = () => [new StablecoinServiceSwapProvider()];
29005
+ const getDefaultProviders$2 = () => [new StablecoinServiceSwapProvider()];
28673
29006
  /**
28674
29007
  * Create a SwapKit context with validated configuration.
28675
29008
  *
@@ -28729,7 +29062,7 @@ const getDefaultProviders$1 = () => [new StablecoinServiceSwapProvider()];
28729
29062
  */
28730
29063
  function createSwapKitContext(config = {}) {
28731
29064
  // Initialize default providers
28732
- const defaultProviders = getDefaultProviders$1();
29065
+ const defaultProviders = getDefaultProviders$2();
28733
29066
  // Merge default and custom providers
28734
29067
  const providers = [...defaultProviders, ...(config.providers ?? [])];
28735
29068
  // Build base context without fee policy
@@ -28757,7 +29090,7 @@ const SWAP_EVENT_TYPES = {
28757
29090
  };
28758
29091
 
28759
29092
  /** SDK name used in telemetry payloads. */
28760
- const SDK_NAME$1 = resolveKitSdkName(pkg$1.name);
29093
+ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
28761
29094
  /**
28762
29095
  * A high-level class-based interface for single-chain token swap operations.
28763
29096
  *
@@ -28884,7 +29217,7 @@ class SwapKit {
28884
29217
  this.disableErrorReporting = config.disableErrorReporting === true;
28885
29218
  this.telemetryConfig = {
28886
29219
  sdkName: SDK_NAME$1,
28887
- sdkVersion: pkg$1.version,
29220
+ sdkVersion: pkg$2.version,
28888
29221
  disabled: this.disableErrorReporting,
28889
29222
  };
28890
29223
  }
@@ -29028,7 +29361,7 @@ class SwapKit {
29028
29361
  * ```
29029
29362
  */
29030
29363
  getSupportedChains() {
29031
- return getSupportedChains$3(this.context);
29364
+ return getSupportedChains$4(this.context);
29032
29365
  }
29033
29366
  /**
29034
29367
  * Set a custom fee policy for all swap operations.
@@ -29152,7 +29485,7 @@ class SwapKit {
29152
29485
  * @packageDocumentation
29153
29486
  */
29154
29487
  // Auto-register this kit for user agent tracking
29155
- registerKit(`${pkg$1.name}/${pkg$1.version}`);
29488
+ registerKit(`${pkg$2.name}/${pkg$2.version}`);
29156
29489
 
29157
29490
  /**
29158
29491
  * Create a SwapKit instance with optional developer fee configuration.
@@ -29250,288 +29583,3138 @@ const createSwapKit = (context) => {
29250
29583
  return kit;
29251
29584
  };
29252
29585
 
29586
+ var name$1 = "@circle-fin/earn-kit";
29587
+ var version$1 = "1.0.0";
29588
+ var pkg$1 = {
29589
+ name: name$1,
29590
+ version: version$1};
29591
+
29253
29592
  /**
29254
- * Register event handlers from a context actions map to a kit instance.
29255
- *
29256
- * This utility function registers event handlers stored in a context actions map
29257
- * with a kit instance that supports event handling via an `on` method. It handles
29258
- * wildcard handlers ('*') and prefixed action handlers, stripping the prefix
29259
- * before registration.
29593
+ * Default base URL for the Earn Service API.
29260
29594
  *
29261
- * The function is designed to be reusable across different operation types
29262
- * (bridge, swap, stake, etc.) by accepting a configurable prefix parameter.
29263
- *
29264
- * @param kit - The kit instance to register handlers with (must have an `on` method)
29265
- * @param handlers - Map of action names to arrays of handler functions
29266
- * @param prefix - Optional prefix to strip from action names (e.g., 'bridge.')
29595
+ * @internal
29596
+ */
29597
+ const EARN_SERVICE_BASE_URL = 'https://api.circle.com';
29598
+ /**
29599
+ * API path prefix for all EarnKit endpoints.
29267
29600
  *
29268
- * @example
29269
- * ```typescript
29270
- * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
29271
- * import { BridgeKit } from '@circle-fin/bridge-kit'
29601
+ * @internal
29602
+ */
29603
+ const EARN_KIT_API_PREFIX = '/v1/earnKit';
29604
+ /**
29605
+ * Map SDK chain identifiers to Zenith API chain strings.
29272
29606
  *
29273
- * const kit = new BridgeKit()
29274
- * const handlers = {
29275
- * '*': [(payload) => console.log('All actions:', payload)],
29276
- * 'bridge.approve': [(payload) => console.log('Approved:', payload)],
29277
- * 'bridge.burn': [(payload) => console.log('Burned:', payload)],
29278
- * }
29607
+ * The Zenith backend uses short-form blockchain identifiers (e.g., 'ARC-TESTNET')
29608
+ * while the SDK uses the {@link Blockchain} enum (e.g., 'Arc_Testnet').
29609
+ * Adding a new {@link EarnSupportedBlockchain} without a corresponding
29610
+ * entry here produces a compile error.
29279
29611
  *
29280
- * registerActionHandlers(kit, handlers, 'bridge.')
29281
- * ```
29612
+ * @internal
29613
+ */
29614
+ const CHAIN_TO_API = {
29615
+ [exports.Blockchain.Arc_Testnet]: 'ARC-TESTNET',
29616
+ };
29617
+ /**
29618
+ * Display symbol for vault share tokens.
29282
29619
  *
29283
- * @example
29284
- * ```typescript
29285
- * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
29286
- * import { SwapKit } from '@circle-fin/swap-kit'
29620
+ * @internal
29621
+ */
29622
+ const VAULT_SHARE_SYMBOL = 'shares';
29623
+ /**
29624
+ * Default polling configuration for Earn Service API calls.
29287
29625
  *
29288
- * const kit = new SwapKit()
29289
- * const handlers = {
29290
- * 'swap.initiate': [(payload) => console.log('Swap initiated:', payload)],
29291
- * }
29626
+ * Match the load-balancer timeout (30 s) with retry settings consistent
29627
+ * with `@core/service-client` defaults.
29292
29628
  *
29293
- * registerActionHandlers(kit, handlers, 'swap.')
29294
- * ```
29629
+ * @internal
29295
29630
  */
29296
- const registerActionHandlers = (kit, handlers, prefix = '') => {
29297
- for (const [action, handlerArray] of Object.entries(handlers)) {
29298
- // Register all handlers for this action
29299
- for (const handler of handlerArray) {
29300
- if (action === '*') {
29301
- // Wildcard handlers are registered as-is
29302
- kit.on('*', handler);
29303
- }
29304
- else if (prefix && action.startsWith(prefix)) {
29305
- // Remove prefix to get the actual kit action name
29306
- const kitAction = action.split('.').at(1);
29307
- if (kitAction) {
29308
- kit.on(kitAction, handler);
29309
- }
29310
- }
29311
- else if (!prefix) {
29312
- // No prefix configured, register action as-is
29313
- kit.on(action, handler);
29314
- }
29315
- // Actions that don't match the prefix are silently ignored
29316
- }
29317
- }
29631
+ const DEFAULT_CONFIG = {
29632
+ timeout: 30_000,
29633
+ maxRetries: 3,
29634
+ retryDelay: 200,
29635
+ headers: {
29636
+ 'Content-Type': 'application/json',
29637
+ },
29318
29638
  };
29319
29639
 
29640
+ const API_TO_CHAIN = Object.fromEntries(Object.entries(CHAIN_TO_API).map(([sdkChain, apiChain]) => [
29641
+ apiChain,
29642
+ sdkChain,
29643
+ ]));
29644
+ function toApiChain(chain) {
29645
+ if (!Object.hasOwn(CHAIN_TO_API, chain)) {
29646
+ return undefined;
29647
+ }
29648
+ return CHAIN_TO_API[chain];
29649
+ }
29650
+ function toSdkChain(chain) {
29651
+ if (Object.hasOwn(CHAIN_TO_API, chain)) {
29652
+ return chain;
29653
+ }
29654
+ if (!Object.hasOwn(API_TO_CHAIN, chain)) {
29655
+ return undefined;
29656
+ }
29657
+ return API_TO_CHAIN[chain];
29658
+ }
29659
+
29320
29660
  /**
29321
- * List of all supported token aliases for App Kit send operations.
29661
+ * Extract address and chain string from an adapter context.
29322
29662
  *
29323
- * This array is used for runtime validation to check if a token string
29324
- * is a known alias rather than a custom address.
29663
+ * Handle both user-controlled adapters (address resolved via
29664
+ * `adapter.getAddress()`) and developer-controlled adapters
29665
+ * (address provided explicitly in the context).
29325
29666
  *
29326
- * @remarks
29327
- * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
29328
- * with the TokenAlias type definition - TypeScript will enforce any changes.
29667
+ * @param from - Adapter context containing adapter, chain, and optional address
29668
+ * @returns Resolved wallet address, API chain string, and chain definition
29669
+ * @throws {@link KitError} If the chain is unsupported by the Earn Service provider.
29670
+ * @throws Propagates errors from `adapter.getAddress(chain)` when
29671
+ * `from.address` is not supplied.
29329
29672
  *
29330
- * For swap operations, additional tokens (EURC, DAI, USDE, PYUSD) are supported
29331
- * via SwapKit's SupportedToken type.
29673
+ * @example
29674
+ * ```typescript
29675
+ * const { address, chain, chainDefinition } =
29676
+ * await resolveAdapterContext(params.from)
29677
+ * ```
29678
+ *
29679
+ * @internal
29332
29680
  */
29333
- const TOKEN_ALIASES = ['USDC', 'USDT', 'NATIVE'];
29681
+ async function resolveAdapterContext(from) {
29682
+ const chainDef = resolveChainIdentifier(from.chain);
29683
+ const apiChain = toApiChain(chainDef.chain);
29684
+ if (apiChain === undefined) {
29685
+ throw createInvalidChainError(chainDef.chain, 'Chain is not supported by the Earn Service provider');
29686
+ }
29687
+ const hasAddress = 'address' in from &&
29688
+ typeof from.address === 'string' &&
29689
+ from.address.length > 0;
29690
+ const address = hasAddress
29691
+ ? from.address
29692
+ : await from.adapter.getAddress(chainDef);
29693
+ return { address, chain: apiChain, chainDefinition: chainDef };
29694
+ }
29695
+
29334
29696
  /**
29335
- * Check if a token string is a known alias.
29336
- *
29337
- * This function performs case-sensitive matching against the list of known
29338
- * token aliases. Known aliases take precedence over custom addresses in the
29339
- * send operation logic.
29697
+ * Assert that a value is a 0x-prefixed 20-byte hex address.
29340
29698
  *
29341
- * @param token - The token identifier to check
29342
- * @returns True if the token is a known alias, false otherwise
29699
+ * @param field - Name of the field being validated.
29700
+ * @param value - Runtime value to validate.
29701
+ * @param message - Optional message describing the expected address.
29702
+ * @returns The validated value narrowed to a 0x-prefixed string.
29703
+ * @throws {@link KitError} If the value is not an EVM address.
29343
29704
  *
29344
29705
  * @example
29345
29706
  * ```typescript
29346
- * import { isTokenAlias } from '@circle-fin/app-kit'
29347
- *
29348
- * console.log(isTokenAlias('USDC')) // true
29349
- * console.log(isTokenAlias('NATIVE')) // true
29350
- * console.log(isTokenAlias('0x6B175474E89094C44Da98b954EedeAC495271d0F')) // false
29707
+ * const token = assertHexAddress(
29708
+ * 'chain.usdcAddress',
29709
+ * '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
29710
+ * )
29351
29711
  * ```
29712
+ *
29713
+ * @internal
29352
29714
  */
29353
- function isTokenAlias(token) {
29354
- return TOKEN_ALIASES.includes(token);
29715
+ function assertHexAddress(field, value, message = `${field} must be a 0x-prefixed 20-byte hex address`) {
29716
+ const result = evmAddressSchema.safeParse(value);
29717
+ if (!result.success) {
29718
+ throw createValidationFailedError$1(field, value, message);
29719
+ }
29720
+ return result.data;
29355
29721
  }
29722
+
29356
29723
  /**
29357
- * Type guard to check if a string is a valid token address for a chain.
29358
- *
29359
- * This function verifies that a token string is:
29360
- * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
29361
- * 2. A valid address format for the specified chain
29362
- *
29363
- * Use this to narrow the type to `TokenAddress` in TypeScript.
29724
+ * Return the adapter contract address configured for the chain, or throw a
29725
+ * fatal KitError if unavailable. Earn operations require an adapter contract
29726
+ * to forward service-signed payloads.
29364
29727
  *
29365
- * @param token - The token string to validate
29366
- * @param chain - The chain definition for address validation
29367
- * @returns True if the token is a valid address (not an alias)
29728
+ * @param chain - Resolved chain definition.
29729
+ * @returns Adapter contract address as a 0x-prefixed hex string.
29730
+ * @throws {@link KitError} If `chain.kitContracts.adapter` is undefined.
29731
+ * @throws {@link KitError} If `chain.kitContracts.adapter` is malformed.
29368
29732
  *
29369
29733
  * @example
29370
29734
  * ```typescript
29371
- * import { isTokenAddress } from '@circle-fin/app-kit'
29372
- * import { Ethereum } from '@core/chains'
29373
- *
29374
- * const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
29375
- * if (isTokenAddress(daiAddress, Ethereum)) {
29376
- * // TypeScript knows daiAddress is TokenAddress here
29377
- * console.log('Valid token address:', daiAddress)
29378
- * }
29379
- *
29380
- * console.log(isTokenAddress('USDC', Ethereum)) // false - it's an alias
29381
- * console.log(isTokenAddress('invalid', Ethereum)) // false - invalid format
29735
+ * const adapterContract = requireAdapterContract(chain)
29382
29736
  * ```
29737
+ *
29738
+ * @internal
29383
29739
  */
29384
- function isTokenAddress(token, chain) {
29385
- return !isTokenAlias(token) && isValidAddressForChain(token, chain);
29740
+ function requireAdapterContract(chain) {
29741
+ const adapterContractAddress = chain.kitContracts?.adapter;
29742
+ if (adapterContractAddress === undefined) {
29743
+ throw createValidationFailedError$1('chain.kitContracts.adapter', adapterContractAddress, `Adapter contract not configured for chain ${chain.name}. Earn operations require an adapter contract.`);
29744
+ }
29745
+ return assertHexAddress('chain.kitContracts.adapter', adapterContractAddress, `Adapter contract for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
29386
29746
  }
29747
+
29387
29748
  /**
29388
- * Validate and classify a token identifier.
29389
- *
29390
- * This function determines whether a token string is:
29391
- * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
29392
- * 2. A valid token address for the given chain
29393
- * 3. An invalid/unrecognized token identifier
29749
+ * Safety multiplier applied to locally estimated gas for earn transactions.
29394
29750
  *
29395
- * Known aliases always take precedence. If the token is not an alias,
29396
- * it's validated as an address for the given chain.
29751
+ * Earn transactions can include nested vault and token contract calls.
29752
+ * Local gas estimation has undercounted those execution paths in production.
29753
+ * A 1.3x buffer matching swap-kit reduces out-of-gas risk while preserving
29754
+ * the adapter fallback path when local estimation fails.
29397
29755
  *
29398
- * @param token - The token identifier to validate
29399
- * @param chain - The chain definition for address validation
29400
- * @returns An object containing validation results
29756
+ * @internal
29757
+ */
29758
+ const GAS_SAFETY_MULTIPLIER = 1.3;
29759
+ /**
29760
+ * Estimate gas for a prepared chain request and apply {@link GAS_SAFETY_MULTIPLIER}.
29401
29761
  *
29402
- * @example
29403
- * ```typescript
29404
- * import { validateToken } from '@circle-fin/app-kit'
29405
- * import { Ethereum } from '@core/chains'
29762
+ * Returns the buffered gas limit, or `undefined` if estimation fails or
29763
+ * produces an invalid value. Callers should fall through to the adapter's
29764
+ * default gas handling when `undefined` is returned.
29406
29765
  *
29407
- * // Known alias
29408
- * const usdc = validateToken('USDC', Ethereum)
29409
- * console.log(usdc) // { isAlias: true, isAddress: false, isValid: true }
29766
+ * @internal
29767
+ */
29768
+ async function estimateBufferedGasLimit(prepared) {
29769
+ try {
29770
+ const estimate = await prepared.estimate();
29771
+ const estimatedGas = Number(estimate.gas);
29772
+ if (Number.isFinite(estimatedGas) && estimatedGas > 0) {
29773
+ const buffered = Math.ceil(estimatedGas * GAS_SAFETY_MULTIPLIER);
29774
+ if (Number.isFinite(buffered) && buffered > 0) {
29775
+ return buffered;
29776
+ }
29777
+ }
29778
+ }
29779
+ catch {
29780
+ // If estimation fails, fall through and let the adapter handle gas internally
29781
+ }
29782
+ return undefined;
29783
+ }
29784
+
29785
+ /**
29786
+ * Maximum `uint256` value. The approval amount used when this helper needs
29787
+ * to send a token approval.
29410
29788
  *
29411
- * // Custom token address
29412
- * const dai = validateToken('0x6B175474E89094C44Da98b954EedeAC495271d0F', Ethereum)
29413
- * console.log(dai) // { isAlias: false, isAddress: true, isValid: true }
29789
+ * @internal
29790
+ */
29791
+ const MAX_UINT256$1 = 2n ** 256n - 1n;
29792
+ /**
29793
+ * Allowance threshold above which a prior max-approve is assumed to still
29794
+ * cover any earn operation. Detects prior max-approvals even when the token
29795
+ * decrements allowance on `transferFrom`, since that decay is negligible
29796
+ * relative to 2^255.
29414
29797
  *
29415
- * // Invalid token
29416
- * const invalid = validateToken('invalid', Ethereum)
29417
- * console.log(invalid) // { isAlias: false, isAddress: false, isValid: false }
29418
- * ```
29798
+ * @internal
29419
29799
  */
29420
- function validateToken(token, chain) {
29421
- // Check if it's a known alias first (aliases take precedence)
29422
- const alias = isTokenAlias(token);
29423
- if (alias) {
29424
- return {
29425
- isAlias: true,
29426
- isAddress: false,
29427
- isValid: true,
29428
- };
29800
+ const ALLOWANCE_THRESHOLD = MAX_UINT256$1 / 2n;
29801
+ function parseAllowanceResponse(allowanceRaw) {
29802
+ if (allowanceRaw === undefined || allowanceRaw === null) {
29803
+ return 0n;
29429
29804
  }
29430
- // Not an alias - check if it's a valid address for the chain
29431
- const address = isTokenAddress(token, chain);
29432
- return {
29433
- isAlias: false,
29434
- isAddress: address,
29435
- isValid: address,
29436
- };
29805
+ let allowance;
29806
+ if (typeof allowanceRaw === 'bigint') {
29807
+ allowance = allowanceRaw;
29808
+ }
29809
+ else if (typeof allowanceRaw === 'string') {
29810
+ try {
29811
+ allowance = BigInt(allowanceRaw);
29812
+ }
29813
+ catch {
29814
+ allowance = undefined;
29815
+ }
29816
+ }
29817
+ if (allowance === undefined || allowance < 0n) {
29818
+ throw createValidationFailedError$1('token.allowance', allowanceRaw, 'token.allowance response must be a non-negative bigint-compatible string or bigint');
29819
+ }
29820
+ return allowance;
29437
29821
  }
29438
-
29439
29822
  /**
29440
- * Estimate the costs and details of a cross-chain bridge transfer using the AppKit context.
29823
+ * Read the current ERC-20 allowance and, if below threshold, send a
29824
+ * max-approval transaction and wait for confirmation.
29441
29825
  *
29442
- * This function provides cost estimation for bridge operations within the AppKit
29443
- * ecosystem. It delegates to the underlying BridgeKit infrastructure while maintaining
29444
- * consistency with the AppKit patterns and context-based architecture.
29826
+ * Uses the generalised `token.allowance` + `token.approve` adapter actions so
29827
+ * the kit stays chain-agnostic. Assumes the target token supports
29828
+ * non-zero-to-non-zero approve (USDC, ERC-4626 share tokens). Tokens with
29829
+ * USDT-style semantics (require allowance == 0 before non-zero approve) need
29830
+ * a different pattern.
29445
29831
  *
29446
- * The function validates parameters, applies context-specific configurations,
29447
- * and returns comprehensive estimation details including fees, gas costs, and
29448
- * transaction parameters without executing the actual transfer.
29832
+ * Approval status is checked explicitly because `waitForTransaction` returns
29833
+ * a receipt with `status: 'reverted'` rather than throwing on revert.
29449
29834
  *
29450
- * @param context - AppKit context containing fee calculation and configuration hooks
29451
- * @param params - Bridge parameters containing source, destination, amount, and token
29452
- * @returns Promise resolving to the estimate result with comprehensive fee and cost information
29453
- * @throws If bridge parameters are invalid
29454
- * @throws If the bridge route is not supported
29455
- * @throws If estimation fails due to network or configuration issues
29835
+ * @returns The approval tx hash when an approval was sent, or `undefined` if
29836
+ * the existing allowance was already above threshold.
29837
+ * @throws {@link KitError} If the allowance response is malformed.
29838
+ * @throws {@link KitError} If the approval transaction reverts on-chain.
29456
29839
  *
29457
29840
  * @example
29458
29841
  * ```typescript
29459
- * import { estimateBridge } from '@circle-fin/app-kit/bridge'
29460
- * import { createContext } from '@circle-fin/app-kit/context'
29461
- *
29462
- * const context = createContext({
29463
- * getFee: async (type, params) => '1000000', // 1 USDC fee
29464
- * getFeeRecipient: async (type, info) => '0xfee-recipient-address'
29465
- * })
29842
+ * const delegate = requireAdapterContract(chain)
29466
29843
  *
29467
- * const estimate = await estimateBridge(context, {
29468
- * from: sourceAdapter,
29469
- * to: { adapter: destAdapter, chain: 'Polygon' },
29470
- * amount: '100.50',
29471
- * token: 'USDC'
29844
+ * const approvalTxHash = await approveMaxIfNeeded({
29845
+ * adapter,
29846
+ * chain,
29847
+ * tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
29848
+ * delegate,
29849
+ * address,
29850
+ * revertMessage: 'USDC approval reverted on-chain',
29472
29851
  * })
29473
- *
29474
- * console.log('Estimated total fee:', estimate.fee)
29475
- * console.log('Estimated gas cost:', estimate.gasEstimate)
29476
29852
  * ```
29853
+ *
29854
+ * @internal
29477
29855
  */
29478
- const estimateBridge = async (context, params) => {
29479
- const kit = createBridgeKit(context);
29480
- // Delegate to the BridgeKit for actual estimation
29481
- return kit.estimate(params);
29482
- };
29856
+ async function approveMaxIfNeeded(params) {
29857
+ const { adapter, chain, tokenAddress, delegate, address, revertMessage } = params;
29858
+ const allowancePrepared = await adapter.prepareAction('token.allowance', { tokenAddress, delegate }, { chain, address });
29859
+ const allowanceRaw = await allowancePrepared.execute();
29860
+ const currentAllowance = parseAllowanceResponse(allowanceRaw);
29861
+ if (currentAllowance >= ALLOWANCE_THRESHOLD) {
29862
+ return undefined;
29863
+ }
29864
+ const approvalPrepared = await adapter.prepareAction('token.approve', { tokenAddress, delegate, amount: MAX_UINT256$1 }, { chain, address });
29865
+ const gasLimitOverride = await estimateBufferedGasLimit(approvalPrepared);
29866
+ const approvalTxHash = approvalPrepared.type === 'evm' && gasLimitOverride !== undefined
29867
+ ? await approvalPrepared.execute({ gasLimit: gasLimitOverride })
29868
+ : await approvalPrepared.execute();
29869
+ const approvalReceipt = await adapter.waitForTransaction(approvalTxHash, { confirmations: 1 }, chain);
29870
+ if (approvalReceipt.status === 'reverted') {
29871
+ throw createTransactionRevertedError(chain.name, revertMessage, undefined, approvalTxHash);
29872
+ }
29873
+ return approvalTxHash;
29874
+ }
29483
29875
 
29484
- const assertSendParamsSymbol = Symbol('assertSendParams');
29485
29876
  /**
29486
- * Matches numeric amount strings with optional thousands separators and decimal part.
29877
+ * Build the `tokenInputs` array forwarded to the adapter contract's
29878
+ * `execute(executeParams, tokenInputs, signature)` call.
29487
29879
  *
29488
- * Accepts both US (e.g. "1,234.56") and EU (e.g. "1.234,56") styles by allowing
29489
- * either "," or "." as the grouping and decimal separators. Also matches integers
29490
- * like "10" and simple decimals like "10.5" or "10,5".
29491
- */
29492
- const amountNumericStringWithSeparatorsRegex = /^\d+(?:[.,]\d{3})*(?:[.,]\d+)?$/;
29493
- /**
29494
- * Schema describing the minimal adapter surface required by the kit.
29880
+ * The adapter contract pulls ERC-20 tokens from `msg.sender` using the
29881
+ * entries in `tokenInputs`, combined either with a pre-existing allowance
29882
+ * ({@link PermitType.NONE}) or a permit signature ({@link PermitType.EIP2612}).
29883
+ * Without an entry, the contract never moves the user's tokens and the
29884
+ * nested instruction reverts with "ERC20: transfer amount exceeds balance"
29885
+ * when the adapter tries to forward tokens it does not hold.
29495
29886
  *
29496
- * The adapter must implement two functions:
29497
- * - `prepare` build the transaction or instruction payload.
29498
- * - `waitForTransaction` await finality/confirmation for a submitted transaction.
29887
+ * Earn deposit and withdraw responses are schema-validated to contain at
29888
+ * least one instruction. This helper emits one `PermitType.NONE` entry per
29889
+ * instruction with a positive `amountToApprove`. Return an empty array when
29890
+ * the service did not request any token pulls.
29499
29891
  *
29500
- * @returns Zod schema validating an object with `prepare` and `waitForTransaction` functions.
29892
+ * @param executionParams - Service-signed `ExecutionParams` forwarded to the adapter.
29893
+ * @param approvedToken - Token approved for adapter spending on this chain.
29894
+ * @returns `TokenInput[]` entries when pulls are required, `[]` otherwise.
29895
+ * @throws {@link KitError} If `tokenIn` differs from the approved token.
29501
29896
  *
29502
29897
  * @example
29503
29898
  * ```typescript
29504
- * import { adapterSchema } from '@circle-fin/app-kit'
29899
+ * const approvedToken = assertHexAddress(
29900
+ * 'chain.usdcAddress',
29901
+ * chain.usdcAddress,
29902
+ * )
29505
29903
  *
29506
- * console.log(adapterSchema.safeParse(ethereumAdapter).success) // true
29904
+ * const tokenInputs = buildEarnTokenInputs(
29905
+ * executionPayload.executionParams,
29906
+ * approvedToken,
29907
+ * )
29507
29908
  * ```
29909
+ *
29910
+ * @internal
29508
29911
  */
29509
- const adapterSchema = zod.z.object({
29510
- prepare: zod.z.function().returns(zod.z.any()),
29511
- waitForTransaction: zod.z.function().returns(zod.z.any()),
29512
- });
29912
+ function buildEarnTokenInputs(executionParams, approvedToken) {
29913
+ const tokenInputs = [];
29914
+ executionParams.instructions.forEach((instruction, index) => {
29915
+ const amount = BigInt(instruction.amountToApprove);
29916
+ if (amount <= 0n) {
29917
+ return;
29918
+ }
29919
+ const { tokenIn } = instruction;
29920
+ if (tokenIn.toLowerCase() !== approvedToken.toLowerCase()) {
29921
+ throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
29922
+ }
29923
+ tokenInputs.push({
29924
+ permitType: PermitType.NONE,
29925
+ token: tokenIn,
29926
+ amount,
29927
+ permitCalldata: '0x',
29928
+ });
29929
+ });
29930
+ return tokenInputs;
29931
+ }
29932
+
29513
29933
  /**
29514
- * Schema for validating adapter contexts.
29515
- * Ensure required fields are present and properly typed. An adapter context must include:
29516
- * - A valid adapter with `prepare` and `waitForTransaction` methods.
29517
- * - A valid chain identifier (string literal or chain object recognized by the kit).
29934
+ * Prepare an earn adapter action, execute it, wait for confirmation, and
29935
+ * throw a structured revert error if the receipt status is `'reverted'`.
29518
29936
  *
29519
- * @returns Zod schema validating an adapter context object.
29520
- * @throws ZodError If validation fails, with details about which properties failed.
29937
+ * Wraps the prepareAction, execute, waitForTransaction, and status check
29938
+ * sequence so the provider can dispatch any `earn.*` action key with
29939
+ * consistent revert handling.
29940
+ *
29941
+ * Gas estimation: after preparing the action the helper calls
29942
+ * {@link estimateBufferedGasLimit}, which applies a safety buffer to the
29943
+ * returned gas value. If estimation fails, execution falls through to the
29944
+ * adapter's default gas handling.
29945
+ *
29946
+ * @typeParam TActionKey - Earn action key being executed.
29947
+ * @param params - Adapter action, execution context, and revert message.
29948
+ * @returns The confirmed on-chain transaction hash and explorer URL.
29949
+ * @throws {@link KitError} If the transaction reverts on-chain.
29521
29950
  *
29522
29951
  * @example
29523
29952
  * ```typescript
29524
- * import { adapterContextSchema } from '@circle-fin/app-kit'
29525
- *
29526
- * const result = adapterContextSchema.safeParse(context)
29527
- * console.log(result.success) // true
29953
+ * const { txHash, explorerUrl } = await executeEarnAction({
29954
+ * adapter,
29955
+ * chain,
29956
+ * address,
29957
+ * actionKey: 'earn.deposit',
29958
+ * actionParams: { executeParams, tokenInputs, signature },
29959
+ * revertMessage: 'Earn deposit reverted on-chain',
29960
+ * })
29528
29961
  * ```
29962
+ *
29963
+ * @internal
29529
29964
  */
29530
- const adapterContextSchema$3 = zod.z.object({
29531
- adapter: adapterSchema,
29532
- /**
29533
- * Note: We cast chainIdentifierSchema to 'never' here to work around a TypeScript
29534
- * limitation (TS2589: Type instantiation is excessively deep and possibly infinite)
29965
+ async function executeEarnAction(params) {
29966
+ const { adapter, chain, address, actionKey, actionParams, revertMessage } = params;
29967
+ const prepared = await adapter.prepareAction(actionKey, actionParams, {
29968
+ chain,
29969
+ address,
29970
+ });
29971
+ const gasLimitOverride = await estimateBufferedGasLimit(prepared);
29972
+ const txHash = prepared.type === 'evm' && gasLimitOverride !== undefined
29973
+ ? await prepared.execute({ gasLimit: gasLimitOverride })
29974
+ : await prepared.execute();
29975
+ const explorerUrl = buildExplorerUrl(chain, txHash);
29976
+ const receipt = await adapter.waitForTransaction(txHash, { confirmations: 1 }, chain);
29977
+ if (receipt.status === 'reverted') {
29978
+ throw createTransactionRevertedError(chain.name, revertMessage, undefined, txHash, explorerUrl);
29979
+ }
29980
+ return { txHash, explorerUrl };
29981
+ }
29982
+
29983
+ /**
29984
+ * Validate that a service-signed execution payload has not expired before
29985
+ * the SDK asks the wallet to broadcast a transaction.
29986
+ *
29987
+ * @internal
29988
+ */
29989
+ function validateExecutionDeadline(executionParams) {
29990
+ const deadline = BigInt(executionParams.deadline);
29991
+ const now = BigInt(Math.floor(Date.now() / 1000));
29992
+ if (deadline <= now) {
29993
+ throw createValidationFailedError$1('executionParams.deadline', executionParams.deadline, 'Earn execution deadline has expired');
29994
+ }
29995
+ }
29996
+
29997
+ // ---------------------------------------------------------------------------
29998
+ // Shared primitives
29999
+ // ---------------------------------------------------------------------------
30000
+ function isBigIntLike(value) {
30001
+ try {
30002
+ BigInt(value);
30003
+ return true;
30004
+ }
30005
+ catch {
30006
+ return false;
30007
+ }
30008
+ }
30009
+ function isNonNegativeBigIntLike(value) {
30010
+ try {
30011
+ return BigInt(value) >= 0n;
30012
+ }
30013
+ catch {
30014
+ return false;
30015
+ }
30016
+ }
30017
+ const hexSignatureSchema = evmSignatureSchema;
30018
+ const hexAddressSchema = evmAddressSchema;
30019
+ /**
30020
+ * Zod schema for a non-negative uint256-like value.
30021
+ *
30022
+ * The service emits uint256 fields as decimal strings in JSON, while tests and
30023
+ * lower-level SDK callers may already hold bigint values. Keep the parsed value
30024
+ * unchanged so signed execution params can be forwarded verbatim.
30025
+ *
30026
+ * @internal
30027
+ */
30028
+ const uint256LikeSchema = zod.z
30029
+ .union([zod.z.string(), zod.z.bigint()])
30030
+ .refine(isNonNegativeBigIntLike, {
30031
+ message: 'value must be a non-negative bigint-compatible string or bigint',
30032
+ });
30033
+ /**
30034
+ * Zod schema for typed amount objects emitted by the API.
30035
+ *
30036
+ * @internal
30037
+ */
30038
+ const amountJsonSchema = zod.z.object({
30039
+ raw: zod.z.string().refine(isBigIntLike, {
30040
+ message: 'raw must be a bigint-compatible string',
30041
+ }),
30042
+ decimals: zod.z.number().int().nonnegative(),
30043
+ });
30044
+ /** @internal */
30045
+ const hexBytesSchema = zod.z.string().regex(/^0x([0-9a-fA-F]{2})*$/, {
30046
+ message: 'value must be a 0x-prefixed even-length hex string',
30047
+ });
30048
+ // ---------------------------------------------------------------------------
30049
+ // Vault response schemas
30050
+ // ---------------------------------------------------------------------------
30051
+ /**
30052
+ * Zod schema for a vault reward token in the API response.
30053
+ *
30054
+ * @internal
30055
+ */
30056
+ const vaultRewardSchema = zod.z.object({
30057
+ token: zod.z.string(),
30058
+ tokenAddress: zod.z.string(),
30059
+ apy: zod.z.number(),
30060
+ });
30061
+ /**
30062
+ * Zod schema for a vault collateral market in the API response.
30063
+ *
30064
+ * @internal
30065
+ */
30066
+ const collateralSchema = zod.z.object({
30067
+ asset: zod.z.string(),
30068
+ assetAddress: zod.z.string(),
30069
+ lltv: zod.z.number(),
30070
+ supplyUsd: zod.z.number(),
30071
+ });
30072
+ /**
30073
+ * Zod schema for a Morpho vault warning in the API response.
30074
+ *
30075
+ * @internal
30076
+ */
30077
+ const vaultWarningSchema = zod.z.object({
30078
+ type: zod.z.string(),
30079
+ level: zod.z.enum(['YELLOW', 'RED']),
30080
+ });
30081
+ /**
30082
+ * Zod schema for a single vault info object in the API response.
30083
+ *
30084
+ * @internal
30085
+ */
30086
+ const vaultInfoResponseSchema = zod.z.object({
30087
+ vaultAddress: zod.z.string(),
30088
+ chain: zod.z.string(),
30089
+ name: zod.z.string(),
30090
+ protocol: zod.z.string(),
30091
+ asset: zod.z.string(),
30092
+ assetAddress: zod.z.string(),
30093
+ currentApy: zod.z.number(),
30094
+ nativeApy: zod.z.number(),
30095
+ vaultFee: zod.z.number(),
30096
+ rewards: zod.z.array(vaultRewardSchema),
30097
+ collateral: zod.z.array(collateralSchema),
30098
+ totalDeposits: amountJsonSchema,
30099
+ liquidity: amountJsonSchema,
30100
+ status: zod.z.enum(['active', 'low_liquidity']),
30101
+ warnings: zod.z.array(vaultWarningSchema).optional(),
30102
+ earnKitWarnings: zod.z.array(zod.z.string()).optional(),
30103
+ });
30104
+ // ---------------------------------------------------------------------------
30105
+ // Position response schema
30106
+ // ---------------------------------------------------------------------------
30107
+ /**
30108
+ * Zod schema for an accrued reward in the position API response.
30109
+ *
30110
+ * @internal
30111
+ */
30112
+ const accruedRewardSchema = zod.z.object({
30113
+ token: zod.z.string(),
30114
+ symbol: zod.z.string(),
30115
+ amount: amountJsonSchema,
30116
+ });
30117
+ const positionPnlSchema = zod.z.discriminatedUnion('status', [
30118
+ zod.z.object({
30119
+ status: zod.z.literal('available'),
30120
+ principalDeposited: amountJsonSchema,
30121
+ totalYieldEarned: amountJsonSchema,
30122
+ }),
30123
+ zod.z.object({
30124
+ status: zod.z.literal('pending'),
30125
+ }),
30126
+ zod.z.object({
30127
+ status: zod.z.literal('unavailable'),
30128
+ reason: zod.z.string(),
30129
+ }),
30130
+ ]);
30131
+ /**
30132
+ * Zod schema for the inner position payload.
30133
+ *
30134
+ * @internal
30135
+ */
30136
+ const positionPayloadSchema = zod.z.object({
30137
+ wallet: zod.z.string(),
30138
+ chain: zod.z.string(),
30139
+ vaultAddress: zod.z.string(),
30140
+ vaultName: zod.z.string(),
30141
+ asset: zod.z.string(),
30142
+ currentBalance: amountJsonSchema,
30143
+ currentApy: zod.z.number(),
30144
+ shares: amountJsonSchema,
30145
+ pnl: positionPnlSchema,
30146
+ accruedRewards: zod.z.array(accruedRewardSchema).optional(),
30147
+ rewardsUnavailableReason: zod.z.string().optional(),
30148
+ });
30149
+ /**
30150
+ * Zod schema for the `GET /v1/earnKit/position/{address}` API response.
30151
+ *
30152
+ * The Earn Service API wraps the position payload in a `data` envelope.
30153
+ *
30154
+ * @internal
30155
+ */
30156
+ const positionResponseSchema = zod.z.object({
30157
+ data: positionPayloadSchema,
30158
+ });
30159
+ // ---------------------------------------------------------------------------
30160
+ // Deposit response schema
30161
+ // ---------------------------------------------------------------------------
30162
+ /**
30163
+ * Zod schema for the deposit instruction fields used by the provider.
30164
+ *
30165
+ * The canonical instruction shape includes more fields, but deposit
30166
+ * orchestration only depends on `tokenIn` and `amountToApprove` to build the
30167
+ * token input and approval flow. Preserve additional signed fields with
30168
+ * `passthrough()` so the adapter receives the full service payload.
30169
+ *
30170
+ * @internal
30171
+ */
30172
+ const earnInstructionSchema = zod.z
30173
+ .object({
30174
+ tokenIn: hexAddressSchema,
30175
+ amountToApprove: uint256LikeSchema,
30176
+ })
30177
+ .passthrough();
30178
+ /**
30179
+ * Zod schema for earn execution params returned by the earn service.
30180
+ *
30181
+ * Require at least one instruction so the provider can derive token pulls
30182
+ * before it submits the signed params on-chain. Claim rewards has its own
30183
+ * schema because it does not run approval preflight.
30184
+ *
30185
+ * @internal
30186
+ */
30187
+ const earnExecutionParamsSchema = zod.z
30188
+ .object({
30189
+ instructions: zod.z.tuple([earnInstructionSchema]).rest(earnInstructionSchema),
30190
+ deadline: uint256LikeSchema,
30191
+ })
30192
+ .passthrough();
30193
+ /** @internal */
30194
+ const depositExecutionParamsSchema = earnExecutionParamsSchema;
30195
+ /** @internal */
30196
+ const withdrawExecutionParamsSchema = earnExecutionParamsSchema;
30197
+ /**
30198
+ * Zod schema for the claim rewards instruction fields that must be safe to
30199
+ * ABI-encode.
30200
+ *
30201
+ * Claim rewards has no approval preflight, so this validates the forwarded
30202
+ * signed payload at the provider boundary. Additional instruction fields are
30203
+ * accepted and preserved.
30204
+ *
30205
+ * @internal
30206
+ */
30207
+ const claimRewardsInstructionSchema = zod.z
30208
+ .object({
30209
+ target: hexAddressSchema,
30210
+ data: hexBytesSchema,
30211
+ value: uint256LikeSchema,
30212
+ tokenIn: hexAddressSchema,
30213
+ amountToApprove: uint256LikeSchema,
30214
+ tokenOut: hexAddressSchema,
30215
+ minTokenOut: uint256LikeSchema,
30216
+ })
30217
+ .passthrough();
30218
+ /** @internal */
30219
+ const claimRewardsTokenSchema = zod.z
30220
+ .object({
30221
+ token: hexAddressSchema,
30222
+ beneficiary: hexAddressSchema,
30223
+ })
30224
+ .passthrough();
30225
+ /**
30226
+ * Zod schema for signed claim rewards execution params.
30227
+ *
30228
+ * This enforces the required envelope and preserves additive fields so the
30229
+ * adapter receives the signed params verbatim.
30230
+ *
30231
+ * @internal
30232
+ */
30233
+ const claimRewardsExecutionParamsSchema = zod.z
30234
+ .object({
30235
+ instructions: zod.z
30236
+ .tuple([claimRewardsInstructionSchema])
30237
+ .rest(claimRewardsInstructionSchema),
30238
+ tokens: zod.z.tuple([claimRewardsTokenSchema]).rest(claimRewardsTokenSchema),
30239
+ execId: uint256LikeSchema,
30240
+ deadline: uint256LikeSchema,
30241
+ metadata: hexBytesSchema,
30242
+ })
30243
+ .passthrough();
30244
+ /**
30245
+ * Zod schema for the deposit payload inside the API `data` envelope.
30246
+ *
30247
+ * @internal
30248
+ */
30249
+ const depositPayloadSchema = zod.z.object({
30250
+ executionParams: depositExecutionParamsSchema,
30251
+ signature: hexSignatureSchema,
30252
+ });
30253
+ /**
30254
+ * Zod schema for the `POST /v1/earnKit/deposit` API response.
30255
+ *
30256
+ * The API wraps the deposit payload in a `data` envelope.
30257
+ *
30258
+ * @internal
30259
+ */
30260
+ const depositResponseSchema = zod.z.object({
30261
+ data: depositPayloadSchema,
30262
+ });
30263
+ // ---------------------------------------------------------------------------
30264
+ // Withdraw response schema
30265
+ // ---------------------------------------------------------------------------
30266
+ /**
30267
+ * Zod schema for the withdraw payload inside the API `data` envelope.
30268
+ *
30269
+ * @internal
30270
+ */
30271
+ const withdrawPayloadSchema = zod.z.object({
30272
+ executionParams: withdrawExecutionParamsSchema,
30273
+ signature: hexSignatureSchema,
30274
+ });
30275
+ /**
30276
+ * Zod schema for the `POST /v1/earnKit/withdraw` API response.
30277
+ *
30278
+ * The Earn Service API wraps the withdraw payload in a `data` envelope.
30279
+ *
30280
+ * @internal
30281
+ */
30282
+ const withdrawResponseSchema = zod.z.object({
30283
+ data: withdrawPayloadSchema,
30284
+ });
30285
+ // ---------------------------------------------------------------------------
30286
+ // Claim rewards response schema
30287
+ // ---------------------------------------------------------------------------
30288
+ /**
30289
+ * Zod schema for a claimed reward amount in the API response.
30290
+ *
30291
+ * @internal
30292
+ */
30293
+ const claimedAmountSchema = zod.z.object({
30294
+ token: hexAddressSchema,
30295
+ symbol: zod.z.string(),
30296
+ amount: amountJsonSchema,
30297
+ });
30298
+ /**
30299
+ * Zod schema for the claim rewards payload inside the API `data` envelope.
30300
+ *
30301
+ * Enforce that `executionParams` and `signature` are either both
30302
+ * present (claimable rewards) or both absent (nothing to claim).
30303
+ *
30304
+ * @internal
30305
+ */
30306
+ const claimRewardsPayloadSchema = zod.z
30307
+ .object({
30308
+ rewards: zod.z.array(claimedAmountSchema),
30309
+ executionParams: claimRewardsExecutionParamsSchema.optional(),
30310
+ signature: hexSignatureSchema.optional(),
30311
+ })
30312
+ .refine((data) => {
30313
+ const signedFieldCount = (data.executionParams === undefined ? 0 : 1) +
30314
+ (data.signature === undefined ? 0 : 1);
30315
+ if (data.rewards.length > 0) {
30316
+ return signedFieldCount === 2;
30317
+ }
30318
+ return signedFieldCount === 0;
30319
+ }, {
30320
+ message: 'claim rewards response must include executionParams and signature only when rewards are claimable',
30321
+ });
30322
+ /**
30323
+ * Zod schema for the `POST /v1/earnKit/claimRewards` API response.
30324
+ *
30325
+ * The Earn Service API wraps the claim rewards payload in a `data` envelope.
30326
+ *
30327
+ * @internal
30328
+ */
30329
+ const claimRewardsResponseSchema = zod.z.object({
30330
+ data: claimRewardsPayloadSchema,
30331
+ });
30332
+ // ---------------------------------------------------------------------------
30333
+ // Deposit quote response schema
30334
+ // ---------------------------------------------------------------------------
30335
+ /**
30336
+ * Zod schema for the inner deposit quote payload.
30337
+ *
30338
+ * @internal
30339
+ */
30340
+ const depositQuotePayloadSchema = zod.z.object({
30341
+ vaultAddress: zod.z.string(),
30342
+ vaultName: zod.z.string(),
30343
+ asset: zod.z.string(),
30344
+ depositAmount: amountJsonSchema,
30345
+ expectedShares: amountJsonSchema,
30346
+ sharePrice: zod.z.string(),
30347
+ currentApy: zod.z.number(),
30348
+ });
30349
+ /**
30350
+ * Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
30351
+ *
30352
+ * The Zenith API wraps the payload in a `data` envelope.
30353
+ *
30354
+ * @internal
30355
+ */
30356
+ const depositQuoteResponseSchema = zod.z.object({
30357
+ data: depositQuotePayloadSchema,
30358
+ });
30359
+ // ---------------------------------------------------------------------------
30360
+ // Withdrawal quote response schema
30361
+ // ---------------------------------------------------------------------------
30362
+ /**
30363
+ * Zod schema for a withdrawal quote fee in the API response.
30364
+ *
30365
+ * @internal
30366
+ */
30367
+ const withdrawalQuoteFeeSchema = zod.z.object({
30368
+ token: zod.z.string(),
30369
+ amount: amountJsonSchema,
30370
+ });
30371
+ /**
30372
+ * Zod schema for the inner withdrawal quote payload.
30373
+ *
30374
+ * @internal
30375
+ */
30376
+ const withdrawalQuotePayloadSchema = zod.z.object({
30377
+ vaultAddress: zod.z.string(),
30378
+ vaultName: zod.z.string(),
30379
+ asset: zod.z.string(),
30380
+ withdrawAmount: amountJsonSchema,
30381
+ sharesToRedeem: amountJsonSchema,
30382
+ sharePrice: zod.z.string(),
30383
+ maxWithdrawable: amountJsonSchema,
30384
+ fees: zod.z.array(withdrawalQuoteFeeSchema),
30385
+ warnings: zod.z.array(zod.z.string()).optional(),
30386
+ });
30387
+ /**
30388
+ * Zod schema for the `POST /v1/earnKit/withdrawal/quote` API response.
30389
+ *
30390
+ * The Zenith API wraps the payload in a `data` envelope.
30391
+ *
30392
+ * @internal
30393
+ */
30394
+ const withdrawalQuoteResponseSchema = zod.z.object({
30395
+ data: withdrawalQuotePayloadSchema,
30396
+ });
30397
+ // ---------------------------------------------------------------------------
30398
+ // Claim rewards quote response schema
30399
+ // ---------------------------------------------------------------------------
30400
+ /**
30401
+ * Zod schema for a reward in the claim rewards quote response.
30402
+ *
30403
+ * @internal
30404
+ */
30405
+ const claimRewardsQuoteRewardSchema = zod.z.object({
30406
+ token: zod.z.string(),
30407
+ symbol: zod.z.string(),
30408
+ amount: amountJsonSchema,
30409
+ });
30410
+ /**
30411
+ * Zod schema for the inner claim rewards quote payload.
30412
+ *
30413
+ * @internal
30414
+ */
30415
+ const claimRewardsQuotePayloadSchema = zod.z.object({
30416
+ rewards: zod.z.array(claimRewardsQuoteRewardSchema),
30417
+ });
30418
+ /**
30419
+ * Zod schema for the `POST /v1/earnKit/claimRewards/quote` API response.
30420
+ *
30421
+ * The Zenith API wraps the payload in a `data` envelope.
30422
+ *
30423
+ * @internal
30424
+ */
30425
+ const claimRewardsQuoteResponseSchema = zod.z.object({
30426
+ data: claimRewardsQuotePayloadSchema,
30427
+ });
30428
+ // ---------------------------------------------------------------------------
30429
+ // Batch vault response schema
30430
+ // ---------------------------------------------------------------------------
30431
+ /**
30432
+ * Zod schema for a per-vault error in the batch vault API response.
30433
+ *
30434
+ * @internal
30435
+ */
30436
+ const vaultErrorSchema = zod.z.object({
30437
+ chain: zod.z.string(),
30438
+ vaultAddress: zod.z.string(),
30439
+ code: zod.z.number(),
30440
+ message: zod.z.string(),
30441
+ });
30442
+ /**
30443
+ * Zod schema for the inner batch vault payload.
30444
+ *
30445
+ * @internal
30446
+ */
30447
+ const getVaultsPayloadSchema = zod.z.object({
30448
+ vaults: zod.z.array(vaultInfoResponseSchema),
30449
+ errors: zod.z.array(vaultErrorSchema),
30450
+ });
30451
+ /**
30452
+ * Zod schema for the `GET /v1/earnKit/vaults` batch API response.
30453
+ *
30454
+ * The Earn Service API wraps the vault payload in a `data` envelope.
30455
+ *
30456
+ * @internal
30457
+ */
30458
+ const getVaultsResponseSchema = zod.z.object({
30459
+ data: getVaultsPayloadSchema,
30460
+ });
30461
+
30462
+ /**
30463
+ * Type guard for the batch vault lookup API response.
30464
+ *
30465
+ * @param value - Unknown response value to validate
30466
+ * @returns True when the value matches the batch vault response shape
30467
+ *
30468
+ * @internal
30469
+ */
30470
+ function isGetVaultsResponse(value) {
30471
+ return getVaultsResponseSchema.safeParse(value).success;
30472
+ }
30473
+ /**
30474
+ * Type guard for the position API response.
30475
+ *
30476
+ * @param value - Unknown response value to validate
30477
+ * @returns True when the value matches the position response shape
30478
+ *
30479
+ * @internal
30480
+ */
30481
+ function isPositionResponse(value) {
30482
+ return positionResponseSchema.safeParse(value).success;
30483
+ }
30484
+ /**
30485
+ * Type guard for the deposit API response.
30486
+ *
30487
+ * @param value - Unknown response value to validate
30488
+ * @returns True when the value matches the deposit response shape
30489
+ *
30490
+ * @internal
30491
+ */
30492
+ function isDepositResponse(value) {
30493
+ return depositResponseSchema.safeParse(value).success;
30494
+ }
30495
+ /**
30496
+ * Type guard for the withdraw API response.
30497
+ *
30498
+ * @param value - Unknown response value to validate
30499
+ * @returns True when the value matches the withdraw response shape
30500
+ *
30501
+ * @internal
30502
+ */
30503
+ function isWithdrawResponse(value) {
30504
+ return withdrawResponseSchema.safeParse(value).success;
30505
+ }
30506
+ /**
30507
+ * Type guard for the claim rewards API response.
30508
+ *
30509
+ * @param value - Unknown response value to validate
30510
+ * @returns True when the value matches the claim rewards response shape
30511
+ *
30512
+ * @internal
30513
+ */
30514
+ function isClaimRewardsResponse(value) {
30515
+ return claimRewardsResponseSchema.safeParse(value).success;
30516
+ }
30517
+ /**
30518
+ * Type guard for the deposit quote API response.
30519
+ *
30520
+ * @param value - Unknown response value to validate
30521
+ * @returns True when the value matches the deposit quote response shape
30522
+ *
30523
+ * @internal
30524
+ */
30525
+ function isDepositQuoteResponse(value) {
30526
+ return depositQuoteResponseSchema.safeParse(value).success;
30527
+ }
30528
+ /**
30529
+ * Type guard for the withdrawal quote API response.
30530
+ *
30531
+ * @param value - Unknown response value to validate
30532
+ * @returns True when the value matches the withdrawal quote response shape
30533
+ *
30534
+ * @internal
30535
+ */
30536
+ function isWithdrawalQuoteResponse(value) {
30537
+ return withdrawalQuoteResponseSchema.safeParse(value).success;
30538
+ }
30539
+ /**
30540
+ * Type guard for the claim rewards quote API response.
30541
+ *
30542
+ * @param value - Unknown response value to validate
30543
+ * @returns True when the value matches the claim rewards quote response shape
30544
+ *
30545
+ * @internal
30546
+ */
30547
+ function isClaimRewardsQuoteResponse(value) {
30548
+ return claimRewardsQuoteResponseSchema.safeParse(value).success;
30549
+ }
30550
+
30551
+ /**
30552
+ * Build an API polling config with optional authorization header,
30553
+ * and resolve the base URL (configurable for testing).
30554
+ *
30555
+ * @param serviceConfig - Optional earn service configuration
30556
+ * @returns Resolved polling config and base URL
30557
+ *
30558
+ * @internal
30559
+ */
30560
+ function buildConfig(serviceConfig) {
30561
+ const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
30562
+ if (serviceConfig?.kitKey === undefined) {
30563
+ return { pollingConfig: DEFAULT_CONFIG, baseUrl };
30564
+ }
30565
+ if (!isValidApiKey(serviceConfig.kitKey)) {
30566
+ throw new KitError({
30567
+ ...InputError.VALIDATION_FAILED,
30568
+ recoverability: 'FATAL',
30569
+ message: 'Invalid kitKey format. Expected KIT_KEY:<keyId>:<keySecret>.',
30570
+ });
30571
+ }
30572
+ return {
30573
+ pollingConfig: {
30574
+ ...DEFAULT_CONFIG,
30575
+ headers: {
30576
+ ...DEFAULT_CONFIG.headers,
30577
+ Authorization: `Bearer ${serviceConfig.kitKey}`,
30578
+ },
30579
+ },
30580
+ baseUrl,
30581
+ };
30582
+ }
30583
+
30584
+ function toVaultInfo(data) {
30585
+ const { totalDeposits, liquidity, ...vault } = data;
30586
+ const chain = toSdkChain(vault.chain);
30587
+ if (chain === undefined) {
30588
+ throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
30589
+ }
30590
+ return {
30591
+ ...vault,
30592
+ chain,
30593
+ totalDeposits: Amount.fromJSON(totalDeposits),
30594
+ liquidity: Amount.fromJSON(liquidity),
30595
+ };
30596
+ }
30597
+ function toVaultError(error) {
30598
+ const chain = toSdkChain(error.chain);
30599
+ if (chain === undefined) {
30600
+ throw createInvalidChainError(error.chain, 'Chain returned by the Earn Service is not supported by the SDK');
30601
+ }
30602
+ return { ...error, chain };
30603
+ }
30604
+ function getVaultQueryChainLabel(chain) {
30605
+ return typeof chain === 'string' ? chain : 'unknown';
30606
+ }
30607
+ function resolveVaultQueryChain(chain) {
30608
+ try {
30609
+ return resolveChainIdentifier(chain);
30610
+ }
30611
+ catch {
30612
+ throw createInvalidChainError(getVaultQueryChainLabel(chain), 'Chain is not supported by the Earn Service provider');
30613
+ }
30614
+ }
30615
+ /**
30616
+ * Fetch vault information from the Earn Service API.
30617
+ *
30618
+ * Call `GET /v1/earnKit/vaults` with repeated chain and vaultAddress
30619
+ * query parameters for batch vault lookup.
30620
+ *
30621
+ * @param params - Vault query parameters
30622
+ * @returns Batch result with vaults and per-vault errors
30623
+ * @throws {@link KitError} When the API call fails
30624
+ *
30625
+ * @internal
30626
+ */
30627
+ async function fetchVaults(params) {
30628
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30629
+ const url = new URL(`${EARN_KIT_API_PREFIX}/vaults`, baseUrl);
30630
+ // The API pairs chain[i] with vaultAddress[i] by insertion order.
30631
+ for (const vault of params.vaults) {
30632
+ const chainDefinition = resolveVaultQueryChain(vault.chain);
30633
+ const apiChain = toApiChain(chainDefinition.chain);
30634
+ if (apiChain === undefined) {
30635
+ throw createInvalidChainError(chainDefinition.chain, 'Chain is not supported by the Earn Service provider');
30636
+ }
30637
+ url.searchParams.append('chain', apiChain);
30638
+ url.searchParams.append('vaultAddress', vault.vaultAddress);
30639
+ }
30640
+ try {
30641
+ const response = await pollApiGet(url.toString(), isGetVaultsResponse, pollingConfig);
30642
+ return {
30643
+ vaults: response.data.vaults.map(toVaultInfo),
30644
+ errors: response.data.errors.map(toVaultError),
30645
+ };
30646
+ }
30647
+ catch (error) {
30648
+ throw parseEarnApiError(error, { operation: 'getVaults' });
30649
+ }
30650
+ }
30651
+
30652
+ function toAccruedReward(reward) {
30653
+ return {
30654
+ tokenAddress: reward.token,
30655
+ symbol: reward.symbol,
30656
+ amount: Amount.fromJSON(reward.amount),
30657
+ };
30658
+ }
30659
+ function assertNever(value) {
30660
+ throw new KitError({
30661
+ ...EarnError.INTERNAL_ERROR,
30662
+ recoverability: 'FATAL',
30663
+ message: `Unhandled PnL status: ${JSON.stringify(value)}`,
30664
+ cause: {
30665
+ trace: value,
30666
+ },
30667
+ });
30668
+ }
30669
+ function toPositionPnL(data) {
30670
+ switch (data.status) {
30671
+ case 'available':
30672
+ return {
30673
+ status: 'available',
30674
+ principalDeposited: Amount.fromJSON(data.principalDeposited),
30675
+ totalYieldEarned: Amount.fromJSON(data.totalYieldEarned),
30676
+ };
30677
+ case 'pending':
30678
+ return { status: 'pending' };
30679
+ case 'unavailable':
30680
+ return { status: 'unavailable', reason: data.reason };
30681
+ default:
30682
+ return assertNever(data);
30683
+ }
30684
+ }
30685
+ function toPositionInfo(data) {
30686
+ const { currentBalance, shares, pnl, accruedRewards = [], ...position } = data;
30687
+ const chain = toSdkChain(position.chain);
30688
+ if (chain === undefined) {
30689
+ throw new KitError({
30690
+ ...EarnError.INTERNAL_ERROR,
30691
+ recoverability: 'FATAL',
30692
+ message: `Unsupported Earn Service chain: ${position.chain}`,
30693
+ cause: {
30694
+ trace: {
30695
+ chain: position.chain,
30696
+ },
30697
+ },
30698
+ });
30699
+ }
30700
+ return {
30701
+ ...position,
30702
+ chain,
30703
+ currentBalance: Amount.fromJSON(currentBalance),
30704
+ shares: Amount.fromJSON(shares),
30705
+ pnl: toPositionPnL(pnl),
30706
+ accruedRewards: accruedRewards.map(toAccruedReward),
30707
+ };
30708
+ }
30709
+ /**
30710
+ * Fetch a user's vault position from the Earn Service API.
30711
+ *
30712
+ * Call `GET /v1/earnKit/position/{address}` with the provided chain
30713
+ * and vault address as query parameters.
30714
+ *
30715
+ * @param params - Position query parameters
30716
+ * @returns The user's position information
30717
+ * @throws {@link KitError} When the API call fails
30718
+ *
30719
+ * @internal
30720
+ */
30721
+ async function fetchPosition(params) {
30722
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30723
+ const url = new URL(`${EARN_KIT_API_PREFIX}/position/${encodeURIComponent(params.address)}`, baseUrl);
30724
+ url.searchParams.set('chain', params.chain);
30725
+ url.searchParams.set('vaultAddress', params.vaultAddress);
30726
+ try {
30727
+ const response = await pollApiGet(url.toString(), isPositionResponse, pollingConfig);
30728
+ return toPositionInfo(response.data);
30729
+ }
30730
+ catch (error) {
30731
+ throw parseEarnApiError(error, { operation: 'getPosition' });
30732
+ }
30733
+ }
30734
+
30735
+ /**
30736
+ * Build signed deposit instructions via the Earn Service API.
30737
+ *
30738
+ * Call `POST /v1/earnKit/deposit` with the vault address, amount,
30739
+ * wallet address, and chain. Return EIP-712 signed execution
30740
+ * parameters for on-chain submission.
30741
+ *
30742
+ * @param params - Deposit parameters
30743
+ * @returns Signed execution parameters for the deposit
30744
+ * @throws {@link KitError} When the API call fails
30745
+ *
30746
+ * @internal
30747
+ */
30748
+ async function fetchDeposit(params) {
30749
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30750
+ const url = new URL(`${EARN_KIT_API_PREFIX}/deposit`, baseUrl);
30751
+ const requestBody = {
30752
+ vaultAddress: params.vaultAddress,
30753
+ amount: params.amount,
30754
+ address: params.address,
30755
+ chain: params.chain,
30756
+ };
30757
+ try {
30758
+ const response = await pollApiPost(url.toString(), requestBody, isDepositResponse, pollingConfig);
30759
+ return response.data;
30760
+ }
30761
+ catch (error) {
30762
+ throw parseEarnApiError(error, { operation: 'deposit' });
30763
+ }
30764
+ }
30765
+
30766
+ /**
30767
+ * Build signed withdrawal instructions via the Earn Service API.
30768
+ *
30769
+ * Call `POST /v1/earnKit/withdraw` with the vault address, amount,
30770
+ * wallet address, and chain. Return EIP-712 signed execution
30771
+ * parameters for on-chain submission.
30772
+ *
30773
+ * @param params - Withdrawal parameters
30774
+ * @returns Signed execution parameters for the withdrawal
30775
+ * @throws {@link KitError} When the API call fails
30776
+ *
30777
+ * @internal
30778
+ */
30779
+ async function fetchWithdraw(params) {
30780
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30781
+ const url = new URL(`${EARN_KIT_API_PREFIX}/withdraw`, baseUrl);
30782
+ const requestBody = {
30783
+ vaultAddress: params.vaultAddress,
30784
+ amount: params.amount,
30785
+ address: params.address,
30786
+ chain: params.chain,
30787
+ };
30788
+ try {
30789
+ const response = await pollApiPost(url.toString(), requestBody, isWithdrawResponse, pollingConfig);
30790
+ return response.data;
30791
+ }
30792
+ catch (error) {
30793
+ throw parseEarnApiError(error, { operation: 'withdraw' });
30794
+ }
30795
+ }
30796
+
30797
+ function toClaimedAmount(reward) {
30798
+ return {
30799
+ address: reward.token,
30800
+ symbol: reward.symbol,
30801
+ amount: Amount.fromJSON(reward.amount),
30802
+ };
30803
+ }
30804
+ /**
30805
+ * Build signed claim rewards instructions via the Earn Service API.
30806
+ *
30807
+ * Call `POST /v1/earnKit/claimRewards` with the wallet address, chain, and
30808
+ * vault address. Return EIP-712 signed execution parameters for on-chain
30809
+ * submission. When no rewards are claimable, return an empty rewards array
30810
+ * without execution parameters.
30811
+ *
30812
+ * @param params - Claim rewards parameters
30813
+ * @returns Signed execution parameters and reward details
30814
+ * @throws {@link KitError} When the API call fails
30815
+ *
30816
+ * @internal
30817
+ */
30818
+ async function fetchClaimRewards(params) {
30819
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30820
+ const url = new URL(`${EARN_KIT_API_PREFIX}/claimRewards`, baseUrl);
30821
+ const requestBody = {
30822
+ address: params.address,
30823
+ chain: params.chain,
30824
+ vaultAddress: params.vaultAddress,
30825
+ };
30826
+ try {
30827
+ const response = await pollApiPost(url.toString(), requestBody, isClaimRewardsResponse, pollingConfig);
30828
+ const { rewards, executionParams, signature } = response.data;
30829
+ return {
30830
+ rewards: rewards.map(toClaimedAmount),
30831
+ ...(executionParams === undefined ? {} : { executionParams }),
30832
+ ...(signature === undefined ? {} : { signature }),
30833
+ };
30834
+ }
30835
+ catch (error) {
30836
+ throw parseEarnApiError(error, { operation: 'claimRewards' });
30837
+ }
30838
+ }
30839
+
30840
+ function toDepositQuoteInfo(data) {
30841
+ return {
30842
+ vaultAddress: data.vaultAddress,
30843
+ vaultName: data.vaultName,
30844
+ deposit: {
30845
+ symbol: data.asset,
30846
+ amount: Amount.fromJSON(data.depositAmount),
30847
+ },
30848
+ expectedShares: {
30849
+ symbol: VAULT_SHARE_SYMBOL,
30850
+ address: data.vaultAddress,
30851
+ amount: Amount.fromJSON(data.expectedShares),
30852
+ },
30853
+ sharePrice: data.sharePrice,
30854
+ currentApy: data.currentApy,
30855
+ fees: [],
30856
+ };
30857
+ }
30858
+ /**
30859
+ * Fetch a deposit quote from the Earn Service API.
30860
+ *
30861
+ * Call `POST /v1/earnKit/deposit/quote` with the vault address, amount,
30862
+ * wallet address, and chain. Return informational quote data including
30863
+ * expected shares, share price, and APY.
30864
+ *
30865
+ * @param params - Deposit quote parameters
30866
+ * @returns Deposit quote information
30867
+ * @throws {@link KitError} When the API call fails
30868
+ *
30869
+ * @internal
30870
+ */
30871
+ async function fetchDepositQuote(params) {
30872
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30873
+ const url = new URL(`${EARN_KIT_API_PREFIX}/deposit/quote`, baseUrl);
30874
+ const requestBody = {
30875
+ vaultAddress: params.vaultAddress,
30876
+ amount: params.amount,
30877
+ address: params.address,
30878
+ chain: params.chain,
30879
+ };
30880
+ try {
30881
+ const response = await pollApiPost(url.toString(), requestBody, isDepositQuoteResponse, pollingConfig);
30882
+ return toDepositQuoteInfo(response.data);
30883
+ }
30884
+ catch (error) {
30885
+ throw parseEarnApiError(error, { operation: 'getDepositQuote' });
30886
+ }
30887
+ }
30888
+
30889
+ function toWithdrawalQuoteInfo(data) {
30890
+ return {
30891
+ vaultAddress: data.vaultAddress,
30892
+ vaultName: data.vaultName,
30893
+ withdrawal: {
30894
+ symbol: data.asset,
30895
+ amount: Amount.fromJSON(data.withdrawAmount),
30896
+ },
30897
+ sharesToRedeem: {
30898
+ symbol: VAULT_SHARE_SYMBOL,
30899
+ address: data.vaultAddress,
30900
+ amount: Amount.fromJSON(data.sharesToRedeem),
30901
+ },
30902
+ sharePrice: data.sharePrice,
30903
+ maxWithdrawable: {
30904
+ symbol: data.asset,
30905
+ amount: Amount.fromJSON(data.maxWithdrawable),
30906
+ },
30907
+ fees: data.fees.map((fee) => ({
30908
+ symbol: fee.token,
30909
+ amount: Amount.fromJSON(fee.amount),
30910
+ })),
30911
+ // Wire format uses `warnings`, but the SDK surface uses
30912
+ // `earnKitWarnings` to match the precedent set by `VaultInfo` —
30913
+ // `warnings` is reserved for the structured `VaultWarning` shape.
30914
+ ...(data.warnings !== undefined && { earnKitWarnings: data.warnings }),
30915
+ };
30916
+ }
30917
+ /**
30918
+ * Fetch a withdrawal quote from the Earn Service API.
30919
+ *
30920
+ * Call `POST /v1/earnKit/withdrawal/quote` with the vault address, amount,
30921
+ * wallet address, and chain. Return informational quote data including
30922
+ * shares to redeem, max withdrawable, and fees.
30923
+ *
30924
+ * @param params - Withdrawal quote parameters
30925
+ * @returns Withdrawal quote information
30926
+ * @throws {@link KitError} When the API call fails
30927
+ *
30928
+ * @internal
30929
+ */
30930
+ async function fetchWithdrawalQuote(params) {
30931
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30932
+ const url = new URL(`${EARN_KIT_API_PREFIX}/withdrawal/quote`, baseUrl);
30933
+ const requestBody = {
30934
+ vaultAddress: params.vaultAddress,
30935
+ amount: params.amount,
30936
+ address: params.address,
30937
+ chain: params.chain,
30938
+ };
30939
+ try {
30940
+ const response = await pollApiPost(url.toString(), requestBody, isWithdrawalQuoteResponse, pollingConfig);
30941
+ return toWithdrawalQuoteInfo(response.data);
30942
+ }
30943
+ catch (error) {
30944
+ throw parseEarnApiError(error, { operation: 'getWithdrawalQuote' });
30945
+ }
30946
+ }
30947
+
30948
+ /**
30949
+ * Fetch a claim rewards quote from the Earn Service API.
30950
+ *
30951
+ * Call `POST /v1/earnKit/claimRewards/quote` with the wallet address,
30952
+ * chain, and vault address. Return informational quote data including
30953
+ * claimable reward details.
30954
+ *
30955
+ * @param params - Claim rewards quote parameters
30956
+ * @returns Claim rewards quote information
30957
+ * @throws {@link KitError} When the API call fails
30958
+ *
30959
+ * @internal
30960
+ */
30961
+ async function fetchClaimRewardsQuote(params) {
30962
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
30963
+ const url = new URL(`${EARN_KIT_API_PREFIX}/claimRewards/quote`, baseUrl);
30964
+ const requestBody = {
30965
+ vaultAddress: params.vaultAddress,
30966
+ address: params.address,
30967
+ chain: params.chain,
30968
+ };
30969
+ try {
30970
+ const response = await pollApiPost(url.toString(), requestBody, isClaimRewardsQuoteResponse, pollingConfig);
30971
+ return {
30972
+ rewards: response.data.rewards.map((r) => ({
30973
+ symbol: r.symbol,
30974
+ amount: Amount.fromJSON(r.amount),
30975
+ address: r.token,
30976
+ })),
30977
+ };
30978
+ }
30979
+ catch (error) {
30980
+ throw parseEarnApiError(error, { operation: 'getClaimRewardsQuote' });
30981
+ }
30982
+ }
30983
+
30984
+ /**
30985
+ * Earn Service provider for the Circle earn service API.
30986
+ *
30987
+ * Implement the {@link EarningProvider} interface for yield-bearing vault
30988
+ * operations including vault discovery, position queries, deposits,
30989
+ * withdrawals, and reward claiming.
30990
+ *
30991
+ * @example
30992
+ * ```typescript
30993
+ * import { EarnServiceProvider } from '@circle-fin/provider-earn-service'
30994
+ *
30995
+ * // Permissionless mode
30996
+ * const provider = new EarnServiceProvider()
30997
+ *
30998
+ * // Permissioned mode with Kit Key
30999
+ * const provider = new EarnServiceProvider({
31000
+ * kitKey: 'KIT_KEY:keyId:keySecret',
31001
+ * })
31002
+ *
31003
+ * // Custom base URL for testing
31004
+ * const provider = new EarnServiceProvider({
31005
+ * baseUrl: 'https://api-staging.circle.com',
31006
+ * })
31007
+ *
31008
+ * const result = await provider.getVaults({
31009
+ * vaults: [
31010
+ * { chain: 'Arc_Testnet', vaultAddress: '0x8eB67...' },
31011
+ * ],
31012
+ * })
31013
+ * ```
31014
+ */
31015
+ class EarnServiceProvider {
31016
+ /** {@inheritdoc} */
31017
+ name = 'EarnService';
31018
+ /** {@inheritdoc} */
31019
+ supportedChains = Object.keys(CHAIN_TO_API).map((chain) => resolveChainIdentifier(chain));
31020
+ defaultConfig;
31021
+ /**
31022
+ * Create a new EarnServiceProvider.
31023
+ *
31024
+ * @param config - Optional default configuration applied to all operations.
31025
+ * Per-operation config takes precedence when provided.
31026
+ */
31027
+ constructor(config) {
31028
+ this.defaultConfig = config;
31029
+ }
31030
+ resolveConfig(config) {
31031
+ const resolvedConfig = this.defaultConfig === undefined
31032
+ ? config
31033
+ : { ...this.defaultConfig, ...config };
31034
+ return resolvedConfig;
31035
+ }
31036
+ /** {@inheritdoc} */
31037
+ async getVaults(params) {
31038
+ const config = this.resolveConfig(params.config);
31039
+ return fetchVaults({
31040
+ vaults: params.vaults,
31041
+ config,
31042
+ });
31043
+ }
31044
+ /** {@inheritdoc} */
31045
+ async getPosition(params) {
31046
+ const config = this.resolveConfig(params.config);
31047
+ const { address, chain } = await resolveAdapterContext(params.from);
31048
+ return fetchPosition({
31049
+ address,
31050
+ chain,
31051
+ vaultAddress: params.vaultAddress,
31052
+ config,
31053
+ });
31054
+ }
31055
+ /** {@inheritdoc} */
31056
+ async deposit(params) {
31057
+ const config = this.resolveConfig(params.config);
31058
+ const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
31059
+ const adapterContractAddress = requireAdapterContract(chain);
31060
+ const rawUsdcAddress = chain.usdcAddress;
31061
+ if (rawUsdcAddress === null) {
31062
+ throw createUnsupportedTokenError('USDC', chain.name);
31063
+ }
31064
+ const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
31065
+ const { executionParams, signature } = await fetchDeposit({
31066
+ vaultAddress: params.vaultAddress,
31067
+ amount: params.amount,
31068
+ address,
31069
+ chain: apiChain,
31070
+ config,
31071
+ });
31072
+ validateExecutionDeadline(executionParams);
31073
+ const { adapter } = params.from;
31074
+ const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
31075
+ const approvalToken = tokenInputs[0]?.token;
31076
+ if (approvalToken !== undefined) {
31077
+ await approveMaxIfNeeded({
31078
+ adapter,
31079
+ chain,
31080
+ tokenAddress: approvalToken,
31081
+ delegate: adapterContractAddress,
31082
+ address,
31083
+ revertMessage: 'USDC approval reverted on-chain',
31084
+ });
31085
+ }
31086
+ const { txHash, explorerUrl } = await executeEarnAction({
31087
+ adapter,
31088
+ chain,
31089
+ address,
31090
+ actionKey: 'earn.deposit',
31091
+ actionParams: {
31092
+ executeParams: executionParams,
31093
+ tokenInputs,
31094
+ signature,
31095
+ },
31096
+ revertMessage: 'Earn deposit reverted on-chain',
31097
+ });
31098
+ return {
31099
+ txHash,
31100
+ explorerUrl,
31101
+ vaultAddress: params.vaultAddress,
31102
+ amount: params.amount,
31103
+ };
31104
+ }
31105
+ /** {@inheritdoc} */
31106
+ async withdraw(params) {
31107
+ const config = this.resolveConfig(params.config);
31108
+ const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
31109
+ const adapterContractAddress = requireAdapterContract(chain);
31110
+ const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
31111
+ const { executionParams, signature } = await fetchWithdraw({
31112
+ vaultAddress,
31113
+ amount: params.amount,
31114
+ address,
31115
+ chain: apiChain,
31116
+ config,
31117
+ });
31118
+ validateExecutionDeadline(executionParams);
31119
+ const { adapter } = params.from;
31120
+ const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
31121
+ const approvalToken = tokenInputs[0]?.token;
31122
+ if (approvalToken !== undefined) {
31123
+ await approveMaxIfNeeded({
31124
+ adapter,
31125
+ chain,
31126
+ tokenAddress: approvalToken,
31127
+ delegate: adapterContractAddress,
31128
+ address,
31129
+ revertMessage: 'Vault share token approval reverted on-chain',
31130
+ });
31131
+ }
31132
+ const { txHash, explorerUrl } = await executeEarnAction({
31133
+ adapter,
31134
+ chain,
31135
+ address,
31136
+ actionKey: 'earn.withdraw',
31137
+ actionParams: {
31138
+ executeParams: executionParams,
31139
+ tokenInputs,
31140
+ signature,
31141
+ },
31142
+ revertMessage: 'Earn withdraw reverted on-chain',
31143
+ });
31144
+ return {
31145
+ txHash,
31146
+ explorerUrl,
31147
+ vaultAddress,
31148
+ amount: params.amount,
31149
+ };
31150
+ }
31151
+ /** {@inheritdoc} */
31152
+ async claimRewards(params) {
31153
+ const config = this.resolveConfig(params.config);
31154
+ const { address, chain: apiChain, chainDefinition: chain, } = await resolveAdapterContext(params.from);
31155
+ // Claim rewards has no approval step, but still requires adapter support.
31156
+ requireAdapterContract(chain);
31157
+ const { rewards, executionParams, signature } = await fetchClaimRewards({
31158
+ address,
31159
+ chain: apiChain,
31160
+ vaultAddress: params.vaultAddress,
31161
+ config,
31162
+ });
31163
+ const nothingToClaim = rewards.length === 0;
31164
+ if (nothingToClaim) {
31165
+ return { status: 'no_rewards', rewards: [] };
31166
+ }
31167
+ const missingExecutionParams = executionParams === undefined;
31168
+ const missingSignature = signature === undefined;
31169
+ if (missingExecutionParams || missingSignature) {
31170
+ throw new KitError({
31171
+ ...EarnError.INTERNAL_ERROR,
31172
+ recoverability: 'RETRYABLE',
31173
+ message: 'Claim rewards response must include executionParams and signature when rewards are claimable',
31174
+ cause: {
31175
+ trace: {
31176
+ rewardsCount: rewards.length,
31177
+ missingExecutionParams,
31178
+ missingSignature,
31179
+ },
31180
+ },
31181
+ });
31182
+ }
31183
+ validateExecutionDeadline(executionParams);
31184
+ const { txHash, explorerUrl } = await executeEarnAction({
31185
+ adapter: params.from.adapter,
31186
+ chain,
31187
+ address,
31188
+ actionKey: 'earn.claimRewards',
31189
+ actionParams: {
31190
+ executeParams: executionParams,
31191
+ tokenInputs: [],
31192
+ signature,
31193
+ },
31194
+ revertMessage: 'Earn claim rewards reverted on-chain',
31195
+ });
31196
+ return { status: 'claimed', rewards, txHash, explorerUrl };
31197
+ }
31198
+ /** {@inheritdoc} */
31199
+ async getDepositQuote(params) {
31200
+ const config = this.resolveConfig(params.config);
31201
+ const { address, chain } = await resolveAdapterContext(params.from);
31202
+ return fetchDepositQuote({
31203
+ vaultAddress: params.vaultAddress,
31204
+ amount: params.amount,
31205
+ address,
31206
+ chain,
31207
+ config,
31208
+ });
31209
+ }
31210
+ /** {@inheritdoc} */
31211
+ async getWithdrawalQuote(params) {
31212
+ const config = this.resolveConfig(params.config);
31213
+ const { address, chain } = await resolveAdapterContext(params.from);
31214
+ return fetchWithdrawalQuote({
31215
+ vaultAddress: params.vaultAddress,
31216
+ amount: params.amount,
31217
+ address,
31218
+ chain,
31219
+ config,
31220
+ });
31221
+ }
31222
+ /** {@inheritdoc} */
31223
+ async getClaimRewardsQuote(params) {
31224
+ const config = this.resolveConfig(params.config);
31225
+ const { address, chain } = await resolveAdapterContext(params.from);
31226
+ return fetchClaimRewardsQuote({
31227
+ vaultAddress: params.vaultAddress,
31228
+ address,
31229
+ chain,
31230
+ config,
31231
+ });
31232
+ }
31233
+ }
31234
+
31235
+ /**
31236
+ * The default providers used when no custom providers are specified.
31237
+ *
31238
+ * @returns An array containing the default EarnServiceProvider
31239
+ * @internal
31240
+ */
31241
+ const getDefaultProviders$1 = () => [new EarnServiceProvider()];
31242
+ /**
31243
+ * Create an EarnKit context with validated configuration.
31244
+ *
31245
+ * Initialize an EarnKitContext with default providers and optional
31246
+ * custom configuration. Custom and default providers are merged,
31247
+ * preserving their exact types for type safety.
31248
+ *
31249
+ * @typeParam TExtraProviders - Array type of additional earn providers
31250
+ * @param config - Optional configuration for the EarnKit context
31251
+ * @returns A fully initialized EarnKitContext ready for earn operations
31252
+ *
31253
+ * @example
31254
+ * ```typescript
31255
+ * import { createEarnKitContext } from '@circle-fin/earn-kit'
31256
+ *
31257
+ * // Create context with defaults
31258
+ * const context = createEarnKitContext()
31259
+ * ```
31260
+ *
31261
+ * @example
31262
+ * ```typescript
31263
+ * import { createEarnKitContext } from '@circle-fin/earn-kit'
31264
+ *
31265
+ * // Create context with custom providers
31266
+ * const context = createEarnKitContext({
31267
+ * providers: [myCustomEarnProvider],
31268
+ * })
31269
+ * ```
31270
+ */
31271
+ function createEarnKitContext(config = {}) {
31272
+ const defaultProviders = getDefaultProviders$1();
31273
+ const providers = [...(config.providers ?? []), ...defaultProviders];
31274
+ const context = {
31275
+ providers,
31276
+ };
31277
+ return context;
31278
+ }
31279
+
31280
+ /**
31281
+ * Symbol used to track that assertEarnParams has validated an object.
31282
+ * @internal
31283
+ */
31284
+ const ASSERT_EARN_PARAMS_SYMBOL = Symbol('assertEarnParams');
31285
+ /**
31286
+ * Assert that the provided value conforms to the given earn params schema.
31287
+ *
31288
+ * Validate earn parameters using the provided Zod schema and track
31289
+ * validation state to avoid duplicate checks. Throw a structured
31290
+ * error with detailed validation messages if any parameter is invalid.
31291
+ *
31292
+ * @typeParam T - The expected type after validation
31293
+ * @param params - The earn parameters to validate
31294
+ * @param schema - The Zod schema to validate against
31295
+ * @throws {@link KitError} If the parameters fail validation
31296
+ *
31297
+ * @example
31298
+ * ```typescript
31299
+ * import { assertEarnParams, depositParamsSchema } from '@circle-fin/earn-kit'
31300
+ *
31301
+ * assertEarnParams(params, depositParamsSchema)
31302
+ * ```
31303
+ */
31304
+ function assertEarnParams(params, schema) {
31305
+ validateWithStateTracking(params, schema, 'earn parameters', ASSERT_EARN_PARAMS_SYMBOL);
31306
+ }
31307
+
31308
+ function findProvider(context, chain, operation = 'earn') {
31309
+ let fallback;
31310
+ for (const provider of context.providers) {
31311
+ fallback ??= provider;
31312
+ if (provider.supportedChains.length === 0)
31313
+ continue;
31314
+ if (chain === undefined ||
31315
+ provider.supportedChains.some((c) => c.chain === chain.chain)) {
31316
+ return provider;
31317
+ }
31318
+ }
31319
+ if (fallback === undefined) {
31320
+ throw createValidationFailedError$1('context.providers', [], 'No earn providers configured');
31321
+ }
31322
+ if (chain !== undefined) {
31323
+ throw createUnsupportedEarnRouteError(operation, chain.name);
31324
+ }
31325
+ return fallback;
31326
+ }
31327
+
31328
+ /**
31329
+ * Format a provider amount object as a human-readable decimal string.
31330
+ *
31331
+ * The kit result surface intentionally exposes only the decimal string value,
31332
+ * so nested provider amount metadata is not retained at this boundary.
31333
+ *
31334
+ * @param amount - Provider amount object to format
31335
+ * @returns Human-readable decimal amount string
31336
+ */
31337
+ function formatAmount$1(amount) {
31338
+ return formatUnits(amount.raw.toString(), amount.decimals);
31339
+ }
31340
+ function formatAssetAmount(assetAmount) {
31341
+ const { amount, ...rest } = assetAmount;
31342
+ return {
31343
+ ...rest,
31344
+ amount: formatAmount$1(amount),
31345
+ };
31346
+ }
31347
+ function formatClaimedAmount(claimedAmount) {
31348
+ const { amount, ...rest } = claimedAmount;
31349
+ return {
31350
+ ...rest,
31351
+ amount: formatAmount$1(amount),
31352
+ };
31353
+ }
31354
+ function formatAccruedReward(reward) {
31355
+ const { amount, ...rest } = reward;
31356
+ return {
31357
+ ...rest,
31358
+ amount: formatAmount$1(amount),
31359
+ };
31360
+ }
31361
+ function formatPositionPnL(pnl) {
31362
+ if (pnl.status !== 'available') {
31363
+ return pnl;
31364
+ }
31365
+ return {
31366
+ ...pnl,
31367
+ principalDeposited: formatAmount$1(pnl.principalDeposited),
31368
+ totalYieldEarned: formatAmount$1(pnl.totalYieldEarned),
31369
+ };
31370
+ }
31371
+ /**
31372
+ * Convert provider vault info into an EarnKit vault result.
31373
+ *
31374
+ * @param vault - Provider vault info with raw amount objects
31375
+ * @returns Vault info with total deposits and liquidity formatted as strings
31376
+ */
31377
+ function formatVaultInfo(vault) {
31378
+ const { totalDeposits, liquidity, ...rest } = vault;
31379
+ return {
31380
+ ...rest,
31381
+ totalDeposits: formatAmount$1(totalDeposits),
31382
+ liquidity: formatAmount$1(liquidity),
31383
+ };
31384
+ }
31385
+ /**
31386
+ * Convert a provider vault lookup result into an EarnKit result.
31387
+ *
31388
+ * @param result - Provider vault lookup result
31389
+ * @returns Vault lookup result with each vault amount formatted as strings
31390
+ */
31391
+ function formatGetVaultsResult(result) {
31392
+ return {
31393
+ ...result,
31394
+ vaults: result.vaults.map(formatVaultInfo),
31395
+ };
31396
+ }
31397
+ /**
31398
+ * Convert provider position info into an EarnKit position result.
31399
+ *
31400
+ * @param position - Provider position info with raw amount objects
31401
+ * @returns Position info with balances, shares, PnL, and rewards formatted as strings
31402
+ */
31403
+ function formatPositionInfo(position) {
31404
+ const { currentBalance, shares, pnl, accruedRewards, ...rest } = position;
31405
+ return {
31406
+ ...rest,
31407
+ currentBalance: formatAmount$1(currentBalance),
31408
+ shares: formatAmount$1(shares),
31409
+ pnl: formatPositionPnL(pnl),
31410
+ accruedRewards: accruedRewards.map(formatAccruedReward),
31411
+ };
31412
+ }
31413
+ /**
31414
+ * Convert a provider deposit quote into an EarnKit deposit quote result.
31415
+ *
31416
+ * @param quote - Provider deposit quote with raw amount objects
31417
+ * @returns Deposit quote with deposit, shares, and fees formatted as strings
31418
+ */
31419
+ function formatDepositQuoteInfo(quote) {
31420
+ return {
31421
+ ...quote,
31422
+ deposit: formatAssetAmount(quote.deposit),
31423
+ expectedShares: formatAssetAmount(quote.expectedShares),
31424
+ fees: quote.fees.map(formatAssetAmount),
31425
+ };
31426
+ }
31427
+ /**
31428
+ * Convert a provider withdrawal quote into an EarnKit withdrawal quote result.
31429
+ *
31430
+ * @param quote - Provider withdrawal quote with raw amount objects
31431
+ * @returns Withdrawal quote with withdrawal, shares, max, and fees formatted as strings
31432
+ */
31433
+ function formatWithdrawalQuoteInfo(quote) {
31434
+ return {
31435
+ ...quote,
31436
+ withdrawal: formatAssetAmount(quote.withdrawal),
31437
+ sharesToRedeem: formatAssetAmount(quote.sharesToRedeem),
31438
+ maxWithdrawable: formatAssetAmount(quote.maxWithdrawable),
31439
+ fees: quote.fees.map(formatAssetAmount),
31440
+ };
31441
+ }
31442
+ /**
31443
+ * Convert a provider claim-rewards quote into an EarnKit quote result.
31444
+ *
31445
+ * @param quote - Provider claim-rewards quote with raw reward amount objects
31446
+ * @returns Claim-rewards quote with rewards formatted as strings
31447
+ */
31448
+ function formatClaimRewardsQuoteInfo(quote) {
31449
+ return {
31450
+ ...quote,
31451
+ rewards: quote.rewards.map(formatAssetAmount),
31452
+ };
31453
+ }
31454
+ /**
31455
+ * Convert a provider claim-rewards result into an EarnKit result.
31456
+ *
31457
+ * @param result - Provider claim-rewards result
31458
+ * @returns Claim-rewards result with claimed reward amounts formatted as strings
31459
+ */
31460
+ function formatClaimRewardsResult(result) {
31461
+ if (result.status === 'no_rewards') {
31462
+ return result;
31463
+ }
31464
+ return {
31465
+ ...result,
31466
+ rewards: result.rewards.map(formatClaimedAmount),
31467
+ };
31468
+ }
31469
+
31470
+ /**
31471
+ * Schema for the adapter context within earn operations.
31472
+ *
31473
+ * Validate that a properly-shaped adapter instance and a valid chain
31474
+ * identifier are provided. The address field is optional (resolved from
31475
+ * the adapter at runtime).
31476
+ *
31477
+ * @internal
31478
+ */
31479
+ const adapterContextSchema$4 = zod.z.object({
31480
+ adapter: adapterSchema$1,
31481
+ // AdapterContext accepts a wider chain field, but runtime validation must
31482
+ // stay constrained to EarnChain identifiers.
31483
+ chain: earnChainIdentifierSchema,
31484
+ address: zod.z.string().optional(),
31485
+ });
31486
+ /**
31487
+ * Schema for the EarnConfig options.
31488
+ *
31489
+ * Validate the optional Kit Key field using the standard `apiKeySchema`
31490
+ * format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
31491
+ * operates in permissionless mode.
31492
+ *
31493
+ * @internal
31494
+ */
31495
+ const earnConfigSchema = zod.z.object({
31496
+ kitKey: apiKeySchema.optional(),
31497
+ });
31498
+ /**
31499
+ * Schema for validating human-readable decimal amount strings.
31500
+ *
31501
+ * Accept positive decimal strings like '100', '100.50', '0.001'.
31502
+ * Reject zero, negative, and non-numeric strings. Support up to 18
31503
+ * decimal places for maximum token compatibility.
31504
+ *
31505
+ * @internal
31506
+ */
31507
+ const amountSchema$1 = zod.z
31508
+ .string({ required_error: 'amount is required' })
31509
+ .min(1, 'amount is required')
31510
+ .pipe(createDecimalStringValidator({
31511
+ allowZero: false,
31512
+ regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
31513
+ attributeName: 'amount',
31514
+ maxDecimals: 18,
31515
+ })(zod.z.string()));
31516
+ /**
31517
+ * Schema for vault address validation.
31518
+ *
31519
+ * Validate that the vault address is a valid EVM address (0x + 40 hex
31520
+ * chars). EarnKit currently supports EVM vault addresses on Arc Testnet.
31521
+ *
31522
+ * @internal
31523
+ */
31524
+ const vaultAddressSchema = evmAddressSchema;
31525
+ /**
31526
+ * Validation schema for VaultQuery.
31527
+ *
31528
+ * @example
31529
+ * ```typescript
31530
+ * import { vaultQuerySchema } from '@circle-fin/earn-kit'
31531
+ *
31532
+ * const result = vaultQuerySchema.safeParse({
31533
+ * chain: 'Arc_Testnet',
31534
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31535
+ * })
31536
+ * ```
31537
+ */
31538
+ const vaultQuerySchema = zod.z.object({
31539
+ chain: earnChainIdentifierSchema,
31540
+ vaultAddress: vaultAddressSchema,
31541
+ });
31542
+ /**
31543
+ * Validation schema for GetVaultsParams.
31544
+ *
31545
+ * @example
31546
+ * ```typescript
31547
+ * import { getVaultsParamsSchema } from '@circle-fin/earn-kit'
31548
+ *
31549
+ * const result = getVaultsParamsSchema.safeParse({
31550
+ * vaults: [
31551
+ * {
31552
+ * chain: 'Arc_Testnet',
31553
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31554
+ * },
31555
+ * ],
31556
+ * })
31557
+ * ```
31558
+ */
31559
+ const getVaultsParamsSchema = zod.z.object({
31560
+ vaults: zod.z
31561
+ .array(vaultQuerySchema)
31562
+ .min(1, 'at least one vault query is required')
31563
+ .max(20, 'maximum 20 vault queries per request'),
31564
+ config: earnConfigSchema.optional(),
31565
+ });
31566
+ /**
31567
+ * Validation schema for GetPositionParams.
31568
+ *
31569
+ * @example
31570
+ * ```typescript
31571
+ * import { getPositionParamsSchema } from '@circle-fin/earn-kit'
31572
+ *
31573
+ * const result = getPositionParamsSchema.safeParse({
31574
+ * from: { adapter, chain: 'Arc_Testnet' },
31575
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31576
+ * })
31577
+ * ```
31578
+ */
31579
+ const getPositionParamsSchema = zod.z.object({
31580
+ from: adapterContextSchema$4,
31581
+ vaultAddress: vaultAddressSchema,
31582
+ config: earnConfigSchema.optional(),
31583
+ });
31584
+ /**
31585
+ * Validation schema for DepositParams.
31586
+ *
31587
+ * @example
31588
+ * ```typescript
31589
+ * import { depositParamsSchema } from '@circle-fin/earn-kit'
31590
+ *
31591
+ * const result = depositParamsSchema.safeParse({
31592
+ * from: { adapter, chain: 'Arc_Testnet' },
31593
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31594
+ * amount: '100.50',
31595
+ * })
31596
+ * ```
31597
+ */
31598
+ const depositParamsSchema$1 = zod.z.object({
31599
+ from: adapterContextSchema$4,
31600
+ vaultAddress: vaultAddressSchema,
31601
+ amount: amountSchema$1,
31602
+ config: earnConfigSchema.optional(),
31603
+ });
31604
+ /**
31605
+ * Validation schema for WithdrawParams.
31606
+ *
31607
+ * @example
31608
+ * ```typescript
31609
+ * import { withdrawParamsSchema } from '@circle-fin/earn-kit'
31610
+ *
31611
+ * const result = withdrawParamsSchema.safeParse({
31612
+ * from: { adapter, chain: 'Arc_Testnet' },
31613
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31614
+ * amount: '50.00',
31615
+ * })
31616
+ * ```
31617
+ */
31618
+ const withdrawParamsSchema = zod.z.object({
31619
+ from: adapterContextSchema$4,
31620
+ vaultAddress: vaultAddressSchema,
31621
+ amount: amountSchema$1,
31622
+ config: earnConfigSchema.optional(),
31623
+ });
31624
+ /**
31625
+ * Validation schema for ClaimRewardsParams.
31626
+ *
31627
+ * @example
31628
+ * ```typescript
31629
+ * import { claimRewardsParamsSchema } from '@circle-fin/earn-kit'
31630
+ *
31631
+ * const result = claimRewardsParamsSchema.safeParse({
31632
+ * from: { adapter, chain: 'Arc_Testnet' },
31633
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31634
+ * })
31635
+ * ```
31636
+ */
31637
+ const claimRewardsParamsSchema = zod.z.object({
31638
+ from: adapterContextSchema$4,
31639
+ vaultAddress: vaultAddressSchema,
31640
+ config: earnConfigSchema.optional(),
31641
+ });
31642
+ /**
31643
+ * Validation schema for GetDepositQuoteParams.
31644
+ *
31645
+ * @example
31646
+ * ```typescript
31647
+ * import { getDepositQuoteParamsSchema } from '@circle-fin/earn-kit'
31648
+ *
31649
+ * const result = getDepositQuoteParamsSchema.safeParse({
31650
+ * from: { adapter, chain: 'Arc_Testnet' },
31651
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31652
+ * amount: '100.50',
31653
+ * })
31654
+ * ```
31655
+ */
31656
+ const getDepositQuoteParamsSchema = zod.z.object({
31657
+ from: adapterContextSchema$4,
31658
+ vaultAddress: vaultAddressSchema,
31659
+ amount: amountSchema$1,
31660
+ config: earnConfigSchema.optional(),
31661
+ });
31662
+ /**
31663
+ * Validation schema for GetWithdrawalQuoteParams.
31664
+ *
31665
+ * @example
31666
+ * ```typescript
31667
+ * import { getWithdrawalQuoteParamsSchema } from '@circle-fin/earn-kit'
31668
+ *
31669
+ * const result = getWithdrawalQuoteParamsSchema.safeParse({
31670
+ * from: { adapter, chain: 'Arc_Testnet' },
31671
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31672
+ * amount: '50.00',
31673
+ * })
31674
+ * ```
31675
+ */
31676
+ const getWithdrawalQuoteParamsSchema = zod.z.object({
31677
+ from: adapterContextSchema$4,
31678
+ vaultAddress: vaultAddressSchema,
31679
+ amount: amountSchema$1,
31680
+ config: earnConfigSchema.optional(),
31681
+ });
31682
+ /**
31683
+ * Validation schema for GetClaimRewardsQuoteParams.
31684
+ *
31685
+ * @example
31686
+ * ```typescript
31687
+ * import { getClaimRewardsQuoteParamsSchema } from '@circle-fin/earn-kit'
31688
+ *
31689
+ * const result = getClaimRewardsQuoteParamsSchema.safeParse({
31690
+ * from: { adapter, chain: 'Arc_Testnet' },
31691
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31692
+ * })
31693
+ * ```
31694
+ */
31695
+ const getClaimRewardsQuoteParamsSchema = zod.z.object({
31696
+ from: adapterContextSchema$4,
31697
+ vaultAddress: vaultAddressSchema,
31698
+ config: earnConfigSchema.optional(),
31699
+ });
31700
+
31701
+ /**
31702
+ * Fetch vault information from the earn service.
31703
+ *
31704
+ * Query the configured earn providers to get information about one
31705
+ * or more DeFi lending vaults. Accept batch queries as chain +
31706
+ * vaultAddress pairs.
31707
+ *
31708
+ * @param context - The EarnKit context containing providers
31709
+ * @param params - Vault query parameters
31710
+ * @returns A promise resolving to the vault lookup results
31711
+ * @throws {@link KitError} If validation fails or no provider is configured
31712
+ *
31713
+ * @example
31714
+ * ```typescript
31715
+ * import { createEarnKitContext, getVaults } from '@circle-fin/earn-kit'
31716
+ *
31717
+ * const context = createEarnKitContext()
31718
+ * const result = await getVaults(context, {
31719
+ * vaults: [{ chain: 'Arc_Testnet', vaultAddress: '0x...' }],
31720
+ * })
31721
+ * console.log(`Found ${result.vaults.length} vaults`)
31722
+ * ```
31723
+ */
31724
+ async function getVaults$1(context, params) {
31725
+ assertEarnParams(params, getVaultsParamsSchema);
31726
+ const provider = findProvider(context);
31727
+ const result = await provider.getVaults({
31728
+ vaults: params.vaults,
31729
+ ...(params.config !== undefined && { config: params.config }),
31730
+ });
31731
+ return formatGetVaultsResult(result);
31732
+ }
31733
+
31734
+ /**
31735
+ * Resolve the wallet address to use for on-chain operations.
31736
+ *
31737
+ * For developer-controlled adapters, callers pass `address` explicitly and we
31738
+ * return it unchanged. For user-controlled adapters, we fall back to
31739
+ * `adapter.getAddress(chain)`.
31740
+ *
31741
+ * @param from - Adapter context containing the adapter and optional explicit address.
31742
+ * @param chain - Resolved chain definition.
31743
+ * @returns The wallet address.
31744
+ * @throws Propagates errors from `adapter.getAddress(chain)` when
31745
+ * `from.address` is not supplied.
31746
+ *
31747
+ * @example
31748
+ * ```typescript
31749
+ * const address = await resolveAdapterAddress(params.from, chain)
31750
+ * ```
31751
+ *
31752
+ * @internal
31753
+ */
31754
+ async function resolveAdapterAddress(from, chain) {
31755
+ if (from.address !== undefined && from.address !== '') {
31756
+ return from.address;
31757
+ }
31758
+ return from.adapter.getAddress(chain);
31759
+ }
31760
+
31761
+ /**
31762
+ * Fetch P&L position data for a wallet.
31763
+ *
31764
+ * Query the configured earn providers to get balance, principal,
31765
+ * earnings, and shares data for the wallet specified in the adapter context.
31766
+ *
31767
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31768
+ * @param context - The EarnKit context containing providers
31769
+ * @param params - Position query parameters with adapter context
31770
+ * @returns A promise resolving to the wallet's position info
31771
+ * @throws {@link KitError} If validation fails or no provider is configured
31772
+ *
31773
+ * @example
31774
+ * ```typescript
31775
+ * import {
31776
+ * createEarnKitContext,
31777
+ * getPosition,
31778
+ * EarnChain,
31779
+ * } from '@circle-fin/earn-kit'
31780
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31781
+ *
31782
+ * const context = createEarnKitContext()
31783
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31784
+ *
31785
+ * const position = await getPosition(context, {
31786
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31787
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31788
+ * })
31789
+ * console.log(`Balance: ${position.currentBalance}`)
31790
+ * ```
31791
+ */
31792
+ async function getPosition$1(context, params) {
31793
+ assertEarnParams(params, getPositionParamsSchema);
31794
+ const chain = resolveChainIdentifier(params.from.chain);
31795
+ params.from.adapter.validateChainSupport(chain);
31796
+ const provider = findProvider(context, chain, 'getPosition');
31797
+ const address = await resolveAdapterAddress(params.from, chain);
31798
+ const result = await provider.getPosition({
31799
+ from: { ...params.from, chain, address },
31800
+ vaultAddress: params.vaultAddress,
31801
+ ...(params.config !== undefined && { config: params.config }),
31802
+ });
31803
+ return formatPositionInfo(result);
31804
+ }
31805
+
31806
+ /**
31807
+ * Execute a deposit into a DeFi lending vault.
31808
+ *
31809
+ * Validate user params, resolve the chain and wallet address, then delegate
31810
+ * the full on-chain flow (fetch signed instructions, issue a max USDC ERC-20
31811
+ * approval when needed, submit the deposit, wait for confirmation) to the
31812
+ * selected earn provider.
31813
+ *
31814
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31815
+ * @param context - The EarnKit context containing providers
31816
+ * @param params - Deposit parameters including vault address, amount, and adapter
31817
+ * @returns A promise resolving to the deposit result with transaction details
31818
+ * @throws {@link KitError} If validation fails or no provider is configured
31819
+ * @throws {@link KitError} If the chain has no adapter contract configured
31820
+ * @throws {@link KitError} If the chain does not support USDC
31821
+ * @throws {@link KitError} If the USDC approval transaction reverts on-chain
31822
+ * @throws {@link KitError} If the deposit transaction reverts on-chain
31823
+ *
31824
+ * @example
31825
+ * ```typescript
31826
+ * import {
31827
+ * createEarnKitContext,
31828
+ * deposit,
31829
+ * EarnChain,
31830
+ * } from '@circle-fin/earn-kit'
31831
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31832
+ *
31833
+ * const context = createEarnKitContext()
31834
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31835
+ *
31836
+ * const result = await deposit(context, {
31837
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31838
+ * vaultAddress: '0x...',
31839
+ * amount: '100.50',
31840
+ * })
31841
+ * console.log(`Deposited ${result.amount} into ${result.vaultAddress}, tx: ${result.txHash}`)
31842
+ * ```
31843
+ */
31844
+ async function deposit$3(context, params) {
31845
+ assertEarnParams(params, depositParamsSchema$1);
31846
+ const chain = resolveChainIdentifier(params.from.chain);
31847
+ params.from.adapter.validateChainSupport(chain);
31848
+ const provider = findProvider(context, chain, 'deposit');
31849
+ const address = await resolveAdapterAddress(params.from, chain);
31850
+ return provider.deposit({
31851
+ from: { ...params.from, chain, address },
31852
+ vaultAddress: params.vaultAddress,
31853
+ amount: params.amount,
31854
+ ...(params.config !== undefined && { config: params.config }),
31855
+ });
31856
+ }
31857
+
31858
+ /**
31859
+ * Execute a withdrawal from a DeFi lending vault.
31860
+ *
31861
+ * Validate user params, resolve the chain and wallet address, then delegate
31862
+ * the full on-chain flow (fetch signed instructions, issue a max vault-share
31863
+ * ERC-20 approval when needed, submit the withdrawal, wait for confirmation)
31864
+ * to the selected earn provider.
31865
+ *
31866
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31867
+ * @param context - The EarnKit context containing providers
31868
+ * @param params - Withdrawal parameters including vault address, amount, and adapter
31869
+ * @returns A promise resolving to the withdrawal result with transaction details
31870
+ * @throws {@link KitError} If validation fails or no provider is configured
31871
+ * @throws {@link KitError} If the chain has no adapter contract configured
31872
+ * @throws {@link KitError} If the vault share token approval transaction reverts on-chain
31873
+ * @throws {@link KitError} If the withdrawal transaction reverts on-chain
31874
+ *
31875
+ * @example
31876
+ * ```typescript
31877
+ * import {
31878
+ * createEarnKitContext,
31879
+ * withdraw,
31880
+ * EarnChain,
31881
+ * } from '@circle-fin/earn-kit'
31882
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31883
+ *
31884
+ * const context = createEarnKitContext()
31885
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31886
+ *
31887
+ * const result = await withdraw(context, {
31888
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31889
+ * vaultAddress: '0x...',
31890
+ * amount: '50.00',
31891
+ * })
31892
+ * console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
31893
+ * ```
31894
+ */
31895
+ async function withdraw$1(context, params) {
31896
+ assertEarnParams(params, withdrawParamsSchema);
31897
+ const chain = resolveChainIdentifier(params.from.chain);
31898
+ params.from.adapter.validateChainSupport(chain);
31899
+ const provider = findProvider(context, chain, 'withdraw');
31900
+ const address = await resolveAdapterAddress(params.from, chain);
31901
+ return provider.withdraw({
31902
+ from: { ...params.from, chain, address },
31903
+ vaultAddress: params.vaultAddress,
31904
+ amount: params.amount,
31905
+ ...(params.config !== undefined && { config: params.config }),
31906
+ });
31907
+ }
31908
+
31909
+ /**
31910
+ * Claim rewards from earn vaults.
31911
+ *
31912
+ * Validate user params, resolve the chain and wallet address, then delegate
31913
+ * the full on-chain flow (fetch signed instructions, submit the claim, wait
31914
+ * for confirmation) to the selected earn provider. When no rewards are
31915
+ * claimable, the provider returns rewards without submitting a transaction.
31916
+ *
31917
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31918
+ * @param context - The EarnKit context containing providers
31919
+ * @param params - Claim parameters including adapter context and vault address
31920
+ * @returns A promise resolving to the claim result with reward details
31921
+ * @throws {@link KitError} If validation fails or no provider is configured
31922
+ * @throws {@link KitError} If the chain has no adapter contract configured
31923
+ * @throws {@link KitError} If the earn service API request or response fails
31924
+ * @throws {@link KitError} If the claim transaction reverts on-chain
31925
+ *
31926
+ * @example
31927
+ * ```typescript
31928
+ * import {
31929
+ * createEarnKitContext,
31930
+ * claimRewards,
31931
+ * EarnChain,
31932
+ * } from '@circle-fin/earn-kit'
31933
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
31934
+ *
31935
+ * const context = createEarnKitContext()
31936
+ * const adapter = createViemAdapterFromPrivateKey({ privateKey: '0x...' })
31937
+ *
31938
+ * const result = await claimRewards(context, {
31939
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31940
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
31941
+ * })
31942
+ * if (result.status === 'no_rewards') {
31943
+ * console.log('No rewards to claim')
31944
+ * } else {
31945
+ * console.log(`Claimed ${result.rewards.length} reward(s), tx: ${result.txHash}`)
31946
+ * }
31947
+ * ```
31948
+ */
31949
+ async function claimRewards$1(context, params) {
31950
+ assertEarnParams(params, claimRewardsParamsSchema);
31951
+ const chain = resolveChainIdentifier(params.from.chain);
31952
+ params.from.adapter.validateChainSupport(chain);
31953
+ const provider = findProvider(context, chain, 'claimRewards');
31954
+ const address = await resolveAdapterAddress(params.from, chain);
31955
+ const result = await provider.claimRewards({
31956
+ from: { ...params.from, chain, address },
31957
+ vaultAddress: params.vaultAddress,
31958
+ ...(params.config === undefined ? {} : { config: params.config }),
31959
+ });
31960
+ return formatClaimRewardsResult(result);
31961
+ }
31962
+
31963
+ /**
31964
+ * Get an informational quote for a deposit into a vault.
31965
+ *
31966
+ * Query expected shares, share price, and APY for the specified
31967
+ * deposit amount without executing any transaction.
31968
+ *
31969
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
31970
+ * @param context - The EarnKit context containing providers
31971
+ * @param params - Deposit quote parameters including vault address and amount
31972
+ * @returns A promise resolving to the deposit quote information
31973
+ * @throws {@link KitError} If validation fails or no provider is configured
31974
+ *
31975
+ * @example
31976
+ * ```typescript
31977
+ * import {
31978
+ * createEarnKitContext,
31979
+ * getDepositQuote,
31980
+ * EarnChain,
31981
+ * } from '@circle-fin/earn-kit'
31982
+ *
31983
+ * const context = createEarnKitContext()
31984
+ * const quote = await getDepositQuote(context, {
31985
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
31986
+ * vaultAddress: '0x...',
31987
+ * amount: '100.50',
31988
+ * })
31989
+ * console.log(`Expected shares: ${quote.expectedShares.amount}`)
31990
+ * ```
31991
+ */
31992
+ async function getDepositQuote$1(context, params) {
31993
+ assertEarnParams(params, getDepositQuoteParamsSchema);
31994
+ const chain = resolveChainIdentifier(params.from.chain);
31995
+ params.from.adapter.validateChainSupport(chain);
31996
+ const provider = findProvider(context, chain, 'getDepositQuote');
31997
+ const address = await resolveAdapterAddress(params.from, chain);
31998
+ const result = await provider.getDepositQuote({
31999
+ from: { ...params.from, chain, address },
32000
+ vaultAddress: params.vaultAddress,
32001
+ amount: params.amount,
32002
+ ...(params.config !== undefined && { config: params.config }),
32003
+ });
32004
+ return formatDepositQuoteInfo(result);
32005
+ }
32006
+
32007
+ /**
32008
+ * Get an informational quote for a withdrawal from a vault.
32009
+ *
32010
+ * Query shares to redeem, max withdrawable, and fees for the specified
32011
+ * withdrawal amount without executing any transaction.
32012
+ *
32013
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
32014
+ * @param context - The EarnKit context containing providers
32015
+ * @param params - Withdrawal quote parameters including vault address and amount
32016
+ * @returns A promise resolving to the withdrawal quote information
32017
+ * @throws {@link KitError} If validation fails or no provider is configured
32018
+ *
32019
+ * @example
32020
+ * ```typescript
32021
+ * import {
32022
+ * createEarnKitContext,
32023
+ * getWithdrawalQuote,
32024
+ * EarnChain,
32025
+ * } from '@circle-fin/earn-kit'
32026
+ *
32027
+ * const context = createEarnKitContext()
32028
+ * const quote = await getWithdrawalQuote(context, {
32029
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
32030
+ * vaultAddress: '0x...',
32031
+ * amount: '50.00',
32032
+ * })
32033
+ * console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
32034
+ * ```
32035
+ */
32036
+ async function getWithdrawalQuote$1(context, params) {
32037
+ assertEarnParams(params, getWithdrawalQuoteParamsSchema);
32038
+ const chain = resolveChainIdentifier(params.from.chain);
32039
+ params.from.adapter.validateChainSupport(chain);
32040
+ const provider = findProvider(context, chain, 'getWithdrawalQuote');
32041
+ const address = await resolveAdapterAddress(params.from, chain);
32042
+ const result = await provider.getWithdrawalQuote({
32043
+ from: { ...params.from, chain, address },
32044
+ vaultAddress: params.vaultAddress,
32045
+ amount: params.amount,
32046
+ ...(params.config !== undefined && { config: params.config }),
32047
+ });
32048
+ return formatWithdrawalQuoteInfo(result);
32049
+ }
32050
+
32051
+ /**
32052
+ * Return all chains supported by configured earn providers.
32053
+ *
32054
+ * @param context - The EarnKit context containing providers
32055
+ * @returns Deduplicated supported chain definitions
32056
+ *
32057
+ * @example
32058
+ * ```typescript
32059
+ * import { createEarnKitContext, getSupportedChains } from '@circle-fin/earn-kit'
32060
+ *
32061
+ * const context = createEarnKitContext()
32062
+ * const chains = getSupportedChains(context)
32063
+ * ```
32064
+ */
32065
+ function getSupportedChains$3(context) {
32066
+ const earnChains = new Set(Object.values(exports.EarnChain));
32067
+ const chainsById = new Map();
32068
+ for (const provider of context.providers) {
32069
+ for (const chain of provider.supportedChains) {
32070
+ if (earnChains.has(chain.chain)) {
32071
+ chainsById.set(chain.chain, chain);
32072
+ }
32073
+ }
32074
+ }
32075
+ return [...chainsById.values()];
32076
+ }
32077
+
32078
+ /**
32079
+ * Get an informational quote for claiming rewards.
32080
+ *
32081
+ * Query claimable reward details without executing any transaction.
32082
+ *
32083
+ * @typeParam TFromAdapterCapabilities - The adapter capabilities type
32084
+ * @param context - The EarnKit context containing providers
32085
+ * @param params - Claim rewards quote parameters including vault address
32086
+ * @returns A promise resolving to the claim rewards quote information
32087
+ * @throws {@link KitError} If validation fails or no provider is configured
32088
+ *
32089
+ * @example
32090
+ * ```typescript
32091
+ * import {
32092
+ * createEarnKitContext,
32093
+ * getClaimRewardsQuote,
32094
+ * EarnChain,
32095
+ * } from '@circle-fin/earn-kit'
32096
+ *
32097
+ * const context = createEarnKitContext()
32098
+ * const quote = await getClaimRewardsQuote(context, {
32099
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
32100
+ * vaultAddress: '0x...',
32101
+ * })
32102
+ * console.log(`Claimable rewards: ${quote.rewards.length}`)
32103
+ * ```
32104
+ */
32105
+ async function getClaimRewardsQuote$1(context, params) {
32106
+ assertEarnParams(params, getClaimRewardsQuoteParamsSchema);
32107
+ const chain = resolveChainIdentifier(params.from.chain);
32108
+ params.from.adapter.validateChainSupport(chain);
32109
+ const provider = findProvider(context, chain, 'getClaimRewardsQuote');
32110
+ const address = await resolveAdapterAddress(params.from, chain);
32111
+ const result = await provider.getClaimRewardsQuote({
32112
+ from: { ...params.from, chain, address },
32113
+ vaultAddress: params.vaultAddress,
32114
+ ...(params.config !== undefined && { config: params.config }),
32115
+ });
32116
+ return formatClaimRewardsQuoteInfo(result);
32117
+ }
32118
+
32119
+ /**
32120
+ * A high-level class-based interface for DeFi lending vault operations.
32121
+ *
32122
+ * EarnKit provides a familiar class-based API for developers who prefer
32123
+ * traditional object-oriented patterns. The class maintains an internal
32124
+ * context and provides methods for depositing, withdrawing, claiming
32125
+ * rewards, and querying vault and position data.
32126
+ *
32127
+ * Key features:
32128
+ * - Strongly typed parameters with comprehensive Zod validation
32129
+ * - Supported earn vault deposits and withdrawals
32130
+ * - Deposit quote queries (expected shares, share price, APY without executing a transaction)
32131
+ * - Withdrawal quote queries (shares to redeem, max withdrawable, fees without executing a transaction)
32132
+ * - Claim rewards quote queries (claimable reward details without executing a transaction)
32133
+ * - Reward claiming
32134
+ * - P&L position tracking
32135
+ * - Dual-mode authentication (permissioned and permissionless)
32136
+ * - Full TypeScript support with IntelliSense
32137
+ *
32138
+ * @example
32139
+ * ```typescript
32140
+ * import { EarnChain, EarnKit } from '@circle-fin/earn-kit'
32141
+ * import { createViemAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
32142
+ *
32143
+ * const kit = new EarnKit()
32144
+ * const adapter = createViemAdapterFromPrivateKey({
32145
+ * privateKey: process.env.PRIVATE_KEY,
32146
+ * })
32147
+ *
32148
+ * // Fetch vault info
32149
+ * const { vaults } = await kit.getVaults({
32150
+ * vaults: [{ chain: EarnChain.Arc_Testnet, vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458' }],
32151
+ * })
32152
+ * const [vault] = vaults
32153
+ * if (vault === undefined) {
32154
+ * throw new Error('No earn vaults available')
32155
+ * }
32156
+ *
32157
+ * // Deposit into a vault
32158
+ * const result = await kit.deposit({
32159
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
32160
+ * vaultAddress: vault.vaultAddress,
32161
+ * amount: '100.50',
32162
+ * })
32163
+ * console.log(`Deposited ${result.amount} into ${result.vaultAddress}, tx: ${result.txHash}`)
32164
+ * ```
32165
+ *
32166
+ * @remarks
32167
+ * For functional usage, import and use the operations directly:
32168
+ * ```typescript
32169
+ * import { createEarnKitContext, deposit } from '@circle-fin/earn-kit'
32170
+ * const context = createEarnKitContext()
32171
+ * await deposit(context, params)
32172
+ * ```
32173
+ */
32174
+ class EarnKit {
32175
+ context;
32176
+ /**
32177
+ * Create a new EarnKit instance.
32178
+ *
32179
+ * Accept an optional configuration object to customize the kit's
32180
+ * behavior. If no configuration is provided, the kit is initialized
32181
+ * with default settings using the built-in EarnServiceProvider.
32182
+ *
32183
+ * @param config - Optional configuration for the EarnKit instance
32184
+ *
32185
+ * @example
32186
+ * ```typescript
32187
+ * import { EarnKit } from '@circle-fin/earn-kit'
32188
+ *
32189
+ * // Create with default configuration
32190
+ * const kit = new EarnKit()
32191
+ * ```
32192
+ */
32193
+ constructor(config = {}) {
32194
+ this.context = createEarnKitContext(config);
32195
+ }
32196
+ /**
32197
+ * Return the chains supported by configured earn providers.
32198
+ *
32199
+ * @returns Deduplicated supported chain definitions
32200
+ *
32201
+ * @example
32202
+ * ```typescript
32203
+ * const kit = new EarnKit()
32204
+ * const chains = kit.getSupportedChains()
32205
+ * ```
32206
+ */
32207
+ getSupportedChains() {
32208
+ return getSupportedChains$3(this.context);
32209
+ }
32210
+ /**
32211
+ * Fetch available vault information.
32212
+ *
32213
+ * Query the configured earn providers to get a list of available
32214
+ * DeFi lending vaults with APY, TVL, and reward information.
32215
+ *
32216
+ * @param params - Query parameters with vault address and optional chain filter
32217
+ * @returns A promise resolving to the list of available vaults
32218
+ * @throws {@link KitError} If no provider is configured
32219
+ *
32220
+ * @example
32221
+ * ```typescript
32222
+ * const kit = new EarnKit()
32223
+ * const result = await kit.getVaults({
32224
+ * vaults: [{ chain: 'Arc_Testnet', vaultAddress: '0x8eB67...' }],
32225
+ * })
32226
+ * result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
32227
+ * ```
32228
+ */
32229
+ async getVaults(params) {
32230
+ return getVaults$1(this.context, params);
32231
+ }
32232
+ /**
32233
+ * Fetch P&L position data for the connected wallet.
32234
+ *
32235
+ * Query balance, principal, earnings, and shares data for the wallet
32236
+ * specified in the adapter context.
32237
+ *
32238
+ * @param params - Position query parameters with adapter context
32239
+ * @returns A promise resolving to the wallet's position info
32240
+ * @throws {@link KitError} If validation fails or no provider is configured
32241
+ *
32242
+ * @example
32243
+ * ```typescript
32244
+ * const position = await kit.getPosition({
32245
+ * from: { adapter, chain: 'Arc_Testnet' },
32246
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
32247
+ * })
32248
+ * if (position.pnl.status === 'available') {
32249
+ * console.log(`Yield: ${position.pnl.totalYieldEarned}`)
32250
+ * }
32251
+ * ```
32252
+ */
32253
+ async getPosition(params) {
32254
+ return getPosition$1(this.context, params);
32255
+ }
32256
+ /**
32257
+ * Execute a deposit into a DeFi lending vault.
32258
+ *
32259
+ * Build signed deposit instructions via the earn service, issue a max USDC
32260
+ * ERC-20 approval for the adapter contract when the current allowance is
32261
+ * below the SDK threshold, then submit the deposit on-chain. Return the
32262
+ * confirmed transaction hash together with the deposited amount and target
32263
+ * vault.
32264
+ *
32265
+ * @param params - Deposit parameters including vault address and amount
32266
+ * @returns A promise resolving to the deposit result with transaction details
32267
+ * @throws {@link KitError} If validation fails, no provider is configured, the
32268
+ * chain has no adapter contract configured, the approval transaction
32269
+ * reverts on-chain, or the deposit transaction reverts on-chain
32270
+ *
32271
+ * @example
32272
+ * ```typescript
32273
+ * const result = await kit.deposit({
32274
+ * from: { adapter, chain: 'Arc_Testnet' },
32275
+ * vaultAddress: '0x...',
32276
+ * amount: '100.50',
32277
+ * })
32278
+ * console.log(`Deposited ${result.amount} into ${result.vaultAddress}, tx: ${result.txHash}`)
32279
+ * ```
32280
+ */
32281
+ async deposit(params) {
32282
+ return deposit$3(this.context, params);
32283
+ }
32284
+ /**
32285
+ * Execute a withdrawal from a DeFi lending vault.
32286
+ *
32287
+ * Build signed withdrawal instructions via the earn service, approve the
32288
+ * adapter contract to spend vault share tokens if needed, then submit the
32289
+ * withdrawal on-chain. Return the withdrawal result including the on-chain
32290
+ * transaction hash, withdrawn amount, and target vault.
32291
+ *
32292
+ * @param params - Withdrawal parameters including vault address and amount
32293
+ * @returns A promise resolving to the withdrawal result with transaction details
32294
+ * @throws {@link KitError} If validation fails, no provider is configured,
32295
+ * the chain has no adapter contract configured, the share-token approval
32296
+ * reverts on-chain, or the withdrawal transaction reverts on-chain.
32297
+ *
32298
+ * @example
32299
+ * ```typescript
32300
+ * const result = await kit.withdraw({
32301
+ * from: { adapter, chain: 'Arc_Testnet' },
32302
+ * vaultAddress: '0x...',
32303
+ * amount: '50.00',
32304
+ * })
32305
+ * console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
32306
+ * ```
32307
+ */
32308
+ async withdraw(params) {
32309
+ return withdraw$1(this.context, params);
32310
+ }
32311
+ /**
32312
+ * Claim rewards from earn vaults.
32313
+ *
32314
+ * Build signed claim rewards instructions via the earn service, submit
32315
+ * them on-chain through the adapter, and return the confirmed transaction
32316
+ * hash. When no rewards are claimable, no transaction is submitted.
32317
+ *
32318
+ * @param params - Claim parameters including adapter context
32319
+ * @returns A promise resolving to the claim result with reward details
32320
+ * @throws {@link KitError} If validation fails or no provider is configured
32321
+ *
32322
+ * @example
32323
+ * ```typescript
32324
+ * const result = await kit.claimRewards({
32325
+ * from: { adapter, chain: 'Arc_Testnet' },
32326
+ * vaultAddress: '0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458',
32327
+ * })
32328
+ *
32329
+ * if (result.status === 'claimed') {
32330
+ * console.log(`Claimed ${result.rewards.length} reward token(s), tx: ${result.txHash}`)
32331
+ * }
32332
+ * ```
32333
+ */
32334
+ async claimRewards(params) {
32335
+ return claimRewards$1(this.context, params);
32336
+ }
32337
+ /**
32338
+ * Get an informational quote for a deposit into a vault.
32339
+ *
32340
+ * Query expected shares, share price, and APY for the specified
32341
+ * deposit amount without executing any transaction.
32342
+ *
32343
+ * @param params - Deposit quote parameters including vault address and amount
32344
+ * @returns A promise resolving to the deposit quote information
32345
+ * @throws {@link KitError} If validation fails or no provider is configured
32346
+ *
32347
+ * @example
32348
+ * ```typescript
32349
+ * const quote = await kit.getDepositQuote({
32350
+ * from: { adapter, chain: 'Arc_Testnet' },
32351
+ * vaultAddress: '0x...',
32352
+ * amount: '100.50',
32353
+ * })
32354
+ * console.log(`Expected shares: ${quote.expectedShares.amount}`)
32355
+ * ```
32356
+ */
32357
+ async getDepositQuote(params) {
32358
+ return getDepositQuote$1(this.context, params);
32359
+ }
32360
+ /**
32361
+ * Get an informational quote for a withdrawal from a vault.
32362
+ *
32363
+ * Query shares to redeem, max withdrawable, and fees for the specified
32364
+ * withdrawal amount without executing any transaction.
32365
+ *
32366
+ * @param params - Withdrawal quote parameters including vault address and amount
32367
+ * @returns A promise resolving to the withdrawal quote information
32368
+ * @throws {@link KitError} If validation fails or no provider is configured
32369
+ *
32370
+ * @example
32371
+ * ```typescript
32372
+ * const quote = await kit.getWithdrawalQuote({
32373
+ * from: { adapter, chain: 'Arc_Testnet' },
32374
+ * vaultAddress: '0x...',
32375
+ * amount: '50.00',
32376
+ * })
32377
+ * console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
32378
+ * ```
32379
+ */
32380
+ async getWithdrawalQuote(params) {
32381
+ return getWithdrawalQuote$1(this.context, params);
32382
+ }
32383
+ /**
32384
+ * Get an informational quote for claiming rewards.
32385
+ *
32386
+ * Query claimable reward details without executing any transaction.
32387
+ *
32388
+ * @param params - Claim rewards quote parameters including vault address
32389
+ * @returns A promise resolving to the claim rewards quote information
32390
+ * @throws {@link KitError} If validation fails or no provider is configured
32391
+ *
32392
+ * @example
32393
+ * ```typescript
32394
+ * const quote = await kit.getClaimRewardsQuote({
32395
+ * from: { adapter, chain: 'Arc_Testnet' },
32396
+ * vaultAddress: '0x...',
32397
+ * })
32398
+ * console.log(`Claimable rewards: ${quote.rewards.length}`)
32399
+ * ```
32400
+ */
32401
+ async getClaimRewardsQuote(params) {
32402
+ return getClaimRewardsQuote$1(this.context, params);
32403
+ }
32404
+ }
32405
+
32406
+ /**
32407
+ *
32408
+ * Earn Kit
32409
+ *
32410
+ * A strongly-typed SDK for DeFi lending vault deposits, withdrawals, and rewards
32411
+ * @packageDocumentation
32412
+ */
32413
+ // Auto-register this kit for user agent tracking
32414
+ registerKit(`${pkg$1.name}/${pkg$1.version}`);
32415
+
32416
+ /**
32417
+ * Create an EarnKit instance for AppKit earn operations.
32418
+ *
32419
+ * @remarks The context parameter is reserved for future EarnKit wiring.
32420
+ * EarnKit does not currently support AppKit developer fee hooks, so the
32421
+ * factory does not read fee callbacks from the context. Earn custom fees remain
32422
+ * reserved until EarnKit fee support ships.
32423
+ *
32424
+ * When EarnKit supports developer fee hooks, this factory can wire AppKit context through.
32425
+ *
32426
+ * @param context - AppKit context reserved for future EarnKit wiring
32427
+ * @returns A new EarnKit instance
32428
+ *
32429
+ * @example
32430
+ * ```typescript
32431
+ * const earnKit = createEarnKit(context)
32432
+ * ```
32433
+ */
32434
+ const createEarnKit = () => new EarnKit();
32435
+
32436
+ /**
32437
+ * Register event handlers from a context actions map to a kit instance.
32438
+ *
32439
+ * This utility function registers event handlers stored in a context actions map
32440
+ * with a kit instance that supports event handling via an `on` method. It handles
32441
+ * wildcard handlers ('*') and prefixed action handlers, stripping the prefix
32442
+ * before registration.
32443
+ *
32444
+ * The function is designed to be reusable across different operation types
32445
+ * (bridge, swap, stake, etc.) by accepting a configurable prefix parameter.
32446
+ *
32447
+ * @param kit - The kit instance to register handlers with (must have an `on` method)
32448
+ * @param handlers - Map of action names to arrays of handler functions
32449
+ * @param prefix - Optional prefix to strip from action names (e.g., 'bridge.')
32450
+ *
32451
+ * @example
32452
+ * ```typescript
32453
+ * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
32454
+ * import { BridgeKit } from '@circle-fin/bridge-kit'
32455
+ *
32456
+ * const kit = new BridgeKit()
32457
+ * const handlers = {
32458
+ * '*': [(payload) => console.log('All actions:', payload)],
32459
+ * 'bridge.approve': [(payload) => console.log('Approved:', payload)],
32460
+ * 'bridge.burn': [(payload) => console.log('Burned:', payload)],
32461
+ * }
32462
+ *
32463
+ * registerActionHandlers(kit, handlers, 'bridge.')
32464
+ * ```
32465
+ *
32466
+ * @example
32467
+ * ```typescript
32468
+ * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
32469
+ * import { SwapKit } from '@circle-fin/swap-kit'
32470
+ *
32471
+ * const kit = new SwapKit()
32472
+ * const handlers = {
32473
+ * 'swap.initiate': [(payload) => console.log('Swap initiated:', payload)],
32474
+ * }
32475
+ *
32476
+ * registerActionHandlers(kit, handlers, 'swap.')
32477
+ * ```
32478
+ */
32479
+ const registerActionHandlers = (kit, handlers, prefix = '') => {
32480
+ for (const [action, handlerArray] of Object.entries(handlers)) {
32481
+ // Register all handlers for this action
32482
+ for (const handler of handlerArray) {
32483
+ if (action === '*') {
32484
+ // Wildcard handlers are registered as-is
32485
+ kit.on('*', handler);
32486
+ }
32487
+ else if (prefix && action.startsWith(prefix)) {
32488
+ // Remove prefix to get the actual kit action name
32489
+ const kitAction = action.split('.').at(1);
32490
+ if (kitAction) {
32491
+ kit.on(kitAction, handler);
32492
+ }
32493
+ }
32494
+ else if (!prefix) {
32495
+ // No prefix configured, register action as-is
32496
+ kit.on(action, handler);
32497
+ }
32498
+ // Actions that don't match the prefix are silently ignored
32499
+ }
32500
+ }
32501
+ };
32502
+
32503
+ /**
32504
+ * List of all supported token aliases for App Kit send operations.
32505
+ *
32506
+ * This array is used for runtime validation to check if a token string
32507
+ * is a known alias rather than a custom address.
32508
+ *
32509
+ * @remarks
32510
+ * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
32511
+ * with the TokenAlias type definition - TypeScript will enforce any changes.
32512
+ *
32513
+ * For swap operations, additional tokens (EURC, DAI, USDE, PYUSD) are supported
32514
+ * via SwapKit's SupportedToken type.
32515
+ */
32516
+ const TOKEN_ALIASES = ['USDC', 'USDT', 'NATIVE'];
32517
+ /**
32518
+ * Check if a token string is a known alias.
32519
+ *
32520
+ * This function performs case-sensitive matching against the list of known
32521
+ * token aliases. Known aliases take precedence over custom addresses in the
32522
+ * send operation logic.
32523
+ *
32524
+ * @param token - The token identifier to check
32525
+ * @returns True if the token is a known alias, false otherwise
32526
+ *
32527
+ * @example
32528
+ * ```typescript
32529
+ * import { isTokenAlias } from '@circle-fin/app-kit'
32530
+ *
32531
+ * console.log(isTokenAlias('USDC')) // true
32532
+ * console.log(isTokenAlias('NATIVE')) // true
32533
+ * console.log(isTokenAlias('0x6B175474E89094C44Da98b954EedeAC495271d0F')) // false
32534
+ * ```
32535
+ */
32536
+ function isTokenAlias(token) {
32537
+ return TOKEN_ALIASES.includes(token);
32538
+ }
32539
+ /**
32540
+ * Type guard to check if a string is a valid token address for a chain.
32541
+ *
32542
+ * This function verifies that a token string is:
32543
+ * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
32544
+ * 2. A valid address format for the specified chain
32545
+ *
32546
+ * Use this to narrow the type to `TokenAddress` in TypeScript.
32547
+ *
32548
+ * @param token - The token string to validate
32549
+ * @param chain - The chain definition for address validation
32550
+ * @returns True if the token is a valid address (not an alias)
32551
+ *
32552
+ * @example
32553
+ * ```typescript
32554
+ * import { isTokenAddress } from '@circle-fin/app-kit'
32555
+ * import { Ethereum } from '@core/chains'
32556
+ *
32557
+ * const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'
32558
+ * if (isTokenAddress(daiAddress, Ethereum)) {
32559
+ * // TypeScript knows daiAddress is TokenAddress here
32560
+ * console.log('Valid token address:', daiAddress)
32561
+ * }
32562
+ *
32563
+ * console.log(isTokenAddress('USDC', Ethereum)) // false - it's an alias
32564
+ * console.log(isTokenAddress('invalid', Ethereum)) // false - invalid format
32565
+ * ```
32566
+ */
32567
+ function isTokenAddress(token, chain) {
32568
+ return !isTokenAlias(token) && isValidAddressForChain(token, chain);
32569
+ }
32570
+ /**
32571
+ * Validate and classify a token identifier.
32572
+ *
32573
+ * This function determines whether a token string is:
32574
+ * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
32575
+ * 2. A valid token address for the given chain
32576
+ * 3. An invalid/unrecognized token identifier
32577
+ *
32578
+ * Known aliases always take precedence. If the token is not an alias,
32579
+ * it's validated as an address for the given chain.
32580
+ *
32581
+ * @param token - The token identifier to validate
32582
+ * @param chain - The chain definition for address validation
32583
+ * @returns An object containing validation results
32584
+ *
32585
+ * @example
32586
+ * ```typescript
32587
+ * import { validateToken } from '@circle-fin/app-kit'
32588
+ * import { Ethereum } from '@core/chains'
32589
+ *
32590
+ * // Known alias
32591
+ * const usdc = validateToken('USDC', Ethereum)
32592
+ * console.log(usdc) // { isAlias: true, isAddress: false, isValid: true }
32593
+ *
32594
+ * // Custom token address
32595
+ * const dai = validateToken('0x6B175474E89094C44Da98b954EedeAC495271d0F', Ethereum)
32596
+ * console.log(dai) // { isAlias: false, isAddress: true, isValid: true }
32597
+ *
32598
+ * // Invalid token
32599
+ * const invalid = validateToken('invalid', Ethereum)
32600
+ * console.log(invalid) // { isAlias: false, isAddress: false, isValid: false }
32601
+ * ```
32602
+ */
32603
+ function validateToken(token, chain) {
32604
+ // Check if it's a known alias first (aliases take precedence)
32605
+ const alias = isTokenAlias(token);
32606
+ if (alias) {
32607
+ return {
32608
+ isAlias: true,
32609
+ isAddress: false,
32610
+ isValid: true,
32611
+ };
32612
+ }
32613
+ // Not an alias - check if it's a valid address for the chain
32614
+ const address = isTokenAddress(token, chain);
32615
+ return {
32616
+ isAlias: false,
32617
+ isAddress: address,
32618
+ isValid: address,
32619
+ };
32620
+ }
32621
+
32622
+ /**
32623
+ * Estimate the costs and details of a cross-chain bridge transfer using the AppKit context.
32624
+ *
32625
+ * This function provides cost estimation for bridge operations within the AppKit
32626
+ * ecosystem. It delegates to the underlying BridgeKit infrastructure while maintaining
32627
+ * consistency with the AppKit patterns and context-based architecture.
32628
+ *
32629
+ * The function validates parameters, applies context-specific configurations,
32630
+ * and returns comprehensive estimation details including fees, gas costs, and
32631
+ * transaction parameters without executing the actual transfer.
32632
+ *
32633
+ * @param context - AppKit context containing fee calculation and configuration hooks
32634
+ * @param params - Bridge parameters containing source, destination, amount, and token
32635
+ * @returns Promise resolving to the estimate result with comprehensive fee and cost information
32636
+ * @throws If bridge parameters are invalid
32637
+ * @throws If the bridge route is not supported
32638
+ * @throws If estimation fails due to network or configuration issues
32639
+ *
32640
+ * @example
32641
+ * ```typescript
32642
+ * import { estimateBridge } from '@circle-fin/app-kit/bridge'
32643
+ * import { createContext } from '@circle-fin/app-kit/context'
32644
+ *
32645
+ * const context = createContext({
32646
+ * getFee: async (type, params) => '1000000', // 1 USDC fee
32647
+ * getFeeRecipient: async (type, info) => '0xfee-recipient-address'
32648
+ * })
32649
+ *
32650
+ * const estimate = await estimateBridge(context, {
32651
+ * from: sourceAdapter,
32652
+ * to: { adapter: destAdapter, chain: 'Polygon' },
32653
+ * amount: '100.50',
32654
+ * token: 'USDC'
32655
+ * })
32656
+ *
32657
+ * console.log('Estimated total fee:', estimate.fee)
32658
+ * console.log('Estimated gas cost:', estimate.gasEstimate)
32659
+ * ```
32660
+ */
32661
+ const estimateBridge = async (context, params) => {
32662
+ const kit = createBridgeKit(context);
32663
+ // Delegate to the BridgeKit for actual estimation
32664
+ return kit.estimate(params);
32665
+ };
32666
+
32667
+ const assertSendParamsSymbol = Symbol('assertSendParams');
32668
+ /**
32669
+ * Matches numeric amount strings with optional thousands separators and decimal part.
32670
+ *
32671
+ * Accepts both US (e.g. "1,234.56") and EU (e.g. "1.234,56") styles by allowing
32672
+ * either "," or "." as the grouping and decimal separators. Also matches integers
32673
+ * like "10" and simple decimals like "10.5" or "10,5".
32674
+ */
32675
+ const amountNumericStringWithSeparatorsRegex = /^\d+(?:[.,]\d{3})*(?:[.,]\d+)?$/;
32676
+ /**
32677
+ * Schema describing the minimal adapter surface required by the kit.
32678
+ *
32679
+ * The adapter must implement two functions:
32680
+ * - `prepare` — build the transaction or instruction payload.
32681
+ * - `waitForTransaction` — await finality/confirmation for a submitted transaction.
32682
+ *
32683
+ * @returns Zod schema validating an object with `prepare` and `waitForTransaction` functions.
32684
+ *
32685
+ * @example
32686
+ * ```typescript
32687
+ * import { adapterSchema } from '@circle-fin/app-kit'
32688
+ *
32689
+ * console.log(adapterSchema.safeParse(ethereumAdapter).success) // true
32690
+ * ```
32691
+ */
32692
+ const adapterSchema = zod.z.object({
32693
+ prepare: zod.z.function().returns(zod.z.any()),
32694
+ waitForTransaction: zod.z.function().returns(zod.z.any()),
32695
+ });
32696
+ /**
32697
+ * Schema for validating adapter contexts.
32698
+ * Ensure required fields are present and properly typed. An adapter context must include:
32699
+ * - A valid adapter with `prepare` and `waitForTransaction` methods.
32700
+ * - A valid chain identifier (string literal or chain object recognized by the kit).
32701
+ *
32702
+ * @returns Zod schema validating an adapter context object.
32703
+ * @throws ZodError If validation fails, with details about which properties failed.
32704
+ *
32705
+ * @example
32706
+ * ```typescript
32707
+ * import { adapterContextSchema } from '@circle-fin/app-kit'
32708
+ *
32709
+ * const result = adapterContextSchema.safeParse(context)
32710
+ * console.log(result.success) // true
32711
+ * ```
32712
+ */
32713
+ const adapterContextSchema$3 = zod.z.object({
32714
+ adapter: adapterSchema,
32715
+ /**
32716
+ * Note: We cast chainIdentifierSchema to 'never' here to work around a TypeScript
32717
+ * limitation (TS2589: Type instantiation is excessively deep and possibly infinite)
29535
32718
  * that can occur with complex Zod unions (e.g., string literals + enums).
29536
32719
  * This cast is safe in this context because runtime validation is correct,
29537
32720
  * and the type is enforced at the API boundary.
@@ -30058,14 +33241,15 @@ const estimateSwap = async (context, params) => {
30058
33241
  * Get chains supported by AppKit operations.
30059
33242
  *
30060
33243
  * This helper returns the set of chains that can be used for a given
30061
- * operation type (bridge, swap, or both). When no operation type is specified,
30062
- * it returns all chains known to support any AppKit operation.
33244
+ * operation type (bridge, swap, earn, unified balance, or all). When no
33245
+ * operation type is specified, it returns all chains known to support any
33246
+ * AppKit operation.
30063
33247
  *
30064
33248
  * The result list contains unique chains and is safe to pass directly into
30065
33249
  * higher-level flows such as chain pickers or routing logic.
30066
33250
  *
30067
33251
  * @param context - AppKit context containing fee configuration.
30068
- * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'unifiedBalance').
33252
+ * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance').
30069
33253
  * @param unifiedBalance - Persistent AppKitUnifiedBalance namespace instance.
30070
33254
  * @returns Array of unique chain definitions supporting the specified operation(s)
30071
33255
  *
@@ -30091,6 +33275,12 @@ const estimateSwap = async (context, params) => {
30091
33275
  * console.log('Chains supporting swap:', swapChains.map(c => c.name))
30092
33276
  * ```
30093
33277
  *
33278
+ * @example Get earn-specific chains
33279
+ * ```typescript
33280
+ * const earnChains = kit.getSupportedChains('earn')
33281
+ * console.log('Chains supporting earn:', earnChains.map(c => c.name))
33282
+ * ```
33283
+ *
30094
33284
  * @example Check if specific chain supports an operation
30095
33285
  * ```typescript
30096
33286
  * import { Ethereum } from '@core/chains'
@@ -30105,11 +33295,12 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
30105
33295
  if (operationType !== undefined &&
30106
33296
  operationType !== 'bridge' &&
30107
33297
  operationType !== 'swap' &&
33298
+ operationType !== 'earn' &&
30108
33299
  operationType !== 'unifiedBalance') {
30109
33300
  throw new KitError({
30110
33301
  ...InputError.VALIDATION_FAILED,
30111
33302
  recoverability: 'FATAL',
30112
- message: `Invalid operationType: "${String(operationType)}". Expected 'bridge', 'swap', or 'unifiedBalance'.`,
33303
+ message: `Invalid operationType: "${String(operationType)}". Expected 'bridge', 'swap', 'earn', or 'unifiedBalance'.`,
30113
33304
  });
30114
33305
  }
30115
33306
  // Return bridge-specific chains
@@ -30122,23 +33313,228 @@ const getSupportedChains$2 = (context, operationType, unifiedBalance) => {
30122
33313
  const swapKit = createSwapKit(context);
30123
33314
  return swapKit.getSupportedChains();
30124
33315
  }
33316
+ // Return earn-specific chains
33317
+ if (operationType === 'earn') {
33318
+ const earnKit = createEarnKit();
33319
+ return earnKit.getSupportedChains();
33320
+ }
30125
33321
  // Return unified balance chains
30126
33322
  if (operationType === 'unifiedBalance') {
30127
33323
  return unifiedBalance.getSupportedChains();
30128
33324
  }
30129
33325
  // No operation type specified - return union of all chains
33326
+ // Reuse kit instances here if provider constructors become expensive or stateful.
30130
33327
  const bridgeKit = createBridgeKit(context);
30131
33328
  const swapKit = createSwapKit(context);
33329
+ const earnKit = createEarnKit();
30132
33330
  const bridgeChains = bridgeKit.getSupportedChains();
30133
33331
  const swapChains = swapKit.getSupportedChains();
33332
+ const earnChains = earnKit.getSupportedChains();
30134
33333
  const ubChains = unifiedBalance.getSupportedChains();
30135
- const allChains = [...bridgeChains, ...swapChains, ...ubChains];
30136
- // Deduplicate by chain identifier
30137
- return Object.values(Object.fromEntries(allChains.map((chain) => [chain.chain, chain])));
33334
+ // Combine chains from all operations
33335
+ const allChains = [...bridgeChains, ...swapChains, ...earnChains, ...ubChains];
33336
+ // Deduplicate by chain identifier. Later duplicates override earlier ones.
33337
+ return [...new Map(allChains.map((chain) => [chain.chain, chain])).values()];
30138
33338
  };
30139
33339
 
33340
+ /**
33341
+ * Execute an earn deposit operation.
33342
+ *
33343
+ * @param context - AppKit context
33344
+ * @param params - Deposit parameters including source adapter, vault address, and amount
33345
+ * @returns Promise resolving to the earn deposit result
33346
+ * @throws If EarnKit validation fails, no provider is configured, or the deposit transaction fails
33347
+ *
33348
+ * @example
33349
+ * ```typescript
33350
+ * import { EarnChain } from '@circle-fin/app-kit'
33351
+ * import { createContext } from '@circle-fin/app-kit/context'
33352
+ * import { deposit } from '@circle-fin/app-kit/earn'
33353
+ *
33354
+ * const context = createContext()
33355
+ * const result = await deposit(context, {
33356
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33357
+ * vaultAddress: '0x...',
33358
+ * amount: '100.50',
33359
+ * })
33360
+ * ```
33361
+ */
33362
+ async function deposit$2(context, params) {
33363
+ return createEarnKit().deposit(params);
33364
+ }
33365
+ /**
33366
+ * Execute an earn withdrawal operation.
33367
+ *
33368
+ * @param context - AppKit context
33369
+ * @param params - Withdrawal parameters including source adapter, vault address, and amount
33370
+ * @returns Promise resolving to the earn withdrawal result
33371
+ * @throws If EarnKit validation fails, no provider is configured, or the withdrawal transaction fails
33372
+ *
33373
+ * @example
33374
+ * ```typescript
33375
+ * import { EarnChain } from '@circle-fin/app-kit'
33376
+ * import { createContext } from '@circle-fin/app-kit/context'
33377
+ * import { withdraw } from '@circle-fin/app-kit/earn'
33378
+ *
33379
+ * const context = createContext()
33380
+ * const result = await withdraw(context, {
33381
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33382
+ * vaultAddress: '0x...',
33383
+ * amount: '50.00',
33384
+ * })
33385
+ * ```
33386
+ */
33387
+ async function withdraw(context, params) {
33388
+ return createEarnKit().withdraw(params);
33389
+ }
33390
+ /**
33391
+ * Claim earn rewards.
33392
+ *
33393
+ * @param context - AppKit context
33394
+ * @param params - Claim rewards parameters including source adapter and vault address
33395
+ * @returns Promise resolving to the earn claim rewards result
33396
+ * @throws If EarnKit validation fails, no provider is configured, or the claim transaction fails
33397
+ *
33398
+ * @example
33399
+ * ```typescript
33400
+ * import { EarnChain } from '@circle-fin/app-kit'
33401
+ * import { createContext } from '@circle-fin/app-kit/context'
33402
+ * import { claimRewards } from '@circle-fin/app-kit/earn'
33403
+ *
33404
+ * const context = createContext()
33405
+ * const result = await claimRewards(context, {
33406
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33407
+ * vaultAddress: '0x...',
33408
+ * })
33409
+ * ```
33410
+ */
33411
+ async function claimRewards(context, params) {
33412
+ return createEarnKit().claimRewards(params);
33413
+ }
33414
+ /**
33415
+ * Fetch vault information.
33416
+ *
33417
+ * @param context - AppKit context
33418
+ * @param params - Vault query parameters. `vaults` selects the chain and vault address pairs to query
33419
+ * @returns Promise resolving to vault data and per-vault errors
33420
+ * @throws If EarnKit validation fails or no provider is configured
33421
+ *
33422
+ * @example
33423
+ * ```typescript
33424
+ * import { EarnChain } from '@circle-fin/app-kit'
33425
+ * import { createContext } from '@circle-fin/app-kit/context'
33426
+ * import { getVaults } from '@circle-fin/app-kit/earn'
33427
+ *
33428
+ * const context = createContext()
33429
+ * const result = await getVaults(context, {
33430
+ * vaults: [{ chain: EarnChain.Arc_Testnet, vaultAddress: '0x...' }],
33431
+ * })
33432
+ * ```
33433
+ */
33434
+ async function getVaults(context, params) {
33435
+ return createEarnKit().getVaults(params);
33436
+ }
33437
+ /**
33438
+ * Fetch a wallet position in a vault.
33439
+ *
33440
+ * @param context - AppKit context
33441
+ * @param params - Position parameters including source adapter and vault address
33442
+ * @returns Promise resolving to wallet position information
33443
+ * @throws If EarnKit validation fails or no provider is configured
33444
+ *
33445
+ * @example
33446
+ * ```typescript
33447
+ * import { EarnChain } from '@circle-fin/app-kit'
33448
+ * import { createContext } from '@circle-fin/app-kit/context'
33449
+ * import { getPosition } from '@circle-fin/app-kit/earn'
33450
+ *
33451
+ * const context = createContext()
33452
+ * const position = await getPosition(context, {
33453
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33454
+ * vaultAddress: '0x...',
33455
+ * })
33456
+ * ```
33457
+ */
33458
+ async function getPosition(context, params) {
33459
+ return createEarnKit().getPosition(params);
33460
+ }
33461
+ /**
33462
+ * Fetch a deposit quote.
33463
+ *
33464
+ * @param context - AppKit context
33465
+ * @param params - Deposit quote parameters including source adapter, vault address, and amount
33466
+ * @returns Promise resolving to deposit quote information
33467
+ * @throws If EarnKit validation fails or no provider is configured
33468
+ *
33469
+ * @example
33470
+ * ```typescript
33471
+ * import { EarnChain } from '@circle-fin/app-kit'
33472
+ * import { createContext } from '@circle-fin/app-kit/context'
33473
+ * import { getDepositQuote } from '@circle-fin/app-kit/earn'
33474
+ *
33475
+ * const context = createContext()
33476
+ * const quote = await getDepositQuote(context, {
33477
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33478
+ * vaultAddress: '0x...',
33479
+ * amount: '100.50',
33480
+ * })
33481
+ * ```
33482
+ */
33483
+ async function getDepositQuote(context, params) {
33484
+ return createEarnKit().getDepositQuote(params);
33485
+ }
33486
+ /**
33487
+ * Fetch a withdrawal quote.
33488
+ *
33489
+ * @param context - AppKit context
33490
+ * @param params - Withdrawal quote parameters including source adapter, vault address, and amount
33491
+ * @returns Promise resolving to withdrawal quote information
33492
+ * @throws If EarnKit validation fails or no provider is configured
33493
+ *
33494
+ * @example
33495
+ * ```typescript
33496
+ * import { EarnChain } from '@circle-fin/app-kit'
33497
+ * import { createContext } from '@circle-fin/app-kit/context'
33498
+ * import { getWithdrawalQuote } from '@circle-fin/app-kit/earn'
33499
+ *
33500
+ * const context = createContext()
33501
+ * const quote = await getWithdrawalQuote(context, {
33502
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33503
+ * vaultAddress: '0x...',
33504
+ * amount: '50.00',
33505
+ * })
33506
+ * ```
33507
+ */
33508
+ async function getWithdrawalQuote(context, params) {
33509
+ return createEarnKit().getWithdrawalQuote(params);
33510
+ }
33511
+ /**
33512
+ * Fetch a claim rewards quote.
33513
+ *
33514
+ * @param context - AppKit context
33515
+ * @param params - Claim rewards quote parameters including source adapter and vault address
33516
+ * @returns Promise resolving to claim rewards quote information
33517
+ * @throws If EarnKit validation fails or no provider is configured
33518
+ *
33519
+ * @example
33520
+ * ```typescript
33521
+ * import { EarnChain } from '@circle-fin/app-kit'
33522
+ * import { createContext } from '@circle-fin/app-kit/context'
33523
+ * import { getClaimRewardsQuote } from '@circle-fin/app-kit/earn'
33524
+ *
33525
+ * const context = createContext()
33526
+ * const quote = await getClaimRewardsQuote(context, {
33527
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
33528
+ * vaultAddress: '0x...',
33529
+ * })
33530
+ * ```
33531
+ */
33532
+ async function getClaimRewardsQuote(context, params) {
33533
+ return createEarnKit().getClaimRewardsQuote(params);
33534
+ }
33535
+
30140
33536
  var name = "@circle-fin/unified-balance-kit";
30141
- var version = "1.1.1";
33537
+ var version = "1.1.2";
30142
33538
  var pkg = {
30143
33539
  name: name,
30144
33540
  version: version};
@@ -37057,7 +40453,7 @@ class AppKitUnifiedBalance {
37057
40453
  }
37058
40454
 
37059
40455
  /**
37060
- * A high-level SDK for stablecoin operations, including bridging and swapping.
40456
+ * A high-level SDK for stablecoin operations, including bridging, swapping, and earn.
37061
40457
  *
37062
40458
  * The AppKit provides a unified interface for various stablecoin operations
37063
40459
  * with strongly typed parameters and comprehensive fee estimation capabilities.
@@ -37068,6 +40464,7 @@ class AppKitUnifiedBalance {
37068
40464
  * - Strongly typed fee estimation with operation-specific parameters
37069
40465
  * - Cross-chain USDC bridging through CCTPv2
37070
40466
  * - Same-chain token swapping between USDC, USDT, and native tokens
40467
+ * - Earn vault deposits, withdrawals, rewards, and quote queries
37071
40468
  * - Token sending operations
37072
40469
  * - Comprehensive error handling and validation
37073
40470
  * - Full TypeScript support with IntelliSense
@@ -37100,6 +40497,13 @@ class AppKitUnifiedBalance {
37100
40497
  * amountIn: '50.00'
37101
40498
  * })
37102
40499
  *
40500
+ * // Query an earn vault
40501
+ * const quote = await kit.earn.getDepositQuote({
40502
+ * from: { adapter, chain: 'Arc_Testnet' },
40503
+ * vaultAddress: '0x...',
40504
+ * amount: '100.50',
40505
+ * })
40506
+ *
37103
40507
  * // Query unified balances across chains
37104
40508
  * const balances = await kit.unifiedBalance.getBalances({
37105
40509
  * token: 'USDC',
@@ -37114,6 +40518,25 @@ class AppKitUnifiedBalance {
37114
40518
  */
37115
40519
  class AppKit {
37116
40520
  context;
40521
+ /**
40522
+ * Earn operations exposed under `kit.earn`.
40523
+ *
40524
+ * @see {@link AppKitEarnOperations}
40525
+ *
40526
+ * @example
40527
+ * ```typescript
40528
+ * import { AppKit, EarnChain } from '@circle-fin/app-kit'
40529
+ *
40530
+ * const kit = new AppKit()
40531
+ *
40532
+ * const result = await kit.earn.deposit({
40533
+ * from: { adapter, chain: EarnChain.Arc_Testnet },
40534
+ * vaultAddress: '0x...',
40535
+ * amount: '100.50',
40536
+ * })
40537
+ * ```
40538
+ */
40539
+ earn;
37117
40540
  /**
37118
40541
  * Namespace object for unified balance operations (deposit, spend,
37119
40542
  * balance queries, delegation, fund removal).
@@ -37167,6 +40590,16 @@ class AppKit {
37167
40590
  disableErrorReporting: config.disableErrorReporting,
37168
40591
  }),
37169
40592
  });
40593
+ this.earn = {
40594
+ deposit: async (params) => deposit$2(this.context, params),
40595
+ withdraw: async (params) => withdraw(this.context, params),
40596
+ claimRewards: async (params) => claimRewards(this.context, params),
40597
+ getVaults: async (params) => getVaults(this.context, params),
40598
+ getPosition: async (params) => getPosition(this.context, params),
40599
+ getDepositQuote: async (params) => getDepositQuote(this.context, params),
40600
+ getWithdrawalQuote: async (params) => getWithdrawalQuote(this.context, params),
40601
+ getClaimRewardsQuote: async (params) => getClaimRewardsQuote(this.context, params),
40602
+ };
37170
40603
  }
37171
40604
  /**
37172
40605
  * Execute a cross-chain USDC bridge transfer.
@@ -37424,9 +40857,9 @@ class AppKit {
37424
40857
  *
37425
40858
  * Returns blockchain networks that support specific stablecoin operations.
37426
40859
  * When no operation type is specified, returns all chains supporting any
37427
- * operation (bridge, swap, or unified balance).
40860
+ * operation (bridge, swap, earn, or unified balance).
37428
40861
  *
37429
- * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'unifiedBalance')
40862
+ * @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance')
37430
40863
  * @returns Array of unique chain definitions supporting the specified operation(s)
37431
40864
  *
37432
40865
  * @example Get all supported chains
@@ -37489,6 +40922,8 @@ class AppKit {
37489
40922
 
37490
40923
  exports.AppKit = AppKit;
37491
40924
  exports.BalanceError = BalanceError;
40925
+ exports.EarnError = EarnError;
40926
+ exports.EarnKit = EarnKit;
37492
40927
  exports.InputError = InputError;
37493
40928
  exports.KitError = KitError;
37494
40929
  exports.NetworkError = NetworkError;
@@ -37497,9 +40932,26 @@ exports.RateLimitError = RateLimitError;
37497
40932
  exports.RpcError = RpcError;
37498
40933
  exports.ServiceError = ServiceError;
37499
40934
  exports.TOKEN_ALIASES = TOKEN_ALIASES;
40935
+ exports.claimRewardsParamsSchema = claimRewardsParamsSchema;
40936
+ exports.createEarnKitContext = createEarnKitContext;
40937
+ exports.depositParamsSchema = depositParamsSchema$1;
40938
+ exports.earnClaimRewards = claimRewards$1;
40939
+ exports.earnDeposit = deposit$3;
40940
+ exports.earnGetClaimRewardsQuote = getClaimRewardsQuote$1;
40941
+ exports.earnGetDepositQuote = getDepositQuote$1;
40942
+ exports.earnGetPosition = getPosition$1;
40943
+ exports.earnGetSupportedChains = getSupportedChains$3;
40944
+ exports.earnGetVaults = getVaults$1;
40945
+ exports.earnGetWithdrawalQuote = getWithdrawalQuote$1;
40946
+ exports.earnWithdraw = withdraw$1;
40947
+ exports.getClaimRewardsQuoteParamsSchema = getClaimRewardsQuoteParamsSchema;
40948
+ exports.getDepositQuoteParamsSchema = getDepositQuoteParamsSchema;
37500
40949
  exports.getErrorCode = getErrorCode;
37501
40950
  exports.getErrorMessage = getErrorMessage;
40951
+ exports.getPositionParamsSchema = getPositionParamsSchema;
37502
40952
  exports.getTokenDecimals = getTokenDecimals;
40953
+ exports.getVaultsParamsSchema = getVaultsParamsSchema;
40954
+ exports.getWithdrawalQuoteParamsSchema = getWithdrawalQuoteParamsSchema;
37503
40955
  exports.isBalanceError = isBalanceError;
37504
40956
  exports.isFatalError = isFatalError;
37505
40957
  exports.isInputError = isInputError;
@@ -37515,4 +40967,5 @@ exports.isTokenAlias = isTokenAlias;
37515
40967
  exports.isUserCancellationError = isUserCancellationError;
37516
40968
  exports.setExternalPrefix = setExternalPrefix;
37517
40969
  exports.validateToken = validateToken;
40970
+ exports.withdrawParamsSchema = withdrawParamsSchema;
37518
40971
  //# sourceMappingURL=index.cjs.map