@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.
package/bridge.mjs CHANGED
@@ -20,9 +20,9 @@ import { z } from 'zod';
20
20
  import pino from 'pino';
21
21
  import { formatUnits as formatUnits$1, parseUnits as parseUnits$1 } from '@ethersproject/units';
22
22
  import { hexlify, hexZeroPad } from '@ethersproject/bytes';
23
+ import '@ethersproject/abi';
23
24
  import { getAddress } from '@ethersproject/address';
24
25
  import bs58 from 'bs58';
25
- import '@ethersproject/abi';
26
26
  import { PublicKey } from '@solana/web3.js';
27
27
  import 'bn.js';
28
28
  import '@coral-xyz/anchor';
@@ -3094,7 +3094,10 @@ var EarnChain;
3094
3094
  contracts: {
3095
3095
  v1: {
3096
3096
  wallet: GATEWAY_WALLET_EVM_TESTNET,
3097
- minter: GATEWAY_MINTER_EVM_TESTNET
3097
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3098
+ // DepositForHandler the GenericExecutor calls to run a fast cross-chain
3099
+ // deposit into the GatewayWallet above.
3100
+ depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
3098
3101
  }
3099
3102
  },
3100
3103
  forwarderSupported: {
@@ -6212,7 +6215,10 @@ var Chains = /*#__PURE__*/Object.freeze({
6212
6215
  minter: z.string({
6213
6216
  required_error: 'Gateway minter address is required. Please provide a valid contract address.',
6214
6217
  invalid_type_error: 'Gateway minter address must be a string.'
6215
- }).min(1, 'Gateway minter address cannot be empty.')
6218
+ }).min(1, 'Gateway minter address cannot be empty.'),
6219
+ depositForHandler: z.string({
6220
+ invalid_type_error: 'Gateway depositForHandler address must be a string.'
6221
+ }).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
6216
6222
  }).strict() // Reject any additional properties not defined in the schema
6217
6223
  ;
6218
6224
  /**
@@ -6734,21 +6740,31 @@ const swapTokenEnumSchema = z.enum([
6734
6740
  * returning the appropriate address based on the requested contract type.
6735
6741
  *
6736
6742
  * @param chain - The chain definition to resolve the contract address for
6737
- * @param contractType - The type of contract address to resolve ('tokenMessenger' or 'messageTransmitter')
6743
+ * @param contractType - The type of contract address to resolve ('tokenMessenger', 'messageTransmitter', or 'tokenMessengerWithFees')
6738
6744
  * @returns The contract address for the specified contract type
6739
6745
  * @throws Error when chain does not support CCTP v2 or has unsupported contract configuration
6746
+ * @throws Error when 'tokenMessengerWithFees' is requested but not configured on the chain
6740
6747
  */ const resolveCCTPV2ContractAddress = (chain, contractType)=>{
6741
6748
  // Handle custom bridge contract for tokenMessenger (burn transaction)
6742
- if (hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
6749
+ if (contractType === 'tokenMessenger' && hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
6743
6750
  return chain.kitContracts.bridge;
6744
6751
  }
6745
6752
  // At this point we know CCTP v2 is supported, so contracts exist
6746
6753
  const cctpConfig = chain.cctp;
6747
6754
  const contracts = cctpConfig.contracts.v2;
6755
+ // The `TokenMessengerWithFees` wrapper (prepaid FORWARD path) is an optional
6756
+ // deployment carried alongside both split and merged configurations.
6757
+ if (contractType === 'tokenMessengerWithFees') {
6758
+ const wrapper = contracts.tokenMessengerWithFees;
6759
+ if (wrapper === undefined || wrapper === '') {
6760
+ throw new Error(`TokenMessengerWithFees is not configured on chain ${chain.name}. The prepaid FORWARD path is unavailable on this chain.`);
6761
+ }
6762
+ return wrapper;
6763
+ }
6748
6764
  // Handle different contract types with explicit type checking
6749
6765
  switch(contracts.type){
6750
6766
  case 'split':
6751
- return contracts.tokenMessenger ;
6767
+ return contractType === 'tokenMessenger' ? contracts.tokenMessenger : contracts.messageTransmitter;
6752
6768
  case 'merged':
6753
6769
  return contracts.contract;
6754
6770
  default:
@@ -9603,7 +9619,7 @@ function resolveOptions(options) {
9603
9619
  }
9604
9620
 
9605
9621
  var name$2 = "@circle-fin/bridge-kit";
9606
- var version$3 = "1.12.0";
9622
+ var version$3 = "1.12.1";
9607
9623
  var pkg$3 = {
9608
9624
  name: name$2,
9609
9625
  version: version$3};
@@ -12107,6 +12123,144 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
12107
12123
  return false;
12108
12124
  };
12109
12125
 
12126
+ /**
12127
+ * The zero address, denoting a native-currency fee in a signed quote.
12128
+ */ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
12129
+ /**
12130
+ * Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
12131
+ *
12132
+ * Determines the native `msg.value` and the ERC-20 approvals required, honouring
12133
+ * the quote's `feeToken`:
12134
+ * - Native fee (`feeToken` is the zero address): attach exactly `feeTotalAmount`
12135
+ * as `msg.value`; approve only the burn amount.
12136
+ * - ERC-20 fee equal to the burn token (both USDC — the `isBurnTokenFee` case):
12137
+ * approve a single combined `amount + feeTotalAmount` and skip the redundant
12138
+ * second approval.
12139
+ * - ERC-20 fee different from the burn token: approve the burn amount and the fee
12140
+ * amount separately.
12141
+ *
12142
+ * This encodes only balance/allowance intent; it does not fetch balances. The
12143
+ * caller is responsible for a balance preflight against the fresh quote.
12144
+ *
12145
+ * @param params - The fee token, burn token, burn amount, and total fee amount.
12146
+ * @returns The resolved fee payment plan.
12147
+ * @throws KitError if `amount` or `feeTotalAmount` is negative.
12148
+ *
12149
+ * @example
12150
+ * ```typescript
12151
+ * // Native fee
12152
+ * resolveFeePayment({
12153
+ * feeToken: '0x0000000000000000000000000000000000000000',
12154
+ * burnToken: '0xUSDC...',
12155
+ * amount: 1_000_000n,
12156
+ * feeTotalAmount: 3_500_000n,
12157
+ * })
12158
+ * // → { isNativeFee: true, isBurnTokenFee: false, nativeValue: 3_500_000n,
12159
+ * // approvals: [{ token: '0xUSDC...', amount: 1_000_000n }] }
12160
+ * ```
12161
+ */ const resolveFeePayment = (params)=>{
12162
+ const { feeToken, burnToken, amount, feeTotalAmount } = params;
12163
+ if (typeof amount !== 'bigint' || amount < 0n) {
12164
+ throw createValidationFailedError$1('amount', amount, 'Must be a non-negative bigint');
12165
+ }
12166
+ if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
12167
+ throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
12168
+ }
12169
+ const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS;
12170
+ const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
12171
+ if (isNativeFee) {
12172
+ return {
12173
+ isNativeFee: true,
12174
+ isBurnTokenFee: false,
12175
+ nativeValue: feeTotalAmount,
12176
+ approvals: [
12177
+ {
12178
+ token: burnToken,
12179
+ amount
12180
+ }
12181
+ ]
12182
+ };
12183
+ }
12184
+ if (isBurnTokenFee) {
12185
+ // Fee and burn draw on the same token — a single combined approval covers
12186
+ // both; the redundant second approval is skipped.
12187
+ return {
12188
+ isNativeFee: false,
12189
+ isBurnTokenFee: true,
12190
+ nativeValue: 0n,
12191
+ approvals: [
12192
+ {
12193
+ token: burnToken,
12194
+ amount: amount + feeTotalAmount
12195
+ }
12196
+ ]
12197
+ };
12198
+ }
12199
+ return {
12200
+ isNativeFee: false,
12201
+ isBurnTokenFee: false,
12202
+ nativeValue: 0n,
12203
+ approvals: [
12204
+ {
12205
+ token: burnToken,
12206
+ amount
12207
+ },
12208
+ {
12209
+ token: feeToken,
12210
+ amount: feeTotalAmount
12211
+ }
12212
+ ]
12213
+ };
12214
+ };
12215
+
12216
+ /**
12217
+ * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
12218
+ * hookData must start with.
12219
+ */ const CCTP_FORWARD_MAGIC_HEX = Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
12220
+ /**
12221
+ * Determine whether a hookData blob begins with the `cctp-forward` envelope.
12222
+ *
12223
+ * The prepaid FORWARD path requires the GenericExecutor blob to be wrapped in a
12224
+ * `cctp-forward` frame; without it the wrapper reverts `ForwardFeeWithoutHook`.
12225
+ *
12226
+ * @param hookData - The 0x-prefixed hookData hex string.
12227
+ * @returns True when the hookData starts with the `cctp-forward` magic.
12228
+ *
12229
+ * @example
12230
+ * ```typescript
12231
+ * hasForwardHook('0x636374702d666f7277617264...') // true
12232
+ * hasForwardHook('0xdeadbeef') // false
12233
+ * ```
12234
+ */ const hasForwardHook = (hookData)=>{
12235
+ if (typeof hookData !== 'string') {
12236
+ return false;
12237
+ }
12238
+ const normalized = (hookData.startsWith('0x') ? hookData.slice(2) : hookData).toLowerCase();
12239
+ return normalized.startsWith(CCTP_FORWARD_MAGIC_HEX);
12240
+ };
12241
+ /**
12242
+ * Assert that a hookData blob is forward-friendly for the prepaid FORWARD path.
12243
+ *
12244
+ * The prepaid FORWARD path always requests a FORWARD fee item, so the wrapper
12245
+ * requires the hookData to start with a `cctp-forward` frame. Validating this
12246
+ * before the burn surfaces the guaranteed `ForwardFeeWithoutHook` revert as a
12247
+ * typed input error instead of an on-chain failure.
12248
+ *
12249
+ * @param hookData - The 0x-prefixed hookData hex string.
12250
+ * @throws KitError (`INPUT_VALIDATION_FAILED`) if the hookData is missing or lacks
12251
+ * the `cctp-forward` frame.
12252
+ *
12253
+ * @example
12254
+ * ```typescript
12255
+ * assertForwardHookData(geForwardHookData) // ok
12256
+ * assertForwardHookData('0xdeadbeef') // throws — would revert ForwardFeeWithoutHook
12257
+ * ```
12258
+ */ const assertForwardHookData = (hookData)=>{
12259
+ if (!hasForwardHook(hookData)) {
12260
+ throw createValidationFailedError$1('hookData', hookData, 'Prepaid FORWARD burns require a cctp-forward-wrapped hookData; without it the TokenMessengerWithFees wrapper reverts ForwardFeeWithoutHook');
12261
+ }
12262
+ };
12263
+
12110
12264
  /**
12111
12265
  * Type guard to validate the forwardFee object structure.
12112
12266
  *
@@ -13038,6 +13192,109 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
13038
13192
  }
13039
13193
  }
13040
13194
 
13195
+ /**
13196
+ * Assert that `params` is a well-formed {@link BurnWithFeesParams} object.
13197
+ *
13198
+ * Validates the full public-boundary input before any field destructuring,
13199
+ * `BigInt()` coercion, or adapter preparation runs, so malformed JS-caller
13200
+ * inputs always produce typed `KitError` validation failures.
13201
+ *
13202
+ * Checks performed (in order):
13203
+ * - `params` must be a non-null plain object
13204
+ * - `source` — valid CCTP v2 wallet context (via `assertCCTPv2WalletContext`)
13205
+ * - `destinationChain` — present and supports CCTP v2
13206
+ * - source and destination chains must both be testnet or both mainnet
13207
+ * - source and destination chains must differ
13208
+ * - `executor` — non-empty string
13209
+ * - `amount` — bigint or non-empty string coercible to bigint
13210
+ * - `feeTotalAmount` — bigint or non-empty string coercible to bigint
13211
+ * - `feeToken` — valid EVM address (`0x` + 40 hex chars)
13212
+ * - `claim.signedQuote` — valid `0x`-prefixed hex string
13213
+ * - `claim.refundAddress` — valid EVM address
13214
+ * - `hookData` — valid `0x`-prefixed hex string when present
13215
+ *
13216
+ * @param params - The value to validate.
13217
+ * @throws {KitError} If any field is missing or invalid.
13218
+ *
13219
+ * @example
13220
+ * ```typescript
13221
+ * assertBurnWithFeesParams(params)
13222
+ * // params is now typed as BurnWithFeesParams and safe to use
13223
+ * const { source, destinationChain, amount } = params
13224
+ * ```
13225
+ */ function assertBurnWithFeesParams(params) {
13226
+ if (params === null || typeof params !== 'object' || Array.isArray(params)) {
13227
+ throw createValidationFailedError$1('params', params, 'Must be a non-null plain object');
13228
+ }
13229
+ const p = params;
13230
+ // Source wallet context
13231
+ assertCCTPv2WalletContext(p['source']);
13232
+ const source = p['source'];
13233
+ // destinationChain
13234
+ const destinationChain = p['destinationChain'];
13235
+ if (destinationChain === null || destinationChain === undefined || typeof destinationChain !== 'object') {
13236
+ throw createValidationFailedError$1('destinationChain', destinationChain, 'Must be a chain definition object');
13237
+ }
13238
+ if (!isCCTPV2Supported(destinationChain)) {
13239
+ throw createValidationFailedError$1('destinationChain', destinationChain, 'destinationChain must support CCTP v2');
13240
+ }
13241
+ const dest = destinationChain;
13242
+ // Testnet / mainnet mismatch
13243
+ if (source.chain.isTestnet !== dest.isTestnet) {
13244
+ throw createNetworkMismatchError(source.chain, dest);
13245
+ }
13246
+ // Same-chain guard
13247
+ if (source.chain.name === dest.name) {
13248
+ throw createUnsupportedRouteError(source.chain.name, dest.name);
13249
+ }
13250
+ // executor
13251
+ const executor = p['executor'];
13252
+ if (typeof executor !== 'string' || executor === '') {
13253
+ throw createValidationFailedError$1('executor', executor, 'A GenericExecutor address is required (used for both mintRecipient and destinationCaller)');
13254
+ }
13255
+ // amount
13256
+ const rawAmount = p['amount'];
13257
+ if (typeof rawAmount !== 'bigint' && typeof rawAmount !== 'string' || rawAmount === '') {
13258
+ throw createValidationFailedError$1('amount', rawAmount, 'Must be a bigint or a numeric string');
13259
+ }
13260
+ try {
13261
+ BigInt(rawAmount);
13262
+ } catch {
13263
+ throw createValidationFailedError$1('amount', rawAmount, 'Must be a valid integer value coercible to bigint');
13264
+ }
13265
+ // feeTotalAmount
13266
+ const rawFee = p['feeTotalAmount'];
13267
+ if (typeof rawFee !== 'bigint' && typeof rawFee !== 'string' || rawFee === '') {
13268
+ throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a bigint or a numeric string');
13269
+ }
13270
+ try {
13271
+ BigInt(rawFee);
13272
+ } catch {
13273
+ throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
13274
+ }
13275
+ // feeToken
13276
+ if (!evmAddressSchema.safeParse(p['feeToken']).success) {
13277
+ throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
13278
+ }
13279
+ // claim
13280
+ const rawClaim = p['claim'];
13281
+ if (rawClaim === null || typeof rawClaim !== 'object' || Array.isArray(rawClaim)) {
13282
+ throw createValidationFailedError$1('claim', rawClaim, 'Must be an object with signedQuote and refundAddress');
13283
+ }
13284
+ const claim = rawClaim;
13285
+ if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
13286
+ throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
13287
+ }
13288
+ if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
13289
+ throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
13290
+ }
13291
+ // hookData (optional)
13292
+ const hookData = p['hookData'];
13293
+ if (hookData !== undefined && hookData !== '' && !hexStringSchema.safeParse(hookData).success) {
13294
+ throw createValidationFailedError$1('hookData', hookData, 'Must be a valid hex string starting with 0x');
13295
+ }
13296
+ }
13297
+
13041
13298
  /**
13042
13299
  * CCTP bridge step names that can occur in the bridging flow.
13043
13300
  *
@@ -14246,10 +14503,15 @@ const mockAttestationMessage = {
14246
14503
  const burnCallData = burnRequest.getCallData();
14247
14504
  // batchExecute may throw before submission (wallet declined) but never
14248
14505
  // after — post-submission errors are returned as empty receipts.
14506
+ // The sender is threaded for adapters whose execution is routed through a
14507
+ // signing strategy (which has no wallet account to read it from); the
14508
+ // wallet-client path ignores it.
14249
14509
  const batchResult = await adapter.batchExecute([
14250
14510
  approveCallData,
14251
14511
  burnCallData
14252
- ], chain);
14512
+ ], chain, {
14513
+ fromAddress: params.source.address
14514
+ });
14253
14515
  const approveReceipt = batchResult.receipts[0];
14254
14516
  const burnReceipt = batchResult.receipts[1];
14255
14517
  const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
@@ -14392,7 +14654,7 @@ const mockAttestationMessage = {
14392
14654
  return step;
14393
14655
  }
14394
14656
 
14395
- var version$2 = "1.9.0";
14657
+ var version$2 = "1.10.0";
14396
14658
  var pkg$2 = {
14397
14659
  version: version$2};
14398
14660
 
@@ -15547,7 +15809,7 @@ function assertCCTPV2Config(config) {
15547
15809
  throw new Error(`Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
15548
15810
  }
15549
15811
  // Resolve spender address with proper error handling
15550
- const spenderAddress = resolveCCTPV2ContractAddress(chain);
15812
+ const spenderAddress = resolveCCTPV2ContractAddress(chain, 'tokenMessenger');
15551
15813
  // Prepare action parameters
15552
15814
  const actionParams = {
15553
15815
  amount: BigInt(amount),
@@ -16027,6 +16289,106 @@ function assertCCTPV2Config(config) {
16027
16289
  return await source.adapter.prepareAction('cctp.v2.depositForBurn', actionParams, resolvedContext);
16028
16290
  }
16029
16291
  /**
16292
+ * Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
16293
+ *
16294
+ * Builds the source-chain `depositForBurnWithHookAndFees` call for the
16295
+ * GenericExecutor FORWARD path: fees are collected up front on the source chain
16296
+ * against a signed quote, `mintRecipient` and `destinationCaller` are both set to
16297
+ * the GenericExecutor, and the GE `hookData` is passed through unchanged.
16298
+ *
16299
+ * This is the low-level on-chain primitive behind the UBK `fastCrossChainDeposit`
16300
+ * and Bridge Kit `bridge({ deposit })` flows. The `hookData` and signed-quote
16301
+ * `claim` are produced elsewhere and passed in here:
16302
+ * - `hookData`: `buildForwardingHookDataWithPayload(version,
16303
+ * buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
16304
+ * - `claim.signedQuote` / `feeToken` / `feeTotalAmount`: from `fetchFeeQuote`
16305
+ * (`@circle-fin/provider-fee-v1`), whose FORWARD item must be bound to the
16306
+ * SAME `hookData` and executor `destinationCaller` used here.
16307
+ *
16308
+ * The returned approvals and burn are NOT executed — the caller executes the
16309
+ * approvals first (in order) and then the burn. The fee payment channel matches
16310
+ * the quote's `feeToken`:
16311
+ * - Native fee: exactly `feeTotalAmount` is attached as the burn's `msg.value`;
16312
+ * only the burn amount is approved.
16313
+ * - USDC fee (same token as the burn): a single combined `amount + feeTotalAmount`
16314
+ * approval covers both; the redundant second approval is skipped.
16315
+ *
16316
+ * @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
16317
+ * @param params - The burn amount, executor, hookData, signed-quote claim, and fee.
16318
+ * @returns The prepared approvals, the prepared burn, and the resolved fee plan.
16319
+ * @throws {KitError} If the wallet context is invalid, `destinationChain` does not
16320
+ * support CCTP v2, the executor is missing, `amount` or `feeTotalAmount` is not
16321
+ * a bigint or a numeric string coercible to bigint, the hookData lacks a
16322
+ * `cctp-forward` frame (guaranteed `ForwardFeeWithoutHook`), or the operation
16323
+ * context cannot be resolved.
16324
+ *
16325
+ * @example
16326
+ * ```typescript
16327
+ * const { approvals, burn } = await provider.burnWithFees({
16328
+ * source,
16329
+ * destinationChain: Arc,
16330
+ * amount: 1_000_000n,
16331
+ * executor: genericExecutorAddress,
16332
+ * hookData: geForwardHookData,
16333
+ * claim: { signedQuote: '0x01...', refundAddress: userAddress },
16334
+ * feeToken: '0x0000000000000000000000000000000000000000', // native
16335
+ * feeTotalAmount: 3_500_000n,
16336
+ * })
16337
+ * for (const approval of approvals) await approval.execute()
16338
+ * const txHash = await burn.execute()
16339
+ * ```
16340
+ */ async burnWithFees(params) {
16341
+ assertBurnWithFeesParams(params);
16342
+ const { source, destinationChain, executor, hookData, claim, feeToken } = params;
16343
+ const amount = BigInt(params.amount);
16344
+ const feeTotalAmount = BigInt(params.feeTotalAmount);
16345
+ // Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
16346
+ // so the hookData must carry a cctp-forward frame; otherwise the wrapper
16347
+ // reverts ForwardFeeWithoutHook. Surface it as a typed input error up front.
16348
+ assertForwardHookData(hookData);
16349
+ const burnToken = source.chain.usdcAddress;
16350
+ const feePayment = resolveFeePayment({
16351
+ feeToken,
16352
+ burnToken,
16353
+ amount,
16354
+ feeTotalAmount
16355
+ });
16356
+ // Resolve operation context from the source wallet context.
16357
+ const operationContext = this.extractOperationContext(source);
16358
+ let resolvedContext;
16359
+ try {
16360
+ resolvedContext = await resolveOperationContext(source.adapter, operationContext);
16361
+ } catch (error) {
16362
+ throw createValidationFailedError$1('source.adapter', undefined, `Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
16363
+ }
16364
+ const context = resolvedContext;
16365
+ const wrapperAddress = resolveCCTPV2ContractAddress(source.chain, 'tokenMessengerWithFees');
16366
+ // Build the ERC-20 approvals to the wrapper (burn token, plus a distinct fee
16367
+ // token only when the fee is not paid in the burn token).
16368
+ const approvals = await Promise.all(feePayment.approvals.map(async (approval)=>source.adapter.prepareAction('token.approve', {
16369
+ tokenAddress: approval.token,
16370
+ delegate: wrapperAddress,
16371
+ amount: approval.amount
16372
+ }, context)));
16373
+ // Build the burn: mintRecipient AND destinationCaller are both the executor.
16374
+ const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
16375
+ fromChain: source.chain,
16376
+ toChain: destinationChain,
16377
+ amount,
16378
+ mintRecipient: executor,
16379
+ destinationCaller: executor,
16380
+ hookData,
16381
+ claim,
16382
+ feeToken,
16383
+ feeTotalAmount
16384
+ }, context);
16385
+ return {
16386
+ approvals,
16387
+ burn,
16388
+ feePayment
16389
+ };
16390
+ }
16391
+ /**
16030
16392
  * Waits for a transaction to be mined and confirmed on the blockchain.
16031
16393
  *
16032
16394
  * This method should block until the transaction is confirmed on the blockchain.
@@ -16906,7 +17268,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16906
17268
  };
16907
17269
 
16908
17270
  var name$1 = "@circle-fin/swap-kit";
16909
- var version$1 = "1.3.2";
17271
+ var version$1 = "1.4.0";
16910
17272
  var pkg$1 = {
16911
17273
  name: name$1,
16912
17274
  version: version$1};
@@ -16971,7 +17333,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
16971
17333
  }).min(1, 'kitKey must be a non-empty string').optional(),
16972
17334
  provider: z.string({
16973
17335
  invalid_type_error: 'provider must be a string'
16974
- }).min(1, 'provider must be a non-empty string').optional()
17336
+ }).min(1, 'provider must be a non-empty string').optional(),
17337
+ batchTransactions: z.boolean({
17338
+ invalid_type_error: 'batchTransactions must be a boolean'
17339
+ }).optional()
16975
17340
  });
16976
17341
  /**
16977
17342
  * Zod schema for adapter context.
@@ -17410,7 +17775,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17410
17775
  /**
17411
17776
  * Circle Stablecoin Service API Key.
17412
17777
  * Must be a valid API key format.
17413
- */ apiKey: apiKeySchema
17778
+ */ apiKey: apiKeySchema.optional()
17414
17779
  }).superRefine(requireCrossChainQuoteToAddress);
17415
17780
  /**
17416
17781
  * Zod schema for validating CreateSwapRequest parameters.
@@ -17468,7 +17833,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17468
17833
  /**
17469
17834
  * Circle Stablecoin Service API Key.
17470
17835
  * Must be a valid API key format.
17471
- */ apiKey: apiKeySchema
17836
+ */ apiKey: apiKeySchema.optional()
17472
17837
  });
17473
17838
  /**
17474
17839
  * Zod schema for validating GetSwapStatusResponse data.
@@ -17504,7 +17869,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17504
17869
  toChain: z.string({
17505
17870
  invalid_type_error: 'toChain must be a string'
17506
17871
  }).min(1, 'toChain must be a non-empty string if provided').optional(),
17507
- apiKey: apiKeySchema
17872
+ apiKey: apiKeySchema.optional()
17508
17873
  });
17509
17874
  /**
17510
17875
  * Zod schema for validating CreateSwapResponse payloads.
@@ -17513,13 +17878,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17513
17878
  required_error: 'fee token is required',
17514
17879
  invalid_type_error: 'fee token must be a string'
17515
17880
  }).min(1, 'fee token must be a non-empty string'),
17516
- amount: feeAmountSchema
17881
+ amount: feeAmountSchema,
17882
+ decimals: z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
17883
+ symbol: z.string({
17884
+ invalid_type_error: 'fee token symbol must be a string'
17885
+ }).min(1, 'fee token symbol must be a non-empty string').optional()
17517
17886
  });
17518
17887
  /**
17519
17888
  * Developer fee item schema with basis field.
17520
- */ const createSwapDeveloperFeeItemSchema = z.object({
17521
- token: z.string().min(1, 'fee token must be a non-empty string'),
17522
- amount: feeAmountSchema,
17889
+ */ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
17523
17890
  basis: z.enum([
17524
17891
  'inputAmount',
17525
17892
  'estimatedAmount'
@@ -17611,7 +17978,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17611
17978
  addresses: z.array(z.string({
17612
17979
  invalid_type_error: 'addresses entries must be strings'
17613
17980
  }).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(),
17614
- apiKey: apiKeySchema
17981
+ apiKey: apiKeySchema.optional()
17615
17982
  });
17616
17983
  /**
17617
17984
  * Zod schema for validating GetTokenRatesResponse payloads.
@@ -18834,7 +19201,7 @@ new Set(Object.values(Blockchain));
18834
19201
  registerKit(`${pkg$1.name}/${pkg$1.version}`);
18835
19202
 
18836
19203
  var name = "@circle-fin/earn-kit";
18837
- var version = "1.2.2";
19204
+ var version = "1.3.0";
18838
19205
  var pkg = {
18839
19206
  name: name,
18840
19207
  version: version};
@@ -18964,7 +19331,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
18964
19331
  asset: z.string(),
18965
19332
  assetAddress: z.string(),
18966
19333
  lltv: z.number(),
18967
- supplyUsd: z.number()
19334
+ supplyUsd: z.number(),
19335
+ // Optional during the expand/contract window (a backend that predates the
19336
+ // field omits the key), mirroring the `.optional()` facets on the base
19337
+ // schema; `null` when the product exposes no per-market allocation (V2).
19338
+ allocationPct: z.number().nullable().optional()
18968
19339
  });
18969
19340
  /**
18970
19341
  * Zod schema for a Morpho vault warning in the API response.
@@ -18978,7 +19349,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
18978
19349
  ])
18979
19350
  });
18980
19351
  /**
18981
- * Zod schema for a single vault info object in the API response.
19352
+ * Zod schema for the manager (curator) facet in the API response.
19353
+ *
19354
+ * @internal
19355
+ */ const managerSchema = z.object({
19356
+ name: z.string(),
19357
+ address: z.string().optional(),
19358
+ // Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
19359
+ // are added here as the providers that emit them land, rather than shipped
19360
+ // speculatively.
19361
+ type: z.enum([
19362
+ 'curator'
19363
+ ])
19364
+ });
19365
+ /**
19366
+ * Zod schema for the APY profile facet in the API response.
19367
+ *
19368
+ * @internal
19369
+ */ const apyProfileSchema = z.object({
19370
+ current: z.number(),
19371
+ native: z.number().nullable(),
19372
+ d7: z.number().nullable(),
19373
+ d30: z.number().nullable(),
19374
+ d90: z.number().nullable(),
19375
+ rewardShare: z.number().nullable(),
19376
+ source: z.string().optional(),
19377
+ asOf: z.string().optional()
19378
+ });
19379
+ /**
19380
+ * Zod schema for the fee split facet in the API response.
19381
+ *
19382
+ * @internal
19383
+ */ const feeInfoSchema = z.object({
19384
+ performance: z.number().nullable(),
19385
+ management: z.number().nullable()
19386
+ });
19387
+ /**
19388
+ * Zod schema for the liquidity profile facet in the API response.
19389
+ *
19390
+ * `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
19391
+ * it is validated as a raw JSON amount, like `totalDeposits`/`available`.
19392
+ *
19393
+ * @internal
19394
+ */ const liquidityProfileSchema = z.object({
19395
+ totalDeposits: amountJsonSchema,
19396
+ available: amountJsonSchema,
19397
+ totalSupply: amountJsonSchema,
19398
+ status: z.enum([
19399
+ 'active',
19400
+ 'low_liquidity'
19401
+ ])
19402
+ });
19403
+ /**
19404
+ * Zod schema for the risk signals facet in the API response.
19405
+ *
19406
+ * @internal
19407
+ */ const riskSignalsSchema = z.object({
19408
+ circleSentinel: z.boolean(),
19409
+ warnings: z.array(vaultWarningSchema).optional(),
19410
+ earnKitWarnings: z.array(z.string()).optional()
19411
+ });
19412
+ /**
19413
+ * Zod schema for the universal earn-opportunity base in the API response.
19414
+ *
19415
+ * Retains every existing deprecated flat field (kept validated through the
19416
+ * expand/contract window so default-strip does not drop them) and adds the
19417
+ * new nested facets. The nested facets are `.optional()` during the
19418
+ * transition so the SDK still validates against a not-yet-fully-deployed
19419
+ * backend; they become required after Expand ships.
18982
19420
  *
18983
19421
  * @internal
18984
19422
  */ const vaultInfoResponseSchema = z.object({
@@ -19003,6 +19441,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
19003
19441
  warnings: z.array(vaultWarningSchema).optional(),
19004
19442
  earnKitWarnings: z.array(z.string()).optional()
19005
19443
  });
19444
+ /**
19445
+ * Shared base schema: existing flat fields (kept) plus the new nested
19446
+ * facets and neutral identity. Facets are `.optional()` during the
19447
+ * transition; flip to required once the backend is confirmed emitting.
19448
+ *
19449
+ * @internal
19450
+ */ const earnBaseSchema = vaultInfoResponseSchema.extend({
19451
+ address: z.string().optional(),
19452
+ asOf: z.string().optional(),
19453
+ manager: managerSchema.nullable().optional(),
19454
+ apyProfile: apyProfileSchema.optional(),
19455
+ fee: feeInfoSchema.optional(),
19456
+ liquidityProfile: liquidityProfileSchema.optional(),
19457
+ riskSignals: riskSignalsSchema.optional()
19458
+ });
19459
+ /**
19460
+ * Zod schema for the `vault` opportunity variant.
19461
+ *
19462
+ * @internal
19463
+ */ const vaultOpportunitySchema = earnBaseSchema.extend({
19464
+ productType: z.literal('vault'),
19465
+ collateral: z.array(collateralSchema)
19466
+ });
19467
+ /**
19468
+ * Discriminated union over `productType`. Add union members here as new
19469
+ * product types (e.g. `lending_market`, `rwa_token`) land.
19470
+ *
19471
+ * @internal
19472
+ */ const earnOpportunityVariants = [
19473
+ vaultOpportunitySchema
19474
+ ];
19475
+ /** @internal */ const earnOpportunitySchema = z.discriminatedUnion('productType', earnOpportunityVariants);
19476
+ /** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
19477
+ /**
19478
+ * Tolerant list parser for earn opportunities.
19479
+ *
19480
+ * `z.discriminatedUnion` throws on an unrecognized discriminant and
19481
+ * `z.array` fails the whole array if any element fails. Two migration-window
19482
+ * cases are smoothed over here so neither breaks an already-shipped SDK:
19483
+ *
19484
+ * - A backend that predates `productType` omits it entirely. `'vault'` was the
19485
+ * only opportunity type then, so default a missing discriminant to `'vault'`
19486
+ * rather than dropping every vault the backend returns.
19487
+ * - A future backend adds a *second* `productType` this SDK version does not
19488
+ * know. Drop those elements (a present-but-unrecognized discriminant) instead
19489
+ * of rejecting the whole list.
19490
+ *
19491
+ * Only the drop above is a *tolerant* case. Anything that is not a plain object
19492
+ * with a present-but-unknown string `productType` — `null`, `undefined`,
19493
+ * primitives, or an object whose `productType` is malformed — is passed through
19494
+ * untouched so `z.array(earnOpportunitySchema)` reports it as a normal
19495
+ * validation failure. It is deliberately not silently dropped (which would hide
19496
+ * malformed backend data) and never throws here (an unguarded property read on
19497
+ * a non-object would escape `safeParse` as a raw `TypeError` instead of a
19498
+ * `ZodError`).
19499
+ *
19500
+ * @internal
19501
+ */ const earnOpportunityListSchema = z.preprocess((raw)=>{
19502
+ if (!Array.isArray(raw)) {
19503
+ return raw;
19504
+ }
19505
+ // Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
19506
+ // map/filter chain stays type-safe and no `any` leaks into the return.
19507
+ const entries = raw;
19508
+ return entries.map((entry)=>{
19509
+ // Only touch plain objects; non-objects fall through to fail validation.
19510
+ if (typeof entry !== 'object' || entry === null) {
19511
+ return entry;
19512
+ }
19513
+ const record = entry;
19514
+ // Older backend predating productType: default to the only type then.
19515
+ return record.productType === undefined ? {
19516
+ ...record,
19517
+ productType: 'vault'
19518
+ } : record;
19519
+ }).filter((entry)=>{
19520
+ // Drop ONLY a present-but-unknown string discriminant (a future
19521
+ // productType this SDK version doesn't know). Everything else —
19522
+ // non-objects, a non-string productType — flows through to
19523
+ // z.array(earnOpportunitySchema) and fails/passes validation normally.
19524
+ if (typeof entry !== 'object' || entry === null) {
19525
+ return true;
19526
+ }
19527
+ const productType = entry.productType;
19528
+ if (typeof productType !== 'string') {
19529
+ return true;
19530
+ }
19531
+ return knownProductTypes.has(productType);
19532
+ });
19533
+ }, z.array(earnOpportunitySchema));
19006
19534
  // ---------------------------------------------------------------------------
19007
19535
  // Position response schema
19008
19536
  // ---------------------------------------------------------------------------
@@ -19132,6 +19660,7 @@ const positionPnlSchema = z.discriminatedUnion('status', [
19132
19660
  *
19133
19661
  * @internal
19134
19662
  */ const depositPayloadSchema = z.object({
19663
+ execId: bridgeDepositExecIdSchema,
19135
19664
  executionParams: depositExecutionParamsSchema,
19136
19665
  signature: hexSignatureSchema
19137
19666
  });
@@ -19223,6 +19752,21 @@ const bridgeDepositPrepareReviewSchema = z.object({
19223
19752
  amount: amountJsonSchema,
19224
19753
  vaultAddress: hexAddressSchema
19225
19754
  }).passthrough();
19755
+ /** @internal */ const bridgeQuoteExpirySchema = z.discriminatedUnion('mode', [
19756
+ z.object({
19757
+ mode: z.literal('TIMESTAMP'),
19758
+ expiresAt: z.string().datetime({
19759
+ offset: true
19760
+ })
19761
+ }),
19762
+ z.object({
19763
+ mode: z.literal('BLOCK_NUMBER'),
19764
+ expiresAtBlock: z.number().int(),
19765
+ blockEstimatedAt: z.string().datetime({
19766
+ offset: true
19767
+ }).optional()
19768
+ })
19769
+ ]).optional().catch(undefined);
19226
19770
  /**
19227
19771
  * Zod schema for the bridge deposit prepare payload.
19228
19772
  *
@@ -19234,6 +19778,10 @@ const bridgeDepositPrepareReviewSchema = z.object({
19234
19778
  execId: bridgeDepositExecIdSchema,
19235
19779
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
19236
19780
  expiresAt: z.string().datetime(),
19781
+ quoteIssuedAt: z.string().datetime({
19782
+ offset: true
19783
+ }).optional().catch(undefined),
19784
+ quoteExpiry: bridgeQuoteExpirySchema,
19237
19785
  review: bridgeDepositPrepareReviewSchema
19238
19786
  });
19239
19787
  /**
@@ -19299,6 +19847,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
19299
19847
  *
19300
19848
  * @internal
19301
19849
  */ const withdrawPayloadSchema = z.object({
19850
+ execId: bridgeDepositExecIdSchema,
19302
19851
  executionParams: withdrawExecutionParamsSchema,
19303
19852
  signature: hexSignatureSchema
19304
19853
  });
@@ -19312,6 +19861,27 @@ const bridgeDepositPrepareReviewSchema = z.object({
19312
19861
  data: withdrawPayloadSchema
19313
19862
  });
19314
19863
  // ---------------------------------------------------------------------------
19864
+ // Transaction report response schema
19865
+ // ---------------------------------------------------------------------------
19866
+ /**
19867
+ * Zod schema for the transaction report payload inside the API `data` envelope.
19868
+ *
19869
+ * The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
19870
+ * schema accepts any object shape and does not require specific fields.
19871
+ *
19872
+ * @internal
19873
+ */ const transactionReportPayloadSchema = z.object({}).passthrough();
19874
+ /**
19875
+ * Zod schema for the `POST /v1/earnKit/transactions/report` API response.
19876
+ *
19877
+ * The Earn Service API wraps the transaction report payload in a `data`
19878
+ * envelope.
19879
+ *
19880
+ * @internal
19881
+ */ z.object({
19882
+ data: transactionReportPayloadSchema
19883
+ });
19884
+ // ---------------------------------------------------------------------------
19315
19885
  // Claim rewards response schema
19316
19886
  // ---------------------------------------------------------------------------
19317
19887
  /**
@@ -19372,6 +19942,30 @@ const bridgeDepositPrepareReviewSchema = z.object({
19372
19942
  token: z.string(),
19373
19943
  amount: amountJsonSchema
19374
19944
  });
19945
+ /**
19946
+ * Zod schema for a native gas-fee entry in an EarnKit quote response.
19947
+ *
19948
+ * The Earn Service backend estimates gas server-side and returns one entry per
19949
+ * action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
19950
+ * `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
19951
+ * integer string in the chain's native base units. When the backend cannot
19952
+ * estimate an action it returns `fees: null` with an `error` message instead.
19953
+ *
19954
+ * The schema deliberately validates almost nothing beyond the envelope: `name`
19955
+ * is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
19956
+ * of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
19957
+ * `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
19958
+ * which degrades a malformed entry to a `fees: null` soft failure. This is
19959
+ * intentional: gas is best-effort, so a single unparseable gas entry (a wrong
19960
+ * type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
19961
+ * `fee`) must never fail Zod validation and reject the entire quote.
19962
+ *
19963
+ * @internal
19964
+ */ const quoteGasFeeSchema = z.object({
19965
+ name: z.string().optional(),
19966
+ fees: z.unknown(),
19967
+ error: z.string().optional()
19968
+ }).passthrough();
19375
19969
  /**
19376
19970
  * Zod schema for the inner deposit quote payload.
19377
19971
  *
@@ -19387,7 +19981,8 @@ const bridgeDepositPrepareReviewSchema = z.object({
19387
19981
  expectedShares: amountJsonSchema,
19388
19982
  sharePrice: z.string(),
19389
19983
  currentApy: z.number(),
19390
- fees: z.array(feeSchema).optional()
19984
+ fees: z.array(feeSchema).optional(),
19985
+ gasFees: z.array(quoteGasFeeSchema).optional()
19391
19986
  });
19392
19987
  /**
19393
19988
  * Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
@@ -19414,6 +20009,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
19414
20009
  sharePrice: z.string(),
19415
20010
  maxWithdrawable: amountJsonSchema,
19416
20011
  fees: z.array(feeSchema),
20012
+ gasFees: z.array(quoteGasFeeSchema).optional(),
19417
20013
  warnings: z.array(z.string()).optional()
19418
20014
  });
19419
20015
  /**
@@ -19471,7 +20067,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
19471
20067
  *
19472
20068
  * @internal
19473
20069
  */ const getVaultsPayloadSchema = z.object({
19474
- vaults: z.array(vaultInfoResponseSchema),
20070
+ vaults: earnOpportunityListSchema,
19475
20071
  errors: z.array(vaultErrorSchema)
19476
20072
  });
19477
20073
  /**
@@ -19501,7 +20097,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
19501
20097
  *
19502
20098
  * @internal
19503
20099
  */ const exploreVaultsPayloadSchema = z.object({
19504
- vaults: z.array(vaultInfoResponseSchema),
20100
+ vaults: earnOpportunityListSchema,
19505
20101
  pagination: explorePaginationSchema
19506
20102
  });
19507
20103
  /**