@circle-fin/app-kit 1.13.0 → 1.14.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.
@@ -33,7 +33,7 @@ var zod = require('zod');
33
33
  var pino = require('pino');
34
34
  var units = require('@ethersproject/units');
35
35
  var bytes = require('@ethersproject/bytes');
36
- require('@ethersproject/abi');
36
+ var abi = require('@ethersproject/abi');
37
37
  var address = require('@ethersproject/address');
38
38
  var bs58 = require('bs58');
39
39
  var web3_js = require('@solana/web3.js');
@@ -3665,8 +3665,45 @@ var EarnChain;
3665
3665
  * This program handles minting operations for Gateway transactions
3666
3666
  * on Solana devnet.
3667
3667
  */ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
3668
- /** TokenMessengerWithFees address shared by enabled EVM mainnet sources. */ const TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET = '0x71f54F818671cD0D7ea140Da213e5C8b5C92a408';
3669
- /** TokenMessengerWithFees address shared by enabled EVM testnet sources. */ const TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET = '0x8745D906D67C346E5eb1aEEED38Eb87F34DF0C0A';
3668
+ /**
3669
+ * The `TokenMessengerWithFees` proxy contract address for EVM mainnet networks
3670
+ * (all chains except Edge).
3671
+ *
3672
+ * Deployed at a CREATE3-derived address; identical across all mainnet EVM
3673
+ * source chains. Present on any chain that supports the prepaid FORWARD path
3674
+ * via `depositForBurnWithHookAndFees`.
3675
+ */ const TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET = '0x71f54F818671cD0D7ea140Da213e5C8b5C92a408';
3676
+ /**
3677
+ * The `TokenMessengerWithFees` proxy contract address for EVM testnet networks.
3678
+ *
3679
+ * Identical across all testnet EVM source chains. Present on any testnet chain
3680
+ * that supports the prepaid FORWARD path via `depositForBurnWithHookAndFees`.
3681
+ */ const TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET = '0x8745D906D67C346E5eb1aEEED38Eb87F34DF0C0A';
3682
+ /**
3683
+ * The `DepositForHandler` proxy contract address for EVM mainnet networks.
3684
+ *
3685
+ * The handler the GenericExecutor calls on a fast-deposit destination chain to
3686
+ * run a cross-chain deposit into the GatewayWallet. Deployed at the same
3687
+ * address across all mainnet EVM destination chains.
3688
+ */ const DEPOSIT_FOR_HANDLER_EVM_MAINNET = '0x16529813203f77E036576666336554a1210dce4D';
3689
+ /**
3690
+ * The `DepositForHandler` proxy contract address for EVM testnet networks.
3691
+ *
3692
+ * Identical across all testnet EVM destination chains.
3693
+ */ const DEPOSIT_FOR_HANDLER_EVM_TESTNET = '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48';
3694
+ /**
3695
+ * The `GenericExecutor` proxy contract address for EVM mainnet networks.
3696
+ *
3697
+ * The GenericExecutor is the `mintRecipient` and `destinationCaller` on the
3698
+ * destination chain for the CCTP v2 prepaid FORWARD path. It receives the CCTP
3699
+ * mint and calls the `DepositForHandler` to complete the fast deposit.
3700
+ * Deployed at the same address across all mainnet EVM destination chains.
3701
+ */ const GENERIC_EXECUTOR_EVM_MAINNET = '0xFa7be2f04F3Ad4ca969260729c6d45B5625984A7';
3702
+ /**
3703
+ * The `GenericExecutor` proxy contract address for EVM testnet networks.
3704
+ *
3705
+ * Identical across all testnet EVM destination chains.
3706
+ */ const GENERIC_EXECUTOR_EVM_TESTNET = '0xEdC81040756AcCfF070c21D37b265b9D0b5Ba45e';
3670
3707
 
3671
3708
  /**
3672
3709
  * Arc Testnet chain definition
@@ -3725,9 +3762,8 @@ var EarnChain;
3725
3762
  v1: {
3726
3763
  wallet: GATEWAY_WALLET_EVM_TESTNET,
3727
3764
  minter: GATEWAY_MINTER_EVM_TESTNET,
3728
- // DepositForHandler the GenericExecutor calls to run a fast cross-chain
3729
- // deposit into the GatewayWallet above.
3730
- depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
3765
+ depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
3766
+ genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
3731
3767
  }
3732
3768
  },
3733
3769
  forwarderSupported: {
@@ -3922,7 +3958,9 @@ var EarnChain;
3922
3958
  contracts: {
3923
3959
  v1: {
3924
3960
  wallet: GATEWAY_WALLET_EVM_MAINNET,
3925
- minter: GATEWAY_MINTER_EVM_MAINNET
3961
+ minter: GATEWAY_MINTER_EVM_MAINNET,
3962
+ depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
3963
+ genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
3926
3964
  }
3927
3965
  },
3928
3966
  forwarderSupported: {
@@ -3986,7 +4024,9 @@ var EarnChain;
3986
4024
  contracts: {
3987
4025
  v1: {
3988
4026
  wallet: GATEWAY_WALLET_EVM_TESTNET,
3989
- minter: GATEWAY_MINTER_EVM_TESTNET
4027
+ minter: GATEWAY_MINTER_EVM_TESTNET,
4028
+ depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
4029
+ genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
3990
4030
  }
3991
4031
  },
3992
4032
  forwarderSupported: {
@@ -4299,6 +4339,7 @@ var EarnChain;
4299
4339
  v2: {
4300
4340
  type: 'split',
4301
4341
  tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
4342
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
4302
4343
  messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
4303
4344
  confirmations: 1,
4304
4345
  fastConfirmations: 1
@@ -4344,6 +4385,7 @@ var EarnChain;
4344
4385
  v2: {
4345
4386
  type: 'split',
4346
4387
  tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
4388
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
4347
4389
  messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
4348
4390
  confirmations: 1,
4349
4391
  fastConfirmations: 1
@@ -4389,6 +4431,7 @@ var EarnChain;
4389
4431
  v2: {
4390
4432
  type: 'split',
4391
4433
  tokenMessenger: '0x98706A006bc632Df31CAdFCBD43F38887ce2ca5c',
4434
+ tokenMessengerWithFees: '0x3Ac96675F9a3E6922713e041645D82f3561d3686',
4392
4435
  messageTransmitter: '0x5b61381Fc9e58E70EfC13a4A97516997019198ee',
4393
4436
  confirmations: 65,
4394
4437
  fastConfirmations: 1
@@ -4434,6 +4477,7 @@ var EarnChain;
4434
4477
  v2: {
4435
4478
  type: 'split',
4436
4479
  tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
4480
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
4437
4481
  messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
4438
4482
  confirmations: 65,
4439
4483
  fastConfirmations: 1
@@ -4782,6 +4826,7 @@ var EarnChain;
4782
4826
  v2: {
4783
4827
  type: 'split',
4784
4828
  tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
4829
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
4785
4830
  messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
4786
4831
  confirmations: 1,
4787
4832
  fastConfirmations: 1
@@ -4829,6 +4874,7 @@ var EarnChain;
4829
4874
  v2: {
4830
4875
  type: 'split',
4831
4876
  tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
4877
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
4832
4878
  messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
4833
4879
  confirmations: 1,
4834
4880
  fastConfirmations: 1
@@ -5110,6 +5156,7 @@ var EarnChain;
5110
5156
  v2: {
5111
5157
  type: 'split',
5112
5158
  tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5159
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
5113
5160
  messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5114
5161
  confirmations: 1,
5115
5162
  fastConfirmations: 1
@@ -5155,6 +5202,7 @@ var EarnChain;
5155
5202
  v2: {
5156
5203
  type: 'split',
5157
5204
  tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5205
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
5158
5206
  messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5159
5207
  confirmations: 64,
5160
5208
  fastConfirmations: 1
@@ -5200,6 +5248,7 @@ var EarnChain;
5200
5248
  v2: {
5201
5249
  type: 'split',
5202
5250
  tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5251
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
5203
5252
  messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5204
5253
  confirmations: 64,
5205
5254
  fastConfirmations: 1
@@ -5501,6 +5550,7 @@ var EarnChain;
5501
5550
  v2: {
5502
5551
  type: 'split',
5503
5552
  tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5553
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
5504
5554
  messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5505
5555
  confirmations: 1,
5506
5556
  fastConfirmations: 1
@@ -5547,6 +5597,7 @@ var EarnChain;
5547
5597
  v2: {
5548
5598
  type: 'split',
5549
5599
  tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5600
+ tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
5550
5601
  messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5551
5602
  confirmations: 1,
5552
5603
  fastConfirmations: 1
@@ -5854,7 +5905,9 @@ var EarnChain;
5854
5905
  contracts: {
5855
5906
  v1: {
5856
5907
  wallet: GATEWAY_WALLET_EVM_MAINNET,
5857
- minter: GATEWAY_MINTER_EVM_MAINNET
5908
+ minter: GATEWAY_MINTER_EVM_MAINNET,
5909
+ depositForHandler: DEPOSIT_FOR_HANDLER_EVM_MAINNET,
5910
+ genericExecutor: GENERIC_EXECUTOR_EVM_MAINNET
5858
5911
  }
5859
5912
  },
5860
5913
  forwarderSupported: {
@@ -5919,7 +5972,9 @@ var EarnChain;
5919
5972
  contracts: {
5920
5973
  v1: {
5921
5974
  wallet: GATEWAY_WALLET_EVM_TESTNET,
5922
- minter: GATEWAY_MINTER_EVM_TESTNET
5975
+ minter: GATEWAY_MINTER_EVM_TESTNET,
5976
+ depositForHandler: DEPOSIT_FOR_HANDLER_EVM_TESTNET,
5977
+ genericExecutor: GENERIC_EXECUTOR_EVM_TESTNET
5923
5978
  }
5924
5979
  },
5925
5980
  forwarderSupported: {
@@ -7005,18 +7060,48 @@ var Chains = {
7005
7060
  return chain.cctp?.contracts.v2 !== undefined;
7006
7061
  }
7007
7062
 
7063
+ /**
7064
+ * Chains the Fee Service accepts as a SOURCE for source-paid ("receive-exact")
7065
+ * CCTP v2 fees. An explicit allowlist is required because the
7066
+ * `TokenMessengerWithFees` wrapper address is now shared with the fast-deposit
7067
+ * forwarder path, so wrapper presence no longer implies source-fee support.
7068
+ * Keep in sync with backend coverage.
7069
+ */ const SOURCE_FEE_SUPPORTED_ALLOWLIST = new Set([
7070
+ // Mainnet
7071
+ Blockchain.Ethereum,
7072
+ Blockchain.Base,
7073
+ Blockchain.Arbitrum,
7074
+ Blockchain.Unichain,
7075
+ Blockchain.Optimism,
7076
+ Blockchain.Codex,
7077
+ Blockchain.Ink,
7078
+ Blockchain.Plume,
7079
+ Blockchain.Linea,
7080
+ Blockchain.World_Chain,
7081
+ // Testnet counterparts
7082
+ Blockchain.Ethereum_Sepolia,
7083
+ Blockchain.Base_Sepolia,
7084
+ Blockchain.Arbitrum_Sepolia,
7085
+ Blockchain.Unichain_Sepolia,
7086
+ Blockchain.Optimism_Sepolia,
7087
+ Blockchain.Codex_Testnet,
7088
+ Blockchain.Ink_Testnet,
7089
+ Blockchain.Plume_Testnet,
7090
+ Blockchain.Linea_Sepolia,
7091
+ Blockchain.World_Chain_Sepolia
7092
+ ]);
7008
7093
  /**
7009
7094
  * Check whether a chain supports source-paid ("receive-exact") CCTP v2 fees.
7010
7095
  *
7011
- * A chain supports source-paid fees when its CCTP v2 configuration carries a
7012
- * deployed `TokenMessengerWithFees` wrapper address. Bridge Kit routes
7013
- * `feePayment: 'source'` transfers through this wrapper via
7014
- * `depositForBurnWithHookAndFees`, so a chain without the wrapper cannot be a
7015
- * source for receive-exact bridging.
7096
+ * A chain qualifies when it supports CCTP v2, carries a `TokenMessengerWithFees`
7097
+ * wrapper, and is in {@link SOURCE_FEE_SUPPORTED_ALLOWLIST}.
7016
7098
  *
7017
- * @param chain - The chain definition to check.
7018
- * @returns `true` when the chain has a `tokenMessengerWithFees` wrapper
7019
- * configured, `false` otherwise.
7099
+ * @param chain - The chain definition to check. A nullish or non-object value
7100
+ * returns `false` rather than throwing, since consumers may call from plain
7101
+ * JavaScript.
7102
+ * @returns `true` when the chain supports CCTP v2, carries a
7103
+ * `TokenMessengerWithFees` wrapper, and is on the source-fee allowlist;
7104
+ * `false` otherwise.
7020
7105
  *
7021
7106
  * @example
7022
7107
  * ```typescript
@@ -7026,11 +7111,18 @@ var Chains = {
7026
7111
  * hasSourceFeeSupport(Chains.Solana) // false
7027
7112
  * ```
7028
7113
  */ function hasSourceFeeSupport(chain) {
7114
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for nullish/non-object input from plain JS
7115
+ if (chain === null || typeof chain !== 'object') {
7116
+ return false;
7117
+ }
7029
7118
  if (!isCCTPV2Supported(chain)) {
7030
7119
  return false;
7031
7120
  }
7032
7121
  const wrapper = chain.cctp.contracts.v2.tokenMessengerWithFees;
7033
- return typeof wrapper === 'string' && wrapper.length > 0;
7122
+ if (typeof wrapper !== 'string' || wrapper.length === 0) {
7123
+ return false;
7124
+ }
7125
+ return SOURCE_FEE_SUPPORTED_ALLOWLIST.has(chain.chain);
7034
7126
  }
7035
7127
 
7036
7128
  /**
@@ -7083,6 +7175,73 @@ var Chains = {
7083
7175
  return typeof contractAddress === 'string' && contractAddress.trim().length > 0;
7084
7176
  }
7085
7177
 
7178
+ /**
7179
+ * Check whether a given chain supports Gateway protocol version 1.
7180
+ *
7181
+ * This type guard function examines a chain definition to determine if it has Gateway v1
7182
+ * contract configurations. It checks that the chain has a gateway object with a
7183
+ * `contracts.v1` entry present.
7184
+ *
7185
+ * @param chain - The chain definition to check for Gateway v1 support
7186
+ * @returns `true` if `chain.gateway?.contracts?.v1` is defined, `false` otherwise
7187
+ *
7188
+ * @example
7189
+ * ```typescript
7190
+ * import { isGatewayV1Supported, Base } from '@core/chains'
7191
+ *
7192
+ * if (isGatewayV1Supported(Base)) {
7193
+ * // TypeScript knows Base.gateway is defined here
7194
+ * console.log('Gateway domain:', Base.gateway.domain)
7195
+ * console.log('Wallet address:', Base.gateway.contracts.v1.wallet)
7196
+ * console.log('Minter address:', Base.gateway.contracts.v1.minter)
7197
+ * }
7198
+ * ```
7199
+ *
7200
+ * @example
7201
+ * ```typescript
7202
+ * // Usage in conditional flow
7203
+ * function getGatewayWalletAddress(chain: ChainDefinition): string | null {
7204
+ * if (isGatewayV1Supported(chain)) {
7205
+ * return chain.gateway.contracts.v1.wallet
7206
+ * }
7207
+ * return null
7208
+ * }
7209
+ * ```
7210
+ */ function isGatewayV1Supported(chain) {
7211
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS consumers may pass a gateway object without contracts
7212
+ return chain.gateway?.contracts?.v1 !== undefined;
7213
+ }
7214
+
7215
+ /**
7216
+ * Temporary allowlist of chains permitted to initiate Gateway fast deposits.
7217
+ * Only chains keyed here are eligible; all others are rejected. Using the
7218
+ * {@link Blockchain} enum keeps entries type-safe and catches typos at compile
7219
+ * time. Remove this allowlist once roll-out is complete.
7220
+ */ new Set([
7221
+ // Mainnet
7222
+ Blockchain.Ethereum,
7223
+ Blockchain.Base,
7224
+ Blockchain.Arbitrum,
7225
+ Blockchain.Unichain,
7226
+ Blockchain.Optimism,
7227
+ Blockchain.Codex,
7228
+ Blockchain.Ink,
7229
+ Blockchain.Plume,
7230
+ Blockchain.Linea,
7231
+ Blockchain.World_Chain,
7232
+ // Testnet counterparts
7233
+ Blockchain.Ethereum_Sepolia,
7234
+ Blockchain.Base_Sepolia,
7235
+ Blockchain.Arbitrum_Sepolia,
7236
+ Blockchain.Unichain_Sepolia,
7237
+ Blockchain.Optimism_Sepolia,
7238
+ Blockchain.Codex_Testnet,
7239
+ Blockchain.Ink_Testnet,
7240
+ Blockchain.Plume_Testnet,
7241
+ Blockchain.Linea_Sepolia,
7242
+ Blockchain.World_Chain_Sepolia
7243
+ ]);
7244
+
7086
7245
  /**
7087
7246
  * Zod schema for validating Gateway v1 contract addresses.
7088
7247
  *
@@ -7104,7 +7263,10 @@ var Chains = {
7104
7263
  }).min(1, 'Gateway minter address cannot be empty.'),
7105
7264
  depositForHandler: zod.z.string({
7106
7265
  invalid_type_error: 'Gateway depositForHandler address must be a string.'
7107
- }).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
7266
+ }).min(1, 'Gateway depositForHandler address cannot be empty.').optional(),
7267
+ genericExecutor: zod.z.string({
7268
+ invalid_type_error: 'Gateway genericExecutor address must be a string.'
7269
+ }).min(1, 'Gateway genericExecutor address cannot be empty.').optional()
7108
7270
  }).strict() // Reject any additional properties not defined in the schema
7109
7271
  ;
7110
7272
  /**
@@ -9975,6 +10137,9 @@ const swapTokenEnumSchema = zod.z.enum([
9975
10137
  * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
9976
10138
  * This prefix is right-padded to 24 bytes in the final hookData.
9977
10139
  */ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
10140
+ /**
10141
+ * Maximum value of the 32-bit `version` field in a `cctp-forward` frame.
10142
+ */ const MAX_UINT32 = 0xffffffff;
9978
10143
  /**
9979
10144
  * CCTP forwarding version number.
9980
10145
  *
@@ -10051,6 +10216,76 @@ function buildForwardingHookData() {
10051
10216
  cachedHookDataHex = '0x' + Array.from(buffer).map((b)=>b.toString(16).padStart(2, '0')).join('');
10052
10217
  return cachedHookDataHex;
10053
10218
  }
10219
+ /**
10220
+ * Build a `cctp-forward` hookData frame with a versioned header and an appended
10221
+ * opaque payload.
10222
+ *
10223
+ * Produces the 32-byte `cctp-forward` header (24-byte ASCII magic + `uint32`
10224
+ * version + `uint32` `dataLength = 0`) followed by `payload` appended verbatim.
10225
+ * Unlike {@link buildForwardingHookData} — which emits only the fixed,
10226
+ * version-0 empty frame — this lets the caller set the frame `version` and
10227
+ * carry an inner payload such as a GenericExecutor blob.
10228
+ *
10229
+ * @remarks
10230
+ * The forwarder reads only the 32-byte header to decide that a hook is
10231
+ * forwardable, then strips it before the inner payload is consumed downstream
10232
+ * (e.g. the GenericExecutor `abi.decode`s the appended blob, never the frame).
10233
+ * `dataLength` stays `0` because the appended bytes are opaque to the forwarder
10234
+ * — it is not the payload's length.
10235
+ *
10236
+ * @param version - The `uint32` frame version (e.g. `1` for the GenericExecutor
10237
+ * FORWARD path). Must be an integer in `[0, 0xFFFFFFFF]`.
10238
+ * @param payload - A 0x-prefixed hex string appended after the header (e.g. the
10239
+ * bare GenericExecutor blob from `buildDepositForGenericExecutorPayload`).
10240
+ * @returns A 0x-prefixed hex string: the 32-byte frame followed by `payload`.
10241
+ * @throws {KitError} If `version` is out of `uint32` range or `payload` is not
10242
+ * a 0x-prefixed hex string (INPUT_VALIDATION_FAILED).
10243
+ *
10244
+ * @example
10245
+ * ```typescript
10246
+ * import {
10247
+ * buildDepositForGenericExecutorPayload,
10248
+ * buildForwardingHookDataWithPayload,
10249
+ * padAddressToBytes32,
10250
+ * } from '@core/utils'
10251
+ *
10252
+ * const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
10253
+ * dappId: 'gateway_deposit',
10254
+ * domainId: 26,
10255
+ * handler: '0xHandlerAddressOnDestinationChain',
10256
+ * params: [USDC_ARC, user, 0],
10257
+ * recoveryAddress: padAddressToBytes32(user),
10258
+ * })
10259
+ *
10260
+ * // Wrap for the prepaid Quote-API FORWARD path (frame version 1).
10261
+ * const hookData = buildForwardingHookDataWithPayload(1, geBlob)
10262
+ * ```
10263
+ */ function buildForwardingHookDataWithPayload(version, payload) {
10264
+ if (!Number.isInteger(version) || version < 0 || version > MAX_UINT32) {
10265
+ throw createValidationFailedError$1('version', version, 'Expected an integer in the uint32 range [0, 4294967295]');
10266
+ }
10267
+ if (!bytes.isHexString(payload)) {
10268
+ throw createValidationFailedError$1('payload', payload, 'Expected a 0x-prefixed hex string');
10269
+ }
10270
+ // `isHexString` accepts odd-length hex (e.g. '0xabc'); catch it here so it
10271
+ // surfaces as a KitError rather than ethers' raw "hex data is odd-length"
10272
+ // from `concat` below.
10273
+ if (payload.length % 2 !== 0) {
10274
+ throw createValidationFailedError$1('payload', payload, 'Expected an even-length (whole-byte) hex string');
10275
+ }
10276
+ // 32-byte header: 24-byte magic + uint32 version + uint32 dataLength (0).
10277
+ const frame = new Uint8Array(32);
10278
+ frame.set(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX), 0);
10279
+ const view = new DataView(frame.buffer);
10280
+ view.setUint32(24, version, false) // big-endian
10281
+ ;
10282
+ view.setUint32(28, CCTP_FORWARD_PAYLOAD_LENGTH, false) // big-endian, 0
10283
+ ;
10284
+ return bytes.hexlify(bytes.concat([
10285
+ frame,
10286
+ payload
10287
+ ]));
10288
+ }
10054
10289
  /**
10055
10290
  * Build a `cctp-forward` hookData frame that instructs Circle's Orbit relayer to
10056
10291
  * create the recipient's Associated Token Account (ATA) before minting on Solana.
@@ -10128,6 +10363,43 @@ function buildForwardingHookData() {
10128
10363
  ]));
10129
10364
  }
10130
10365
 
10366
+ /**
10367
+ * `version` field of the GenericExecutor hookData, in both the
10368
+ * `circle-generic-executor` header (`uint32`) and the ABI tuple (`uint8`). The
10369
+ * executor reverts if it is not `1`.
10370
+ *
10371
+ * @see https://circlepay.atlassian.net/wiki/spaces/~712020cd79585b52ea4353b4720c277fbfcca6/pages/3049291839
10372
+ */ const GENERIC_EXECUTOR_HOOK_DATA_VERSION = 1;
10373
+ /**
10374
+ * ASCII magic that prefixes a GenericExecutor hookData blob.
10375
+ *
10376
+ * The executor auto-detects its payload by this string. It is left-aligned and
10377
+ * zero-padded to 24 bytes in the header, mirroring the `cctp-forward` frame
10378
+ * layout (magic + `uint32` version + `uint32` dataLength).
10379
+ */ const GENERIC_EXECUTOR_MAGIC_PREFIX = 'circle-generic-executor';
10380
+ /**
10381
+ * Prepend the 32-byte `circle-generic-executor` header to the ABI tuple.
10382
+ *
10383
+ * Header layout (mirrors the `cctp-forward` frame): 24-byte zero-padded ASCII
10384
+ * magic + `uint32` version + `uint32` dataLength. Unlike the `cctp-forward`
10385
+ * frame (which the forwarder strips and so carries `dataLength = 0`), this
10386
+ * header's dataLength is the byte length of the ABI tuple that follows, since
10387
+ * the executor consumes both.
10388
+ */ function prependGenericExecutorHeader(abiTuple) {
10389
+ const header = new Uint8Array(32);
10390
+ header.set(new TextEncoder().encode(GENERIC_EXECUTOR_MAGIC_PREFIX), 0);
10391
+ // Byte length of the ABI tuple that the header announces.
10392
+ const tupleByteLength = (abiTuple.length - 2) / 2;
10393
+ const view = new DataView(header.buffer);
10394
+ view.setUint32(24, GENERIC_EXECUTOR_HOOK_DATA_VERSION, false) // big-endian
10395
+ ;
10396
+ view.setUint32(28, tupleByteLength, false) // big-endian
10397
+ ;
10398
+ return bytes.hexlify(bytes.concat([
10399
+ header,
10400
+ abiTuple
10401
+ ]));
10402
+ }
10131
10403
  /**
10132
10404
  * Left-pad a 20-byte EVM address to a 32-byte (`bytes32`) hex string.
10133
10405
  *
@@ -10153,6 +10425,309 @@ function buildForwardingHookData() {
10153
10425
  // matches ABI-decoded output.
10154
10426
  return bytes.hexZeroPad(address.getAddress(address$1), 32).toLowerCase();
10155
10427
  }
10428
+ /**
10429
+ * Encode the bare GenericExecutor + DepositForHandler payload for a CCTP v2
10430
+ * fast-transfer deposit into a dApp.
10431
+ *
10432
+ * Builds the layers inner→outer:
10433
+ * 1. dApp calldata — the dApp function selector + ABI params, with each amount
10434
+ * slot left as the caller-supplied placeholder.
10435
+ * 2. handler calldata — `(depositContract, approvalTarget, depositCalldata, amountIndices)`
10436
+ * for `DepositForHandler`.
10437
+ * 3. ABI tuple — `(uint8 version, bytes32 recoveryAddress, address handler, bytes handlerCalldata)`.
10438
+ * No handler selector travels on the wire; the executor applies a fixed one.
10439
+ * 4. header — the 32-byte `circle-generic-executor` magic frame prepended to the
10440
+ * tuple, by which the executor auto-detects the payload.
10441
+ *
10442
+ * The returned `hookData` is the bare GenericExecutor blob (header ‖ tuple) — the
10443
+ * exact bytes the executor consumes. It carries no `cctp-forward` envelope. For
10444
+ * the prepaid Quote-API FORWARD path, wrap it with
10445
+ * {@link buildForwardingHookDataWithPayload}; the forwarder strips that envelope
10446
+ * before the executor reads the blob.
10447
+ *
10448
+ * @param options - See {@link BuildDepositForGenericExecutorPayloadParams}.
10449
+ * @returns The encoded {@link DepositForGenericExecutorPayload} layers.
10450
+ * @throws {KitError} If `options` is not an object, `dappId` is unknown,
10451
+ * `config.deployments` is not an array, neither `domainId` nor
10452
+ * `destinationChain` resolves a domain (or the two disagree), no deployment
10453
+ * exists for the resolved domain, the deposit contract cannot be resolved (a
10454
+ * built-in deployment supplied without a `destinationChain`), no `handler` is
10455
+ * supplied and it cannot be resolved from `destinationChain`,
10456
+ * `recoveryAddress`/`handler`/contract addresses are malformed,
10457
+ * `config.function` is not a valid Solidity function signature, `params`
10458
+ * length does not match the dApp signature, `params` values fail ABI encoding
10459
+ * (type mismatch), `config.dynamicAmountIndices` is not an array, or an amount
10460
+ * index is out of range (all INPUT_VALIDATION_FAILED).
10461
+ *
10462
+ * @example Encode a 1-click cross-chain Circle Gateway deposit
10463
+ * ```typescript
10464
+ * import { buildDepositForGenericExecutorPayload, padAddressToBytes32 } from '@core/utils'
10465
+ * import { ArcTestnet } from '@core/chains'
10466
+ *
10467
+ * const user = '0x75275Aff2D01699D922f045b69ed291311209738'
10468
+ * const usdcArc = '0x3600000000000000000000000000000000000000'
10469
+ * const { hookData } = buildDepositForGenericExecutorPayload({
10470
+ * dappId: 'gateway_deposit',
10471
+ * destinationChain: ArcTestnet, // resolves the GatewayWallet + DepositForHandler
10472
+ * // depositFor(address token, address depositor, uint256 value), amount idx [2]
10473
+ * params: [usdcArc, user, 0],
10474
+ * recoveryAddress: padAddressToBytes32(user),
10475
+ * })
10476
+ *
10477
+ * // Pass hookData straight into the CCTP v2 fast transfer.
10478
+ * console.log(hookData)
10479
+ * ```
10480
+ */ function buildDepositForGenericExecutorPayload(options) {
10481
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for plain-JS callers
10482
+ if (options === null || typeof options !== 'object') {
10483
+ throw createValidationFailedError$1('options', options, 'Expected an options object');
10484
+ }
10485
+ const { dappId, params: dappParams } = options;
10486
+ const registry = options.config ?? DAPP_CONFIG;
10487
+ // Look up the dApp before resolving the deposit contract or handler, so an
10488
+ // unknown dApp reports the actionable "Unknown dApp" error rather than an
10489
+ // unresolved-handler error.
10490
+ const config = registry[dappId];
10491
+ if (config === undefined) {
10492
+ throw createValidationFailedError$1('dappId', dappId, `Unknown dApp. Known dApps: ${Object.keys(registry).join(', ')}`);
10493
+ }
10494
+ if (!Array.isArray(config.deployments)) {
10495
+ throw createValidationFailedError$1('deployments', config.deployments, `Expected an array of deployments for dApp '${dappId}'`);
10496
+ }
10497
+ // Array.isArray narrows to `any[]`; re-assert the concrete type.
10498
+ const deployments = config.deployments;
10499
+ // Destination CCTP domain: taken from `destinationChain` when supplied (the
10500
+ // chain is the (network, domain) key), else the explicit `domainId`.
10501
+ const domainId = resolveDomainId(options);
10502
+ const deployment = deployments.find((d)=>d.domainId === domainId);
10503
+ if (deployment === undefined) {
10504
+ throw createValidationFailedError$1('domainId', domainId, `No '${dappId}' deployment for domain ${String(domainId)}`);
10505
+ }
10506
+ if (!Array.isArray(dappParams)) {
10507
+ throw createValidationFailedError$1('params', dappParams, 'Expected an array of ABI-ordered parameters');
10508
+ }
10509
+ if (!bytes.isHexString(options.recoveryAddress, 32)) {
10510
+ throw createValidationFailedError$1('recoveryAddress', options.recoveryAddress, 'Expected a 0x-prefixed 32-byte (bytes32) hex string');
10511
+ }
10512
+ // Deposit contract: the deployment's own address, or — for the built-in
10513
+ // gateway_deposit deployment, which carries none — the destination chain's
10514
+ // GatewayWallet, so the address lives only in @core/chains.
10515
+ const resolvedDepositContract = resolveDepositContract(deployment, options, dappId);
10516
+ // Handler: an explicit `handler` always wins; otherwise resolve the
10517
+ // DepositForHandler from `destinationChain`. The chain is the (network, domain)
10518
+ // key, so a shared CCTP domain (Arc is 26 on both testnet and mainnet) can
10519
+ // never resolve the wrong network's handler and strand funds.
10520
+ const handler = resolveDepositForHandler(options, domainId);
10521
+ if (!address.isAddress(handler)) {
10522
+ throw createValidationFailedError$1('handler', handler, 'Expected a valid EVM address');
10523
+ }
10524
+ const depositContract = assertAddress(resolvedDepositContract, 'depositContract');
10525
+ const approvalTarget = assertAddress(deployment.approvalTarget ?? resolvedDepositContract, 'approvalTarget');
10526
+ // 1. dApp calldata: selector + ABI-encoded params (amount slots stay as placeholders).
10527
+ let dappInterface;
10528
+ try {
10529
+ dappInterface = new abi.Interface([
10530
+ `function ${config.function}`
10531
+ ]);
10532
+ } catch {
10533
+ throw createValidationFailedError$1('function', config.function, 'Expected a valid Solidity function signature');
10534
+ }
10535
+ const rawFragment = dappInterface.fragments[0];
10536
+ /* v8 ignore start -- defensive: guards against unexpected library behavior */ if (rawFragment === undefined || rawFragment.type !== 'function') {
10537
+ throw createValidationFailedError$1('function', config.function, 'Expected a valid Solidity function signature');
10538
+ }
10539
+ /* v8 ignore stop */ const fragment = rawFragment;
10540
+ if (dappParams.length !== fragment.inputs.length) {
10541
+ throw createValidationFailedError$1('params', dappParams, `'${config.function}' expects ${String(fragment.inputs.length)} params, got ${String(dappParams.length)}`);
10542
+ }
10543
+ let depositCalldata;
10544
+ try {
10545
+ depositCalldata = dappInterface.encodeFunctionData(fragment, dappParams);
10546
+ } catch {
10547
+ throw createValidationFailedError$1('params', dappParams, 'ABI encoding failed — check that each param matches the expected Solidity type');
10548
+ }
10549
+ if (!Array.isArray(config.dynamicAmountIndices)) {
10550
+ throw createValidationFailedError$1('dynamicAmountIndices', config.dynamicAmountIndices, `Expected an array of amount indices for dApp '${dappId}'`);
10551
+ }
10552
+ // Array.isArray narrows to `any[]`; re-assert the concrete type.
10553
+ const dynamicAmountIndices = config.dynamicAmountIndices;
10554
+ // Amount byte offsets in depositCalldata: 4 (selector) + paramIndex * 32.
10555
+ const amountIndices = dynamicAmountIndices.map((paramIndex)=>{
10556
+ if (!Number.isInteger(paramIndex) || paramIndex < 0 || paramIndex >= fragment.inputs.length) {
10557
+ throw createValidationFailedError$1('dynamicAmountIndices', paramIndex, `Index out of range for '${config.function}' (${String(fragment.inputs.length)} params)`);
10558
+ }
10559
+ return BigInt(4 + paramIndex * 32);
10560
+ });
10561
+ // 2. Handler layer.
10562
+ const handlerCalldata = abi.defaultAbiCoder.encode([
10563
+ 'address',
10564
+ 'address',
10565
+ 'bytes',
10566
+ 'uint256[]'
10567
+ ], [
10568
+ depositContract,
10569
+ approvalTarget,
10570
+ depositCalldata,
10571
+ amountIndices
10572
+ ]);
10573
+ // 3. Executor ABI tuple. No handler selector travels on the wire — the
10574
+ // executor applies a fixed selector internally.
10575
+ const executorTuple = abi.defaultAbiCoder.encode([
10576
+ 'uint8',
10577
+ 'bytes32',
10578
+ 'address',
10579
+ 'bytes'
10580
+ ], [
10581
+ GENERIC_EXECUTOR_HOOK_DATA_VERSION,
10582
+ options.recoveryAddress,
10583
+ address.getAddress(handler),
10584
+ handlerCalldata
10585
+ ]);
10586
+ // 4. Prepend the circle-generic-executor magic header; this is the final
10587
+ // bare GE blob the executor consumes.
10588
+ const hookData = prependGenericExecutorHeader(executorTuple);
10589
+ return {
10590
+ hookData,
10591
+ handlerCalldata,
10592
+ depositCalldata,
10593
+ amountIndices,
10594
+ depositContract,
10595
+ approvalTarget
10596
+ };
10597
+ }
10598
+ /**
10599
+ * Config key of the built-in Circle Gateway deposit dApp. Its deposit contract
10600
+ * is the destination chain's GatewayWallet, resolved from `destinationChain`
10601
+ * (not a hardcoded address), so this is the only dApp whose deployment may omit
10602
+ * `depositContract`.
10603
+ */ const GATEWAY_DEPOSIT_DAPP_ID = 'gateway_deposit';
10604
+ /**
10605
+ * Built-in dApp registry. Adding a new `depositFor`-style dApp is a config entry
10606
+ * here (or via {@link BuildDepositForGenericExecutorPayloadParams.config}).
10607
+ *
10608
+ * @remarks
10609
+ * Only dApps with confirmed deployment addresses and active callers are included.
10610
+ * The built-in `gateway_deposit` entry omits `depositContract` — it is resolved
10611
+ * from the destination chain's GatewayWallet (`@core/chains`) rather than
10612
+ * duplicated here. Pass a custom registry via `config` for unlisted dApps.
10613
+ */ const DAPP_CONFIG = {
10614
+ // Circle Gateway: depositFor(address token, address depositor, uint256 value)
10615
+ [GATEWAY_DEPOSIT_DAPP_ID]: {
10616
+ function: 'depositFor(address,address,uint256)',
10617
+ dynamicAmountIndices: [
10618
+ 2
10619
+ ],
10620
+ deployments: [
10621
+ // Arc Testnet (CCTP domain 26). The deposit contract is the chain's
10622
+ // GatewayWallet, resolved from `destinationChain` (@core/chains) rather
10623
+ // than duplicated here.
10624
+ {
10625
+ domainId: 26
10626
+ }
10627
+ ]
10628
+ }
10629
+ };
10630
+ /**
10631
+ * Resolve the destination CCTP domain for an encode request.
10632
+ *
10633
+ * Prefers {@link BuildDepositForGenericExecutorPayloadParams.destinationChain}
10634
+ * (`chain.cctp.domain`) — the chain is the (network, domain) key. Falls back to
10635
+ * an explicit `domainId`. When both are supplied they must agree.
10636
+ *
10637
+ * @param options - The encode request.
10638
+ * @returns The destination CCTP domain.
10639
+ * @throws {KitError} If no domain is available, or `domainId` disagrees with
10640
+ * `destinationChain` (INPUT_VALIDATION_FAILED).
10641
+ * @internal
10642
+ */ function resolveDomainId(options) {
10643
+ const chain = options.destinationChain;
10644
+ if (chain !== undefined) {
10645
+ const chainDomain = chain.cctp?.domain;
10646
+ if (chainDomain !== undefined) {
10647
+ if (options.domainId !== undefined && options.domainId !== chainDomain) {
10648
+ throw createValidationFailedError$1('domainId', options.domainId, `does not match destinationChain '${chain.name}' CCTP domain ` + String(chainDomain));
10649
+ }
10650
+ return chainDomain;
10651
+ }
10652
+ }
10653
+ if (options.domainId !== undefined) {
10654
+ return options.domainId;
10655
+ }
10656
+ throw createValidationFailedError$1('domainId', options.domainId, "Provide 'domainId', or a 'destinationChain' with a CCTP domain");
10657
+ }
10658
+ /**
10659
+ * Resolve the deposit contract the handler calls.
10660
+ *
10661
+ * Uses the deployment's own `depositContract` when present. Only the built-in
10662
+ * {@link GATEWAY_DEPOSIT_DAPP_ID} may omit it: its deposit contract is the
10663
+ * destination chain's Gateway v1 wallet, resolved from
10664
+ * {@link BuildDepositForGenericExecutorPayloadParams.destinationChain} so the
10665
+ * address is owned once in `@core/chains`. Any other dApp that omits
10666
+ * `depositContract` is a config error and fails here rather than silently
10667
+ * targeting the GatewayWallet.
10668
+ *
10669
+ * @param deployment - The resolved dApp deployment.
10670
+ * @param options - The encode request.
10671
+ * @param dappId - The dApp key, checked against {@link GATEWAY_DEPOSIT_DAPP_ID}.
10672
+ * @returns The deposit contract address (unvalidated; the caller checks it).
10673
+ * @throws {KitError} If a non-`gateway_deposit` deployment omits
10674
+ * `depositContract`, or if `gateway_deposit` has no Gateway v1
10675
+ * `destinationChain` to resolve one (INPUT_VALIDATION_FAILED).
10676
+ * @internal
10677
+ */ function resolveDepositContract(deployment, options, dappId) {
10678
+ if (deployment.depositContract !== undefined) {
10679
+ return deployment.depositContract;
10680
+ }
10681
+ // Only gateway_deposit may omit its address (it targets the chain's
10682
+ // GatewayWallet). Any other addressless deployment is a config mistake and
10683
+ // must fail rather than silently resolve to the GatewayWallet.
10684
+ if (dappId !== GATEWAY_DEPOSIT_DAPP_ID) {
10685
+ throw createValidationFailedError$1('depositContract', dappId, `dApp '${dappId}' must declare a 'depositContract'; only the built-in ` + `'${GATEWAY_DEPOSIT_DAPP_ID}' resolves its address from the ` + "destination chain's GatewayWallet");
10686
+ }
10687
+ const chain = options.destinationChain;
10688
+ if (chain === undefined || !isGatewayV1Supported(chain)) {
10689
+ throw createValidationFailedError$1('depositContract', dappId, `'${GATEWAY_DEPOSIT_DAPP_ID}' needs a 'destinationChain' with Gateway v1 ` + 'support to resolve its GatewayWallet, or an explicit deployment address');
10690
+ }
10691
+ return chain.gateway.contracts.v1.wallet;
10692
+ }
10693
+ /**
10694
+ * Resolve the `DepositForHandler` address for an encode request.
10695
+ *
10696
+ * An explicit `options.handler` always wins. Otherwise the handler is resolved
10697
+ * from {@link BuildDepositForGenericExecutorPayloadParams.destinationChain}
10698
+ * (`chain.gateway.contracts.v1.depositForHandler`). The chain is the (network,
10699
+ * domain) key, so a CCTP domain shared across a chain's testnet and mainnet
10700
+ * cannot resolve the wrong network's handler; a missing chain or an unregistered
10701
+ * handler throws rather than guessing.
10702
+ *
10703
+ * @param options - The encode request.
10704
+ * @param domainId - The resolved destination domain, reported in the error.
10705
+ * @returns The resolved handler address (unvalidated; the caller checks it).
10706
+ * @throws {KitError} If `handler` is omitted and cannot be resolved
10707
+ * (INPUT_VALIDATION_FAILED).
10708
+ * @internal
10709
+ */ function resolveDepositForHandler(options, domainId) {
10710
+ if (options.handler !== undefined) {
10711
+ return options.handler;
10712
+ }
10713
+ const chain = options.destinationChain;
10714
+ if (chain === undefined) {
10715
+ throw createValidationFailedError$1('handler', domainId, "No 'handler' supplied; pass 'handler' explicitly, or a " + "'destinationChain' whose Gateway config registers a DepositForHandler");
10716
+ }
10717
+ const registered = isGatewayV1Supported(chain) ? chain.gateway.contracts.v1.depositForHandler : undefined;
10718
+ if (registered === undefined) {
10719
+ throw createValidationFailedError$1('handler', domainId, `No DepositForHandler registered for domain ${String(domainId)} on ` + `chain '${chain.name}'; pass 'handler' explicitly`);
10720
+ }
10721
+ return registered;
10722
+ }
10723
+ /**
10724
+ * Validate and checksum an EVM address, throwing a consistent validation error.
10725
+ */ function assertAddress(address$1, field) {
10726
+ if (!address.isAddress(address$1)) {
10727
+ throw createValidationFailedError$1(field, address$1, 'Expected a valid EVM address');
10728
+ }
10729
+ return address.getAddress(address$1);
10730
+ }
10156
10731
 
10157
10732
  /**
10158
10733
  * Configuration for {@link retryAsync}.
@@ -10242,7 +10817,7 @@ function resolveOptions(options) {
10242
10817
  * allowlisted {@link ClientLogPayload} fields (and the allowlisted
10243
10818
  * sub-fields of `errorDetails` / `clientContext`) are copied across.
10244
10819
  * A regressing upstream mapper — or a plain-JS caller that bypasses the
10245
- * type — therefore cannot exfiltrate stray properties (secrets, PII,
10820
+ * type — therefore cannot exfiltrate stray properties (secrets,
10246
10821
  * raw error stacks) through the analytics channel. Optional fields are
10247
10822
  * only included when present so the serialised shape matches the
10248
10823
  * server's strict schema.
@@ -10265,6 +10840,9 @@ function resolveOptions(options) {
10265
10840
  if (payload.destinationChain !== undefined) safe['destinationChain'] = payload.destinationChain;
10266
10841
  if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
10267
10842
  if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
10843
+ if (payload.amountIn !== undefined) safe['amountIn'] = payload.amountIn;
10844
+ if (payload.durationMs !== undefined) safe['durationMs'] = payload.durationMs;
10845
+ if (payload.sourceAddress !== undefined) safe['sourceAddress'] = payload.sourceAddress;
10268
10846
  if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
10269
10847
  if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
10270
10848
  if (payload.errorDetails !== undefined) {
@@ -10461,14 +11039,28 @@ function resolveOptions(options) {
10461
11039
  }
10462
11040
 
10463
11041
  /**
10464
- * Soft signal for the case where building or emitting a telemetry payload
10465
- * threw — for example, a buggy `TelemetryContextResolver`, a regression in
11042
+ * Emit a stable console warning when building or emitting a telemetry payload
11043
+ * throws — for example, a buggy `TelemetryContextResolver`, a regression in
10466
11044
  * `extractErrorDetails`, or a synchronous failure inside `emitAnalyticsLog`
10467
- * before it could swallow the error itself. Logged with a stable prefix so
10468
- * consumers can grep for it. We deliberately do not re-throw: the caller's
10469
- * original operation error must always win.
11045
+ * before it could swallow the error itself. Uses a stable prefix so the
11046
+ * drop is discoverable via grep. Never re-throws: the caller's original
11047
+ * operation error must always win.
10470
11048
  *
10471
11049
  * @internal
11050
+ *
11051
+ * @param eventType - The telemetry event type that was being emitted.
11052
+ * @param cause - The error or value that caused the drop.
11053
+ *
11054
+ * @example
11055
+ * ```typescript
11056
+ * import { warnTelemetryDrop } from '@core/utils'
11057
+ *
11058
+ * try {
11059
+ * void emitAnalyticsLog(payload)
11060
+ * } catch (err) {
11061
+ * warnTelemetryDrop('my_event', err)
11062
+ * }
11063
+ * ```
10472
11064
  */ function warnTelemetryDrop(eventType, cause) {
10473
11065
  try {
10474
11066
  // Pass `cause` as the second console.warn argument rather than
@@ -10508,6 +11100,9 @@ function resolveOptions(options) {
10508
11100
  ...context?.tokenOut != null && {
10509
11101
  tokenOut: context.tokenOut
10510
11102
  },
11103
+ ...context?.amountIn != null && {
11104
+ amountIn: context.amountIn
11105
+ },
10511
11106
  ...context?.txHash != null && {
10512
11107
  txHash: context.txHash
10513
11108
  },
@@ -10609,7 +11204,7 @@ function resolveOptions(options) {
10609
11204
  const stepEntry = stepEventMap.find(([name])=>name === failedStep?.name);
10610
11205
  // `failedStep.errorMessage` is intentionally **not** copied into the payload.
10611
11206
  // Provider messages are unbounded and frequently contain operator data
10612
- // (addresses, signatures, partial intent payloads, raw RPC responses). The
11207
+ // (signatures, partial intent payloads, raw RPC responses). The
10613
11208
  // step name plus the surrounding context fields already identify *which*
10614
11209
  // phase failed; the *why* is left to the corresponding step-level logs that
10615
11210
  // the provider emits separately. The thrown-error path (`extractErrorDetails`
@@ -10626,7 +11221,7 @@ function resolveOptions(options) {
10626
11221
  }
10627
11222
 
10628
11223
  var name$2 = "@circle-fin/bridge-kit";
10629
- var version$3 = "1.14.0";
11224
+ var version$3 = "1.14.1";
10630
11225
  var pkg$3 = {
10631
11226
  name: name$2,
10632
11227
  version: version$3};
@@ -12320,7 +12915,7 @@ var TransferSpeed;
12320
12915
  * })
12321
12916
  * ```
12322
12917
  */ function createLogger(options, stream) {
12323
- const { redact, ...pinoOptions } = {};
12918
+ const { redact, ...pinoOptions } = options ?? {};
12324
12919
  // Build redaction config
12325
12920
  const redactConfig = buildRedactConfig(redact);
12326
12921
  // Build final pino options, only include redact if defined
@@ -13674,6 +14269,8 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
13674
14269
  ;
13675
14270
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
13676
14271
  ;
14272
+ /** Gas units consumed by `depositForBurnWithHookAndFees` on an EVM chain (prepaid-FORWARD path). */ const DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM = 626_584n // avg of the last 10 txns
14273
+ ;
13677
14274
  // Gas FLOORS, not ceilings — kept separate from the fee-estimate averages
13678
14275
  // above. `executePreparedChainRequest` submits
13679
14276
  // max(estimate * buffer, floor), so a chain whose real cost exceeds the floor
@@ -15955,7 +16552,7 @@ const mockAttestationMessage = {
15955
16552
  return step;
15956
16553
  }
15957
16554
 
15958
- var version$2 = "1.12.0";
16555
+ var version$2 = "1.13.0";
15959
16556
  var pkg$2 = {
15960
16557
  version: version$2};
15961
16558
 
@@ -16661,6 +17258,9 @@ var pkg$2 = {
16661
17258
  }
16662
17259
  }
16663
17260
 
17261
+ const logger = createLogger({
17262
+ name: 'provider-cctp-v2'
17263
+ });
16664
17264
  function isPlainObject(value) {
16665
17265
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
16666
17266
  return false;
@@ -17023,6 +17623,116 @@ function assertCCTPV2Config(config) {
17023
17623
  return estimateResult;
17024
17624
  }
17025
17625
  /**
17626
+ * Estimate source-chain gas for a prepaid-FORWARD deposit burn via
17627
+ * `TokenMessengerWithFees.depositForBurnWithHookAndFees`.
17628
+ *
17629
+ * Builds a size-correct GenericExecutor hookData placeholder — using the
17630
+ * pre-validated `contracts.executor` and `contracts.depositForHandler` — so
17631
+ * the EVM calldata length matches production. Attempts a live
17632
+ * `eth_estimateGas` via the adapter's `cctp.v2.depositForBurnWithFees`
17633
+ * action and falls back to the static
17634
+ * {@link DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM} constant when the live
17635
+ * RPC call fails.
17636
+ *
17637
+ * Gateway eligibility is the caller's responsibility: validate the
17638
+ * destination chain with `resolveGatewayExecutorContracts` (UBK) before
17639
+ * calling this method.
17640
+ *
17641
+ * @param params - Estimation parameters including adapter, chains, amount,
17642
+ * the signed fee quote returned by the Quote API, and the pre-validated
17643
+ * Gateway executor contracts resolved by the caller.
17644
+ * @returns Promise resolving to the estimated (or fallback) gas cost.
17645
+ * @throws KitError `SERVICE_INTERNAL_ERROR` (FATAL) if `dstChain`'s
17646
+ * `usdcAddress` is `null`.
17647
+ *
17648
+ * @example
17649
+ * ```typescript
17650
+ * const contracts = resolveGatewayExecutorContracts(srcChain, dstChain)
17651
+ * const gasEstimate = await CCTPV2BridgingProvider.estimateDepositBurn({
17652
+ * adapter: evmAdapter,
17653
+ * srcChain: Ethereum,
17654
+ * dstChain: ArcTestnet,
17655
+ * amountMinorUnits: 100_000_000n,
17656
+ * refundAddress: '0xUserWallet',
17657
+ * signedQuote: quote.signedQuote,
17658
+ * feeToken: quote.feeToken,
17659
+ * feeTotalAmount: BigInt(quote.feeTotalAmount),
17660
+ * resolvedContext,
17661
+ * contracts,
17662
+ * })
17663
+ * ```
17664
+ */ static async estimateDepositBurn(params) {
17665
+ const { adapter, srcChain, dstChain, amountMinorUnits, refundAddress, signedQuote, feeToken, feeTotalAmount, resolvedContext, contracts } = params;
17666
+ const { executor, depositForHandler } = contracts;
17667
+ if (dstChain.usdcAddress === null) {
17668
+ throw new KitError({
17669
+ ...ServiceError.INTERNAL_ERROR,
17670
+ recoverability: 'FATAL',
17671
+ message: `Destination chain ${dstChain.name} has no USDC address configured`
17672
+ });
17673
+ }
17674
+ // Build a size-correct hookData for eth_estimateGas. The EVM uses calldata
17675
+ // length to compute gas, so the byte layout must match production even
17676
+ // though field values are not final.
17677
+ const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
17678
+ dappId: 'gateway_deposit',
17679
+ destinationChain: dstChain,
17680
+ handler: depositForHandler,
17681
+ params: [
17682
+ dstChain.usdcAddress,
17683
+ refundAddress,
17684
+ 0n
17685
+ ],
17686
+ recoveryAddress: padAddressToBytes32(refundAddress),
17687
+ // Override deployments only — spread the canonical function signature
17688
+ // and amount indices from DAPP_CONFIG so they stay in sync.
17689
+ config: {
17690
+ gateway_deposit: {
17691
+ ...DAPP_CONFIG.gateway_deposit,
17692
+ deployments: [
17693
+ {
17694
+ domainId: dstChain.cctp.domain
17695
+ }
17696
+ ]
17697
+ }
17698
+ }
17699
+ });
17700
+ const hookData = buildForwardingHookDataWithPayload(GENERIC_EXECUTOR_HOOK_DATA_VERSION, geBlob);
17701
+ // Coupling guard: the prepaid FORWARD path always requests a FORWARD fee
17702
+ // item, so the hookData must carry a `cctp-forward` frame — matches the
17703
+ // assertion in `prepareDepositForBurn`.
17704
+ assertForwardHookData(hookData);
17705
+ try {
17706
+ const prepared = await adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
17707
+ fromChain: srcChain,
17708
+ toChain: dstChain,
17709
+ amount: amountMinorUnits,
17710
+ mintRecipient: executor,
17711
+ destinationCaller: executor,
17712
+ claim: {
17713
+ signedQuote,
17714
+ refundAddress
17715
+ },
17716
+ feeToken,
17717
+ feeTotalAmount,
17718
+ hookData
17719
+ }, resolvedContext);
17720
+ return await prepared.estimate(undefined);
17721
+ } catch (err) {
17722
+ logger.debug('estimateDepositBurn: live eth_estimateGas failed, using static fallback', {
17723
+ err,
17724
+ chain: srcChain.name
17725
+ });
17726
+ try {
17727
+ return await adapter.calculateTransactionFee(DEPOSIT_FOR_BURN_WITH_FEES_GAS_ESTIMATE_EVM, undefined, srcChain);
17728
+ } catch (feeErr) {
17729
+ throw createRpcEndpointError(srcChain.name, {
17730
+ rawError: feeErr
17731
+ });
17732
+ }
17733
+ }
17734
+ }
17735
+ /**
17026
17736
  * Extracts OperationContext from bridge parameters for a given wallet context.
17027
17737
  *
17028
17738
  * This method extracts the chain and address information from the wallet context
@@ -17649,6 +18359,99 @@ function assertCCTPV2Config(config) {
17649
18359
  return await source.adapter.prepareAction('cctp.v2.depositForBurn', actionParams, resolvedContext);
17650
18360
  }
17651
18361
  /**
18362
+ * Prepare the source-chain `depositForBurnWithHookAndFees` call for the
18363
+ * GenericExecutor FORWARD path.
18364
+ *
18365
+ * Exposed as a public static method so the UBK fast-deposit flow can invoke
18366
+ * it directly without holding a provider instance. The byte layout mirrors
18367
+ * {@link CCTPV2BridgingProvider.estimateDepositBurn} so gas estimates and the
18368
+ * executed call agree.
18369
+ *
18370
+ * @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
18371
+ * @param params - Burn parameters including adapter, chains, deposit action,
18372
+ * amount, signer address, signed fee quote, and pre-resolved adapter context.
18373
+ * @returns The prepared `depositForBurnWithHookAndFees` burn transaction.
18374
+ * @throws {KitError} `UNSUPPORTED_ROUTE` when `dstChain` lacks a
18375
+ * GenericExecutor or DepositForHandler, or the `deposit.dappId` is unknown.
18376
+ *
18377
+ * @example
18378
+ * ```typescript
18379
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
18380
+ * import { Ethereum, ArcTestnet } from '@core/chains'
18381
+ *
18382
+ * const prepared = await CCTPV2BridgingProvider.prepareDepositForBurn({
18383
+ * adapter,
18384
+ * srcChain: Ethereum,
18385
+ * dstChain: ArcTestnet,
18386
+ * deposit: { dappId: 'gateway_deposit', params: [usdcAddress, recipient, 0n] },
18387
+ * amountMinorUnits: 100_000_000n,
18388
+ * refundAddress: '0xSender...',
18389
+ * signedQuote: quote.signedQuote,
18390
+ * feeToken: quote.feeToken,
18391
+ * feeTotalAmount: BigInt(quote.feeTotalAmount),
18392
+ * resolvedContext,
18393
+ * })
18394
+ * const txHash = await prepared.execute()
18395
+ * ```
18396
+ */ static async prepareDepositForBurn(params) {
18397
+ const { adapter, srcChain, dstChain, deposit, amountMinorUnits, refundAddress, signedQuote, feeToken, feeTotalAmount, resolvedContext } = params;
18398
+ // Resolve executor and depositForHandler from the destination chain's
18399
+ // gateway config. Throw an unsupported-route error if either is absent.
18400
+ const executor = dstChain.gateway?.contracts?.v1?.genericExecutor;
18401
+ const depositForHandler = dstChain.gateway?.contracts?.v1?.depositForHandler;
18402
+ if (!executor || !depositForHandler) {
18403
+ throw createUnsupportedRouteError(srcChain.name, dstChain.name);
18404
+ }
18405
+ // Resolve the canonical dApp config (function signature + amount indices)
18406
+ // for THIS deposit's `dappId`. Reject an unknown `dappId` rather than
18407
+ // encoding the wrong ABI selector, which would burn on the source but
18408
+ // revert in the executor call on the destination.
18409
+ const dappConfig = DAPP_CONFIG[deposit.dappId];
18410
+ if (dappConfig === undefined) {
18411
+ throw createUnsupportedRouteError(srcChain.name, dstChain.name);
18412
+ }
18413
+ // Build the bare GenericExecutor payload, then wrap it in the `cctp-forward`
18414
+ // frame required by the prepaid-FORWARD wrapper.
18415
+ const { hookData: geBlob } = buildDepositForGenericExecutorPayload({
18416
+ dappId: deposit.dappId,
18417
+ destinationChain: dstChain,
18418
+ handler: depositForHandler,
18419
+ params: deposit.params,
18420
+ recoveryAddress: padAddressToBytes32(refundAddress),
18421
+ config: {
18422
+ [deposit.dappId]: {
18423
+ ...dappConfig,
18424
+ deployments: [
18425
+ {
18426
+ domainId: dstChain.cctp.domain
18427
+ }
18428
+ ]
18429
+ }
18430
+ }
18431
+ });
18432
+ const hookData = buildForwardingHookDataWithPayload(GENERIC_EXECUTOR_HOOK_DATA_VERSION, geBlob);
18433
+ // Coupling guard: the prepaid FORWARD path always requests a FORWARD fee
18434
+ // item, so the hookData must carry a `cctp-forward` frame; otherwise the
18435
+ // wrapper reverts `ForwardFeeWithoutHook`.
18436
+ assertForwardHookData(hookData);
18437
+ // Source-chain burn: `mintRecipient` AND `destinationCaller` are both the
18438
+ // executor; fees are prepaid against the signed quote.
18439
+ return adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
18440
+ fromChain: srcChain,
18441
+ toChain: dstChain,
18442
+ amount: amountMinorUnits,
18443
+ mintRecipient: executor,
18444
+ destinationCaller: executor,
18445
+ hookData,
18446
+ claim: {
18447
+ signedQuote,
18448
+ refundAddress
18449
+ },
18450
+ feeToken,
18451
+ feeTotalAmount
18452
+ }, resolvedContext);
18453
+ }
18454
+ /**
17652
18455
  * Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
17653
18456
  *
17654
18457
  * Build the source-chain `depositForBurnWithHookAndFees` call. Fees are
@@ -17913,9 +18716,13 @@ function assertCCTPV2Config(config) {
17913
18716
 
17914
18717
  /**
17915
18718
  * Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
18719
+ *
18720
+ * @internal
17916
18721
  */ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
17917
18722
  /**
17918
18723
  * Base URL for Circle's Quote API (hosted in Iris) on testnet/sandbox.
18724
+ *
18725
+ * @internal
17919
18726
  */ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
17920
18727
  /**
17921
18728
  * Native fee-token sentinel (the zero address).
@@ -17923,19 +18730,25 @@ function assertCCTPV2Config(config) {
17923
18730
  * When `feeToken` is the zero address the quote prices fees in the source
17924
18731
  * chain's native gas token (paid as `msg.value` on-chain). Pass a USDC token
17925
18732
  * address instead to denominate fees in USDC.
18733
+ *
18734
+ * @internal
17926
18735
  */ const NATIVE_FEE_TOKEN = '0x0000000000000000000000000000000000000000';
17927
18736
  /**
17928
18737
  * API path prefix for the CCTP v2 USDC burn quote endpoint.
17929
18738
  *
17930
18739
  * The full path is `${QUOTE_BURN_USDC_PATH}/{sourceDomain}/{destinationDomain}`;
17931
18740
  * `usdc` is a fixed literal, not a token parameter.
18741
+ *
18742
+ * @internal
17932
18743
  */ const QUOTE_BURN_USDC_PATH = '/v2/quote/burn/usdc';
17933
18744
  /**
17934
18745
  * API path prefix for the CCTP v2 USDC quote validate endpoint.
17935
18746
  *
17936
18747
  * The full path is `${QUOTE_VALIDATE_USDC_PATH}/{sourceDomain}`; accepts a
17937
- * `POST { abiSignature, args }` body and returns whether the signed quote is
17938
- * currently claimable together with its authoritative expiry status.
18748
+ * `POST { abiSignature, args }` body and returns `claimable`, `failedChecks`,
18749
+ * and the decoded `expiry`, `feeToken`, and `feeTotalAmount`.
18750
+ *
18751
+ * @internal
17939
18752
  */ const QUOTE_VALIDATE_USDC_PATH = '/v2/quote/validate/usdc';
17940
18753
  /**
17941
18754
  * Default polling configuration for Quote API calls.
@@ -17952,13 +18765,19 @@ function assertCCTPV2Config(config) {
17952
18765
  * `Content-Type: application/json` and adds `User-Agent` in Node. Browser
17953
18766
  * requests omit a user-agent header to avoid a CORS preflight, so duplicating
17954
18767
  * either header here would be dead configuration.
18768
+ *
18769
+ * @internal
17955
18770
  */ const FEE_QUOTE_DEFAULT_CONFIG = {
17956
18771
  timeout: 15_000,
17957
18772
  maxRetries: 1,
17958
18773
  retryDelay: 200
17959
18774
  };
17960
18775
 
17961
- /** Decimal string in token minor units, constrained to be strictly positive. */ const positiveAmountSchema = zod.z.string().regex(/^\d+$/, 'must be a non-negative integer string')// Re-check the digit shape here: zod still runs this refinement when the
18776
+ /**
18777
+ * Decimal string in token minor units, constrained to be strictly positive.
18778
+ *
18779
+ * @internal
18780
+ */ const positiveAmountSchema = zod.z.string().regex(/^\d+$/, 'must be a non-negative integer string')// Re-check the digit shape here: zod still runs this refinement when the
17962
18781
  // regex check above fails ("dirty"), so guard BigInt() against throwing on a
17963
18782
  // non-numeric value before comparing.
17964
18783
  .refine((value)=>/^\d+$/.test(value) && BigInt(value) > 0n, 'must be greater than zero');
@@ -17993,11 +18812,19 @@ const forwardRequestSchema = zod.z.object({
17993
18812
  const preFinalityRequestSchema = zod.z.object({
17994
18813
  type: zod.z.literal('PRE_FINALITY')
17995
18814
  }).strict();
17996
- /** A single quote request item (`FORWARD` or `PRE_FINALITY`). */ const feeQuoteRequestSchema = zod.z.discriminatedUnion('type', [
18815
+ /**
18816
+ * A single quote request item (`FORWARD` or `PRE_FINALITY`).
18817
+ *
18818
+ * @internal
18819
+ */ const feeQuoteRequestSchema = zod.z.discriminatedUnion('type', [
17997
18820
  forwardRequestSchema,
17998
18821
  preFinalityRequestSchema
17999
18822
  ]);
18000
- /** A non-empty list of quote request items with unique types. */ const feeQuoteRequestsSchema = zod.z.array(feeQuoteRequestSchema).min(1, 'at least one request item is required').refine((items)=>new Set(items.map((item)=>item.type)).size === items.length, 'request item types must be unique');
18823
+ /**
18824
+ * A non-empty list of quote request items with unique types.
18825
+ *
18826
+ * @internal
18827
+ */ const feeQuoteRequestsSchema = zod.z.array(feeQuoteRequestSchema).min(1, 'at least one request item is required').refine((items)=>new Set(items.map((item)=>item.type)).size === items.length, 'request item types must be unique');
18001
18828
  /**
18002
18829
  * A structured `Partial<ApiPollingConfig>` polling override.
18003
18830
  *
@@ -18025,6 +18852,8 @@ const preFinalityRequestSchema = zod.z.object({
18025
18852
  * it `undefined`, which is falsy and silently selects the production base URL.
18026
18853
  * (`buildFeeQuoteUrl` independently re-validates the domains for standalone
18027
18854
  * callers.)
18855
+ *
18856
+ * @internal
18028
18857
  */ const fetchFeeQuoteInputSchema = zod.z.object({
18029
18858
  sourceDomain: zod.z.number().int().nonnegative(),
18030
18859
  destinationDomain: zod.z.number().int().nonnegative(),
@@ -18060,7 +18889,11 @@ const metadataSchema = zod.z.object({
18060
18889
  blockEstimatedAt: zod.z.number().int().nonnegative().optional()
18061
18890
  }).passthrough()
18062
18891
  ]);
18063
- /** Schema for a signed fee quote returned by the Quote API. */ const signedFeeQuoteSchema = zod.z.object({
18892
+ /**
18893
+ * Schema for a signed fee quote returned by the Quote API.
18894
+ *
18895
+ * @internal
18896
+ */ const signedFeeQuoteSchema = zod.z.object({
18064
18897
  // The runtime YAML spec maps signedQuote to a looser `hex` (which allows
18065
18898
  // an empty `0x`); we keep the stricter non-empty form. Do not relax
18066
18899
  // without a reason.
@@ -18089,10 +18922,16 @@ const metadataSchema = zod.z.object({
18089
18922
  * console.log(payload.feeTotalAmount)
18090
18923
  * }
18091
18924
  * ```
18925
+ *
18926
+ * @internal
18092
18927
  */ function isSignedFeeQuote(value) {
18093
18928
  return signedFeeQuoteSchema.safeParse(value).success;
18094
18929
  }
18095
- /** Validate input to {@link validateQuote}. */ const validateQuoteInputSchema = zod.z.object({
18930
+ /**
18931
+ * Validates input to {@link validateQuote}.
18932
+ *
18933
+ * @internal
18934
+ */ const validateQuoteInputSchema = zod.z.object({
18096
18935
  sourceDomain: zod.z.number().int().nonnegative(),
18097
18936
  abiSignature: zod.z.string().min(1),
18098
18937
  args: zod.z.array(zod.z.union([
@@ -18127,11 +18966,13 @@ const validateQuoteItemSchema = zod.z.object({
18127
18966
  computedArgsHash: bytes32Schema.optional()
18128
18967
  }).passthrough();
18129
18968
  /**
18130
- * Schema for a response from the Quote API validation endpoint.
18969
+ * Schema for a validate-quote result returned by the Iris `/validate/usdc/:sourceDomain` endpoint.
18131
18970
  *
18132
18971
  * The endpoint takes the source domain as a URL path parameter and does not
18133
18972
  * return it in the response body, so `sourceDomain` is intentionally not part
18134
18973
  * of this schema.
18974
+ *
18975
+ * @internal
18135
18976
  */ const validateQuoteResultSchema = zod.z.object({
18136
18977
  signedQuote: nonEmptyHexSchema,
18137
18978
  expiry: quoteExpiryStatusSchema,
@@ -18145,20 +18986,22 @@ const validateQuoteItemSchema = zod.z.object({
18145
18986
  items: zod.z.array(validateQuoteItemSchema)
18146
18987
  }).passthrough();
18147
18988
  /**
18148
- * Validate a Quote API validation response.
18989
+ * Validate that an unknown value is a validate-quote result.
18149
18990
  *
18150
- * @param value - The unknown response value.
18151
- * @returns `true` when the value has the expected validation response shape.
18991
+ * @param value - The unknown value to validate.
18992
+ * @returns `true` when the value matches the validate-quote response shape.
18152
18993
  *
18153
18994
  * @example
18154
18995
  * ```typescript
18155
18996
  * import { isValidateQuoteResult } from '@circle-fin/provider-fee-v1'
18156
18997
  *
18157
- * declare const response: unknown
18158
- * if (isValidateQuoteResult(response)) {
18159
- * console.log(response.claimable)
18998
+ * declare const payload: unknown
18999
+ * if (isValidateQuoteResult(payload)) {
19000
+ * console.log(payload.claimable, payload.failedChecks)
18160
19001
  * }
18161
19002
  * ```
19003
+ *
19004
+ * @internal
18162
19005
  */ function isValidateQuoteResult(value) {
18163
19006
  return validateQuoteResultSchema.safeParse(value).success;
18164
19007
  }
@@ -18207,6 +19050,8 @@ const validateQuoteItemSchema = zod.z.object({
18207
19050
  * })
18208
19051
  * // => 'https://iris-api.circle.com/v2/quote/burn/usdc/3/26'
18209
19052
  * ```
19053
+ *
19054
+ * @internal
18210
19055
  */ function buildFeeQuoteUrl(params) {
18211
19056
  const { sourceDomain, destinationDomain, isTestnet, baseUrl } = params;
18212
19057
  assertDomain(sourceDomain, 'sourceDomain');
@@ -18333,6 +19178,8 @@ const OPERATION$1 = 'getFeeQuote';
18333
19178
  * })
18334
19179
  * console.log(quote.feeTotalAmount, quote.expiry)
18335
19180
  * ```
19181
+ *
19182
+ * @internal
18336
19183
  */ async function fetchFeeQuote(params) {
18337
19184
  const { sourceDomain, destinationDomain, amount, requests, feeToken, isTestnet, baseUrl, config } = params;
18338
19185
  const parsed = fetchFeeQuoteInputSchema.safeParse({
@@ -18407,7 +19254,13 @@ const OPERATION$1 = 'getFeeQuote';
18407
19254
  const SERVICE = 'Quote API';
18408
19255
  const OPERATION = 'validateQuote';
18409
19256
  /**
18410
- * Validate a signed quote against a complete source-chain contract call.
19257
+ * Validate a signed fee quote against a full on-chain call via Circle's Iris
19258
+ * `/v2/quote/validate/usdc/:sourceDomain` endpoint.
19259
+ *
19260
+ * POSTs the ABI function signature and the encoded call arguments to Iris,
19261
+ * which verifies the signature, checks expiry, confirms the argsHash committed
19262
+ * in the quote matches the submitted args, and returns `claimable` plus the
19263
+ * decoded `expiry`, `feeToken`, and `feeTotalAmount`.
18411
19264
  *
18412
19265
  * @param params - The source domain, ABI signature, call arguments, and environment.
18413
19266
  * @returns The claimability, binding checks, and authoritative expiry status.
@@ -18434,7 +19287,12 @@ const OPERATION = 'validateQuote';
18434
19287
  * isTestnet: false,
18435
19288
  * })
18436
19289
  * console.log(result.claimable, result.expiry.secondsRemaining)
19290
+ * if (!result.claimable) {
19291
+ * console.error('Quote rejected:', result.failedChecks)
19292
+ * }
18437
19293
  * ```
19294
+ *
19295
+ * @internal
18438
19296
  */ async function validateQuote(params) {
18439
19297
  const parsed = validateQuoteInputSchema.safeParse(params);
18440
19298
  if (!parsed.success) {
@@ -18497,9 +19355,14 @@ function isQuoteNearEstimatedExpiry(quote) {
18497
19355
  }
18498
19356
  function assertSourceFeeRoute(params) {
18499
19357
  const { source, destination, config } = params;
19358
+ // Keep these checks explicit so a successful assertion guarantees the CCTP
19359
+ // v2 narrowing; hasSourceFeeSupport returns a plain boolean.
18500
19360
  if (source.chain.type !== 'evm' || destination.chain.type !== 'evm' || !isCCTPV2Supported(source.chain) || !isCCTPV2Supported(destination.chain)) {
18501
19361
  throw createUnsupportedRouteError(source.chain.name, destination.chain.name);
18502
19362
  }
19363
+ if (!hasSourceFeeSupport(source.chain)) {
19364
+ throw createValidationFailedError$1('config.feePayment', 'source', `Source-paid ("receive-exact") fees are not supported from ${source.chain.name}. ` + 'Use the default destination-paid fees for this source chain');
19365
+ }
18503
19366
  const useForwarder = destination.useForwarder;
18504
19367
  if (useForwarder !== true) {
18505
19368
  throw createValidationFailedError$1('to.useForwarder', useForwarder, "feePayment: 'source' requires useForwarder: true");
@@ -19587,7 +20450,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
19587
20450
  };
19588
20451
 
19589
20452
  var name$1 = "@circle-fin/swap-kit";
19590
- var version$1 = "1.6.0";
20453
+ var version$1 = "1.6.1";
19591
20454
  var pkg$1 = {
19592
20455
  name: name$1,
19593
20456
  version: version$1};
@@ -21609,7 +22472,7 @@ new Set(Object.values(Blockchain));
21609
22472
  registerKit(`${pkg$1.name}/${pkg$1.version}`);
21610
22473
 
21611
22474
  var name = "@circle-fin/earn-kit";
21612
- var version = "1.6.0";
22475
+ var version = "1.6.1";
21613
22476
  var pkg = {
21614
22477
  name: name,
21615
22478
  version: version};