@circle-fin/app-kit 1.9.0 → 1.10.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.
@@ -22,9 +22,9 @@ var zod = require('zod');
22
22
  var pino = require('pino');
23
23
  var units = require('@ethersproject/units');
24
24
  var bytes = require('@ethersproject/bytes');
25
+ require('@ethersproject/abi');
25
26
  var address = require('@ethersproject/address');
26
27
  var bs58 = require('bs58');
27
- require('@ethersproject/abi');
28
28
  var web3_js = require('@solana/web3.js');
29
29
  require('bn.js');
30
30
  require('@coral-xyz/anchor');
@@ -3101,7 +3101,10 @@ var EarnChain;
3101
3101
  contracts: {
3102
3102
  v1: {
3103
3103
  wallet: GATEWAY_WALLET_EVM_TESTNET,
3104
- minter: GATEWAY_MINTER_EVM_TESTNET
3104
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3105
+ // DepositForHandler the GenericExecutor calls to run a fast cross-chain
3106
+ // deposit into the GatewayWallet above.
3107
+ depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
3105
3108
  }
3106
3109
  },
3107
3110
  forwarderSupported: {
@@ -6219,7 +6222,10 @@ var Chains = {
6219
6222
  minter: zod.z.string({
6220
6223
  required_error: 'Gateway minter address is required. Please provide a valid contract address.',
6221
6224
  invalid_type_error: 'Gateway minter address must be a string.'
6222
- }).min(1, 'Gateway minter address cannot be empty.')
6225
+ }).min(1, 'Gateway minter address cannot be empty.'),
6226
+ depositForHandler: zod.z.string({
6227
+ invalid_type_error: 'Gateway depositForHandler address must be a string.'
6228
+ }).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
6223
6229
  }).strict() // Reject any additional properties not defined in the schema
6224
6230
  ;
6225
6231
  /**
@@ -6741,21 +6747,31 @@ const swapTokenEnumSchema = zod.z.enum([
6741
6747
  * returning the appropriate address based on the requested contract type.
6742
6748
  *
6743
6749
  * @param chain - The chain definition to resolve the contract address for
6744
- * @param contractType - The type of contract address to resolve ('tokenMessenger' or 'messageTransmitter')
6750
+ * @param contractType - The type of contract address to resolve ('tokenMessenger', 'messageTransmitter', or 'tokenMessengerWithFees')
6745
6751
  * @returns The contract address for the specified contract type
6746
6752
  * @throws Error when chain does not support CCTP v2 or has unsupported contract configuration
6753
+ * @throws Error when 'tokenMessengerWithFees' is requested but not configured on the chain
6747
6754
  */ const resolveCCTPV2ContractAddress = (chain, contractType)=>{
6748
6755
  // Handle custom bridge contract for tokenMessenger (burn transaction)
6749
- if (hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
6756
+ if (contractType === 'tokenMessenger' && hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
6750
6757
  return chain.kitContracts.bridge;
6751
6758
  }
6752
6759
  // At this point we know CCTP v2 is supported, so contracts exist
6753
6760
  const cctpConfig = chain.cctp;
6754
6761
  const contracts = cctpConfig.contracts.v2;
6762
+ // The `TokenMessengerWithFees` wrapper (prepaid FORWARD path) is an optional
6763
+ // deployment carried alongside both split and merged configurations.
6764
+ if (contractType === 'tokenMessengerWithFees') {
6765
+ const wrapper = contracts.tokenMessengerWithFees;
6766
+ if (wrapper === undefined || wrapper === '') {
6767
+ throw new Error(`TokenMessengerWithFees is not configured on chain ${chain.name}. The prepaid FORWARD path is unavailable on this chain.`);
6768
+ }
6769
+ return wrapper;
6770
+ }
6755
6771
  // Handle different contract types with explicit type checking
6756
6772
  switch(contracts.type){
6757
6773
  case 'split':
6758
- return contracts.tokenMessenger ;
6774
+ return contractType === 'tokenMessenger' ? contracts.tokenMessenger : contracts.messageTransmitter;
6759
6775
  case 'merged':
6760
6776
  return contracts.contract;
6761
6777
  default:
@@ -9610,7 +9626,7 @@ function resolveOptions(options) {
9610
9626
  }
9611
9627
 
9612
9628
  var name$2 = "@circle-fin/bridge-kit";
9613
- var version$3 = "1.12.0";
9629
+ var version$3 = "1.12.1";
9614
9630
  var pkg$3 = {
9615
9631
  name: name$2,
9616
9632
  version: version$3};
@@ -12114,6 +12130,144 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
12114
12130
  return false;
12115
12131
  };
12116
12132
 
12133
+ /**
12134
+ * The zero address, denoting a native-currency fee in a signed quote.
12135
+ */ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
12136
+ /**
12137
+ * Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
12138
+ *
12139
+ * Determines the native `msg.value` and the ERC-20 approvals required, honouring
12140
+ * the quote's `feeToken`:
12141
+ * - Native fee (`feeToken` is the zero address): attach exactly `feeTotalAmount`
12142
+ * as `msg.value`; approve only the burn amount.
12143
+ * - ERC-20 fee equal to the burn token (both USDC — the `isBurnTokenFee` case):
12144
+ * approve a single combined `amount + feeTotalAmount` and skip the redundant
12145
+ * second approval.
12146
+ * - ERC-20 fee different from the burn token: approve the burn amount and the fee
12147
+ * amount separately.
12148
+ *
12149
+ * This encodes only balance/allowance intent; it does not fetch balances. The
12150
+ * caller is responsible for a balance preflight against the fresh quote.
12151
+ *
12152
+ * @param params - The fee token, burn token, burn amount, and total fee amount.
12153
+ * @returns The resolved fee payment plan.
12154
+ * @throws KitError if `amount` or `feeTotalAmount` is negative.
12155
+ *
12156
+ * @example
12157
+ * ```typescript
12158
+ * // Native fee
12159
+ * resolveFeePayment({
12160
+ * feeToken: '0x0000000000000000000000000000000000000000',
12161
+ * burnToken: '0xUSDC...',
12162
+ * amount: 1_000_000n,
12163
+ * feeTotalAmount: 3_500_000n,
12164
+ * })
12165
+ * // → { isNativeFee: true, isBurnTokenFee: false, nativeValue: 3_500_000n,
12166
+ * // approvals: [{ token: '0xUSDC...', amount: 1_000_000n }] }
12167
+ * ```
12168
+ */ const resolveFeePayment = (params)=>{
12169
+ const { feeToken, burnToken, amount, feeTotalAmount } = params;
12170
+ if (typeof amount !== 'bigint' || amount < 0n) {
12171
+ throw createValidationFailedError$1('amount', amount, 'Must be a non-negative bigint');
12172
+ }
12173
+ if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
12174
+ throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
12175
+ }
12176
+ const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS;
12177
+ const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
12178
+ if (isNativeFee) {
12179
+ return {
12180
+ isNativeFee: true,
12181
+ isBurnTokenFee: false,
12182
+ nativeValue: feeTotalAmount,
12183
+ approvals: [
12184
+ {
12185
+ token: burnToken,
12186
+ amount
12187
+ }
12188
+ ]
12189
+ };
12190
+ }
12191
+ if (isBurnTokenFee) {
12192
+ // Fee and burn draw on the same token — a single combined approval covers
12193
+ // both; the redundant second approval is skipped.
12194
+ return {
12195
+ isNativeFee: false,
12196
+ isBurnTokenFee: true,
12197
+ nativeValue: 0n,
12198
+ approvals: [
12199
+ {
12200
+ token: burnToken,
12201
+ amount: amount + feeTotalAmount
12202
+ }
12203
+ ]
12204
+ };
12205
+ }
12206
+ return {
12207
+ isNativeFee: false,
12208
+ isBurnTokenFee: false,
12209
+ nativeValue: 0n,
12210
+ approvals: [
12211
+ {
12212
+ token: burnToken,
12213
+ amount
12214
+ },
12215
+ {
12216
+ token: feeToken,
12217
+ amount: feeTotalAmount
12218
+ }
12219
+ ]
12220
+ };
12221
+ };
12222
+
12223
+ /**
12224
+ * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
12225
+ * hookData must start with.
12226
+ */ const CCTP_FORWARD_MAGIC_HEX = Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
12227
+ /**
12228
+ * Determine whether a hookData blob begins with the `cctp-forward` envelope.
12229
+ *
12230
+ * The prepaid FORWARD path requires the GenericExecutor blob to be wrapped in a
12231
+ * `cctp-forward` frame; without it the wrapper reverts `ForwardFeeWithoutHook`.
12232
+ *
12233
+ * @param hookData - The 0x-prefixed hookData hex string.
12234
+ * @returns True when the hookData starts with the `cctp-forward` magic.
12235
+ *
12236
+ * @example
12237
+ * ```typescript
12238
+ * hasForwardHook('0x636374702d666f7277617264...') // true
12239
+ * hasForwardHook('0xdeadbeef') // false
12240
+ * ```
12241
+ */ const hasForwardHook = (hookData)=>{
12242
+ if (typeof hookData !== 'string') {
12243
+ return false;
12244
+ }
12245
+ const normalized = (hookData.startsWith('0x') ? hookData.slice(2) : hookData).toLowerCase();
12246
+ return normalized.startsWith(CCTP_FORWARD_MAGIC_HEX);
12247
+ };
12248
+ /**
12249
+ * Assert that a hookData blob is forward-friendly for the prepaid FORWARD path.
12250
+ *
12251
+ * The prepaid FORWARD path always requests a FORWARD fee item, so the wrapper
12252
+ * requires the hookData to start with a `cctp-forward` frame. Validating this
12253
+ * before the burn surfaces the guaranteed `ForwardFeeWithoutHook` revert as a
12254
+ * typed input error instead of an on-chain failure.
12255
+ *
12256
+ * @param hookData - The 0x-prefixed hookData hex string.
12257
+ * @throws KitError (`INPUT_VALIDATION_FAILED`) if the hookData is missing or lacks
12258
+ * the `cctp-forward` frame.
12259
+ *
12260
+ * @example
12261
+ * ```typescript
12262
+ * assertForwardHookData(geForwardHookData) // ok
12263
+ * assertForwardHookData('0xdeadbeef') // throws — would revert ForwardFeeWithoutHook
12264
+ * ```
12265
+ */ const assertForwardHookData = (hookData)=>{
12266
+ if (!hasForwardHook(hookData)) {
12267
+ throw createValidationFailedError$1('hookData', hookData, 'Prepaid FORWARD burns require a cctp-forward-wrapped hookData; without it the TokenMessengerWithFees wrapper reverts ForwardFeeWithoutHook');
12268
+ }
12269
+ };
12270
+
12117
12271
  /**
12118
12272
  * Type guard to validate the forwardFee object structure.
12119
12273
  *
@@ -13045,6 +13199,109 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
13045
13199
  }
13046
13200
  }
13047
13201
 
13202
+ /**
13203
+ * Assert that `params` is a well-formed {@link BurnWithFeesParams} object.
13204
+ *
13205
+ * Validates the full public-boundary input before any field destructuring,
13206
+ * `BigInt()` coercion, or adapter preparation runs, so malformed JS-caller
13207
+ * inputs always produce typed `KitError` validation failures.
13208
+ *
13209
+ * Checks performed (in order):
13210
+ * - `params` must be a non-null plain object
13211
+ * - `source` — valid CCTP v2 wallet context (via `assertCCTPv2WalletContext`)
13212
+ * - `destinationChain` — present and supports CCTP v2
13213
+ * - source and destination chains must both be testnet or both mainnet
13214
+ * - source and destination chains must differ
13215
+ * - `executor` — non-empty string
13216
+ * - `amount` — bigint or non-empty string coercible to bigint
13217
+ * - `feeTotalAmount` — bigint or non-empty string coercible to bigint
13218
+ * - `feeToken` — valid EVM address (`0x` + 40 hex chars)
13219
+ * - `claim.signedQuote` — valid `0x`-prefixed hex string
13220
+ * - `claim.refundAddress` — valid EVM address
13221
+ * - `hookData` — valid `0x`-prefixed hex string when present
13222
+ *
13223
+ * @param params - The value to validate.
13224
+ * @throws {KitError} If any field is missing or invalid.
13225
+ *
13226
+ * @example
13227
+ * ```typescript
13228
+ * assertBurnWithFeesParams(params)
13229
+ * // params is now typed as BurnWithFeesParams and safe to use
13230
+ * const { source, destinationChain, amount } = params
13231
+ * ```
13232
+ */ function assertBurnWithFeesParams(params) {
13233
+ if (params === null || typeof params !== 'object' || Array.isArray(params)) {
13234
+ throw createValidationFailedError$1('params', params, 'Must be a non-null plain object');
13235
+ }
13236
+ const p = params;
13237
+ // Source wallet context
13238
+ assertCCTPv2WalletContext(p['source']);
13239
+ const source = p['source'];
13240
+ // destinationChain
13241
+ const destinationChain = p['destinationChain'];
13242
+ if (destinationChain === null || destinationChain === undefined || typeof destinationChain !== 'object') {
13243
+ throw createValidationFailedError$1('destinationChain', destinationChain, 'Must be a chain definition object');
13244
+ }
13245
+ if (!isCCTPV2Supported(destinationChain)) {
13246
+ throw createValidationFailedError$1('destinationChain', destinationChain, 'destinationChain must support CCTP v2');
13247
+ }
13248
+ const dest = destinationChain;
13249
+ // Testnet / mainnet mismatch
13250
+ if (source.chain.isTestnet !== dest.isTestnet) {
13251
+ throw createNetworkMismatchError(source.chain, dest);
13252
+ }
13253
+ // Same-chain guard
13254
+ if (source.chain.name === dest.name) {
13255
+ throw createUnsupportedRouteError(source.chain.name, dest.name);
13256
+ }
13257
+ // executor
13258
+ const executor = p['executor'];
13259
+ if (typeof executor !== 'string' || executor === '') {
13260
+ throw createValidationFailedError$1('executor', executor, 'A GenericExecutor address is required (used for both mintRecipient and destinationCaller)');
13261
+ }
13262
+ // amount
13263
+ const rawAmount = p['amount'];
13264
+ if (typeof rawAmount !== 'bigint' && typeof rawAmount !== 'string' || rawAmount === '') {
13265
+ throw createValidationFailedError$1('amount', rawAmount, 'Must be a bigint or a numeric string');
13266
+ }
13267
+ try {
13268
+ BigInt(rawAmount);
13269
+ } catch {
13270
+ throw createValidationFailedError$1('amount', rawAmount, 'Must be a valid integer value coercible to bigint');
13271
+ }
13272
+ // feeTotalAmount
13273
+ const rawFee = p['feeTotalAmount'];
13274
+ if (typeof rawFee !== 'bigint' && typeof rawFee !== 'string' || rawFee === '') {
13275
+ throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a bigint or a numeric string');
13276
+ }
13277
+ try {
13278
+ BigInt(rawFee);
13279
+ } catch {
13280
+ throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
13281
+ }
13282
+ // feeToken
13283
+ if (!evmAddressSchema.safeParse(p['feeToken']).success) {
13284
+ throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
13285
+ }
13286
+ // claim
13287
+ const rawClaim = p['claim'];
13288
+ if (rawClaim === null || typeof rawClaim !== 'object' || Array.isArray(rawClaim)) {
13289
+ throw createValidationFailedError$1('claim', rawClaim, 'Must be an object with signedQuote and refundAddress');
13290
+ }
13291
+ const claim = rawClaim;
13292
+ if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
13293
+ throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
13294
+ }
13295
+ if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
13296
+ throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
13297
+ }
13298
+ // hookData (optional)
13299
+ const hookData = p['hookData'];
13300
+ if (hookData !== undefined && hookData !== '' && !hexStringSchema.safeParse(hookData).success) {
13301
+ throw createValidationFailedError$1('hookData', hookData, 'Must be a valid hex string starting with 0x');
13302
+ }
13303
+ }
13304
+
13048
13305
  /**
13049
13306
  * CCTP bridge step names that can occur in the bridging flow.
13050
13307
  *
@@ -14253,10 +14510,15 @@ const mockAttestationMessage = {
14253
14510
  const burnCallData = burnRequest.getCallData();
14254
14511
  // batchExecute may throw before submission (wallet declined) but never
14255
14512
  // after — post-submission errors are returned as empty receipts.
14513
+ // The sender is threaded for adapters whose execution is routed through a
14514
+ // signing strategy (which has no wallet account to read it from); the
14515
+ // wallet-client path ignores it.
14256
14516
  const batchResult = await adapter.batchExecute([
14257
14517
  approveCallData,
14258
14518
  burnCallData
14259
- ], chain);
14519
+ ], chain, {
14520
+ fromAddress: params.source.address
14521
+ });
14260
14522
  const approveReceipt = batchResult.receipts[0];
14261
14523
  const burnReceipt = batchResult.receipts[1];
14262
14524
  const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
@@ -14399,7 +14661,7 @@ const mockAttestationMessage = {
14399
14661
  return step;
14400
14662
  }
14401
14663
 
14402
- var version$2 = "1.9.0";
14664
+ var version$2 = "1.10.0";
14403
14665
  var pkg$2 = {
14404
14666
  version: version$2};
14405
14667
 
@@ -15554,7 +15816,7 @@ function assertCCTPV2Config(config) {
15554
15816
  throw new Error(`Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
15555
15817
  }
15556
15818
  // Resolve spender address with proper error handling
15557
- const spenderAddress = resolveCCTPV2ContractAddress(chain);
15819
+ const spenderAddress = resolveCCTPV2ContractAddress(chain, 'tokenMessenger');
15558
15820
  // Prepare action parameters
15559
15821
  const actionParams = {
15560
15822
  amount: BigInt(amount),
@@ -16034,6 +16296,106 @@ function assertCCTPV2Config(config) {
16034
16296
  return await source.adapter.prepareAction('cctp.v2.depositForBurn', actionParams, resolvedContext);
16035
16297
  }
16036
16298
  /**
16299
+ * Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
16300
+ *
16301
+ * Builds the source-chain `depositForBurnWithHookAndFees` call for the
16302
+ * GenericExecutor FORWARD path: fees are collected up front on the source chain
16303
+ * against a signed quote, `mintRecipient` and `destinationCaller` are both set to
16304
+ * the GenericExecutor, and the GE `hookData` is passed through unchanged.
16305
+ *
16306
+ * This is the low-level on-chain primitive behind the UBK `fastCrossChainDeposit`
16307
+ * and Bridge Kit `bridge({ deposit })` flows. The `hookData` and signed-quote
16308
+ * `claim` are produced elsewhere and passed in here:
16309
+ * - `hookData`: `buildForwardingHookDataWithPayload(version,
16310
+ * buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
16311
+ * - `claim.signedQuote` / `feeToken` / `feeTotalAmount`: from `fetchFeeQuote`
16312
+ * (`@circle-fin/provider-fee-v1`), whose FORWARD item must be bound to the
16313
+ * SAME `hookData` and executor `destinationCaller` used here.
16314
+ *
16315
+ * The returned approvals and burn are NOT executed — the caller executes the
16316
+ * approvals first (in order) and then the burn. The fee payment channel matches
16317
+ * the quote's `feeToken`:
16318
+ * - Native fee: exactly `feeTotalAmount` is attached as the burn's `msg.value`;
16319
+ * only the burn amount is approved.
16320
+ * - USDC fee (same token as the burn): a single combined `amount + feeTotalAmount`
16321
+ * approval covers both; the redundant second approval is skipped.
16322
+ *
16323
+ * @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
16324
+ * @param params - The burn amount, executor, hookData, signed-quote claim, and fee.
16325
+ * @returns The prepared approvals, the prepared burn, and the resolved fee plan.
16326
+ * @throws {KitError} If the wallet context is invalid, `destinationChain` does not
16327
+ * support CCTP v2, the executor is missing, `amount` or `feeTotalAmount` is not
16328
+ * a bigint or a numeric string coercible to bigint, the hookData lacks a
16329
+ * `cctp-forward` frame (guaranteed `ForwardFeeWithoutHook`), or the operation
16330
+ * context cannot be resolved.
16331
+ *
16332
+ * @example
16333
+ * ```typescript
16334
+ * const { approvals, burn } = await provider.burnWithFees({
16335
+ * source,
16336
+ * destinationChain: Arc,
16337
+ * amount: 1_000_000n,
16338
+ * executor: genericExecutorAddress,
16339
+ * hookData: geForwardHookData,
16340
+ * claim: { signedQuote: '0x01...', refundAddress: userAddress },
16341
+ * feeToken: '0x0000000000000000000000000000000000000000', // native
16342
+ * feeTotalAmount: 3_500_000n,
16343
+ * })
16344
+ * for (const approval of approvals) await approval.execute()
16345
+ * const txHash = await burn.execute()
16346
+ * ```
16347
+ */ async burnWithFees(params) {
16348
+ assertBurnWithFeesParams(params);
16349
+ const { source, destinationChain, executor, hookData, claim, feeToken } = params;
16350
+ const amount = BigInt(params.amount);
16351
+ const feeTotalAmount = BigInt(params.feeTotalAmount);
16352
+ // Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
16353
+ // so the hookData must carry a cctp-forward frame; otherwise the wrapper
16354
+ // reverts ForwardFeeWithoutHook. Surface it as a typed input error up front.
16355
+ assertForwardHookData(hookData);
16356
+ const burnToken = source.chain.usdcAddress;
16357
+ const feePayment = resolveFeePayment({
16358
+ feeToken,
16359
+ burnToken,
16360
+ amount,
16361
+ feeTotalAmount
16362
+ });
16363
+ // Resolve operation context from the source wallet context.
16364
+ const operationContext = this.extractOperationContext(source);
16365
+ let resolvedContext;
16366
+ try {
16367
+ resolvedContext = await resolveOperationContext(source.adapter, operationContext);
16368
+ } catch (error) {
16369
+ throw createValidationFailedError$1('source.adapter', undefined, `Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
16370
+ }
16371
+ const context = resolvedContext;
16372
+ const wrapperAddress = resolveCCTPV2ContractAddress(source.chain, 'tokenMessengerWithFees');
16373
+ // Build the ERC-20 approvals to the wrapper (burn token, plus a distinct fee
16374
+ // token only when the fee is not paid in the burn token).
16375
+ const approvals = await Promise.all(feePayment.approvals.map(async (approval)=>source.adapter.prepareAction('token.approve', {
16376
+ tokenAddress: approval.token,
16377
+ delegate: wrapperAddress,
16378
+ amount: approval.amount
16379
+ }, context)));
16380
+ // Build the burn: mintRecipient AND destinationCaller are both the executor.
16381
+ const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
16382
+ fromChain: source.chain,
16383
+ toChain: destinationChain,
16384
+ amount,
16385
+ mintRecipient: executor,
16386
+ destinationCaller: executor,
16387
+ hookData,
16388
+ claim,
16389
+ feeToken,
16390
+ feeTotalAmount
16391
+ }, context);
16392
+ return {
16393
+ approvals,
16394
+ burn,
16395
+ feePayment
16396
+ };
16397
+ }
16398
+ /**
16037
16399
  * Waits for a transaction to be mined and confirmed on the blockchain.
16038
16400
  *
16039
16401
  * This method should block until the transaction is confirmed on the blockchain.
@@ -16913,7 +17275,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16913
17275
  };
16914
17276
 
16915
17277
  var name$1 = "@circle-fin/swap-kit";
16916
- var version$1 = "1.3.2";
17278
+ var version$1 = "1.4.0";
16917
17279
  var pkg$1 = {
16918
17280
  name: name$1,
16919
17281
  version: version$1};
@@ -16978,7 +17340,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
16978
17340
  }).min(1, 'kitKey must be a non-empty string').optional(),
16979
17341
  provider: zod.z.string({
16980
17342
  invalid_type_error: 'provider must be a string'
16981
- }).min(1, 'provider must be a non-empty string').optional()
17343
+ }).min(1, 'provider must be a non-empty string').optional(),
17344
+ batchTransactions: zod.z.boolean({
17345
+ invalid_type_error: 'batchTransactions must be a boolean'
17346
+ }).optional()
16982
17347
  });
16983
17348
  /**
16984
17349
  * Zod schema for adapter context.
@@ -17417,7 +17782,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17417
17782
  /**
17418
17783
  * Circle Stablecoin Service API Key.
17419
17784
  * Must be a valid API key format.
17420
- */ apiKey: apiKeySchema
17785
+ */ apiKey: apiKeySchema.optional()
17421
17786
  }).superRefine(requireCrossChainQuoteToAddress);
17422
17787
  /**
17423
17788
  * Zod schema for validating CreateSwapRequest parameters.
@@ -17475,7 +17840,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17475
17840
  /**
17476
17841
  * Circle Stablecoin Service API Key.
17477
17842
  * Must be a valid API key format.
17478
- */ apiKey: apiKeySchema
17843
+ */ apiKey: apiKeySchema.optional()
17479
17844
  });
17480
17845
  /**
17481
17846
  * Zod schema for validating GetSwapStatusResponse data.
@@ -17511,7 +17876,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17511
17876
  toChain: zod.z.string({
17512
17877
  invalid_type_error: 'toChain must be a string'
17513
17878
  }).min(1, 'toChain must be a non-empty string if provided').optional(),
17514
- apiKey: apiKeySchema
17879
+ apiKey: apiKeySchema.optional()
17515
17880
  });
17516
17881
  /**
17517
17882
  * Zod schema for validating CreateSwapResponse payloads.
@@ -17520,13 +17885,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17520
17885
  required_error: 'fee token is required',
17521
17886
  invalid_type_error: 'fee token must be a string'
17522
17887
  }).min(1, 'fee token must be a non-empty string'),
17523
- amount: feeAmountSchema
17888
+ amount: feeAmountSchema,
17889
+ decimals: zod.z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
17890
+ symbol: zod.z.string({
17891
+ invalid_type_error: 'fee token symbol must be a string'
17892
+ }).min(1, 'fee token symbol must be a non-empty string').optional()
17524
17893
  });
17525
17894
  /**
17526
17895
  * Developer fee item schema with basis field.
17527
- */ const createSwapDeveloperFeeItemSchema = zod.z.object({
17528
- token: zod.z.string().min(1, 'fee token must be a non-empty string'),
17529
- amount: feeAmountSchema,
17896
+ */ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
17530
17897
  basis: zod.z.enum([
17531
17898
  'inputAmount',
17532
17899
  'estimatedAmount'
@@ -17618,7 +17985,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17618
17985
  addresses: zod.z.array(zod.z.string({
17619
17986
  invalid_type_error: 'addresses entries must be strings'
17620
17987
  }).min(1, 'addresses entries must be non-empty strings')).min(1, 'addresses must contain at least one entry when provided').max(MAX_RATE_ADDRESSES_PER_REQUEST, `addresses supports at most ${String(MAX_RATE_ADDRESSES_PER_REQUEST)} values per request`).optional(),
17621
- apiKey: apiKeySchema
17988
+ apiKey: apiKeySchema.optional()
17622
17989
  });
17623
17990
  /**
17624
17991
  * Zod schema for validating GetTokenRatesResponse payloads.
@@ -18841,7 +19208,7 @@ new Set(Object.values(Blockchain));
18841
19208
  registerKit(`${pkg$1.name}/${pkg$1.version}`);
18842
19209
 
18843
19210
  var name = "@circle-fin/earn-kit";
18844
- var version = "1.2.2";
19211
+ var version = "1.3.0";
18845
19212
  var pkg = {
18846
19213
  name: name,
18847
19214
  version: version};
@@ -18971,7 +19338,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
18971
19338
  asset: zod.z.string(),
18972
19339
  assetAddress: zod.z.string(),
18973
19340
  lltv: zod.z.number(),
18974
- supplyUsd: zod.z.number()
19341
+ supplyUsd: zod.z.number(),
19342
+ // Optional during the expand/contract window (a backend that predates the
19343
+ // field omits the key), mirroring the `.optional()` facets on the base
19344
+ // schema; `null` when the product exposes no per-market allocation (V2).
19345
+ allocationPct: zod.z.number().nullable().optional()
18975
19346
  });
18976
19347
  /**
18977
19348
  * Zod schema for a Morpho vault warning in the API response.
@@ -18985,7 +19356,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
18985
19356
  ])
18986
19357
  });
18987
19358
  /**
18988
- * Zod schema for a single vault info object in the API response.
19359
+ * Zod schema for the manager (curator) facet in the API response.
19360
+ *
19361
+ * @internal
19362
+ */ const managerSchema = zod.z.object({
19363
+ name: zod.z.string(),
19364
+ address: zod.z.string().optional(),
19365
+ // Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
19366
+ // are added here as the providers that emit them land, rather than shipped
19367
+ // speculatively.
19368
+ type: zod.z.enum([
19369
+ 'curator'
19370
+ ])
19371
+ });
19372
+ /**
19373
+ * Zod schema for the APY profile facet in the API response.
19374
+ *
19375
+ * @internal
19376
+ */ const apyProfileSchema = zod.z.object({
19377
+ current: zod.z.number(),
19378
+ native: zod.z.number().nullable(),
19379
+ d7: zod.z.number().nullable(),
19380
+ d30: zod.z.number().nullable(),
19381
+ d90: zod.z.number().nullable(),
19382
+ rewardShare: zod.z.number().nullable(),
19383
+ source: zod.z.string().optional(),
19384
+ asOf: zod.z.string().optional()
19385
+ });
19386
+ /**
19387
+ * Zod schema for the fee split facet in the API response.
19388
+ *
19389
+ * @internal
19390
+ */ const feeInfoSchema = zod.z.object({
19391
+ performance: zod.z.number().nullable(),
19392
+ management: zod.z.number().nullable()
19393
+ });
19394
+ /**
19395
+ * Zod schema for the liquidity profile facet in the API response.
19396
+ *
19397
+ * `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
19398
+ * it is validated as a raw JSON amount, like `totalDeposits`/`available`.
19399
+ *
19400
+ * @internal
19401
+ */ const liquidityProfileSchema = zod.z.object({
19402
+ totalDeposits: amountJsonSchema,
19403
+ available: amountJsonSchema,
19404
+ totalSupply: amountJsonSchema,
19405
+ status: zod.z.enum([
19406
+ 'active',
19407
+ 'low_liquidity'
19408
+ ])
19409
+ });
19410
+ /**
19411
+ * Zod schema for the risk signals facet in the API response.
19412
+ *
19413
+ * @internal
19414
+ */ const riskSignalsSchema = zod.z.object({
19415
+ circleSentinel: zod.z.boolean(),
19416
+ warnings: zod.z.array(vaultWarningSchema).optional(),
19417
+ earnKitWarnings: zod.z.array(zod.z.string()).optional()
19418
+ });
19419
+ /**
19420
+ * Zod schema for the universal earn-opportunity base in the API response.
19421
+ *
19422
+ * Retains every existing deprecated flat field (kept validated through the
19423
+ * expand/contract window so default-strip does not drop them) and adds the
19424
+ * new nested facets. The nested facets are `.optional()` during the
19425
+ * transition so the SDK still validates against a not-yet-fully-deployed
19426
+ * backend; they become required after Expand ships.
18989
19427
  *
18990
19428
  * @internal
18991
19429
  */ const vaultInfoResponseSchema = zod.z.object({
@@ -19010,6 +19448,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
19010
19448
  warnings: zod.z.array(vaultWarningSchema).optional(),
19011
19449
  earnKitWarnings: zod.z.array(zod.z.string()).optional()
19012
19450
  });
19451
+ /**
19452
+ * Shared base schema: existing flat fields (kept) plus the new nested
19453
+ * facets and neutral identity. Facets are `.optional()` during the
19454
+ * transition; flip to required once the backend is confirmed emitting.
19455
+ *
19456
+ * @internal
19457
+ */ const earnBaseSchema = vaultInfoResponseSchema.extend({
19458
+ address: zod.z.string().optional(),
19459
+ asOf: zod.z.string().optional(),
19460
+ manager: managerSchema.nullable().optional(),
19461
+ apyProfile: apyProfileSchema.optional(),
19462
+ fee: feeInfoSchema.optional(),
19463
+ liquidityProfile: liquidityProfileSchema.optional(),
19464
+ riskSignals: riskSignalsSchema.optional()
19465
+ });
19466
+ /**
19467
+ * Zod schema for the `vault` opportunity variant.
19468
+ *
19469
+ * @internal
19470
+ */ const vaultOpportunitySchema = earnBaseSchema.extend({
19471
+ productType: zod.z.literal('vault'),
19472
+ collateral: zod.z.array(collateralSchema)
19473
+ });
19474
+ /**
19475
+ * Discriminated union over `productType`. Add union members here as new
19476
+ * product types (e.g. `lending_market`, `rwa_token`) land.
19477
+ *
19478
+ * @internal
19479
+ */ const earnOpportunityVariants = [
19480
+ vaultOpportunitySchema
19481
+ ];
19482
+ /** @internal */ const earnOpportunitySchema = zod.z.discriminatedUnion('productType', earnOpportunityVariants);
19483
+ /** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
19484
+ /**
19485
+ * Tolerant list parser for earn opportunities.
19486
+ *
19487
+ * `z.discriminatedUnion` throws on an unrecognized discriminant and
19488
+ * `z.array` fails the whole array if any element fails. Two migration-window
19489
+ * cases are smoothed over here so neither breaks an already-shipped SDK:
19490
+ *
19491
+ * - A backend that predates `productType` omits it entirely. `'vault'` was the
19492
+ * only opportunity type then, so default a missing discriminant to `'vault'`
19493
+ * rather than dropping every vault the backend returns.
19494
+ * - A future backend adds a *second* `productType` this SDK version does not
19495
+ * know. Drop those elements (a present-but-unrecognized discriminant) instead
19496
+ * of rejecting the whole list.
19497
+ *
19498
+ * Only the drop above is a *tolerant* case. Anything that is not a plain object
19499
+ * with a present-but-unknown string `productType` — `null`, `undefined`,
19500
+ * primitives, or an object whose `productType` is malformed — is passed through
19501
+ * untouched so `z.array(earnOpportunitySchema)` reports it as a normal
19502
+ * validation failure. It is deliberately not silently dropped (which would hide
19503
+ * malformed backend data) and never throws here (an unguarded property read on
19504
+ * a non-object would escape `safeParse` as a raw `TypeError` instead of a
19505
+ * `ZodError`).
19506
+ *
19507
+ * @internal
19508
+ */ const earnOpportunityListSchema = zod.z.preprocess((raw)=>{
19509
+ if (!Array.isArray(raw)) {
19510
+ return raw;
19511
+ }
19512
+ // Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
19513
+ // map/filter chain stays type-safe and no `any` leaks into the return.
19514
+ const entries = raw;
19515
+ return entries.map((entry)=>{
19516
+ // Only touch plain objects; non-objects fall through to fail validation.
19517
+ if (typeof entry !== 'object' || entry === null) {
19518
+ return entry;
19519
+ }
19520
+ const record = entry;
19521
+ // Older backend predating productType: default to the only type then.
19522
+ return record.productType === undefined ? {
19523
+ ...record,
19524
+ productType: 'vault'
19525
+ } : record;
19526
+ }).filter((entry)=>{
19527
+ // Drop ONLY a present-but-unknown string discriminant (a future
19528
+ // productType this SDK version doesn't know). Everything else —
19529
+ // non-objects, a non-string productType — flows through to
19530
+ // z.array(earnOpportunitySchema) and fails/passes validation normally.
19531
+ if (typeof entry !== 'object' || entry === null) {
19532
+ return true;
19533
+ }
19534
+ const productType = entry.productType;
19535
+ if (typeof productType !== 'string') {
19536
+ return true;
19537
+ }
19538
+ return knownProductTypes.has(productType);
19539
+ });
19540
+ }, zod.z.array(earnOpportunitySchema));
19013
19541
  // ---------------------------------------------------------------------------
19014
19542
  // Position response schema
19015
19543
  // ---------------------------------------------------------------------------
@@ -19139,6 +19667,7 @@ const positionPnlSchema = zod.z.discriminatedUnion('status', [
19139
19667
  *
19140
19668
  * @internal
19141
19669
  */ const depositPayloadSchema = zod.z.object({
19670
+ execId: bridgeDepositExecIdSchema,
19142
19671
  executionParams: depositExecutionParamsSchema,
19143
19672
  signature: hexSignatureSchema
19144
19673
  });
@@ -19230,6 +19759,21 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19230
19759
  amount: amountJsonSchema,
19231
19760
  vaultAddress: hexAddressSchema
19232
19761
  }).passthrough();
19762
+ /** @internal */ const bridgeQuoteExpirySchema = zod.z.discriminatedUnion('mode', [
19763
+ zod.z.object({
19764
+ mode: zod.z.literal('TIMESTAMP'),
19765
+ expiresAt: zod.z.string().datetime({
19766
+ offset: true
19767
+ })
19768
+ }),
19769
+ zod.z.object({
19770
+ mode: zod.z.literal('BLOCK_NUMBER'),
19771
+ expiresAtBlock: zod.z.number().int(),
19772
+ blockEstimatedAt: zod.z.string().datetime({
19773
+ offset: true
19774
+ }).optional()
19775
+ })
19776
+ ]).optional().catch(undefined);
19233
19777
  /**
19234
19778
  * Zod schema for the bridge deposit prepare payload.
19235
19779
  *
@@ -19241,6 +19785,10 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19241
19785
  execId: bridgeDepositExecIdSchema,
19242
19786
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
19243
19787
  expiresAt: zod.z.string().datetime(),
19788
+ quoteIssuedAt: zod.z.string().datetime({
19789
+ offset: true
19790
+ }).optional().catch(undefined),
19791
+ quoteExpiry: bridgeQuoteExpirySchema,
19244
19792
  review: bridgeDepositPrepareReviewSchema
19245
19793
  });
19246
19794
  /**
@@ -19306,6 +19854,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19306
19854
  *
19307
19855
  * @internal
19308
19856
  */ const withdrawPayloadSchema = zod.z.object({
19857
+ execId: bridgeDepositExecIdSchema,
19309
19858
  executionParams: withdrawExecutionParamsSchema,
19310
19859
  signature: hexSignatureSchema
19311
19860
  });
@@ -19319,6 +19868,27 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19319
19868
  data: withdrawPayloadSchema
19320
19869
  });
19321
19870
  // ---------------------------------------------------------------------------
19871
+ // Transaction report response schema
19872
+ // ---------------------------------------------------------------------------
19873
+ /**
19874
+ * Zod schema for the transaction report payload inside the API `data` envelope.
19875
+ *
19876
+ * The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
19877
+ * schema accepts any object shape and does not require specific fields.
19878
+ *
19879
+ * @internal
19880
+ */ const transactionReportPayloadSchema = zod.z.object({}).passthrough();
19881
+ /**
19882
+ * Zod schema for the `POST /v1/earnKit/transactions/report` API response.
19883
+ *
19884
+ * The Earn Service API wraps the transaction report payload in a `data`
19885
+ * envelope.
19886
+ *
19887
+ * @internal
19888
+ */ zod.z.object({
19889
+ data: transactionReportPayloadSchema
19890
+ });
19891
+ // ---------------------------------------------------------------------------
19322
19892
  // Claim rewards response schema
19323
19893
  // ---------------------------------------------------------------------------
19324
19894
  /**
@@ -19379,6 +19949,30 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19379
19949
  token: zod.z.string(),
19380
19950
  amount: amountJsonSchema
19381
19951
  });
19952
+ /**
19953
+ * Zod schema for a native gas-fee entry in an EarnKit quote response.
19954
+ *
19955
+ * The Earn Service backend estimates gas server-side and returns one entry per
19956
+ * action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
19957
+ * `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
19958
+ * integer string in the chain's native base units. When the backend cannot
19959
+ * estimate an action it returns `fees: null` with an `error` message instead.
19960
+ *
19961
+ * The schema deliberately validates almost nothing beyond the envelope: `name`
19962
+ * is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
19963
+ * of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
19964
+ * `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
19965
+ * which degrades a malformed entry to a `fees: null` soft failure. This is
19966
+ * intentional: gas is best-effort, so a single unparseable gas entry (a wrong
19967
+ * type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
19968
+ * `fee`) must never fail Zod validation and reject the entire quote.
19969
+ *
19970
+ * @internal
19971
+ */ const quoteGasFeeSchema = zod.z.object({
19972
+ name: zod.z.string().optional(),
19973
+ fees: zod.z.unknown(),
19974
+ error: zod.z.string().optional()
19975
+ }).passthrough();
19382
19976
  /**
19383
19977
  * Zod schema for the inner deposit quote payload.
19384
19978
  *
@@ -19394,7 +19988,8 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19394
19988
  expectedShares: amountJsonSchema,
19395
19989
  sharePrice: zod.z.string(),
19396
19990
  currentApy: zod.z.number(),
19397
- fees: zod.z.array(feeSchema).optional()
19991
+ fees: zod.z.array(feeSchema).optional(),
19992
+ gasFees: zod.z.array(quoteGasFeeSchema).optional()
19398
19993
  });
19399
19994
  /**
19400
19995
  * Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
@@ -19421,6 +20016,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19421
20016
  sharePrice: zod.z.string(),
19422
20017
  maxWithdrawable: amountJsonSchema,
19423
20018
  fees: zod.z.array(feeSchema),
20019
+ gasFees: zod.z.array(quoteGasFeeSchema).optional(),
19424
20020
  warnings: zod.z.array(zod.z.string()).optional()
19425
20021
  });
19426
20022
  /**
@@ -19478,7 +20074,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19478
20074
  *
19479
20075
  * @internal
19480
20076
  */ const getVaultsPayloadSchema = zod.z.object({
19481
- vaults: zod.z.array(vaultInfoResponseSchema),
20077
+ vaults: earnOpportunityListSchema,
19482
20078
  errors: zod.z.array(vaultErrorSchema)
19483
20079
  });
19484
20080
  /**
@@ -19508,7 +20104,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19508
20104
  *
19509
20105
  * @internal
19510
20106
  */ const exploreVaultsPayloadSchema = zod.z.object({
19511
- vaults: zod.z.array(vaultInfoResponseSchema),
20107
+ vaults: earnOpportunityListSchema,
19512
20108
  pagination: explorePaginationSchema
19513
20109
  });
19514
20110
  /**