@circle-fin/app-kit 1.12.1 → 1.13.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/CHANGELOG.md +59 -0
- package/README.md +31 -12
- package/bridge.cjs +2214 -186
- package/bridge.d.cts +86 -17
- package/bridge.d.mts +86 -17
- package/bridge.d.ts +86 -17
- package/bridge.mjs +2216 -188
- package/chains.cjs +134 -0
- package/chains.d.cts +126 -1
- package/chains.d.mts +126 -1
- package/chains.d.ts +126 -1
- package/chains.mjs +133 -1
- package/context.d.cts +86 -17
- package/context.d.mts +86 -17
- package/context.d.ts +86 -17
- package/earn.cjs +467 -48
- package/earn.d.cts +86 -17
- package/earn.d.mts +86 -17
- package/earn.d.ts +86 -17
- package/earn.mjs +467 -48
- package/estimateBridge.cjs +2214 -186
- package/estimateBridge.d.cts +196 -18
- package/estimateBridge.d.mts +196 -18
- package/estimateBridge.d.ts +196 -18
- package/estimateBridge.mjs +2216 -188
- package/estimateSwap.cjs +571 -131
- package/estimateSwap.d.cts +86 -17
- package/estimateSwap.d.mts +86 -17
- package/estimateSwap.d.ts +86 -17
- package/estimateSwap.mjs +571 -131
- package/index.cjs +2101 -434
- package/index.d.cts +949 -147
- package/index.d.mts +949 -147
- package/index.d.ts +949 -147
- package/index.mjs +2103 -436
- package/package.json +6 -6
- package/swap.cjs +571 -131
- package/swap.d.cts +93 -18
- package/swap.d.mts +93 -18
- package/swap.d.ts +93 -18
- package/swap.mjs +571 -131
- package/unifiedBalance.cjs +384 -67
- package/unifiedBalance.d.cts +76 -2
- package/unifiedBalance.d.mts +76 -2
- package/unifiedBalance.d.ts +76 -2
- package/unifiedBalance.mjs +384 -67
package/index.mjs
CHANGED
|
@@ -30,9 +30,9 @@ if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
|
30
30
|
import { z } from 'zod';
|
|
31
31
|
import pino from 'pino';
|
|
32
32
|
import { parseUnits as parseUnits$1, formatUnits as formatUnits$1 } from '@ethersproject/units';
|
|
33
|
-
import { hexlify, hexZeroPad } from '@ethersproject/bytes';
|
|
33
|
+
import { hexlify, hexZeroPad, concat } from '@ethersproject/bytes';
|
|
34
34
|
import '@ethersproject/abi';
|
|
35
|
-
import { getAddress } from '@ethersproject/address';
|
|
35
|
+
import { getAddress, isAddress } from '@ethersproject/address';
|
|
36
36
|
import bs58 from 'bs58';
|
|
37
37
|
import { PublicKey } from '@solana/web3.js';
|
|
38
38
|
import 'bn.js';
|
|
@@ -4064,6 +4064,8 @@ function getOptionalString(value) {
|
|
|
4064
4064
|
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
4065
4065
|
Blockchain["Pharos"] = "Pharos";
|
|
4066
4066
|
Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
4067
|
+
Blockchain["Plasma"] = "Plasma";
|
|
4068
|
+
Blockchain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
4067
4069
|
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
4068
4070
|
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
4069
4071
|
Blockchain["Plume"] = "Plume";
|
|
@@ -4133,6 +4135,7 @@ var BridgeChain;
|
|
|
4133
4135
|
BridgeChain["Morph"] = "Morph";
|
|
4134
4136
|
BridgeChain["Optimism"] = "Optimism";
|
|
4135
4137
|
BridgeChain["Pharos"] = "Pharos";
|
|
4138
|
+
BridgeChain["Plasma"] = "Plasma";
|
|
4136
4139
|
BridgeChain["Plume"] = "Plume";
|
|
4137
4140
|
BridgeChain["Polygon"] = "Polygon";
|
|
4138
4141
|
BridgeChain["Sei"] = "Sei";
|
|
@@ -4159,6 +4162,7 @@ var BridgeChain;
|
|
|
4159
4162
|
BridgeChain["Morph_Testnet"] = "Morph_Testnet";
|
|
4160
4163
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
4161
4164
|
BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
4165
|
+
BridgeChain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
4162
4166
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
4163
4167
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
4164
4168
|
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
@@ -4616,6 +4620,8 @@ var EarnChain;
|
|
|
4616
4620
|
* This program handles minting operations for Gateway transactions
|
|
4617
4621
|
* on Solana devnet.
|
|
4618
4622
|
*/ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
|
|
4623
|
+
/** TokenMessengerWithFees address shared by enabled EVM mainnet sources. */ const TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET = '0x71f54F818671cD0D7ea140Da213e5C8b5C92a408';
|
|
4624
|
+
/** TokenMessengerWithFees address shared by enabled EVM testnet sources. */ const TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET = '0x8745D906D67C346E5eb1aEEED38Eb87F34DF0C0A';
|
|
4619
4625
|
/**
|
|
4620
4626
|
* Circle's fee recipient address for EVM chains.
|
|
4621
4627
|
*
|
|
@@ -4663,6 +4669,7 @@ var EarnChain;
|
|
|
4663
4669
|
v2: {
|
|
4664
4670
|
type: 'split',
|
|
4665
4671
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4672
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4666
4673
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4667
4674
|
confirmations: 1,
|
|
4668
4675
|
fastConfirmations: 1
|
|
@@ -4730,6 +4737,7 @@ var EarnChain;
|
|
|
4730
4737
|
v2: {
|
|
4731
4738
|
type: 'split',
|
|
4732
4739
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4740
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4733
4741
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4734
4742
|
confirmations: 65,
|
|
4735
4743
|
fastConfirmations: 1
|
|
@@ -4794,6 +4802,7 @@ var EarnChain;
|
|
|
4794
4802
|
v2: {
|
|
4795
4803
|
type: 'split',
|
|
4796
4804
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4805
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4797
4806
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4798
4807
|
confirmations: 65,
|
|
4799
4808
|
fastConfirmations: 1
|
|
@@ -4858,6 +4867,7 @@ var EarnChain;
|
|
|
4858
4867
|
v2: {
|
|
4859
4868
|
type: 'split',
|
|
4860
4869
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4870
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4861
4871
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4862
4872
|
confirmations: 1,
|
|
4863
4873
|
fastConfirmations: 1
|
|
@@ -4919,6 +4929,7 @@ var EarnChain;
|
|
|
4919
4929
|
v2: {
|
|
4920
4930
|
type: 'split',
|
|
4921
4931
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4932
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4922
4933
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
4923
4934
|
confirmations: 1,
|
|
4924
4935
|
fastConfirmations: 1
|
|
@@ -4986,6 +4997,7 @@ var EarnChain;
|
|
|
4986
4997
|
v2: {
|
|
4987
4998
|
type: 'split',
|
|
4988
4999
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5000
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4989
5001
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4990
5002
|
confirmations: 65,
|
|
4991
5003
|
fastConfirmations: 1
|
|
@@ -5050,6 +5062,7 @@ var EarnChain;
|
|
|
5050
5062
|
v2: {
|
|
5051
5063
|
type: 'split',
|
|
5052
5064
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5065
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5053
5066
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5054
5067
|
confirmations: 65,
|
|
5055
5068
|
fastConfirmations: 1
|
|
@@ -5160,6 +5173,7 @@ var EarnChain;
|
|
|
5160
5173
|
v2: {
|
|
5161
5174
|
type: 'split',
|
|
5162
5175
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5176
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5163
5177
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5164
5178
|
confirmations: 65,
|
|
5165
5179
|
fastConfirmations: 1
|
|
@@ -5204,6 +5218,7 @@ var EarnChain;
|
|
|
5204
5218
|
v2: {
|
|
5205
5219
|
type: 'split',
|
|
5206
5220
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5221
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5207
5222
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5208
5223
|
confirmations: 65,
|
|
5209
5224
|
fastConfirmations: 1
|
|
@@ -5435,6 +5450,7 @@ var EarnChain;
|
|
|
5435
5450
|
v2: {
|
|
5436
5451
|
type: 'split',
|
|
5437
5452
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5453
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5438
5454
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5439
5455
|
confirmations: 65,
|
|
5440
5456
|
fastConfirmations: 2
|
|
@@ -5499,6 +5515,7 @@ var EarnChain;
|
|
|
5499
5515
|
v2: {
|
|
5500
5516
|
type: 'split',
|
|
5501
5517
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5518
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5502
5519
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5503
5520
|
confirmations: 65,
|
|
5504
5521
|
fastConfirmations: 2
|
|
@@ -5609,6 +5626,7 @@ var EarnChain;
|
|
|
5609
5626
|
v2: {
|
|
5610
5627
|
type: 'split',
|
|
5611
5628
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5629
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5612
5630
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5613
5631
|
confirmations: 1,
|
|
5614
5632
|
fastConfirmations: 1
|
|
@@ -5668,6 +5686,7 @@ var EarnChain;
|
|
|
5668
5686
|
v2: {
|
|
5669
5687
|
type: 'split',
|
|
5670
5688
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5689
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5671
5690
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5672
5691
|
confirmations: 1,
|
|
5673
5692
|
fastConfirmations: 1
|
|
@@ -5822,6 +5841,7 @@ var EarnChain;
|
|
|
5822
5841
|
v2: {
|
|
5823
5842
|
type: 'split',
|
|
5824
5843
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5844
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5825
5845
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5826
5846
|
confirmations: 65,
|
|
5827
5847
|
fastConfirmations: 1
|
|
@@ -5869,6 +5889,7 @@ var EarnChain;
|
|
|
5869
5889
|
v2: {
|
|
5870
5890
|
type: 'split',
|
|
5871
5891
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5892
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5872
5893
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5873
5894
|
confirmations: 65,
|
|
5874
5895
|
fastConfirmations: 1
|
|
@@ -5913,6 +5934,7 @@ var EarnChain;
|
|
|
5913
5934
|
v2: {
|
|
5914
5935
|
type: 'split',
|
|
5915
5936
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5937
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5916
5938
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5917
5939
|
confirmations: 1,
|
|
5918
5940
|
fastConfirmations: 1
|
|
@@ -5958,6 +5980,7 @@ var EarnChain;
|
|
|
5958
5980
|
v2: {
|
|
5959
5981
|
type: 'split',
|
|
5960
5982
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5983
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5961
5984
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5962
5985
|
confirmations: 1,
|
|
5963
5986
|
fastConfirmations: 1
|
|
@@ -6004,6 +6027,7 @@ var EarnChain;
|
|
|
6004
6027
|
v2: {
|
|
6005
6028
|
type: 'split',
|
|
6006
6029
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6030
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6007
6031
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6008
6032
|
confirmations: 1,
|
|
6009
6033
|
fastConfirmations: 1
|
|
@@ -6317,6 +6341,7 @@ var EarnChain;
|
|
|
6317
6341
|
v2: {
|
|
6318
6342
|
type: 'split',
|
|
6319
6343
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6344
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6320
6345
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6321
6346
|
confirmations: 65,
|
|
6322
6347
|
fastConfirmations: 1
|
|
@@ -6381,6 +6406,7 @@ var EarnChain;
|
|
|
6381
6406
|
v2: {
|
|
6382
6407
|
type: 'split',
|
|
6383
6408
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
6409
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6384
6410
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
6385
6411
|
confirmations: 65,
|
|
6386
6412
|
fastConfirmations: 1
|
|
@@ -6501,6 +6527,98 @@ var EarnChain;
|
|
|
6501
6527
|
}
|
|
6502
6528
|
});
|
|
6503
6529
|
|
|
6530
|
+
/**
|
|
6531
|
+
* Plasma Mainnet chain definition
|
|
6532
|
+
* @remarks
|
|
6533
|
+
* This represents the official production network for the Plasma blockchain.
|
|
6534
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
6535
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
6536
|
+
*/ const Plasma = defineChain({
|
|
6537
|
+
type: 'evm',
|
|
6538
|
+
chain: Blockchain.Plasma,
|
|
6539
|
+
name: 'Plasma',
|
|
6540
|
+
title: 'Plasma Mainnet',
|
|
6541
|
+
nativeCurrency: {
|
|
6542
|
+
name: 'Plasma',
|
|
6543
|
+
symbol: 'XPL',
|
|
6544
|
+
decimals: 18
|
|
6545
|
+
},
|
|
6546
|
+
chainId: 9745,
|
|
6547
|
+
isTestnet: false,
|
|
6548
|
+
explorerUrl: 'https://plasmascan.to/tx/{hash}',
|
|
6549
|
+
rpcEndpoints: [
|
|
6550
|
+
'https://rpc.plasma.to'
|
|
6551
|
+
],
|
|
6552
|
+
eurcAddress: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
6553
|
+
usdcAddress: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
6554
|
+
usdtAddress: null,
|
|
6555
|
+
cctp: {
|
|
6556
|
+
domain: 33,
|
|
6557
|
+
contracts: {
|
|
6558
|
+
v2: {
|
|
6559
|
+
type: 'split',
|
|
6560
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6561
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6562
|
+
confirmations: 3,
|
|
6563
|
+
fastConfirmations: 1
|
|
6564
|
+
}
|
|
6565
|
+
},
|
|
6566
|
+
forwarderSupported: {
|
|
6567
|
+
source: false,
|
|
6568
|
+
destination: false
|
|
6569
|
+
}
|
|
6570
|
+
},
|
|
6571
|
+
kitContracts: {
|
|
6572
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
6573
|
+
}
|
|
6574
|
+
});
|
|
6575
|
+
|
|
6576
|
+
/**
|
|
6577
|
+
* Plasma Testnet chain definition
|
|
6578
|
+
* @remarks
|
|
6579
|
+
* This represents the official test network for the Plasma blockchain.
|
|
6580
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
6581
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
6582
|
+
*/ const PlasmaTestnet = defineChain({
|
|
6583
|
+
type: 'evm',
|
|
6584
|
+
chain: Blockchain.Plasma_Testnet,
|
|
6585
|
+
name: 'Plasma Testnet',
|
|
6586
|
+
title: 'Plasma Testnet',
|
|
6587
|
+
nativeCurrency: {
|
|
6588
|
+
name: 'Plasma',
|
|
6589
|
+
symbol: 'XPL',
|
|
6590
|
+
decimals: 18
|
|
6591
|
+
},
|
|
6592
|
+
chainId: 9746,
|
|
6593
|
+
isTestnet: true,
|
|
6594
|
+
explorerUrl: 'https://testnet.plasmascan.to/tx/{hash}',
|
|
6595
|
+
rpcEndpoints: [
|
|
6596
|
+
'https://testnet-rpc.plasma.to'
|
|
6597
|
+
],
|
|
6598
|
+
eurcAddress: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330',
|
|
6599
|
+
usdcAddress: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
6600
|
+
usdtAddress: null,
|
|
6601
|
+
cctp: {
|
|
6602
|
+
domain: 33,
|
|
6603
|
+
contracts: {
|
|
6604
|
+
v2: {
|
|
6605
|
+
type: 'split',
|
|
6606
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6607
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6608
|
+
confirmations: 3,
|
|
6609
|
+
fastConfirmations: 1
|
|
6610
|
+
}
|
|
6611
|
+
},
|
|
6612
|
+
forwarderSupported: {
|
|
6613
|
+
source: false,
|
|
6614
|
+
destination: false
|
|
6615
|
+
}
|
|
6616
|
+
},
|
|
6617
|
+
kitContracts: {
|
|
6618
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
6619
|
+
}
|
|
6620
|
+
});
|
|
6621
|
+
|
|
6504
6622
|
/**
|
|
6505
6623
|
* Plume Mainnet chain definition
|
|
6506
6624
|
* @remarks
|
|
@@ -6532,6 +6650,7 @@ var EarnChain;
|
|
|
6532
6650
|
v2: {
|
|
6533
6651
|
type: 'split',
|
|
6534
6652
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6653
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6535
6654
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6536
6655
|
confirmations: 65,
|
|
6537
6656
|
fastConfirmations: 1
|
|
@@ -6578,6 +6697,7 @@ var EarnChain;
|
|
|
6578
6697
|
v2: {
|
|
6579
6698
|
type: 'split',
|
|
6580
6699
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6700
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6581
6701
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6582
6702
|
confirmations: 65,
|
|
6583
6703
|
fastConfirmations: 1
|
|
@@ -6679,6 +6799,7 @@ var EarnChain;
|
|
|
6679
6799
|
v2: {
|
|
6680
6800
|
type: 'split',
|
|
6681
6801
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6802
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6682
6803
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6683
6804
|
confirmations: 33,
|
|
6684
6805
|
fastConfirmations: 13
|
|
@@ -6744,6 +6865,7 @@ var EarnChain;
|
|
|
6744
6865
|
v2: {
|
|
6745
6866
|
type: 'split',
|
|
6746
6867
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6868
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6747
6869
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6748
6870
|
confirmations: 33,
|
|
6749
6871
|
fastConfirmations: 13
|
|
@@ -6803,6 +6925,7 @@ var EarnChain;
|
|
|
6803
6925
|
v2: {
|
|
6804
6926
|
type: 'split',
|
|
6805
6927
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6928
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6806
6929
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6807
6930
|
confirmations: 1,
|
|
6808
6931
|
fastConfirmations: 1
|
|
@@ -6862,6 +6985,7 @@ var EarnChain;
|
|
|
6862
6985
|
v2: {
|
|
6863
6986
|
type: 'split',
|
|
6864
6987
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6988
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6865
6989
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6866
6990
|
confirmations: 1,
|
|
6867
6991
|
fastConfirmations: 1
|
|
@@ -6919,6 +7043,7 @@ var EarnChain;
|
|
|
6919
7043
|
v2: {
|
|
6920
7044
|
type: 'split',
|
|
6921
7045
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7046
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6922
7047
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6923
7048
|
confirmations: 1,
|
|
6924
7049
|
fastConfirmations: 1
|
|
@@ -6977,6 +7102,7 @@ var EarnChain;
|
|
|
6977
7102
|
v2: {
|
|
6978
7103
|
type: 'split',
|
|
6979
7104
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7105
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6980
7106
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6981
7107
|
confirmations: 1,
|
|
6982
7108
|
fastConfirmations: 1
|
|
@@ -7292,6 +7418,7 @@ var EarnChain;
|
|
|
7292
7418
|
v2: {
|
|
7293
7419
|
type: 'split',
|
|
7294
7420
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7421
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
7295
7422
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7296
7423
|
confirmations: 65,
|
|
7297
7424
|
fastConfirmations: 1
|
|
@@ -7356,6 +7483,7 @@ var EarnChain;
|
|
|
7356
7483
|
v2: {
|
|
7357
7484
|
type: 'split',
|
|
7358
7485
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7486
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
7359
7487
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
7360
7488
|
confirmations: 65,
|
|
7361
7489
|
fastConfirmations: 1
|
|
@@ -7413,6 +7541,7 @@ var EarnChain;
|
|
|
7413
7541
|
v2: {
|
|
7414
7542
|
type: 'split',
|
|
7415
7543
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cF5d',
|
|
7544
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
7416
7545
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7417
7546
|
confirmations: 65,
|
|
7418
7547
|
fastConfirmations: 1
|
|
@@ -7472,6 +7601,7 @@ var EarnChain;
|
|
|
7472
7601
|
v2: {
|
|
7473
7602
|
type: 'split',
|
|
7474
7603
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
7604
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
7475
7605
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
7476
7606
|
confirmations: 65,
|
|
7477
7607
|
fastConfirmations: 1
|
|
@@ -7532,6 +7662,7 @@ var EarnChain;
|
|
|
7532
7662
|
v2: {
|
|
7533
7663
|
type: 'split',
|
|
7534
7664
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7665
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
7535
7666
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7536
7667
|
confirmations: 3,
|
|
7537
7668
|
fastConfirmations: 3
|
|
@@ -7577,6 +7708,7 @@ var EarnChain;
|
|
|
7577
7708
|
v2: {
|
|
7578
7709
|
type: 'split',
|
|
7579
7710
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7711
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
7580
7712
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
7581
7713
|
confirmations: 3,
|
|
7582
7714
|
fastConfirmations: 1
|
|
@@ -7787,6 +7919,8 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
7787
7919
|
OptimismSepolia: OptimismSepolia,
|
|
7788
7920
|
Pharos: Pharos,
|
|
7789
7921
|
PharosTestnet: PharosTestnet,
|
|
7922
|
+
Plasma: Plasma,
|
|
7923
|
+
PlasmaTestnet: PlasmaTestnet,
|
|
7790
7924
|
Plume: Plume,
|
|
7791
7925
|
PlumeTestnet: PlumeTestnet,
|
|
7792
7926
|
PolkadotAssetHub: PolkadotAssetHub,
|
|
@@ -7836,6 +7970,34 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
7836
7970
|
return chain.cctp?.contracts.v2 !== undefined;
|
|
7837
7971
|
}
|
|
7838
7972
|
|
|
7973
|
+
/**
|
|
7974
|
+
* Check whether a chain supports source-paid ("receive-exact") CCTP v2 fees.
|
|
7975
|
+
*
|
|
7976
|
+
* A chain supports source-paid fees when its CCTP v2 configuration carries a
|
|
7977
|
+
* deployed `TokenMessengerWithFees` wrapper address. Bridge Kit routes
|
|
7978
|
+
* `feePayment: 'source'` transfers through this wrapper via
|
|
7979
|
+
* `depositForBurnWithHookAndFees`, so a chain without the wrapper cannot be a
|
|
7980
|
+
* source for receive-exact bridging.
|
|
7981
|
+
*
|
|
7982
|
+
* @param chain - The chain definition to check.
|
|
7983
|
+
* @returns `true` when the chain has a `tokenMessengerWithFees` wrapper
|
|
7984
|
+
* configured, `false` otherwise.
|
|
7985
|
+
*
|
|
7986
|
+
* @example
|
|
7987
|
+
* ```typescript
|
|
7988
|
+
* import { Chains, hasSourceFeeSupport } from '@core/chains'
|
|
7989
|
+
*
|
|
7990
|
+
* hasSourceFeeSupport(Chains.Optimism) // true
|
|
7991
|
+
* hasSourceFeeSupport(Chains.Solana) // false
|
|
7992
|
+
* ```
|
|
7993
|
+
*/ function hasSourceFeeSupport(chain) {
|
|
7994
|
+
if (!isCCTPV2Supported(chain)) {
|
|
7995
|
+
return false;
|
|
7996
|
+
}
|
|
7997
|
+
const wrapper = chain.cctp.contracts.v2.tokenMessengerWithFees;
|
|
7998
|
+
return typeof wrapper === 'string' && wrapper.length > 0;
|
|
7999
|
+
}
|
|
8000
|
+
|
|
7839
8001
|
/**
|
|
7840
8002
|
* Check if a chain supports a specific type of custom smart contract logic.
|
|
7841
8003
|
*
|
|
@@ -10163,6 +10325,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10163
10325
|
[Blockchain.Noble]: 'uusdc',
|
|
10164
10326
|
[Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
10165
10327
|
[Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
10328
|
+
[Blockchain.Plasma]: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
10166
10329
|
[Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
10167
10330
|
[Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
10168
10331
|
[Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
@@ -10199,6 +10362,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10199
10362
|
[Blockchain.Noble_Testnet]: 'uusdc',
|
|
10200
10363
|
[Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
10201
10364
|
[Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
10365
|
+
[Blockchain.Plasma_Testnet]: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
10202
10366
|
[Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
10203
10367
|
[Blockchain.Polkadot_Westmint]: '31337',
|
|
10204
10368
|
[Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -10261,6 +10425,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10261
10425
|
[Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
|
|
10262
10426
|
[Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
|
|
10263
10427
|
[Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
|
|
10428
|
+
[Blockchain.Plasma]: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
10264
10429
|
[Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
|
|
10265
10430
|
[Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
|
|
10266
10431
|
// =========================================================================
|
|
@@ -10269,7 +10434,8 @@ function parseOrThrow(value, schema, context) {
|
|
|
10269
10434
|
[Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
|
|
10270
10435
|
[Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
|
|
10271
10436
|
[Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
|
|
10272
|
-
[Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
|
|
10437
|
+
[Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
|
|
10438
|
+
[Blockchain.Plasma_Testnet]: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330'
|
|
10273
10439
|
}
|
|
10274
10440
|
};
|
|
10275
10441
|
|
|
@@ -11222,6 +11388,16 @@ function parseOrThrow(value, schema, context) {
|
|
|
11222
11388
|
*
|
|
11223
11389
|
* Set to 0 when no additional Circle-reserved data is needed.
|
|
11224
11390
|
*/ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
|
|
11391
|
+
/**
|
|
11392
|
+
* Length in bytes of a Solana owner (ed25519 / PDA) public key.
|
|
11393
|
+
*/ const SOLANA_PUBKEY_LENGTH = 32;
|
|
11394
|
+
/**
|
|
11395
|
+
* Byte length of the Solana ATA-creation forwarding payload appended after the
|
|
11396
|
+
* `cctp-forward` frame: `createAta` (1 byte) + `ataOwner` (32 bytes).
|
|
11397
|
+
*
|
|
11398
|
+
* Circle's Orbit relayer decodes exactly this many bytes; see
|
|
11399
|
+
* {@link buildSolanaAtaForwardingHookData}.
|
|
11400
|
+
*/ const SOLANA_ATA_FORWARD_PAYLOAD_LENGTH = 1 + SOLANA_PUBKEY_LENGTH;
|
|
11225
11401
|
/**
|
|
11226
11402
|
* Build the hookData bytes for CCTP forwarding.
|
|
11227
11403
|
*
|
|
@@ -11278,6 +11454,108 @@ function buildForwardingHookData() {
|
|
|
11278
11454
|
cachedHookDataHex = '0x' + Array.from(buffer).map((b)=>b.toString(16).padStart(2, '0')).join('');
|
|
11279
11455
|
return cachedHookDataHex;
|
|
11280
11456
|
}
|
|
11457
|
+
/**
|
|
11458
|
+
* Build a `cctp-forward` hookData frame that instructs Circle's Orbit relayer to
|
|
11459
|
+
* create the recipient's Associated Token Account (ATA) before minting on Solana.
|
|
11460
|
+
*
|
|
11461
|
+
* When an EVM→Solana bridge is forwarded, the destination mint targets the
|
|
11462
|
+
* recipient's USDC ATA — which does not exist for a fresh wallet. This frame
|
|
11463
|
+
* tells the relayer to prepend an idempotent `createAssociatedTokenAccount`
|
|
11464
|
+
* instruction (the relayer pays the rent) so the mint always succeeds.
|
|
11465
|
+
*
|
|
11466
|
+
* Unlike {@link buildForwardingHookData} (an empty version-0 frame), this emits
|
|
11467
|
+
* a version-0 frame whose 32-bit `dataLength` is set to
|
|
11468
|
+
* {@link SOLANA_ATA_FORWARD_PAYLOAD_LENGTH} (33), followed by the payload the
|
|
11469
|
+
* relayer decodes:
|
|
11470
|
+
* - Byte 0: `createAta` flag, always `1`
|
|
11471
|
+
* - Bytes 1-32: the recipient's 32-byte Solana owner public key (`ataOwner`)
|
|
11472
|
+
*
|
|
11473
|
+
* @remarks
|
|
11474
|
+
* `ataOwner` is the recipient's *wallet* public key, not the derived ATA. The
|
|
11475
|
+
* relayer re-derives the ATA from `ataOwner` and the USDC mint and requires it
|
|
11476
|
+
* to equal the burn's `mintRecipient`, so callers must pass the same owner used
|
|
11477
|
+
* to derive `mintRecipient`. The all-zero key is reserved as "absent owner" and
|
|
11478
|
+
* is rejected.
|
|
11479
|
+
*
|
|
11480
|
+
* @param ataOwner - The recipient's 32-byte Solana owner public key.
|
|
11481
|
+
* @returns A 0x-prefixed hex string: the 32-byte frame followed by the 33-byte
|
|
11482
|
+
* Solana ATA payload.
|
|
11483
|
+
* @throws {KitError} If `ataOwner` is not exactly 32 bytes, or is the all-zero
|
|
11484
|
+
* key (INPUT_VALIDATION_FAILED).
|
|
11485
|
+
*
|
|
11486
|
+
* @example
|
|
11487
|
+
* ```typescript
|
|
11488
|
+
* import { PublicKey } from '@solana/web3.js'
|
|
11489
|
+
* import { buildSolanaAtaForwardingHookData } from '@core/utils'
|
|
11490
|
+
*
|
|
11491
|
+
* const owner = new PublicKey('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM')
|
|
11492
|
+
* const hookData = buildSolanaAtaForwardingHookData(owner.toBytes())
|
|
11493
|
+
*
|
|
11494
|
+
* // Use with the forwarded depositForBurnWithHook action so the relayer
|
|
11495
|
+
* // creates the recipient ATA before minting.
|
|
11496
|
+
* await adapter.prepareAction('cctp.v2.depositForBurnWithHook', {
|
|
11497
|
+
* amount: BigInt('1000000'),
|
|
11498
|
+
* mintRecipient: '0x...',
|
|
11499
|
+
* maxFee: BigInt('50000'),
|
|
11500
|
+
* minFinalityThreshold: 1000,
|
|
11501
|
+
* fromChain: ethereum,
|
|
11502
|
+
* toChain: solana,
|
|
11503
|
+
* hookData,
|
|
11504
|
+
* })
|
|
11505
|
+
* ```
|
|
11506
|
+
*/ function buildSolanaAtaForwardingHookData(ataOwner) {
|
|
11507
|
+
if (!(ataOwner instanceof Uint8Array) || ataOwner.length !== SOLANA_PUBKEY_LENGTH) {
|
|
11508
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, `Expected a ${String(SOLANA_PUBKEY_LENGTH)}-byte Solana owner public key`);
|
|
11509
|
+
}
|
|
11510
|
+
if (ataOwner.every((byte)=>byte === 0)) {
|
|
11511
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, 'Expected a non-zero Solana owner public key; the all-zero key is reserved as "absent owner"');
|
|
11512
|
+
}
|
|
11513
|
+
// Inner payload: createAta(1) + ataOwner(32).
|
|
11514
|
+
const payload = new Uint8Array(SOLANA_ATA_FORWARD_PAYLOAD_LENGTH);
|
|
11515
|
+
payload[0] = 1 // createAta = true
|
|
11516
|
+
;
|
|
11517
|
+
payload.set(ataOwner, 1);
|
|
11518
|
+
// 32-byte header: 24-byte magic + uint32 version(0) + uint32 dataLength(33).
|
|
11519
|
+
// The relayer reads dataLength from the v0 frame to slice the inner payload,
|
|
11520
|
+
// so it MUST reflect the appended byte count (unlike the GenericExecutor path).
|
|
11521
|
+
const frame = new Uint8Array(32);
|
|
11522
|
+
frame.set(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX), 0);
|
|
11523
|
+
const view = new DataView(frame.buffer);
|
|
11524
|
+
view.setUint32(24, CCTP_FORWARD_VERSION, false) // big-endian, 0
|
|
11525
|
+
;
|
|
11526
|
+
view.setUint32(28, SOLANA_ATA_FORWARD_PAYLOAD_LENGTH, false) // big-endian, 33
|
|
11527
|
+
;
|
|
11528
|
+
return hexlify(concat([
|
|
11529
|
+
frame,
|
|
11530
|
+
payload
|
|
11531
|
+
]));
|
|
11532
|
+
}
|
|
11533
|
+
|
|
11534
|
+
/**
|
|
11535
|
+
* Left-pad a 20-byte EVM address to a 32-byte (`bytes32`) hex string.
|
|
11536
|
+
*
|
|
11537
|
+
* Mirrors viem's `pad(address, size 32)` and CCTP's `mintRecipient`
|
|
11538
|
+
* convention. Solana addresses are already 32 bytes and need no padding.
|
|
11539
|
+
*
|
|
11540
|
+
* @param address - A 0x-prefixed 20-byte EVM address.
|
|
11541
|
+
* @returns The address left-zero-padded to a 0x-prefixed 32-byte hex string.
|
|
11542
|
+
* @throws {KitError} If `address` is not a valid EVM address (INPUT_VALIDATION_FAILED).
|
|
11543
|
+
*
|
|
11544
|
+
* @example
|
|
11545
|
+
* ```typescript
|
|
11546
|
+
* import { padAddressToBytes32 } from '@core/utils'
|
|
11547
|
+
*
|
|
11548
|
+
* padAddressToBytes32('0x75275Aff2D01699D922f045b69ed291311209738')
|
|
11549
|
+
* // '0x00000000000000000000000075275aff2d01699d922f045b69ed291311209738'
|
|
11550
|
+
* ```
|
|
11551
|
+
*/ function padAddressToBytes32(address) {
|
|
11552
|
+
if (!isAddress(address)) {
|
|
11553
|
+
throw createValidationFailedError$1('address', address, 'Expected a valid 20-byte EVM address');
|
|
11554
|
+
}
|
|
11555
|
+
// bytes32 is raw bytes, not a checksummed address — emit lowercase so it
|
|
11556
|
+
// matches ABI-decoded output.
|
|
11557
|
+
return hexZeroPad(getAddress(address), 32).toLowerCase();
|
|
11558
|
+
}
|
|
11281
11559
|
|
|
11282
11560
|
/**
|
|
11283
11561
|
* Configuration for {@link retryAsync}.
|
|
@@ -11866,7 +12144,7 @@ function resolveOptions(options) {
|
|
|
11866
12144
|
}
|
|
11867
12145
|
|
|
11868
12146
|
var name$4 = "@circle-fin/bridge-kit";
|
|
11869
|
-
var version$5 = "1.
|
|
12147
|
+
var version$5 = "1.14.0";
|
|
11870
12148
|
var pkg$5 = {
|
|
11871
12149
|
name: name$4,
|
|
11872
12150
|
version: version$5};
|
|
@@ -12316,7 +12594,7 @@ const READ_ACTION_KEY_SET = new Set(READ_ACTION_KEYS);
|
|
|
12316
12594
|
* const result = evmAddressSchema.safeParse(validAddress)
|
|
12317
12595
|
* console.log(result.success) // true
|
|
12318
12596
|
* ```
|
|
12319
|
-
*/ const evmAddressSchema = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
12597
|
+
*/ const evmAddressSchema$1 = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
12320
12598
|
/**
|
|
12321
12599
|
* Schema for validating transaction hashes.
|
|
12322
12600
|
*
|
|
@@ -13005,6 +13283,10 @@ var TransferSpeed;
|
|
|
13005
13283
|
token: z.literal('USDC').optional(),
|
|
13006
13284
|
config: z.object({
|
|
13007
13285
|
transferSpeed: z.nativeEnum(TransferSpeed).optional(),
|
|
13286
|
+
feePayment: z.enum([
|
|
13287
|
+
'source',
|
|
13288
|
+
'destination'
|
|
13289
|
+
]).optional(),
|
|
13008
13290
|
maxFee: z.string().min(1, 'Required').pipe(createDecimalStringValidator({
|
|
13009
13291
|
allowZero: true,
|
|
13010
13292
|
regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
|
|
@@ -13012,7 +13294,8 @@ var TransferSpeed;
|
|
|
13012
13294
|
maxDecimals: 6
|
|
13013
13295
|
})(z.string())).optional(),
|
|
13014
13296
|
customFee: customFeeSchema.optional()
|
|
13015
|
-
}).optional()
|
|
13297
|
+
}).optional(),
|
|
13298
|
+
quote: z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex').optional()
|
|
13016
13299
|
});
|
|
13017
13300
|
|
|
13018
13301
|
/**
|
|
@@ -14226,16 +14509,73 @@ var TransferSpeed;
|
|
|
14226
14509
|
};
|
|
14227
14510
|
}
|
|
14228
14511
|
|
|
14512
|
+
/**
|
|
14513
|
+
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
14514
|
+
*
|
|
14515
|
+
* Constructs the appropriate action payload and dispatches it to any registered
|
|
14516
|
+
* event listeners. Handles type-safe dispatching for different step types.
|
|
14517
|
+
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
14518
|
+
*
|
|
14519
|
+
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
14520
|
+
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
14521
|
+
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
14522
|
+
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
14523
|
+
*
|
|
14524
|
+
* @example
|
|
14525
|
+
* ```typescript
|
|
14526
|
+
* const step: BridgeStep = {
|
|
14527
|
+
* name: 'burn',
|
|
14528
|
+
* state: 'success',
|
|
14529
|
+
* txHash: '0xabc...',
|
|
14530
|
+
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
14531
|
+
* data: { ... }
|
|
14532
|
+
* }
|
|
14533
|
+
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
14534
|
+
* ```
|
|
14535
|
+
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
14536
|
+
if (!provider.actionDispatcher) {
|
|
14537
|
+
return;
|
|
14538
|
+
}
|
|
14539
|
+
// Extract traceId from invocation context if provided
|
|
14540
|
+
const traceId = invocation?.traceId;
|
|
14541
|
+
const actionValues = {
|
|
14542
|
+
protocol: 'cctp',
|
|
14543
|
+
version: 'v2',
|
|
14544
|
+
...traceId !== undefined && {
|
|
14545
|
+
traceId
|
|
14546
|
+
},
|
|
14547
|
+
values: step
|
|
14548
|
+
};
|
|
14549
|
+
switch(name){
|
|
14550
|
+
case 'approve':
|
|
14551
|
+
case 'burn':
|
|
14552
|
+
case 'mint':
|
|
14553
|
+
provider.actionDispatcher.dispatch(name, {
|
|
14554
|
+
...actionValues,
|
|
14555
|
+
method: name
|
|
14556
|
+
});
|
|
14557
|
+
break;
|
|
14558
|
+
case 'fetchAttestation':
|
|
14559
|
+
case 'reAttest':
|
|
14560
|
+
provider.actionDispatcher.dispatch(name, {
|
|
14561
|
+
...actionValues,
|
|
14562
|
+
method: name,
|
|
14563
|
+
values: step
|
|
14564
|
+
});
|
|
14565
|
+
break;
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14568
|
+
|
|
14229
14569
|
/**
|
|
14230
14570
|
* Base URL for Circle's IRIS API (mainnet/production).
|
|
14231
14571
|
*
|
|
14232
14572
|
* The IRIS API provides attestation services for CCTP cross-chain transfers.
|
|
14233
|
-
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
14573
|
+
*/ const IRIS_API_BASE_URL$1 = 'https://iris-api.circle.com';
|
|
14234
14574
|
/**
|
|
14235
14575
|
* Base URL for Circle's IRIS API (testnet/sandbox).
|
|
14236
14576
|
*
|
|
14237
14577
|
* Used for development and testing on testnet chains.
|
|
14238
|
-
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
14578
|
+
*/ const IRIS_API_SANDBOX_BASE_URL$1 = 'https://iris-api-sandbox.circle.com';
|
|
14239
14579
|
|
|
14240
14580
|
/**
|
|
14241
14581
|
* Type guard to validate the API response structure.
|
|
@@ -14278,7 +14618,7 @@ const isFastBurnFeeResponse = (data)=>{
|
|
|
14278
14618
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
14279
14619
|
* @returns The complete API URL
|
|
14280
14620
|
*/ function buildFastBurnFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
14281
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
14621
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
14282
14622
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}`;
|
|
14283
14623
|
}
|
|
14284
14624
|
const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
@@ -14489,6 +14829,77 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14489
14829
|
}
|
|
14490
14830
|
};
|
|
14491
14831
|
|
|
14832
|
+
/**
|
|
14833
|
+
* Build the forwarding `hookData` for a forwarded (Orbit-relayed) CCTP v2 burn,
|
|
14834
|
+
* tailored to the destination chain.
|
|
14835
|
+
*
|
|
14836
|
+
* For EVM destinations the recipient already holds ERC-20 USDC directly, so the
|
|
14837
|
+
* empty version-0 `cctp-forward` frame is sufficient. For Solana destinations
|
|
14838
|
+
* USDC is held in an Associated Token Account (ATA) that may not exist for a
|
|
14839
|
+
* fresh wallet, so this emits a frame carrying `createAta` + `ataOwner` that
|
|
14840
|
+
* instructs the relayer to create the recipient ATA (idempotently, at the
|
|
14841
|
+
* relayer's expense) before minting.
|
|
14842
|
+
*
|
|
14843
|
+
* `@solana/web3.js` is imported lazily so EVM-only consumers never load Solana
|
|
14844
|
+
* code, mirroring {@link getMintRecipientAccount}.
|
|
14845
|
+
*
|
|
14846
|
+
* @param chainType - The destination blockchain type ('evm' or 'solana').
|
|
14847
|
+
* @param ownerAddress - The recipient's wallet address on the destination chain
|
|
14848
|
+
* (base58 for Solana). Must be the same owner used to derive `mintRecipient`.
|
|
14849
|
+
* @returns A 0x-prefixed hookData hex string for the forwarded burn.
|
|
14850
|
+
* @throws {KitError} If `chainType` is neither 'evm' nor 'solana', if
|
|
14851
|
+
* `@solana/web3.js` cannot be loaded, or if `ownerAddress` is not a valid
|
|
14852
|
+
* Solana public key (all FATAL).
|
|
14853
|
+
*
|
|
14854
|
+
* @example
|
|
14855
|
+
* ```typescript
|
|
14856
|
+
* import { getForwarderHookData } from './getForwarderHookData'
|
|
14857
|
+
*
|
|
14858
|
+
* // EVM: empty forwarding frame
|
|
14859
|
+
* const evmHook = await getForwarderHookData('evm', '0x742d35Cc...')
|
|
14860
|
+
*
|
|
14861
|
+
* // Solana: frame instructing the relayer to create the recipient ATA
|
|
14862
|
+
* const solanaHook = await getForwarderHookData(
|
|
14863
|
+
* 'solana',
|
|
14864
|
+
* '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
14865
|
+
* )
|
|
14866
|
+
* ```
|
|
14867
|
+
*/ const getForwarderHookData = async (/** The destination blockchain type - determines the hookData shape */ chainType, /** The recipient's wallet address (hex for EVM, base58 for Solana) */ ownerAddress)=>{
|
|
14868
|
+
if (chainType === 'evm') {
|
|
14869
|
+
// EVM: the recipient holds USDC directly; no ATA setup is needed.
|
|
14870
|
+
return buildForwardingHookData();
|
|
14871
|
+
}
|
|
14872
|
+
// Fail closed: only EVM and Solana forwarding destinations are supported.
|
|
14873
|
+
// Without this guard any future non-EVM chain type would silently fall
|
|
14874
|
+
// through to the Solana path and mis-encode hookData on a money-movement path.
|
|
14875
|
+
if (chainType !== 'solana') {
|
|
14876
|
+
throw new KitError({
|
|
14877
|
+
...InputError.VALIDATION_FAILED,
|
|
14878
|
+
recoverability: 'FATAL',
|
|
14879
|
+
message: `Forwarded burns are not supported for destination chain type "${chainType}"`
|
|
14880
|
+
});
|
|
14881
|
+
}
|
|
14882
|
+
// Solana: encode the owner so the relayer creates the recipient ATA.
|
|
14883
|
+
// Resolve @solana/web3.js lazily so EVM-only consumers never load Solana code.
|
|
14884
|
+
const { PublicKey } = await import('@solana/web3.js').catch(()=>{
|
|
14885
|
+
throw new KitError({
|
|
14886
|
+
...InputError.VALIDATION_FAILED,
|
|
14887
|
+
recoverability: 'FATAL',
|
|
14888
|
+
message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js'
|
|
14889
|
+
});
|
|
14890
|
+
});
|
|
14891
|
+
try {
|
|
14892
|
+
const owner = new PublicKey(ownerAddress);
|
|
14893
|
+
return buildSolanaAtaForwardingHookData(owner.toBytes());
|
|
14894
|
+
} catch (error) {
|
|
14895
|
+
throw new KitError({
|
|
14896
|
+
...InputError.INVALID_ADDRESS,
|
|
14897
|
+
recoverability: 'FATAL',
|
|
14898
|
+
message: `Failed to build Solana forwarder hookData for recipient "${ownerAddress}": ${error instanceof Error ? error.message : String(error)}`
|
|
14899
|
+
});
|
|
14900
|
+
}
|
|
14901
|
+
};
|
|
14902
|
+
|
|
14492
14903
|
/**
|
|
14493
14904
|
* Validates and converts a fee value to bigint.
|
|
14494
14905
|
*
|
|
@@ -14591,7 +15002,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14591
15002
|
|
|
14592
15003
|
/**
|
|
14593
15004
|
* The zero address, denoting a native-currency fee in a signed quote.
|
|
14594
|
-
*/ const ZERO_ADDRESS$
|
|
15005
|
+
*/ const ZERO_ADDRESS$2 = '0x0000000000000000000000000000000000000000';
|
|
14595
15006
|
/**
|
|
14596
15007
|
* Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
|
|
14597
15008
|
*
|
|
@@ -14632,7 +15043,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14632
15043
|
if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
|
|
14633
15044
|
throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
|
|
14634
15045
|
}
|
|
14635
|
-
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS$
|
|
15046
|
+
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS$2;
|
|
14636
15047
|
const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
|
|
14637
15048
|
if (isNativeFee) {
|
|
14638
15049
|
return {
|
|
@@ -14770,7 +15181,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14770
15181
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
14771
15182
|
* @returns The complete API URL with forward=true query parameter
|
|
14772
15183
|
*/ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
14773
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
15184
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
14774
15185
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true`;
|
|
14775
15186
|
}
|
|
14776
15187
|
/**
|
|
@@ -15831,7 +16242,7 @@ function hasPendingState(analysis, result) {
|
|
|
15831
16242
|
* // => 'https://iris-api-sandbox.circle.com/v2/messages/1?transactionHash=0xdef...'
|
|
15832
16243
|
* ```
|
|
15833
16244
|
*/ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet)=>{
|
|
15834
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
16245
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
15835
16246
|
const url = new URL(`${baseUrl}/v2/messages/${String(sourceDomainId)}`);
|
|
15836
16247
|
url.searchParams.set('transactionHash', transactionHash);
|
|
15837
16248
|
return url.toString();
|
|
@@ -15999,7 +16410,7 @@ function hasPendingState(analysis, result) {
|
|
|
15999
16410
|
* // => 'https://iris-api-sandbox.circle.com/v2/reattest/0xabc'
|
|
16000
16411
|
* ```
|
|
16001
16412
|
*/ const buildReAttestUrl = (nonce, isTestnet)=>{
|
|
16002
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
16413
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
16003
16414
|
const url = new URL(`${baseUrl}/v2/reattest/${nonce}`);
|
|
16004
16415
|
return url.toString();
|
|
16005
16416
|
};
|
|
@@ -16454,7 +16865,8 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16454
16865
|
* - `destinationChain` — present and supports CCTP v2
|
|
16455
16866
|
* - source and destination chains must both be testnet or both mainnet
|
|
16456
16867
|
* - source and destination chains must differ
|
|
16457
|
-
* - `executor
|
|
16868
|
+
* - destination — either `executor`, or both `mintRecipient` and
|
|
16869
|
+
* `destinationCaller`; not both
|
|
16458
16870
|
* - `amount` — bigint or non-empty string coercible to bigint
|
|
16459
16871
|
* - `feeTotalAmount` — bigint or non-empty string coercible to bigint
|
|
16460
16872
|
* - `feeToken` — valid EVM address (`0x` + 40 hex chars)
|
|
@@ -16496,10 +16908,17 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16496
16908
|
if (source.chain.name === dest.name) {
|
|
16497
16909
|
throw createUnsupportedRouteError(source.chain.name, dest.name);
|
|
16498
16910
|
}
|
|
16499
|
-
//
|
|
16911
|
+
// Destination: GenericExecutor shorthand or explicit recipient + caller.
|
|
16500
16912
|
const executor = p['executor'];
|
|
16501
|
-
|
|
16502
|
-
|
|
16913
|
+
const mintRecipient = p['mintRecipient'];
|
|
16914
|
+
const destinationCaller = p['destinationCaller'];
|
|
16915
|
+
const hasExecutor = typeof executor === 'string' && executor !== '';
|
|
16916
|
+
const hasDirectDestination = typeof mintRecipient === 'string' && mintRecipient !== '' && typeof destinationCaller === 'string' && destinationCaller !== '';
|
|
16917
|
+
if (!hasExecutor && !hasDirectDestination) {
|
|
16918
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor, or both mintRecipient and destinationCaller');
|
|
16919
|
+
}
|
|
16920
|
+
if (hasExecutor && hasDirectDestination) {
|
|
16921
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor or direct destination fields, not both');
|
|
16503
16922
|
}
|
|
16504
16923
|
// amount
|
|
16505
16924
|
const rawAmount = p['amount'];
|
|
@@ -16522,7 +16941,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16522
16941
|
throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
|
|
16523
16942
|
}
|
|
16524
16943
|
// feeToken
|
|
16525
|
-
if (!evmAddressSchema.safeParse(p['feeToken']).success) {
|
|
16944
|
+
if (!evmAddressSchema$1.safeParse(p['feeToken']).success) {
|
|
16526
16945
|
throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
|
|
16527
16946
|
}
|
|
16528
16947
|
// claim
|
|
@@ -16534,7 +16953,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16534
16953
|
if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
|
|
16535
16954
|
throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
|
|
16536
16955
|
}
|
|
16537
|
-
if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
|
|
16956
|
+
if (!evmAddressSchema$1.safeParse(claim['refundAddress']).success) {
|
|
16538
16957
|
throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
|
|
16539
16958
|
}
|
|
16540
16959
|
// hookData (optional)
|
|
@@ -16916,63 +17335,6 @@ const mockAttestationMessage = {
|
|
|
16916
17335
|
});
|
|
16917
17336
|
}
|
|
16918
17337
|
|
|
16919
|
-
/**
|
|
16920
|
-
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
16921
|
-
*
|
|
16922
|
-
* Constructs the appropriate action payload and dispatches it to any registered
|
|
16923
|
-
* event listeners. Handles type-safe dispatching for different step types.
|
|
16924
|
-
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
16925
|
-
*
|
|
16926
|
-
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
16927
|
-
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
16928
|
-
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
16929
|
-
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
16930
|
-
*
|
|
16931
|
-
* @example
|
|
16932
|
-
* ```typescript
|
|
16933
|
-
* const step: BridgeStep = {
|
|
16934
|
-
* name: 'burn',
|
|
16935
|
-
* state: 'success',
|
|
16936
|
-
* txHash: '0xabc...',
|
|
16937
|
-
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
16938
|
-
* data: { ... }
|
|
16939
|
-
* }
|
|
16940
|
-
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
16941
|
-
* ```
|
|
16942
|
-
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
16943
|
-
if (!provider.actionDispatcher) {
|
|
16944
|
-
return;
|
|
16945
|
-
}
|
|
16946
|
-
// Extract traceId from invocation context if provided
|
|
16947
|
-
const traceId = invocation?.traceId;
|
|
16948
|
-
const actionValues = {
|
|
16949
|
-
protocol: 'cctp',
|
|
16950
|
-
version: 'v2',
|
|
16951
|
-
...traceId !== undefined && {
|
|
16952
|
-
traceId
|
|
16953
|
-
},
|
|
16954
|
-
values: step
|
|
16955
|
-
};
|
|
16956
|
-
switch(name){
|
|
16957
|
-
case 'approve':
|
|
16958
|
-
case 'burn':
|
|
16959
|
-
case 'mint':
|
|
16960
|
-
provider.actionDispatcher.dispatch(name, {
|
|
16961
|
-
...actionValues,
|
|
16962
|
-
method: name
|
|
16963
|
-
});
|
|
16964
|
-
break;
|
|
16965
|
-
case 'fetchAttestation':
|
|
16966
|
-
case 'reAttest':
|
|
16967
|
-
provider.actionDispatcher.dispatch(name, {
|
|
16968
|
-
...actionValues,
|
|
16969
|
-
method: name,
|
|
16970
|
-
values: step
|
|
16971
|
-
});
|
|
16972
|
-
break;
|
|
16973
|
-
}
|
|
16974
|
-
}
|
|
16975
|
-
|
|
16976
17338
|
/**
|
|
16977
17339
|
* Check whether the source adapter supports EIP-5792 atomic batching and
|
|
16978
17340
|
* the consumer has not explicitly opted out via `config.batchTransactions`.
|
|
@@ -17219,7 +17581,7 @@ const mockAttestationMessage = {
|
|
|
17219
17581
|
return step;
|
|
17220
17582
|
}
|
|
17221
17583
|
|
|
17222
|
-
var version$4 = "1.
|
|
17584
|
+
var version$4 = "1.12.0";
|
|
17223
17585
|
var pkg$4 = {
|
|
17224
17586
|
version: version$4};
|
|
17225
17587
|
|
|
@@ -17925,18 +18287,18 @@ var pkg$4 = {
|
|
|
17925
18287
|
}
|
|
17926
18288
|
}
|
|
17927
18289
|
|
|
17928
|
-
function isPlainObject(value) {
|
|
18290
|
+
function isPlainObject$1(value) {
|
|
17929
18291
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
17930
18292
|
return false;
|
|
17931
18293
|
}
|
|
17932
18294
|
const prototype = Object.getPrototypeOf(value);
|
|
17933
18295
|
return prototype === Object.prototype || prototype === null;
|
|
17934
18296
|
}
|
|
17935
|
-
function assertHeadersConfig(headers, field) {
|
|
18297
|
+
function assertHeadersConfig$1(headers, field) {
|
|
17936
18298
|
if (headers === undefined) {
|
|
17937
18299
|
return;
|
|
17938
18300
|
}
|
|
17939
|
-
if (!isPlainObject(headers)) {
|
|
18301
|
+
if (!isPlainObject$1(headers)) {
|
|
17940
18302
|
throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
|
|
17941
18303
|
}
|
|
17942
18304
|
for (const [name, value] of Object.entries(headers)){
|
|
@@ -17946,16 +18308,16 @@ function assertHeadersConfig(headers, field) {
|
|
|
17946
18308
|
}
|
|
17947
18309
|
}
|
|
17948
18310
|
function assertCCTPV2Config(config) {
|
|
17949
|
-
if (!isPlainObject(config)) {
|
|
18311
|
+
if (!isPlainObject$1(config)) {
|
|
17950
18312
|
throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
|
|
17951
18313
|
}
|
|
17952
|
-
assertHeadersConfig(config['headers'], 'config.headers');
|
|
18314
|
+
assertHeadersConfig$1(config['headers'], 'config.headers');
|
|
17953
18315
|
const attestation = config['attestation'];
|
|
17954
18316
|
if (attestation !== undefined) {
|
|
17955
|
-
if (!isPlainObject(attestation)) {
|
|
18317
|
+
if (!isPlainObject$1(attestation)) {
|
|
17956
18318
|
throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
|
|
17957
18319
|
}
|
|
17958
|
-
assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
|
|
18320
|
+
assertHeadersConfig$1(attestation['headers'], 'config.attestation.headers');
|
|
17959
18321
|
}
|
|
17960
18322
|
}
|
|
17961
18323
|
/**
|
|
@@ -18030,6 +18392,34 @@ function assertCCTPV2Config(config) {
|
|
|
18030
18392
|
this.config = config;
|
|
18031
18393
|
}
|
|
18032
18394
|
/**
|
|
18395
|
+
* Emit a bridge step event through the provider's registered action
|
|
18396
|
+
* dispatcher.
|
|
18397
|
+
*
|
|
18398
|
+
* Kit-level orchestration that drives the burn primitives directly instead
|
|
18399
|
+
* of {@link CCTPV2BridgingProvider.bridge} (for example the receive-exact
|
|
18400
|
+
* source-fee flow) uses this to surface the same `approve`/`burn`/`mint`
|
|
18401
|
+
* events as the standard bridge path. It is a no-op when no dispatcher is
|
|
18402
|
+
* registered.
|
|
18403
|
+
*
|
|
18404
|
+
* @param name - The step name (`approve`, `burn`, `mint`, ...).
|
|
18405
|
+
* @param step - The completed bridge step to broadcast.
|
|
18406
|
+
* @param invocation - Optional invocation context carrying a `traceId` for
|
|
18407
|
+
* end-to-end correlation.
|
|
18408
|
+
* @returns Nothing.
|
|
18409
|
+
*
|
|
18410
|
+
* @example
|
|
18411
|
+
* ```typescript
|
|
18412
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
18413
|
+
* provider.emitBridgeStep('burn', {
|
|
18414
|
+
* name: 'burn',
|
|
18415
|
+
* state: 'success',
|
|
18416
|
+
* txHash: '0xabc...',
|
|
18417
|
+
* })
|
|
18418
|
+
* ```
|
|
18419
|
+
*/ emitBridgeStep(name, step, invocation) {
|
|
18420
|
+
dispatchStepEvent(name, step, this, invocation);
|
|
18421
|
+
}
|
|
18422
|
+
/**
|
|
18033
18423
|
* Resolves the effective polling configuration for an attestation request.
|
|
18034
18424
|
*
|
|
18035
18425
|
* Precedence (lowest to highest): provider `config.attestation`, then the
|
|
@@ -18855,8 +19245,11 @@ function assertCCTPV2Config(config) {
|
|
|
18855
19245
|
// 2. Forwarder: Does the user want Circle's relayer to handle attestation/mint?
|
|
18856
19246
|
const useCustomBurn = hasCustomContractSupport(source.chain, 'bridge');
|
|
18857
19247
|
const useForwarder = destination.useForwarder === true;
|
|
18858
|
-
// Build hookData once if forwarder is enabled
|
|
18859
|
-
|
|
19248
|
+
// Build hookData once if forwarder is enabled. EVM destinations get the
|
|
19249
|
+
// empty forwarding frame; Solana destinations get a frame instructing the
|
|
19250
|
+
// relayer to create the recipient's ATA (using the same owner that derived
|
|
19251
|
+
// `mintRecipient`) so the mint succeeds even for a fresh wallet.
|
|
19252
|
+
const hookData = useForwarder ? await getForwarderHookData(destination.chain.type, destinationAddressForMint) : undefined;
|
|
18860
19253
|
if (useCustomBurn) {
|
|
18861
19254
|
// Custom burn path: use bridge contract (with or without hook)
|
|
18862
19255
|
const customBurnParams = {
|
|
@@ -18884,13 +19277,14 @@ function assertCCTPV2Config(config) {
|
|
|
18884
19277
|
/**
|
|
18885
19278
|
* Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
|
|
18886
19279
|
*
|
|
18887
|
-
*
|
|
18888
|
-
*
|
|
18889
|
-
*
|
|
18890
|
-
*
|
|
19280
|
+
* Build the source-chain `depositForBurnWithHookAndFees` call. Fees are
|
|
19281
|
+
* collected up front on the source chain against a signed quote. The
|
|
19282
|
+
* destination may use the GenericExecutor shorthand, or an explicit mint
|
|
19283
|
+
* recipient and destination caller for direct forwarding.
|
|
18891
19284
|
*
|
|
18892
|
-
* This is the low-level on-chain primitive behind the
|
|
18893
|
-
*
|
|
19285
|
+
* This is the low-level on-chain primitive behind the Unified Balance Kit
|
|
19286
|
+
* `fastCrossChainDeposit` and the Bridge Kit source-fee
|
|
19287
|
+
* (`feePayment: 'source'`) flow. The `hookData` and signed-quote
|
|
18894
19288
|
* `claim` are produced elsewhere and passed in here:
|
|
18895
19289
|
* - `hookData`: `buildForwardingHookDataWithPayload(version,
|
|
18896
19290
|
* buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
|
|
@@ -18907,32 +19301,48 @@ function assertCCTPV2Config(config) {
|
|
|
18907
19301
|
* approval covers both; the redundant second approval is skipped.
|
|
18908
19302
|
*
|
|
18909
19303
|
* @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
|
|
18910
|
-
* @param params - The burn amount,
|
|
19304
|
+
* @param params - The burn amount, destination, hook data, signed quote, and fee.
|
|
18911
19305
|
* @returns The prepared approvals, the prepared burn, and the resolved fee plan.
|
|
18912
19306
|
* @throws {KitError} If the wallet context is invalid, `destinationChain` does not
|
|
18913
|
-
* support CCTP v2, the
|
|
18914
|
-
* a bigint or a numeric string coercible to bigint,
|
|
18915
|
-
* `cctp-forward` frame (guaranteed
|
|
18916
|
-
* context cannot be resolved.
|
|
19307
|
+
* support CCTP v2, the destination fields are missing, `amount` or
|
|
19308
|
+
* `feeTotalAmount` is not a bigint or a numeric string coercible to bigint,
|
|
19309
|
+
* the hook data lacks a `cctp-forward` frame (guaranteed
|
|
19310
|
+
* `ForwardFeeWithoutHook`), or the operation context cannot be resolved.
|
|
18917
19311
|
*
|
|
18918
19312
|
* @example
|
|
18919
19313
|
* ```typescript
|
|
19314
|
+
* import {
|
|
19315
|
+
* CCTPV2BridgingProvider,
|
|
19316
|
+
* type BurnWithFeesParams,
|
|
19317
|
+
* } from '@circle-fin/provider-cctp-v2'
|
|
19318
|
+
*
|
|
19319
|
+
* declare const source: BurnWithFeesParams['source']
|
|
19320
|
+
* declare const destinationChain: BurnWithFeesParams['destinationChain']
|
|
19321
|
+
* declare const recipient: string
|
|
19322
|
+
* declare const hookData: string
|
|
19323
|
+
* declare const claim: BurnWithFeesParams['claim']
|
|
19324
|
+
*
|
|
19325
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
18920
19326
|
* const { approvals, burn } = await provider.burnWithFees({
|
|
18921
19327
|
* source,
|
|
18922
|
-
* destinationChain
|
|
19328
|
+
* destinationChain,
|
|
18923
19329
|
* amount: 1_000_000n,
|
|
18924
|
-
*
|
|
18925
|
-
*
|
|
18926
|
-
*
|
|
18927
|
-
*
|
|
18928
|
-
*
|
|
19330
|
+
* mintRecipient: recipient,
|
|
19331
|
+
* destinationCaller: '0x0000000000000000000000000000000000000000',
|
|
19332
|
+
* hookData,
|
|
19333
|
+
* claim,
|
|
19334
|
+
* feeToken: source.chain.usdcAddress,
|
|
19335
|
+
* feeTotalAmount: 10_000n,
|
|
18929
19336
|
* })
|
|
18930
19337
|
* for (const approval of approvals) await approval.execute()
|
|
18931
19338
|
* const txHash = await burn.execute()
|
|
18932
19339
|
* ```
|
|
18933
19340
|
*/ async burnWithFees(params) {
|
|
18934
19341
|
assertBurnWithFeesParams(params);
|
|
18935
|
-
const { source, destinationChain,
|
|
19342
|
+
const { source, destinationChain, hookData, claim, feeToken } = params;
|
|
19343
|
+
const hasExecutor = 'executor' in params && params.executor !== undefined;
|
|
19344
|
+
const mintRecipient = hasExecutor ? params.executor : params.mintRecipient;
|
|
19345
|
+
const destinationCaller = hasExecutor ? params.executor : params.destinationCaller;
|
|
18936
19346
|
const amount = BigInt(params.amount);
|
|
18937
19347
|
const feeTotalAmount = BigInt(params.feeTotalAmount);
|
|
18938
19348
|
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
|
|
@@ -18963,13 +19373,14 @@ function assertCCTPV2Config(config) {
|
|
|
18963
19373
|
delegate: wrapperAddress,
|
|
18964
19374
|
amount: approval.amount
|
|
18965
19375
|
}, context)));
|
|
18966
|
-
// Build the burn
|
|
19376
|
+
// Build the burn with either the GenericExecutor shorthand or the explicit
|
|
19377
|
+
// direct-forwarding recipient and caller.
|
|
18967
19378
|
const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
18968
19379
|
fromChain: source.chain,
|
|
18969
19380
|
toChain: destinationChain,
|
|
18970
19381
|
amount,
|
|
18971
|
-
mintRecipient
|
|
18972
|
-
destinationCaller
|
|
19382
|
+
mintRecipient,
|
|
19383
|
+
destinationCaller,
|
|
18973
19384
|
hookData,
|
|
18974
19385
|
claim,
|
|
18975
19386
|
feeToken,
|
|
@@ -19126,6 +19537,948 @@ function assertCCTPV2Config(config) {
|
|
|
19126
19537
|
]
|
|
19127
19538
|
];
|
|
19128
19539
|
|
|
19540
|
+
/**
|
|
19541
|
+
* Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
|
|
19542
|
+
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
19543
|
+
/**
|
|
19544
|
+
* Base URL for Circle's Quote API (hosted in Iris) on testnet/sandbox.
|
|
19545
|
+
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
19546
|
+
/**
|
|
19547
|
+
* Native fee-token sentinel (the zero address).
|
|
19548
|
+
*
|
|
19549
|
+
* When `feeToken` is the zero address the quote prices fees in the source
|
|
19550
|
+
* chain's native gas token (paid as `msg.value` on-chain). Pass a USDC token
|
|
19551
|
+
* address instead to denominate fees in USDC.
|
|
19552
|
+
*/ const NATIVE_FEE_TOKEN = '0x0000000000000000000000000000000000000000';
|
|
19553
|
+
/**
|
|
19554
|
+
* API path prefix for the CCTP v2 USDC burn quote endpoint.
|
|
19555
|
+
*
|
|
19556
|
+
* The full path is `${QUOTE_BURN_USDC_PATH}/{sourceDomain}/{destinationDomain}`;
|
|
19557
|
+
* `usdc` is a fixed literal, not a token parameter.
|
|
19558
|
+
*/ const QUOTE_BURN_USDC_PATH = '/v2/quote/burn/usdc';
|
|
19559
|
+
/**
|
|
19560
|
+
* API path prefix for the CCTP v2 USDC quote validate endpoint.
|
|
19561
|
+
*
|
|
19562
|
+
* The full path is `${QUOTE_VALIDATE_USDC_PATH}/{sourceDomain}`; accepts a
|
|
19563
|
+
* `POST { abiSignature, args }` body and returns whether the signed quote is
|
|
19564
|
+
* currently claimable together with its authoritative expiry status.
|
|
19565
|
+
*/ const QUOTE_VALIDATE_USDC_PATH = '/v2/quote/validate/usdc';
|
|
19566
|
+
/**
|
|
19567
|
+
* Default polling configuration for Quote API calls.
|
|
19568
|
+
*
|
|
19569
|
+
* A signed quote is short-lived (typically ~2 minutes, varying per chain) and
|
|
19570
|
+
* a feature-flag-disabled source chain returns a
|
|
19571
|
+
* permanent `503 SERVICE_NOT_ENABLED`, so retrying buys little and risks
|
|
19572
|
+
* outliving the quote. The client therefore makes a single attempt
|
|
19573
|
+
* (`maxRetries: 1`) with a 15s timeout, mirroring the reference
|
|
19574
|
+
* implementation; callers refresh by requesting a new quote rather than
|
|
19575
|
+
* relying on transport retries.
|
|
19576
|
+
*
|
|
19577
|
+
* No `headers` are set here: `pollApiWithValidation` always injects
|
|
19578
|
+
* `Content-Type: application/json` and adds `User-Agent` in Node. Browser
|
|
19579
|
+
* requests omit a user-agent header to avoid a CORS preflight, so duplicating
|
|
19580
|
+
* either header here would be dead configuration.
|
|
19581
|
+
*/ const FEE_QUOTE_DEFAULT_CONFIG = {
|
|
19582
|
+
timeout: 15_000,
|
|
19583
|
+
maxRetries: 1,
|
|
19584
|
+
retryDelay: 200
|
|
19585
|
+
};
|
|
19586
|
+
|
|
19587
|
+
/** Decimal string in token minor units, constrained to be strictly positive. */ const positiveAmountSchema = z.string().regex(/^\d+$/, 'must be a non-negative integer string')// Re-check the digit shape here: zod still runs this refinement when the
|
|
19588
|
+
// regex check above fails ("dirty"), so guard BigInt() against throwing on a
|
|
19589
|
+
// non-numeric value before comparing.
|
|
19590
|
+
.refine((value)=>/^\d+$/.test(value) && BigInt(value) > 0n, 'must be greater than zero');
|
|
19591
|
+
/**
|
|
19592
|
+
* A 20-byte EVM address in `0x` hex.
|
|
19593
|
+
*
|
|
19594
|
+
* The MVP prepaid-`FORWARD` `burn/usdc` path targets EVM contracts
|
|
19595
|
+
* (`TokenMessengerWithFees` / `GenericExecutor`), so `feeToken` and
|
|
19596
|
+
* `destinationCaller` are constrained to EVM addresses by design. This is an
|
|
19597
|
+
* intentional scope limit, not a permanent one: it can be widened to other
|
|
19598
|
+
* address formats as the fee service expands to more chains.
|
|
19599
|
+
*/ const evmAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'must be a 20-byte 0x address');
|
|
19600
|
+
/** Even-length `0x` hex (the empty `0x` is allowed). */ const hexSchema = z.string().regex(/^0x([a-fA-F0-9]{2})*$/, 'must be even-length 0x hex');
|
|
19601
|
+
/** Non-empty, even-length `0x` hex. */ const nonEmptyHexSchema = z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex');
|
|
19602
|
+
/** A 32-byte `0x` hex hash. */ const bytes32Schema = z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'must be a 32-byte 0x hash');
|
|
19603
|
+
/** Decimal string in minor units, allowing zero. */ const numericStringSchema$1 = z.string().regex(/^\d+$/, 'must be a non-negative integer string');
|
|
19604
|
+
/** An `https:` URL, used for the optional base-URL override. */ const httpsUrlSchema = z.string().refine((value)=>{
|
|
19605
|
+
try {
|
|
19606
|
+
return new URL(value).protocol === 'https:';
|
|
19607
|
+
} catch {
|
|
19608
|
+
return false;
|
|
19609
|
+
}
|
|
19610
|
+
}, 'must be an https URL');
|
|
19611
|
+
const forwardParamsSchema = z.object({
|
|
19612
|
+
hookData: hexSchema.optional(),
|
|
19613
|
+
destinationCaller: evmAddressSchema.optional()
|
|
19614
|
+
}).strict();
|
|
19615
|
+
const forwardRequestSchema = z.object({
|
|
19616
|
+
type: z.literal('FORWARD'),
|
|
19617
|
+
params: forwardParamsSchema.optional()
|
|
19618
|
+
}).strict();
|
|
19619
|
+
const preFinalityRequestSchema = z.object({
|
|
19620
|
+
type: z.literal('PRE_FINALITY')
|
|
19621
|
+
}).strict();
|
|
19622
|
+
/** A single quote request item (`FORWARD` or `PRE_FINALITY`). */ const feeQuoteRequestSchema = z.discriminatedUnion('type', [
|
|
19623
|
+
forwardRequestSchema,
|
|
19624
|
+
preFinalityRequestSchema
|
|
19625
|
+
]);
|
|
19626
|
+
/** A non-empty list of quote request items with unique types. */ const feeQuoteRequestsSchema = 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');
|
|
19627
|
+
/**
|
|
19628
|
+
* A structured `Partial<ApiPollingConfig>` polling override.
|
|
19629
|
+
*
|
|
19630
|
+
* Validates the field types callers actually set, so a plain-JS caller passing
|
|
19631
|
+
* `{ timeout: 'soon' }` is rejected at the boundary rather than failing opaquely
|
|
19632
|
+
* inside the transport. Unknown keys pass through so a future `ApiPollingConfig`
|
|
19633
|
+
* field is forwarded rather than silently dropped.
|
|
19634
|
+
*/ const apiPollingConfigSchema = z.object({
|
|
19635
|
+
timeout: z.number().int().positive().optional(),
|
|
19636
|
+
maxRetries: z.number().int().nonnegative().optional(),
|
|
19637
|
+
retryDelay: z.number().int().nonnegative().optional(),
|
|
19638
|
+
backoff: z.enum([
|
|
19639
|
+
'fixed',
|
|
19640
|
+
'exponential'
|
|
19641
|
+
]).optional(),
|
|
19642
|
+
maxRetryDelayMs: z.number().int().positive().optional(),
|
|
19643
|
+
headers: z.record(z.string()).optional()
|
|
19644
|
+
}).passthrough();
|
|
19645
|
+
/**
|
|
19646
|
+
* The validatable input for {@link fetchFeeQuote}.
|
|
19647
|
+
*
|
|
19648
|
+
* This is the single source of truth for input validation, including the CCTP
|
|
19649
|
+
* domains and the `isTestnet` environment flag. Validating `isTestnet` at
|
|
19650
|
+
* runtime matters because a plain-JS caller who omits it would otherwise leave
|
|
19651
|
+
* it `undefined`, which is falsy and silently selects the production base URL.
|
|
19652
|
+
* (`buildFeeQuoteUrl` independently re-validates the domains for standalone
|
|
19653
|
+
* callers.)
|
|
19654
|
+
*/ const fetchFeeQuoteInputSchema = z.object({
|
|
19655
|
+
sourceDomain: z.number().int().nonnegative(),
|
|
19656
|
+
destinationDomain: z.number().int().nonnegative(),
|
|
19657
|
+
amount: positiveAmountSchema,
|
|
19658
|
+
feeToken: evmAddressSchema.optional(),
|
|
19659
|
+
requests: feeQuoteRequestsSchema,
|
|
19660
|
+
isTestnet: z.boolean(),
|
|
19661
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
19662
|
+
config: apiPollingConfigSchema.optional()
|
|
19663
|
+
}).strict();
|
|
19664
|
+
const feeQuoteItemSchema = z.object({
|
|
19665
|
+
type: z.string().min(1),
|
|
19666
|
+
amount: numericStringSchema$1,
|
|
19667
|
+
args: z.array(z.string()),
|
|
19668
|
+
argsHash: bytes32Schema
|
|
19669
|
+
}).passthrough();
|
|
19670
|
+
const exchangeRatesSchema = z.object({
|
|
19671
|
+
feeTokenUsd: z.string(),
|
|
19672
|
+
destinationTokenUsd: z.string()
|
|
19673
|
+
}).passthrough();
|
|
19674
|
+
const metadataSchema = z.object({
|
|
19675
|
+
destinationGasPrice: z.string().optional(),
|
|
19676
|
+
exchangeRates: exchangeRatesSchema.optional()
|
|
19677
|
+
}).passthrough();
|
|
19678
|
+
/** The `expiry` object the Quote API nests the quote TTL under. */ const expirySchema = z.discriminatedUnion('mode', [
|
|
19679
|
+
z.object({
|
|
19680
|
+
mode: z.literal('TIMESTAMP'),
|
|
19681
|
+
expiresAt: z.number().int().nonnegative()
|
|
19682
|
+
}).passthrough(),
|
|
19683
|
+
z.object({
|
|
19684
|
+
mode: z.literal('BLOCK_NUMBER'),
|
|
19685
|
+
expiresAtBlock: z.number().int().nonnegative(),
|
|
19686
|
+
blockEstimatedAt: z.number().int().nonnegative().optional()
|
|
19687
|
+
}).passthrough()
|
|
19688
|
+
]);
|
|
19689
|
+
/** Schema for a signed fee quote returned by the Quote API. */ const signedFeeQuoteSchema = z.object({
|
|
19690
|
+
// The runtime YAML spec maps signedQuote to a looser `hex` (which allows
|
|
19691
|
+
// an empty `0x`); we keep the stricter non-empty form. Do not relax
|
|
19692
|
+
// without a reason.
|
|
19693
|
+
signedQuote: nonEmptyHexSchema,
|
|
19694
|
+
issuedAt: z.number().int().nonnegative(),
|
|
19695
|
+
// The API returns a mode-specific timestamp or source-block deadline.
|
|
19696
|
+
expiry: expirySchema,
|
|
19697
|
+
feeTotalAmount: numericStringSchema$1,
|
|
19698
|
+
feeToken: evmAddressSchema,
|
|
19699
|
+
nonce: numericStringSchema$1,
|
|
19700
|
+
items: z.array(feeQuoteItemSchema),
|
|
19701
|
+
metadata: metadataSchema.optional()
|
|
19702
|
+
}).passthrough();
|
|
19703
|
+
/**
|
|
19704
|
+
* Validate that an unknown value is a signed fee quote.
|
|
19705
|
+
*
|
|
19706
|
+
* @param value - The unknown value to validate.
|
|
19707
|
+
* @returns `true` when the value matches the signed-quote response shape.
|
|
19708
|
+
*
|
|
19709
|
+
* @example
|
|
19710
|
+
* ```typescript
|
|
19711
|
+
* import { isSignedFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
19712
|
+
*
|
|
19713
|
+
* declare const payload: unknown
|
|
19714
|
+
* if (isSignedFeeQuote(payload)) {
|
|
19715
|
+
* console.log(payload.feeTotalAmount)
|
|
19716
|
+
* }
|
|
19717
|
+
* ```
|
|
19718
|
+
*/ function isSignedFeeQuote(value) {
|
|
19719
|
+
return signedFeeQuoteSchema.safeParse(value).success;
|
|
19720
|
+
}
|
|
19721
|
+
/** Validate input to {@link validateQuote}. */ const validateQuoteInputSchema = z.object({
|
|
19722
|
+
sourceDomain: z.number().int().nonnegative(),
|
|
19723
|
+
abiSignature: z.string().min(1),
|
|
19724
|
+
args: z.array(z.union([
|
|
19725
|
+
z.string(),
|
|
19726
|
+
z.array(z.string())
|
|
19727
|
+
])),
|
|
19728
|
+
isTestnet: z.boolean(),
|
|
19729
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
19730
|
+
config: apiPollingConfigSchema.optional()
|
|
19731
|
+
}).strict();
|
|
19732
|
+
const quoteExpiryStatusSchema = z.discriminatedUnion('mode', [
|
|
19733
|
+
z.object({
|
|
19734
|
+
mode: z.literal('TIMESTAMP'),
|
|
19735
|
+
expired: z.boolean(),
|
|
19736
|
+
secondsRemaining: z.number().int().nonnegative(),
|
|
19737
|
+
expiresAt: z.number().int().nonnegative()
|
|
19738
|
+
}).passthrough(),
|
|
19739
|
+
z.object({
|
|
19740
|
+
mode: z.literal('BLOCK_NUMBER'),
|
|
19741
|
+
expired: z.boolean(),
|
|
19742
|
+
secondsRemaining: z.number().int().nonnegative(),
|
|
19743
|
+
expiresAtBlock: z.number().int().nonnegative(),
|
|
19744
|
+
blockEstimatedAt: z.number().int().nonnegative().optional()
|
|
19745
|
+
}).passthrough()
|
|
19746
|
+
]);
|
|
19747
|
+
const validateQuoteItemSchema = z.object({
|
|
19748
|
+
type: z.string().min(1),
|
|
19749
|
+
argsMatch: z.boolean(),
|
|
19750
|
+
amount: numericStringSchema$1.optional(),
|
|
19751
|
+
args: z.array(z.string()).optional(),
|
|
19752
|
+
argsHash: bytes32Schema.optional(),
|
|
19753
|
+
computedArgsHash: bytes32Schema.optional()
|
|
19754
|
+
}).passthrough();
|
|
19755
|
+
/**
|
|
19756
|
+
* Schema for a response from the Quote API validation endpoint.
|
|
19757
|
+
*
|
|
19758
|
+
* The endpoint takes the source domain as a URL path parameter and does not
|
|
19759
|
+
* return it in the response body, so `sourceDomain` is intentionally not part
|
|
19760
|
+
* of this schema.
|
|
19761
|
+
*/ const validateQuoteResultSchema = z.object({
|
|
19762
|
+
signedQuote: nonEmptyHexSchema,
|
|
19763
|
+
expiry: quoteExpiryStatusSchema,
|
|
19764
|
+
feeTotalAmount: numericStringSchema$1,
|
|
19765
|
+
feeToken: evmAddressSchema,
|
|
19766
|
+
nonce: numericStringSchema$1,
|
|
19767
|
+
claimable: z.boolean(),
|
|
19768
|
+
// Preserve newly introduced server-side reasons as opaque strings rather
|
|
19769
|
+
// than rejecting the entire safety response before the SDK is updated.
|
|
19770
|
+
failedChecks: z.array(z.string().min(1)),
|
|
19771
|
+
items: z.array(validateQuoteItemSchema)
|
|
19772
|
+
}).passthrough();
|
|
19773
|
+
/**
|
|
19774
|
+
* Validate a Quote API validation response.
|
|
19775
|
+
*
|
|
19776
|
+
* @param value - The unknown response value.
|
|
19777
|
+
* @returns `true` when the value has the expected validation response shape.
|
|
19778
|
+
*
|
|
19779
|
+
* @example
|
|
19780
|
+
* ```typescript
|
|
19781
|
+
* import { isValidateQuoteResult } from '@circle-fin/provider-fee-v1'
|
|
19782
|
+
*
|
|
19783
|
+
* declare const response: unknown
|
|
19784
|
+
* if (isValidateQuoteResult(response)) {
|
|
19785
|
+
* console.log(response.claimable)
|
|
19786
|
+
* }
|
|
19787
|
+
* ```
|
|
19788
|
+
*/ function isValidateQuoteResult(value) {
|
|
19789
|
+
return validateQuoteResultSchema.safeParse(value).success;
|
|
19790
|
+
}
|
|
19791
|
+
|
|
19792
|
+
/**
|
|
19793
|
+
* Validate that a CCTP domain id is a non-negative integer.
|
|
19794
|
+
*
|
|
19795
|
+
* @param value - The domain id to validate.
|
|
19796
|
+
* @param label - The parameter name, used in the error message.
|
|
19797
|
+
* @returns Nothing.
|
|
19798
|
+
* @throws {@link KitError} When the value is not a non-negative integer.
|
|
19799
|
+
* @internal
|
|
19800
|
+
*/ function assertDomain(value, label) {
|
|
19801
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
19802
|
+
throw new KitError({
|
|
19803
|
+
...InputError.VALIDATION_FAILED,
|
|
19804
|
+
recoverability: 'FATAL',
|
|
19805
|
+
message: `Quote API getFeeQuote failed: ${label} must be a ` + `non-negative integer, received ${String(value)}`,
|
|
19806
|
+
cause: {
|
|
19807
|
+
trace: {
|
|
19808
|
+
[label]: value
|
|
19809
|
+
}
|
|
19810
|
+
}
|
|
19811
|
+
});
|
|
19812
|
+
}
|
|
19813
|
+
}
|
|
19814
|
+
/**
|
|
19815
|
+
* Build the Quote API URL for a CCTP v2 USDC burn quote.
|
|
19816
|
+
*
|
|
19817
|
+
* Resolves the environment base URL (or an explicit `baseUrl` override) and
|
|
19818
|
+
* appends the burn/usdc path with the source and destination CCTP domains.
|
|
19819
|
+
* `usdc` is a fixed path literal, not a token parameter.
|
|
19820
|
+
*
|
|
19821
|
+
* @param params - The domains and environment selector.
|
|
19822
|
+
* @returns The fully-qualified Quote API URL.
|
|
19823
|
+
* @throws {@link KitError} When either domain is not a non-negative integer.
|
|
19824
|
+
*
|
|
19825
|
+
* @example
|
|
19826
|
+
* ```typescript
|
|
19827
|
+
* import { buildFeeQuoteUrl } from '@circle-fin/provider-fee-v1'
|
|
19828
|
+
*
|
|
19829
|
+
* const url = buildFeeQuoteUrl({
|
|
19830
|
+
* sourceDomain: 3,
|
|
19831
|
+
* destinationDomain: 26,
|
|
19832
|
+
* isTestnet: false,
|
|
19833
|
+
* })
|
|
19834
|
+
* // => 'https://iris-api.circle.com/v2/quote/burn/usdc/3/26'
|
|
19835
|
+
* ```
|
|
19836
|
+
*/ function buildFeeQuoteUrl(params) {
|
|
19837
|
+
const { sourceDomain, destinationDomain, isTestnet, baseUrl } = params;
|
|
19838
|
+
assertDomain(sourceDomain, 'sourceDomain');
|
|
19839
|
+
assertDomain(destinationDomain, 'destinationDomain');
|
|
19840
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
19841
|
+
return new URL(`${QUOTE_BURN_USDC_PATH}/${String(sourceDomain)}/${String(destinationDomain)}`, resolvedBaseUrl).toString();
|
|
19842
|
+
}
|
|
19843
|
+
|
|
19844
|
+
/**
|
|
19845
|
+
* Assert that a quote's per-item fee amounts sum to its `feeTotalAmount`.
|
|
19846
|
+
*
|
|
19847
|
+
* A defensive integrity check on the Quote API response, enforced internally
|
|
19848
|
+
* by `fetchFeeQuote`. It does not compare against the destination-side
|
|
19849
|
+
* `feeExecuted`, which is expected to be zero for prepaid forward-only burns.
|
|
19850
|
+
*
|
|
19851
|
+
* @param quote - The signed fee quote to check.
|
|
19852
|
+
* @returns Nothing.
|
|
19853
|
+
* @throws {@link KitError} When the item amounts do not sum to `feeTotalAmount`.
|
|
19854
|
+
* @internal
|
|
19855
|
+
*/ function assertFeeItemsSumToTotal(quote) {
|
|
19856
|
+
const itemsTotal = quote.items.reduce((sum, item)=>sum + BigInt(item.amount), 0n);
|
|
19857
|
+
const declaredTotal = BigInt(quote.feeTotalAmount);
|
|
19858
|
+
if (itemsTotal !== declaredTotal) {
|
|
19859
|
+
throw new KitError({
|
|
19860
|
+
...InputError.VALIDATION_FAILED,
|
|
19861
|
+
recoverability: 'FATAL',
|
|
19862
|
+
message: `Quote API getFeeQuote failed: fee items sum ` + `(${itemsTotal.toString()}) does not equal feeTotalAmount ` + `(${declaredTotal.toString()})`,
|
|
19863
|
+
cause: {
|
|
19864
|
+
trace: {
|
|
19865
|
+
itemsTotal: itemsTotal.toString(),
|
|
19866
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19867
|
+
}
|
|
19868
|
+
}
|
|
19869
|
+
});
|
|
19870
|
+
}
|
|
19871
|
+
}
|
|
19872
|
+
|
|
19873
|
+
/**
|
|
19874
|
+
* Determine whether a Quote API error represents a disabled source chain.
|
|
19875
|
+
*
|
|
19876
|
+
* @param error - The error thrown by the HTTP layer.
|
|
19877
|
+
* @returns `true` when the error contains the `SERVICE_NOT_ENABLED` marker.
|
|
19878
|
+
* @internal
|
|
19879
|
+
*/ function isServiceNotEnabled(error) {
|
|
19880
|
+
const body = typeof error === 'object' && error !== null && 'responseBody' in error ? error.responseBody : undefined;
|
|
19881
|
+
if (typeof body === 'object' && body !== null) {
|
|
19882
|
+
const fields = body;
|
|
19883
|
+
const candidates = [
|
|
19884
|
+
fields['errorCode'],
|
|
19885
|
+
fields['code'],
|
|
19886
|
+
fields['message'],
|
|
19887
|
+
fields['externalMessage'],
|
|
19888
|
+
fields['error']
|
|
19889
|
+
];
|
|
19890
|
+
if (candidates.some((value)=>typeof value === 'string' && value.toUpperCase().includes('SERVICE_NOT_ENABLED'))) {
|
|
19891
|
+
return true;
|
|
19892
|
+
}
|
|
19893
|
+
}
|
|
19894
|
+
return getErrorMessage(error).toUpperCase().includes('SERVICE_NOT_ENABLED');
|
|
19895
|
+
}
|
|
19896
|
+
|
|
19897
|
+
const SERVICE$1 = 'Quote API';
|
|
19898
|
+
const OPERATION$1 = 'getFeeQuote';
|
|
19899
|
+
/**
|
|
19900
|
+
* Serialize request items for the wire body.
|
|
19901
|
+
*
|
|
19902
|
+
* `PRE_FINALITY` is emitted with no `params` key, and a `FORWARD` item only
|
|
19903
|
+
* carries the binding fields that are present.
|
|
19904
|
+
*
|
|
19905
|
+
* @param requests - The request items to serialize.
|
|
19906
|
+
* @returns The serialized request items.
|
|
19907
|
+
* @internal
|
|
19908
|
+
*/ function serializeRequests(requests) {
|
|
19909
|
+
return requests.map((request)=>{
|
|
19910
|
+
if (request.type === 'PRE_FINALITY') {
|
|
19911
|
+
return {
|
|
19912
|
+
type: 'PRE_FINALITY'
|
|
19913
|
+
};
|
|
19914
|
+
}
|
|
19915
|
+
const params = request.params;
|
|
19916
|
+
if (params === undefined) {
|
|
19917
|
+
return {
|
|
19918
|
+
type: 'FORWARD'
|
|
19919
|
+
};
|
|
19920
|
+
}
|
|
19921
|
+
const forwardParams = {};
|
|
19922
|
+
if (params.hookData !== undefined) {
|
|
19923
|
+
forwardParams.hookData = params.hookData;
|
|
19924
|
+
}
|
|
19925
|
+
if (params.destinationCaller !== undefined) {
|
|
19926
|
+
forwardParams.destinationCaller = params.destinationCaller;
|
|
19927
|
+
}
|
|
19928
|
+
return {
|
|
19929
|
+
type: 'FORWARD',
|
|
19930
|
+
params: forwardParams
|
|
19931
|
+
};
|
|
19932
|
+
});
|
|
19933
|
+
}
|
|
19934
|
+
/**
|
|
19935
|
+
* Fetch a signed fee quote from Circle's Quote API for a CCTP v2 USDC burn.
|
|
19936
|
+
*
|
|
19937
|
+
* Validates inputs, POSTs to
|
|
19938
|
+
* `/v2/quote/burn/usdc/{sourceDomain}/{destinationDomain}` with a single
|
|
19939
|
+
* attempt (the signed quote is short-lived), and returns the typed quote. A
|
|
19940
|
+
* disabled source chain (`503 SERVICE_NOT_ENABLED`) surfaces as a fatal,
|
|
19941
|
+
* non-retryable error; other failures are mapped to a {@link KitError} via the
|
|
19942
|
+
* shared API error parser.
|
|
19943
|
+
*
|
|
19944
|
+
* @param params - The domains, amount, request items, and environment.
|
|
19945
|
+
* @returns The signed fee quote.
|
|
19946
|
+
* @throws {@link KitError} On invalid input, a disabled source chain, an HTTP
|
|
19947
|
+
* error, or an invalid response shape.
|
|
19948
|
+
*
|
|
19949
|
+
* @example
|
|
19950
|
+
* ```typescript
|
|
19951
|
+
* import { fetchFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
19952
|
+
*
|
|
19953
|
+
* const quote = await fetchFeeQuote({
|
|
19954
|
+
* sourceDomain: 3,
|
|
19955
|
+
* destinationDomain: 26,
|
|
19956
|
+
* amount: '1000000',
|
|
19957
|
+
* requests: [{ type: 'FORWARD' }, { type: 'PRE_FINALITY' }],
|
|
19958
|
+
* isTestnet: false,
|
|
19959
|
+
* })
|
|
19960
|
+
* console.log(quote.feeTotalAmount, quote.expiry)
|
|
19961
|
+
* ```
|
|
19962
|
+
*/ async function fetchFeeQuote(params) {
|
|
19963
|
+
const { sourceDomain, destinationDomain, amount, requests, feeToken, isTestnet, baseUrl, config } = params;
|
|
19964
|
+
const parsed = fetchFeeQuoteInputSchema.safeParse({
|
|
19965
|
+
sourceDomain,
|
|
19966
|
+
destinationDomain,
|
|
19967
|
+
amount,
|
|
19968
|
+
feeToken,
|
|
19969
|
+
requests,
|
|
19970
|
+
isTestnet,
|
|
19971
|
+
baseUrl,
|
|
19972
|
+
config
|
|
19973
|
+
});
|
|
19974
|
+
if (!parsed.success) {
|
|
19975
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
19976
|
+
throw new KitError({
|
|
19977
|
+
...InputError.VALIDATION_FAILED,
|
|
19978
|
+
recoverability: 'FATAL',
|
|
19979
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: ${detail}`,
|
|
19980
|
+
cause: {
|
|
19981
|
+
trace: parsed.error.issues
|
|
19982
|
+
}
|
|
19983
|
+
});
|
|
19984
|
+
}
|
|
19985
|
+
const url = baseUrl === undefined ? buildFeeQuoteUrl({
|
|
19986
|
+
sourceDomain,
|
|
19987
|
+
destinationDomain,
|
|
19988
|
+
isTestnet
|
|
19989
|
+
}) : buildFeeQuoteUrl({
|
|
19990
|
+
sourceDomain,
|
|
19991
|
+
destinationDomain,
|
|
19992
|
+
isTestnet,
|
|
19993
|
+
baseUrl
|
|
19994
|
+
});
|
|
19995
|
+
const body = {
|
|
19996
|
+
amount,
|
|
19997
|
+
feeToken: feeToken ?? NATIVE_FEE_TOKEN,
|
|
19998
|
+
requests: serializeRequests(requests)
|
|
19999
|
+
};
|
|
20000
|
+
const pollingConfig = {
|
|
20001
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
20002
|
+
...config
|
|
20003
|
+
};
|
|
20004
|
+
let quote;
|
|
20005
|
+
try {
|
|
20006
|
+
quote = await pollApiPost(url, body, isSignedFeeQuote, pollingConfig);
|
|
20007
|
+
} catch (error) {
|
|
20008
|
+
// Only one service-specific code (SERVICE_NOT_ENABLED) needs bespoke
|
|
20009
|
+
// mapping, so it is detected inline rather than via a dedicated
|
|
20010
|
+
// `parseFeeQuoteApiError` parser; everything else flows through the shared
|
|
20011
|
+
// `parseApiError`. Promote to a parser if more coded errors appear.
|
|
20012
|
+
if (isServiceNotEnabled(error)) {
|
|
20013
|
+
throw new KitError({
|
|
20014
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
20015
|
+
recoverability: 'FATAL',
|
|
20016
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: source chain not enabled for fee ` + `quotes (SERVICE_NOT_ENABLED)`,
|
|
20017
|
+
cause: {
|
|
20018
|
+
trace: error
|
|
20019
|
+
}
|
|
20020
|
+
});
|
|
20021
|
+
}
|
|
20022
|
+
throw parseApiError(error, {
|
|
20023
|
+
service: SERVICE$1,
|
|
20024
|
+
operation: OPERATION$1
|
|
20025
|
+
});
|
|
20026
|
+
}
|
|
20027
|
+
// Defense-in-depth: a self-consistent quote's per-item fees sum to the
|
|
20028
|
+
// declared total. Enforced here so callers cannot forget the check.
|
|
20029
|
+
assertFeeItemsSumToTotal(quote);
|
|
20030
|
+
return quote;
|
|
20031
|
+
}
|
|
20032
|
+
|
|
20033
|
+
const SERVICE = 'Quote API';
|
|
20034
|
+
const OPERATION = 'validateQuote';
|
|
20035
|
+
/**
|
|
20036
|
+
* Validate a signed quote against a complete source-chain contract call.
|
|
20037
|
+
*
|
|
20038
|
+
* @param params - The source domain, ABI signature, call arguments, and environment.
|
|
20039
|
+
* @returns The claimability, binding checks, and authoritative expiry status.
|
|
20040
|
+
* @throws {@link KitError} When input, transport, or response validation fails.
|
|
20041
|
+
*
|
|
20042
|
+
* @example
|
|
20043
|
+
* ```typescript
|
|
20044
|
+
* import { validateQuote } from '@circle-fin/provider-fee-v1'
|
|
20045
|
+
*
|
|
20046
|
+
* const result = await validateQuote({
|
|
20047
|
+
* sourceDomain: 3,
|
|
20048
|
+
* // The exact function + arguments, in ABI order, that will be burned on-chain.
|
|
20049
|
+
* abiSignature:
|
|
20050
|
+
* 'depositForBurnWithHookAndFees(uint256,uint32,bytes32,address,bytes32,bytes,(bytes,address))',
|
|
20051
|
+
* args: [
|
|
20052
|
+
* '1000000',
|
|
20053
|
+
* '26',
|
|
20054
|
+
* '0x0000000000000000000000001111111111111111111111111111111111111111',
|
|
20055
|
+
* '0x2222222222222222222222222222222222222222',
|
|
20056
|
+
* '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
20057
|
+
* '0x636374702d666f72776172640000000000000000000000000000000000000000',
|
|
20058
|
+
* ['0x01abcd', '0x3333333333333333333333333333333333333333'],
|
|
20059
|
+
* ],
|
|
20060
|
+
* isTestnet: false,
|
|
20061
|
+
* })
|
|
20062
|
+
* console.log(result.claimable, result.expiry.secondsRemaining)
|
|
20063
|
+
* ```
|
|
20064
|
+
*/ async function validateQuote(params) {
|
|
20065
|
+
const parsed = validateQuoteInputSchema.safeParse(params);
|
|
20066
|
+
if (!parsed.success) {
|
|
20067
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
20068
|
+
throw new KitError({
|
|
20069
|
+
...InputError.VALIDATION_FAILED,
|
|
20070
|
+
recoverability: 'FATAL',
|
|
20071
|
+
message: `${SERVICE} ${OPERATION} failed: ${detail}`,
|
|
20072
|
+
cause: {
|
|
20073
|
+
trace: parsed.error.issues
|
|
20074
|
+
}
|
|
20075
|
+
});
|
|
20076
|
+
}
|
|
20077
|
+
const { sourceDomain, abiSignature, args, isTestnet, baseUrl } = parsed.data;
|
|
20078
|
+
// `config` was validated by the schema above; spread the caller's original,
|
|
20079
|
+
// precisely typed `Partial<ApiPollingConfig>` so the merged polling config
|
|
20080
|
+
// stays assignable under `exactOptionalPropertyTypes`.
|
|
20081
|
+
const pollingConfig = {
|
|
20082
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
20083
|
+
...params.config
|
|
20084
|
+
};
|
|
20085
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
20086
|
+
const url = new URL(`${QUOTE_VALIDATE_USDC_PATH}/${String(sourceDomain)}`, resolvedBaseUrl).toString();
|
|
20087
|
+
try {
|
|
20088
|
+
return await pollApiPost(url, {
|
|
20089
|
+
abiSignature,
|
|
20090
|
+
args
|
|
20091
|
+
}, isValidateQuoteResult, pollingConfig);
|
|
20092
|
+
} catch (error) {
|
|
20093
|
+
if (isServiceNotEnabled(error)) {
|
|
20094
|
+
throw new KitError({
|
|
20095
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
20096
|
+
recoverability: 'FATAL',
|
|
20097
|
+
message: `${SERVICE} ${OPERATION} failed: source chain not enabled for ` + `quote validation (SERVICE_NOT_ENABLED)`,
|
|
20098
|
+
cause: {
|
|
20099
|
+
trace: error
|
|
20100
|
+
}
|
|
20101
|
+
});
|
|
20102
|
+
}
|
|
20103
|
+
throw parseApiError(error, {
|
|
20104
|
+
service: SERVICE,
|
|
20105
|
+
operation: OPERATION
|
|
20106
|
+
});
|
|
20107
|
+
}
|
|
20108
|
+
}
|
|
20109
|
+
|
|
20110
|
+
/** Refresh timestamp quotes this many seconds before submission. */ const QUOTE_EXPIRY_SAFETY_SECONDS = 30;
|
|
20111
|
+
/** ABI signature validated by the Quote API before source-chain submission. */ const BURN_WITH_FEES_ABI_SIGNATURE = 'depositForBurnWithHookAndFees(uint256,uint32,bytes32,address,bytes32,bytes,(bytes,address))';
|
|
20112
|
+
/** Unrestricted CCTP destination caller used by the forwarding relayer. */ const ZERO_ADDRESS$1 = '0x0000000000000000000000000000000000000000';
|
|
20113
|
+
function isQuoteNearEstimatedExpiry(quote) {
|
|
20114
|
+
const currentSeconds = Math.floor(Date.now() / 1_000);
|
|
20115
|
+
const expiresAt = quote.expiry.mode === 'TIMESTAMP' ? quote.expiry.expiresAt : quote.expiry.blockEstimatedAt;
|
|
20116
|
+
// A BLOCK_NUMBER quote may omit the advisory `blockEstimatedAt` estimate; when
|
|
20117
|
+
// it is absent, skip this wall-clock pre-check and defer to the authoritative
|
|
20118
|
+
// source-chain-tip validation performed downstream.
|
|
20119
|
+
if (expiresAt === undefined) {
|
|
20120
|
+
return false;
|
|
20121
|
+
}
|
|
20122
|
+
return expiresAt <= currentSeconds + QUOTE_EXPIRY_SAFETY_SECONDS;
|
|
20123
|
+
}
|
|
20124
|
+
function assertSourceFeeRoute(params) {
|
|
20125
|
+
const { source, destination, config } = params;
|
|
20126
|
+
if (source.chain.type !== 'evm' || destination.chain.type !== 'evm' || !isCCTPV2Supported(source.chain) || !isCCTPV2Supported(destination.chain)) {
|
|
20127
|
+
throw createUnsupportedRouteError(source.chain.name, destination.chain.name);
|
|
20128
|
+
}
|
|
20129
|
+
const useForwarder = destination.useForwarder;
|
|
20130
|
+
if (useForwarder !== true) {
|
|
20131
|
+
throw createValidationFailedError$1('to.useForwarder', useForwarder, "feePayment: 'source' requires useForwarder: true");
|
|
20132
|
+
}
|
|
20133
|
+
if (config.customFee !== undefined) {
|
|
20134
|
+
throw createValidationFailedError$1('config.customFee', config.customFee, "Custom fees are not supported with feePayment: 'source'. Remove the " + 'per-call customFee or the kit-level custom fee policy for this route.');
|
|
20135
|
+
}
|
|
20136
|
+
}
|
|
20137
|
+
function buildQuoteBinding(params) {
|
|
20138
|
+
assertSourceFeeRoute(params);
|
|
20139
|
+
const mintRecipient = params.destination.recipientAddress ?? params.destination.address;
|
|
20140
|
+
const hookData = buildForwardingHookData();
|
|
20141
|
+
const requests = [
|
|
20142
|
+
{
|
|
20143
|
+
type: 'FORWARD',
|
|
20144
|
+
params: {
|
|
20145
|
+
hookData,
|
|
20146
|
+
destinationCaller: ZERO_ADDRESS$1
|
|
20147
|
+
}
|
|
20148
|
+
}
|
|
20149
|
+
];
|
|
20150
|
+
if ((params.config.transferSpeed ?? TransferSpeed.FAST) === TransferSpeed.FAST) {
|
|
20151
|
+
requests.push({
|
|
20152
|
+
type: 'PRE_FINALITY'
|
|
20153
|
+
});
|
|
20154
|
+
}
|
|
20155
|
+
return {
|
|
20156
|
+
sourceDomain: params.source.chain.cctp.domain,
|
|
20157
|
+
destinationDomain: params.destination.chain.cctp.domain,
|
|
20158
|
+
isTestnet: params.source.chain.isTestnet,
|
|
20159
|
+
amount: params.amount,
|
|
20160
|
+
mintRecipient,
|
|
20161
|
+
hookData,
|
|
20162
|
+
destinationCaller: ZERO_ADDRESS$1,
|
|
20163
|
+
feeToken: params.source.chain.usdcAddress,
|
|
20164
|
+
requests
|
|
20165
|
+
};
|
|
20166
|
+
}
|
|
20167
|
+
async function fetchBoundQuote(binding) {
|
|
20168
|
+
try {
|
|
20169
|
+
const quote = await fetchFeeQuote({
|
|
20170
|
+
sourceDomain: binding.sourceDomain,
|
|
20171
|
+
destinationDomain: binding.destinationDomain,
|
|
20172
|
+
amount: binding.amount,
|
|
20173
|
+
feeToken: binding.feeToken,
|
|
20174
|
+
requests: binding.requests,
|
|
20175
|
+
isTestnet: binding.isTestnet
|
|
20176
|
+
});
|
|
20177
|
+
if (quote.feeToken.toLowerCase() !== binding.feeToken.toLowerCase()) {
|
|
20178
|
+
throw createValidationFailedError$1('feeToken', quote.feeToken, 'Fee Service must return source-chain USDC for source-fee bridging');
|
|
20179
|
+
}
|
|
20180
|
+
return quote;
|
|
20181
|
+
} catch (error) {
|
|
20182
|
+
if (isRateLimitError(error)) {
|
|
20183
|
+
throw new KitError({
|
|
20184
|
+
...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
20185
|
+
recoverability: 'RETRYABLE',
|
|
20186
|
+
message: 'Fee Service rate limit exceeded. Retry with caller-managed exponential backoff; Bridge Kit does not retry signed quote requests automatically.',
|
|
20187
|
+
cause: {
|
|
20188
|
+
trace: error
|
|
20189
|
+
}
|
|
20190
|
+
});
|
|
20191
|
+
}
|
|
20192
|
+
throw error;
|
|
20193
|
+
}
|
|
20194
|
+
}
|
|
20195
|
+
async function fetchSubmissionQuote(binding) {
|
|
20196
|
+
let quote = await fetchBoundQuote(binding);
|
|
20197
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
20198
|
+
quote = await fetchBoundQuote(binding);
|
|
20199
|
+
}
|
|
20200
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
20201
|
+
throw createValidationFailedError$1('quote', undefined, 'Fee Service returned a quote too close to expiry for safe submission');
|
|
20202
|
+
}
|
|
20203
|
+
return quote;
|
|
20204
|
+
}
|
|
20205
|
+
async function validateBoundQuote(binding, signedQuote, refundAddress) {
|
|
20206
|
+
return validateQuote({
|
|
20207
|
+
sourceDomain: binding.sourceDomain,
|
|
20208
|
+
abiSignature: BURN_WITH_FEES_ABI_SIGNATURE,
|
|
20209
|
+
args: [
|
|
20210
|
+
binding.amount,
|
|
20211
|
+
String(binding.destinationDomain),
|
|
20212
|
+
padAddressToBytes32(binding.mintRecipient),
|
|
20213
|
+
binding.feeToken,
|
|
20214
|
+
padAddressToBytes32(binding.destinationCaller),
|
|
20215
|
+
binding.hookData,
|
|
20216
|
+
[
|
|
20217
|
+
signedQuote,
|
|
20218
|
+
refundAddress
|
|
20219
|
+
]
|
|
20220
|
+
],
|
|
20221
|
+
isTestnet: binding.isTestnet
|
|
20222
|
+
});
|
|
20223
|
+
}
|
|
20224
|
+
function isValidationSafe(binding, signedQuote, validation, expectedQuote) {
|
|
20225
|
+
return validation.claimable && !validation.expiry.expired && validation.expiry.secondsRemaining > QUOTE_EXPIRY_SAFETY_SECONDS && validation.signedQuote.toLowerCase() === signedQuote.toLowerCase() && validation.feeToken.toLowerCase() === binding.feeToken.toLowerCase() && (expectedQuote === undefined || validation.feeTotalAmount === expectedQuote.feeTotalAmount && validation.nonce === expectedQuote.nonce);
|
|
20226
|
+
}
|
|
20227
|
+
function unsafeQuoteError(validation) {
|
|
20228
|
+
const detail = validation.failedChecks.length > 0 ? ` (${validation.failedChecks.join(', ')})` : '';
|
|
20229
|
+
return createValidationFailedError$1('quote', undefined, `The fee quote is not safe for submission${detail}; ` + 'call estimate again to obtain a valid quote');
|
|
20230
|
+
}
|
|
20231
|
+
function toExecutionFeeQuote(quote) {
|
|
20232
|
+
return {
|
|
20233
|
+
signedQuote: quote.signedQuote,
|
|
20234
|
+
feeToken: quote.feeToken,
|
|
20235
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
20236
|
+
};
|
|
20237
|
+
}
|
|
20238
|
+
function toFeeItems(quote) {
|
|
20239
|
+
return quote.items.map((item)=>({
|
|
20240
|
+
type: item.type,
|
|
20241
|
+
amount: formatUnits(item.amount, 6),
|
|
20242
|
+
args: item.args,
|
|
20243
|
+
argsHash: item.argsHash
|
|
20244
|
+
}));
|
|
20245
|
+
}
|
|
20246
|
+
/**
|
|
20247
|
+
* Estimate a source-fee bridge using a source-denominated signed fee quote.
|
|
20248
|
+
*
|
|
20249
|
+
* @internal
|
|
20250
|
+
*/ async function estimateSourceFeeBridge(params) {
|
|
20251
|
+
const binding = buildQuoteBinding(params);
|
|
20252
|
+
const quote = await fetchSubmissionQuote(binding);
|
|
20253
|
+
const feeTotal = formatUnits(quote.feeTotalAmount, 6);
|
|
20254
|
+
return {
|
|
20255
|
+
token: 'USDC',
|
|
20256
|
+
amount: formatUnits(params.amount, 6),
|
|
20257
|
+
source: {
|
|
20258
|
+
address: params.source.address,
|
|
20259
|
+
chain: params.source.chain.chain
|
|
20260
|
+
},
|
|
20261
|
+
destination: {
|
|
20262
|
+
address: params.destination.address,
|
|
20263
|
+
chain: params.destination.chain.chain,
|
|
20264
|
+
...params.destination.recipientAddress !== undefined && {
|
|
20265
|
+
recipientAddress: params.destination.recipientAddress
|
|
20266
|
+
}
|
|
20267
|
+
},
|
|
20268
|
+
gasFees: [],
|
|
20269
|
+
fees: quote.items.map((item)=>({
|
|
20270
|
+
type: item.type === 'FORWARD' ? 'forwarder' : 'provider',
|
|
20271
|
+
token: 'USDC',
|
|
20272
|
+
amount: formatUnits(item.amount, 6)
|
|
20273
|
+
})),
|
|
20274
|
+
amountReceived: formatUnits(params.amount, 6),
|
|
20275
|
+
feeTotal,
|
|
20276
|
+
feeItems: toFeeItems(quote),
|
|
20277
|
+
totalDebit: formatUnits((BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString(), 6),
|
|
20278
|
+
quoteExpiry: quote.expiry,
|
|
20279
|
+
quote: quote.signedQuote
|
|
20280
|
+
};
|
|
20281
|
+
}
|
|
20282
|
+
async function readAllowance(params, delegate) {
|
|
20283
|
+
const operationContext = {
|
|
20284
|
+
chain: params.source.chain,
|
|
20285
|
+
address: params.source.address
|
|
20286
|
+
};
|
|
20287
|
+
const prepared = await params.source.adapter.prepareAction('usdc.allowance', {
|
|
20288
|
+
walletAddress: params.source.address,
|
|
20289
|
+
delegate
|
|
20290
|
+
}, operationContext);
|
|
20291
|
+
return BigInt(String(await prepared.execute()));
|
|
20292
|
+
}
|
|
20293
|
+
async function executeAndConfirm(request, params, provider) {
|
|
20294
|
+
const txHash = await request.execute();
|
|
20295
|
+
const data = await provider.waitForTransaction(params.source.adapter, txHash, params.source.chain);
|
|
20296
|
+
return {
|
|
20297
|
+
txHash,
|
|
20298
|
+
data
|
|
20299
|
+
};
|
|
20300
|
+
}
|
|
20301
|
+
async function prepareAndPreflight(params, binding, quote, provider) {
|
|
20302
|
+
assertSourceFeeRoute(params);
|
|
20303
|
+
const totalDebit = (BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString();
|
|
20304
|
+
const operationContext = {
|
|
20305
|
+
chain: params.source.chain,
|
|
20306
|
+
address: params.source.address
|
|
20307
|
+
};
|
|
20308
|
+
await validateBalanceForTransaction({
|
|
20309
|
+
adapter: params.source.adapter,
|
|
20310
|
+
amount: totalDebit,
|
|
20311
|
+
token: 'USDC',
|
|
20312
|
+
tokenAddress: params.source.chain.usdcAddress,
|
|
20313
|
+
operationContext
|
|
20314
|
+
});
|
|
20315
|
+
const prepared = await provider.burnWithFees({
|
|
20316
|
+
source: params.source,
|
|
20317
|
+
destinationChain: params.destination.chain,
|
|
20318
|
+
amount: params.amount,
|
|
20319
|
+
mintRecipient: binding.mintRecipient,
|
|
20320
|
+
destinationCaller: binding.destinationCaller,
|
|
20321
|
+
hookData: binding.hookData,
|
|
20322
|
+
claim: {
|
|
20323
|
+
signedQuote: quote.signedQuote,
|
|
20324
|
+
refundAddress: params.source.address
|
|
20325
|
+
},
|
|
20326
|
+
feeToken: quote.feeToken,
|
|
20327
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
20328
|
+
});
|
|
20329
|
+
const wrapper = resolveCCTPV2ContractAddress(params.source.chain, 'tokenMessengerWithFees');
|
|
20330
|
+
const allowance = await readAllowance(params, wrapper);
|
|
20331
|
+
if (allowance >= BigInt(totalDebit)) {
|
|
20332
|
+
return {
|
|
20333
|
+
prepared,
|
|
20334
|
+
approveStep: {
|
|
20335
|
+
name: 'approve',
|
|
20336
|
+
state: 'noop'
|
|
20337
|
+
}
|
|
20338
|
+
};
|
|
20339
|
+
}
|
|
20340
|
+
let lastApproval;
|
|
20341
|
+
for (const approval of prepared.approvals){
|
|
20342
|
+
lastApproval = await executeAndConfirm(approval, params, provider);
|
|
20343
|
+
}
|
|
20344
|
+
return {
|
|
20345
|
+
prepared,
|
|
20346
|
+
approveStep: {
|
|
20347
|
+
name: 'approve',
|
|
20348
|
+
state: 'success',
|
|
20349
|
+
data: lastApproval?.data,
|
|
20350
|
+
...lastApproval?.txHash !== undefined && {
|
|
20351
|
+
txHash: lastApproval.txHash,
|
|
20352
|
+
explorerUrl: buildExplorerUrl(params.source.chain, lastApproval.txHash)
|
|
20353
|
+
}
|
|
20354
|
+
}
|
|
20355
|
+
};
|
|
20356
|
+
}
|
|
20357
|
+
async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
20358
|
+
if (suppliedQuote === undefined) {
|
|
20359
|
+
const fetchedQuote = await fetchSubmissionQuote(binding);
|
|
20360
|
+
return {
|
|
20361
|
+
quote: toExecutionFeeQuote(fetchedQuote),
|
|
20362
|
+
fetchedQuote
|
|
20363
|
+
};
|
|
20364
|
+
}
|
|
20365
|
+
const validation = await validateBoundQuote(binding, suppliedQuote, refundAddress);
|
|
20366
|
+
if (!isValidationSafe(binding, suppliedQuote, validation)) {
|
|
20367
|
+
throw unsafeQuoteError(validation);
|
|
20368
|
+
}
|
|
20369
|
+
return {
|
|
20370
|
+
quote: toExecutionFeeQuote(validation),
|
|
20371
|
+
fetchedQuote: undefined
|
|
20372
|
+
};
|
|
20373
|
+
}
|
|
20374
|
+
/**
|
|
20375
|
+
* Execute a source-fee bridge while preserving receive-exact semantics.
|
|
20376
|
+
*
|
|
20377
|
+
* @internal
|
|
20378
|
+
*/ async function executeSourceFeeBridge(rawParams, params, provider) {
|
|
20379
|
+
// Narrows `params` to the CCTP v2 route type for the rest of this function.
|
|
20380
|
+
// buildQuoteBinding asserts too, but that narrows its own scope, not this one.
|
|
20381
|
+
assertSourceFeeRoute(params);
|
|
20382
|
+
const binding = buildQuoteBinding(params);
|
|
20383
|
+
const suppliedQuote = rawParams.quote;
|
|
20384
|
+
let { quote, fetchedQuote } = await resolveExecutionQuote(binding, suppliedQuote, params.source.address);
|
|
20385
|
+
let { prepared, approveStep } = await prepareAndPreflight(params, binding, quote, provider);
|
|
20386
|
+
// Validate against the current source-chain tip after approval confirmation.
|
|
20387
|
+
// BLOCK_NUMBER expiries cannot be checked safely with wall-clock time alone.
|
|
20388
|
+
let validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
20389
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
20390
|
+
if (suppliedQuote === undefined) {
|
|
20391
|
+
fetchedQuote = await fetchSubmissionQuote(binding);
|
|
20392
|
+
quote = toExecutionFeeQuote(fetchedQuote);
|
|
20393
|
+
const refreshedPreparation = await prepareAndPreflight(params, binding, quote, provider);
|
|
20394
|
+
prepared = refreshedPreparation.prepared;
|
|
20395
|
+
if (refreshedPreparation.approveStep.state !== 'noop') {
|
|
20396
|
+
approveStep = refreshedPreparation.approveStep;
|
|
20397
|
+
}
|
|
20398
|
+
validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
20399
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
20400
|
+
throw unsafeQuoteError(validation);
|
|
20401
|
+
}
|
|
20402
|
+
} else {
|
|
20403
|
+
throw unsafeQuoteError(validation);
|
|
20404
|
+
}
|
|
20405
|
+
}
|
|
20406
|
+
// Surface the same step events as the standard bridge() path so
|
|
20407
|
+
// kit.on('approve'|'burn'|'mint', ...) handlers fire for source-fee bridges.
|
|
20408
|
+
if (approveStep.state !== 'noop') {
|
|
20409
|
+
provider.emitBridgeStep('approve', approveStep);
|
|
20410
|
+
}
|
|
20411
|
+
const burn = await executeAndConfirm(prepared.burn, params, provider);
|
|
20412
|
+
const burnStep = {
|
|
20413
|
+
name: 'burn',
|
|
20414
|
+
state: 'success',
|
|
20415
|
+
txHash: burn.txHash,
|
|
20416
|
+
data: burn.data,
|
|
20417
|
+
explorerUrl: buildExplorerUrl(params.source.chain, burn.txHash)
|
|
20418
|
+
};
|
|
20419
|
+
provider.emitBridgeStep('burn', burnStep);
|
|
20420
|
+
const resultBase = {
|
|
20421
|
+
amount: params.amount,
|
|
20422
|
+
token: 'USDC',
|
|
20423
|
+
config: params.config,
|
|
20424
|
+
provider: provider.name,
|
|
20425
|
+
source: {
|
|
20426
|
+
address: params.source.address,
|
|
20427
|
+
chain: params.source.chain
|
|
20428
|
+
},
|
|
20429
|
+
destination: {
|
|
20430
|
+
address: params.destination.address,
|
|
20431
|
+
chain: params.destination.chain,
|
|
20432
|
+
...params.destination.recipientAddress !== undefined && {
|
|
20433
|
+
recipientAddress: params.destination.recipientAddress
|
|
20434
|
+
},
|
|
20435
|
+
useForwarder: true
|
|
20436
|
+
}
|
|
20437
|
+
};
|
|
20438
|
+
const attestation = await provider.fetchRelayerMint(params.source, burn.txHash);
|
|
20439
|
+
const forwardTxHash = attestation.forwardTxHash;
|
|
20440
|
+
// The burn already moved funds. If the relayer confirms without a
|
|
20441
|
+
// destination hash, surface an error-state result that preserves the burn
|
|
20442
|
+
// step (so `retry()` can resume the mint) instead of throwing and discarding
|
|
20443
|
+
// the completed burn.
|
|
20444
|
+
if (typeof forwardTxHash !== 'string' || forwardTxHash.trim() === '') {
|
|
20445
|
+
const mintStep = {
|
|
20446
|
+
name: 'mint',
|
|
20447
|
+
state: 'error',
|
|
20448
|
+
forwarded: true,
|
|
20449
|
+
errorCategory: 'failed_offchain',
|
|
20450
|
+
errorMessage: 'Relayer confirmation did not include a destination transaction hash'
|
|
20451
|
+
};
|
|
20452
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
20453
|
+
return {
|
|
20454
|
+
...resultBase,
|
|
20455
|
+
state: 'error',
|
|
20456
|
+
steps: [
|
|
20457
|
+
approveStep,
|
|
20458
|
+
burnStep,
|
|
20459
|
+
mintStep
|
|
20460
|
+
]
|
|
20461
|
+
};
|
|
20462
|
+
}
|
|
20463
|
+
const mintStep = {
|
|
20464
|
+
name: 'mint',
|
|
20465
|
+
state: 'success',
|
|
20466
|
+
forwarded: true,
|
|
20467
|
+
txHash: forwardTxHash,
|
|
20468
|
+
explorerUrl: buildExplorerUrl(params.destination.chain, forwardTxHash)
|
|
20469
|
+
};
|
|
20470
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
20471
|
+
return {
|
|
20472
|
+
...resultBase,
|
|
20473
|
+
state: 'success',
|
|
20474
|
+
steps: [
|
|
20475
|
+
approveStep,
|
|
20476
|
+
burnStep,
|
|
20477
|
+
mintStep
|
|
20478
|
+
]
|
|
20479
|
+
};
|
|
20480
|
+
}
|
|
20481
|
+
|
|
19129
20482
|
/** SDK name used in telemetry payloads. */ const SDK_NAME$3 = resolveKitSdkName(pkg$5.name);
|
|
19130
20483
|
/**
|
|
19131
20484
|
* Pick the most-relevant `txHash` to attach to an error telemetry payload.
|
|
@@ -19353,11 +20706,18 @@ function assertCCTPV2Config(config) {
|
|
|
19353
20706
|
this.validateNetworkCompatibility(resolvedParams);
|
|
19354
20707
|
// Merge the custom fee config into the resolved params
|
|
19355
20708
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
//
|
|
19359
|
-
|
|
19360
|
-
|
|
20709
|
+
let result;
|
|
20710
|
+
// Execute the explicit source-fee path without changing legacy
|
|
20711
|
+
// useForwarder behavior for callers that did not opt in.
|
|
20712
|
+
if (params.config?.feePayment === 'source') {
|
|
20713
|
+
const sourceFeeProvider = this.findSourceFeeProvider(finalResolvedParams);
|
|
20714
|
+
result = formatBridgeResult(await executeSourceFeeBridge(params, finalResolvedParams, sourceFeeProvider), 'to-human-readable');
|
|
20715
|
+
} else {
|
|
20716
|
+
// Find a provider that supports this route
|
|
20717
|
+
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
20718
|
+
// Execute the transfer using the provider and format the result.
|
|
20719
|
+
result = formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
|
|
20720
|
+
}
|
|
19361
20721
|
// Emit error telemetry when the provider returns an error state
|
|
19362
20722
|
// (provider records step failures in the result instead of throwing).
|
|
19363
20723
|
if (result.state === 'error') {
|
|
@@ -19480,42 +20840,7 @@ function assertCCTPV2Config(config) {
|
|
|
19480
20840
|
tokenIn: result.token
|
|
19481
20841
|
});
|
|
19482
20842
|
}
|
|
19483
|
-
|
|
19484
|
-
* Estimate the cost and fees for a cross-chain USDC bridge operation.
|
|
19485
|
-
*
|
|
19486
|
-
* This method calculates the expected gas fees and protocol costs for bridging
|
|
19487
|
-
* without actually executing the transaction. It performs the same validation
|
|
19488
|
-
* as the bridge method but stops before execution.
|
|
19489
|
-
*
|
|
19490
|
-
* @param params - The bridge parameters for cost estimation, including optional invocation metadata
|
|
19491
|
-
* @returns Promise resolving to detailed cost breakdown including gas estimates
|
|
19492
|
-
* @throws {KitError} When the parameters are invalid.
|
|
19493
|
-
* @throws {UnsupportedRouteError} When the route is not supported.
|
|
19494
|
-
*
|
|
19495
|
-
* @example
|
|
19496
|
-
* ```typescript
|
|
19497
|
-
* // Basic usage
|
|
19498
|
-
* const estimate = await kit.estimate({
|
|
19499
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
19500
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
19501
|
-
* amount: '10.50',
|
|
19502
|
-
* token: 'USDC'
|
|
19503
|
-
* })
|
|
19504
|
-
* console.log('Estimated cost:', estimate.totalCost)
|
|
19505
|
-
*
|
|
19506
|
-
* // With custom invocation metadata
|
|
19507
|
-
* const estimate = await kit.estimate({
|
|
19508
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
19509
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
19510
|
-
* amount: '10.50',
|
|
19511
|
-
* token: 'USDC',
|
|
19512
|
-
* invocationMeta: {
|
|
19513
|
-
* traceId: 'custom-trace-id',
|
|
19514
|
-
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
19515
|
-
* },
|
|
19516
|
-
* })
|
|
19517
|
-
* ```
|
|
19518
|
-
*/ async estimate(params) {
|
|
20843
|
+
async estimate(params) {
|
|
19519
20844
|
return withErrorTelemetry(async ()=>{
|
|
19520
20845
|
// First validate the parameters
|
|
19521
20846
|
assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
|
|
@@ -19525,6 +20850,10 @@ function assertCCTPV2Config(config) {
|
|
|
19525
20850
|
this.validateNetworkCompatibility(resolvedParams);
|
|
19526
20851
|
// Merge the custom fee config into the resolved params
|
|
19527
20852
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
20853
|
+
if (params.config?.feePayment === 'source') {
|
|
20854
|
+
this.findSourceFeeProvider(finalResolvedParams);
|
|
20855
|
+
return estimateSourceFeeBridge(finalResolvedParams);
|
|
20856
|
+
}
|
|
19528
20857
|
// Find a provider that supports this route
|
|
19529
20858
|
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
19530
20859
|
// Estimate the transfer using the provider and format amounts to human-readable strings
|
|
@@ -19573,6 +20902,9 @@ function assertCCTPV2Config(config) {
|
|
|
19573
20902
|
* // Get only chains that support forwarding
|
|
19574
20903
|
* const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
|
|
19575
20904
|
*
|
|
20905
|
+
* // Get only chains that can pay fees on the source chain (receive-exact)
|
|
20906
|
+
* const sourceFeeChains = kit.getSupportedChains({ sourceFeeSupported: true })
|
|
20907
|
+
*
|
|
19576
20908
|
* console.log('Supported chains:')
|
|
19577
20909
|
* allChains.forEach(chain => {
|
|
19578
20910
|
* console.log(`- ${chain.name} (${chain.type})`)
|
|
@@ -19621,6 +20953,10 @@ function assertCCTPV2Config(config) {
|
|
|
19621
20953
|
return options.forwarderSupported ? fs.source || fs.destination : !fs.source && !fs.destination;
|
|
19622
20954
|
});
|
|
19623
20955
|
}
|
|
20956
|
+
// Apply source-paid ("receive-exact") fee support filter if provided
|
|
20957
|
+
if (options?.sourceFeeSupported !== undefined) {
|
|
20958
|
+
chains = chains.filter((chain)=>hasSourceFeeSupport(chain) === options.sourceFeeSupported);
|
|
20959
|
+
}
|
|
19624
20960
|
return chains;
|
|
19625
20961
|
}
|
|
19626
20962
|
/**
|
|
@@ -19655,6 +20991,20 @@ function assertCCTPV2Config(config) {
|
|
|
19655
20991
|
return provider;
|
|
19656
20992
|
}
|
|
19657
20993
|
/**
|
|
20994
|
+
* Find the default CCTP v2 provider for a source-fee forwarding route.
|
|
20995
|
+
*
|
|
20996
|
+
* @param params - The resolved provider parameters.
|
|
20997
|
+
* @returns The CCTP v2 provider that supports the forwarded route.
|
|
20998
|
+
* @throws {UnsupportedRouteError} When no source-fee provider supports the route.
|
|
20999
|
+
* @internal
|
|
21000
|
+
*/ findSourceFeeProvider(params) {
|
|
21001
|
+
const provider = this.providers.find((candidate)=>candidate instanceof CCTPV2BridgingProvider && candidate.supportsRoute(params.source.chain, params.destination.chain, params.token, true));
|
|
21002
|
+
if (!(provider instanceof CCTPV2BridgingProvider)) {
|
|
21003
|
+
throw createUnsupportedRouteError(params.source.chain.name, params.destination.chain.name);
|
|
21004
|
+
}
|
|
21005
|
+
return provider;
|
|
21006
|
+
}
|
|
21007
|
+
/**
|
|
19658
21008
|
* Merge custom fee configuration into provider parameters.
|
|
19659
21009
|
*
|
|
19660
21010
|
* Prioritizes any custom fee configuration already present on the
|
|
@@ -19794,7 +21144,7 @@ function assertCCTPV2Config(config) {
|
|
|
19794
21144
|
registerKit(`${pkg$5.name}/${pkg$5.version}`);
|
|
19795
21145
|
|
|
19796
21146
|
var name$3 = "@circle-fin/swap-kit";
|
|
19797
|
-
var version$3 = "1.
|
|
21147
|
+
var version$3 = "1.6.0";
|
|
19798
21148
|
var pkg$3 = {
|
|
19799
21149
|
name: name$3,
|
|
19800
21150
|
version: version$3};
|
|
@@ -19808,7 +21158,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
19808
21158
|
* Catches obviously malformed addresses at parse time; chain-specific validation
|
|
19809
21159
|
* is performed in buildServiceParams.
|
|
19810
21160
|
*/ const destinationAddressSchema = z.union([
|
|
19811
|
-
evmAddressSchema,
|
|
21161
|
+
evmAddressSchema$1,
|
|
19812
21162
|
solanaAddressSchema
|
|
19813
21163
|
]);
|
|
19814
21164
|
/**
|
|
@@ -19854,9 +21204,16 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
19854
21204
|
message: 'stopLimit must be greater than 0'
|
|
19855
21205
|
}).optional(),
|
|
19856
21206
|
customFee: serviceSwapCustomFeeSchema.optional(),
|
|
21207
|
+
apiKey: z.string({
|
|
21208
|
+
invalid_type_error: 'apiKey must be a string'
|
|
21209
|
+
})// Tolerate '' so the `process.env.CIRCLE_API_KEY ?? ''` idiom falls back to
|
|
21210
|
+
// kitKey via resolveApiKey instead of being rejected here.
|
|
21211
|
+
.optional(),
|
|
19857
21212
|
kitKey: z.string({
|
|
19858
21213
|
invalid_type_error: 'kitKey must be a string'
|
|
19859
|
-
})
|
|
21214
|
+
})// Tolerate '' so an unset kit-key env var yields the permissionless path,
|
|
21215
|
+
// matching buildServiceParams (which already omits an empty credential).
|
|
21216
|
+
.optional(),
|
|
19860
21217
|
provider: z.string({
|
|
19861
21218
|
invalid_type_error: 'provider must be a string'
|
|
19862
21219
|
}).min(1, 'provider must be a non-empty string').optional(),
|
|
@@ -20021,23 +21378,49 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20021
21378
|
fees: z.array(serviceSwapFeeSchema).optional()
|
|
20022
21379
|
}).passthrough();
|
|
20023
21380
|
|
|
20024
|
-
/** Placeholder used wherever a
|
|
21381
|
+
/** Placeholder used wherever a credential is scrubbed from logs, traces, or errors. */ const REDACTED_CREDENTIAL = '[redacted]';
|
|
21382
|
+
/**
|
|
21383
|
+
* Config fields that carry a Stablecoin Service credential.
|
|
21384
|
+
*
|
|
21385
|
+
* `apiKey` is the supported field; `kitKey` is the deprecated alias. Redaction
|
|
21386
|
+
* and credential resolution both read this list so a newly added field is
|
|
21387
|
+
* scrubbed everywhere it needs to be.
|
|
21388
|
+
*/ const CREDENTIAL_CONFIG_FIELDS = [
|
|
21389
|
+
'apiKey',
|
|
21390
|
+
'kitKey'
|
|
21391
|
+
];
|
|
20025
21392
|
|
|
21393
|
+
/**
|
|
21394
|
+
* Replaces any credential field present on a config with a redaction marker.
|
|
21395
|
+
*
|
|
21396
|
+
* Covers `apiKey` and the deprecated `kitKey`, so adding a credential field
|
|
21397
|
+
* without extending this list cannot silently leak it into a trace. Returns the
|
|
21398
|
+
* config unchanged when it carries no credential.
|
|
21399
|
+
*/ const redactCredentialFields = (config)=>{
|
|
21400
|
+
const present = CREDENTIAL_CONFIG_FIELDS.filter((field)=>field in config);
|
|
21401
|
+
if (present.length === 0) {
|
|
21402
|
+
return config;
|
|
21403
|
+
}
|
|
21404
|
+
return present.reduce((acc, field)=>({
|
|
21405
|
+
...acc,
|
|
21406
|
+
[field]: REDACTED_CREDENTIAL
|
|
21407
|
+
}), {
|
|
21408
|
+
...config
|
|
21409
|
+
});
|
|
21410
|
+
};
|
|
20026
21411
|
/**
|
|
20027
21412
|
* Builds a redacted projection of swap params that is safe to embed in error
|
|
20028
21413
|
* traces (which may be logged downstream). Allow-lists only non-sensitive
|
|
20029
21414
|
* fields and drops `from` entirely — `from.adapter` can carry signer/private-key
|
|
20030
|
-
* material for signer-backed (viem/ethers) flows — and redacts
|
|
21415
|
+
* material for signer-backed (viem/ethers) flows — and redacts every credential
|
|
21416
|
+
* field on `config`.
|
|
20031
21417
|
*/ const redactSwapParamsForTrace = (params)=>{
|
|
20032
21418
|
if (typeof params !== 'object' || params === null) {
|
|
20033
21419
|
return params;
|
|
20034
21420
|
}
|
|
20035
21421
|
const record = params;
|
|
20036
21422
|
const config = record['config'];
|
|
20037
|
-
const redactedConfig = typeof config === 'object' && config !== null
|
|
20038
|
-
...config,
|
|
20039
|
-
kitKey: REDACTED_KIT_KEY
|
|
20040
|
-
} : config;
|
|
21423
|
+
const redactedConfig = typeof config === 'object' && config !== null ? redactCredentialFields(config) : config;
|
|
20041
21424
|
return {
|
|
20042
21425
|
tokenIn: record['tokenIn'],
|
|
20043
21426
|
tokenOut: record['tokenOut'],
|
|
@@ -20121,6 +21504,41 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20121
21504
|
*
|
|
20122
21505
|
* @internal
|
|
20123
21506
|
*/ const MAX_RATE_ADDRESSES_PER_REQUEST = 100;
|
|
21507
|
+
/**
|
|
21508
|
+
* Environment prefixes carried by Circle platform API keys.
|
|
21509
|
+
*
|
|
21510
|
+
* A Circle API key is `<ENV>_API_KEY:<keyId>:<keySecret>`, where `<ENV>` is one
|
|
21511
|
+
* of these prefixes.
|
|
21512
|
+
*
|
|
21513
|
+
* @internal
|
|
21514
|
+
*/ const API_KEY_ENV_PREFIXES = [
|
|
21515
|
+
'TEST',
|
|
21516
|
+
'LIVE',
|
|
21517
|
+
'SAND',
|
|
21518
|
+
'SANDBOX',
|
|
21519
|
+
'SMOK',
|
|
21520
|
+
'PROD',
|
|
21521
|
+
'STAG',
|
|
21522
|
+
'DEV'
|
|
21523
|
+
];
|
|
21524
|
+
/**
|
|
21525
|
+
* Accepted credential formats for Stablecoin Service authentication.
|
|
21526
|
+
*
|
|
21527
|
+
* Matches a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
21528
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`). API keys are the
|
|
21529
|
+
* recommended credential; kit keys remain accepted as the legacy path.
|
|
21530
|
+
*
|
|
21531
|
+
* @remarks
|
|
21532
|
+
* This is a local pre-flight check, not the authority — the Stablecoin Service
|
|
21533
|
+
* validates the credential and answers 401 when it rejects one. The prefix list
|
|
21534
|
+
* is therefore deliberately permissive: a valid key carrying a prefix this SDK
|
|
21535
|
+
* has not been taught about should reach the service and be judged there rather
|
|
21536
|
+
* than be refused locally, since refusing locally is indistinguishable from an
|
|
21537
|
+
* outage to the caller. Kept as the single source of truth so the pattern is
|
|
21538
|
+
* not restated per call site.
|
|
21539
|
+
*
|
|
21540
|
+
* @internal
|
|
21541
|
+
*/ const API_KEY_PATTERN = new RegExp(`^(?:KIT_KEY|(?:${API_KEY_ENV_PREFIXES.join('|')})_API_KEY)` + ':[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$');
|
|
20124
21542
|
|
|
20125
21543
|
/**
|
|
20126
21544
|
* Zod schema for validating stop limits.
|
|
@@ -20189,13 +21607,14 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20189
21607
|
/**
|
|
20190
21608
|
* Zod schema for validating API keys.
|
|
20191
21609
|
*
|
|
20192
|
-
*
|
|
21610
|
+
* Accepts a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
21611
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`).
|
|
20193
21612
|
*
|
|
20194
21613
|
* @example
|
|
20195
21614
|
* ```typescript
|
|
20196
21615
|
* import { apiKeySchema } from '@core/service-client'
|
|
20197
21616
|
*
|
|
20198
|
-
* const result = apiKeySchema.safeParse('
|
|
21617
|
+
* const result = apiKeySchema.safeParse('TEST_API_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
|
|
20199
21618
|
* if (!result.success) {
|
|
20200
21619
|
* console.error('Invalid API key format:', result.error.issues)
|
|
20201
21620
|
* }
|
|
@@ -20203,7 +21622,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20203
21622
|
*/ const apiKeySchema = z.string({
|
|
20204
21623
|
required_error: 'API key is required',
|
|
20205
21624
|
invalid_type_error: 'Invalid API key format'
|
|
20206
|
-
}).regex(
|
|
21625
|
+
}).regex(API_KEY_PATTERN, 'Invalid API key format');
|
|
20207
21626
|
/**
|
|
20208
21627
|
* Zod schema for platform fees configuration.
|
|
20209
21628
|
*
|
|
@@ -20727,8 +22146,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20727
22146
|
* Validates that an API key is properly formatted for Circle Stablecoin Service.
|
|
20728
22147
|
*
|
|
20729
22148
|
* This function performs validation on API keys to ensure they conform to the
|
|
20730
|
-
* expected format before making API requests. The key must follow the structure
|
|
20731
|
-
* `KIT_KEY:keyId:keySecret
|
|
22149
|
+
* expected format before making API requests. The key must follow the structure
|
|
22150
|
+
* `<ENV>_API_KEY:keyId:keySecret`, or the legacy `KIT_KEY:keyId:keySecret`.
|
|
20732
22151
|
*
|
|
20733
22152
|
* @param apiKey - The API key to validate (can be any type for graceful handling).
|
|
20734
22153
|
* @returns True if the API key is valid, false otherwise.
|
|
@@ -20736,7 +22155,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20736
22155
|
* @remarks
|
|
20737
22156
|
* This function handles invalid inputs gracefully by returning false rather than
|
|
20738
22157
|
* throwing errors. It validates:
|
|
20739
|
-
* - The key
|
|
22158
|
+
* - The key carries a recognized prefix — `<ENV>_API_KEY:` for a Circle platform
|
|
22159
|
+
* API key, or `KIT_KEY:` for the legacy kit key
|
|
20740
22160
|
* - Contains exactly three colon-separated parts
|
|
20741
22161
|
* - keyId and keySecret contain only valid characters (alphanumeric, `-`, `_`, `.`)
|
|
20742
22162
|
* - keyId and keySecret are non-empty
|
|
@@ -20744,40 +22164,80 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20744
22164
|
*
|
|
20745
22165
|
* Valid characters in keyId and keySecret: `a-z`, `A-Z`, `0-9`, `-`, `_`, `.`
|
|
20746
22166
|
*
|
|
22167
|
+
* Format is checked locally only so an obviously malformed credential fails
|
|
22168
|
+
* before a request is made. The Stablecoin Service remains the authority on
|
|
22169
|
+
* whether a well-formed key is actually accepted.
|
|
22170
|
+
*
|
|
20747
22171
|
* @example
|
|
20748
22172
|
* ```typescript
|
|
20749
22173
|
* import { isValidApiKey } from '@core/service-client'
|
|
20750
22174
|
*
|
|
20751
22175
|
* // Valid API key
|
|
20752
|
-
* const isValid = isValidApiKey('
|
|
22176
|
+
* const isValid = isValidApiKey('TEST_API_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
|
|
20753
22177
|
* console.log(isValid) // true
|
|
20754
22178
|
*
|
|
22179
|
+
* // Legacy kit keys remain valid
|
|
22180
|
+
* console.log(isValidApiKey('KIT_KEY:my-key-id:my-key-secret')) // true
|
|
22181
|
+
*
|
|
20755
22182
|
* ```
|
|
20756
22183
|
*/ const isValidApiKey = (apiKey)=>{
|
|
20757
22184
|
// Handle invalid input types
|
|
20758
22185
|
if (typeof apiKey !== 'string') {
|
|
20759
22186
|
return false;
|
|
20760
22187
|
}
|
|
20761
|
-
const apiKeyPattern = /^KIT_KEY:[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$/;
|
|
20762
22188
|
// Validate without trimming - any whitespace will cause validation to fail
|
|
20763
|
-
return
|
|
22189
|
+
return API_KEY_PATTERN.test(apiKey);
|
|
20764
22190
|
};
|
|
20765
22191
|
/**
|
|
20766
|
-
*
|
|
22192
|
+
* Resolve the credential to authenticate a Stablecoin Service request with.
|
|
22193
|
+
*
|
|
22194
|
+
* `apiKey` is the supported field; `kitKey` is the deprecated alias kept for
|
|
22195
|
+
* existing integrations. When both are supplied `apiKey` wins, so a caller
|
|
22196
|
+
* migrating field-by-field cannot be silently pinned to a stale credential.
|
|
20767
22197
|
*
|
|
20768
|
-
*
|
|
20769
|
-
*
|
|
20770
|
-
*
|
|
20771
|
-
*
|
|
20772
|
-
*
|
|
20773
|
-
*
|
|
20774
|
-
*
|
|
20775
|
-
*
|
|
22198
|
+
* An empty-string value is treated as absent on either field. Without this, the
|
|
22199
|
+
* common `process.env.CIRCLE_API_KEY ?? ''` idiom (which yields `''` when the
|
|
22200
|
+
* variable is unset) would either shadow a working `kitKey` or, on a bare
|
|
22201
|
+
* `kitKey: ''`, reach downstream validation as an invalid credential instead of
|
|
22202
|
+
* falling through to the permissionless path.
|
|
22203
|
+
*
|
|
22204
|
+
* @param source - Object carrying either credential field, or neither.
|
|
22205
|
+
* @returns The credential to use, or `undefined` for the permissionless
|
|
22206
|
+
* (keyless) path.
|
|
22207
|
+
*
|
|
22208
|
+
* @example
|
|
22209
|
+
* ```typescript
|
|
22210
|
+
* import { resolveApiKey } from '@core/service-client'
|
|
20776
22211
|
*
|
|
20777
|
-
*
|
|
20778
|
-
*
|
|
22212
|
+
* resolveApiKey({ apiKey: 'TEST_API_KEY:id:secret' }) // 'TEST_API_KEY:id:secret'
|
|
22213
|
+
* resolveApiKey({ kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
22214
|
+
* resolveApiKey({ apiKey: '', kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
22215
|
+
* resolveApiKey({ kitKey: '' }) // undefined
|
|
22216
|
+
* resolveApiKey({}) // undefined
|
|
22217
|
+
* ```
|
|
22218
|
+
*/ const resolveApiKey = (source)=>{
|
|
22219
|
+
// Treat an empty-string value as absent on either field so the
|
|
22220
|
+
// `env ?? ''` idiom falls through to the next credential (or permissionless).
|
|
22221
|
+
const normalize = (value)=>value !== undefined && value !== '' ? value : undefined;
|
|
22222
|
+
return normalize(source.apiKey) ?? normalize(source.kitKey);
|
|
22223
|
+
};
|
|
22224
|
+
/**
|
|
22225
|
+
* Assert that a Stablecoin Service credential is not supplied from a browser.
|
|
22226
|
+
*
|
|
22227
|
+
* An API key (`<ENV>_API_KEY:<id>:<secret>`) and a legacy kit key
|
|
22228
|
+
* (`KIT_KEY:<id>:<secret>`) are both server-only secrets. Every Stablecoin
|
|
22229
|
+
* Service request that attaches an `Authorization: Bearer` header funnels
|
|
22230
|
+
* through this package, so calling this guard before that header is built
|
|
22231
|
+
* prevents the secret from being sent from — and thus bundled into — a client
|
|
22232
|
+
* application. In Node.js the check is a no-op, preserving the legitimate "hold
|
|
22233
|
+
* the credential on the server, forward the prepared transaction to the client"
|
|
22234
|
+
* flow. When no credential is supplied the permissionless (keyless) client path
|
|
22235
|
+
* remains fully allowed.
|
|
22236
|
+
*
|
|
22237
|
+
* @param apiKey - The inline credential for the request, or `undefined` when
|
|
22238
|
+
* none was supplied (permissionless mode).
|
|
20779
22239
|
* @returns Nothing.
|
|
20780
|
-
* @throws KitError with VALIDATION_FAILED when a
|
|
22240
|
+
* @throws KitError with VALIDATION_FAILED when a credential is supplied while
|
|
20781
22241
|
* running in a browser environment. The secret value is never echoed.
|
|
20782
22242
|
*
|
|
20783
22243
|
* @example
|
|
@@ -20785,10 +22245,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20785
22245
|
* import { assertBrowserSafeApiKey } from '@core/service-client'
|
|
20786
22246
|
*
|
|
20787
22247
|
* // Server (Node.js): no-op, request proceeds with the Authorization header.
|
|
20788
|
-
* assertBrowserSafeApiKey('
|
|
22248
|
+
* assertBrowserSafeApiKey('TEST_API_KEY:my-id:my-secret')
|
|
20789
22249
|
*
|
|
20790
22250
|
* // Browser: throws to stop the secret from leaking into the client bundle.
|
|
20791
|
-
* assertBrowserSafeApiKey('
|
|
22251
|
+
* assertBrowserSafeApiKey('TEST_API_KEY:my-id:my-secret')
|
|
20792
22252
|
*
|
|
20793
22253
|
* // Browser, permissionless: allowed.
|
|
20794
22254
|
* assertBrowserSafeApiKey(undefined)
|
|
@@ -20798,7 +22258,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20798
22258
|
return;
|
|
20799
22259
|
}
|
|
20800
22260
|
if (isBrowserEnvironment()) {
|
|
20801
|
-
throw createValidationFailedError$1('
|
|
22261
|
+
throw createValidationFailedError$1('apiKey', '[redacted]', 'apiKey must not be provided in a browser environment — it is a server-only secret. Run kit operations that use an API key on your server, or omit apiKey (and the legacy kitKey) to use the permissionless (keyless) client path');
|
|
20802
22262
|
}
|
|
20803
22263
|
};
|
|
20804
22264
|
|
|
@@ -20810,7 +22270,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20810
22270
|
*
|
|
20811
22271
|
* @remarks
|
|
20812
22272
|
* - Input validation leverages Zod schemas to surface actionable KitErrors.
|
|
20813
|
-
* - API key format must match
|
|
22273
|
+
* - API key format must match `<ENV>_API_KEY:<keyId>:<keySecret>`, or the
|
|
22274
|
+
* legacy `KIT_KEY:<keyId>:<keySecret>`
|
|
20814
22275
|
*
|
|
20815
22276
|
* @param params - The create transaction request parameters with API key.
|
|
20816
22277
|
* @returns A promise that resolves to a typed {@link CreateSwapResponse}.
|
|
@@ -20839,7 +22300,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20839
22300
|
* recipientAddress: '0x050928e1f9086b20539c767c96909663ff7fbdf2',
|
|
20840
22301
|
* },
|
|
20841
22302
|
* },
|
|
20842
|
-
* apiKey: '
|
|
22303
|
+
* apiKey: 'TEST_API_KEY:example-key:example-secret',
|
|
20843
22304
|
* })
|
|
20844
22305
|
*
|
|
20845
22306
|
* console.log(`Transaction target: ${transaction.transaction.target}`)
|
|
@@ -20858,13 +22319,13 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20858
22319
|
const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
|
|
20859
22320
|
// Remove the API key from the request body
|
|
20860
22321
|
const { apiKey, ...requestBody } = validatedParams;
|
|
20861
|
-
// Never let a server-only
|
|
22322
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
20862
22323
|
assertBrowserSafeApiKey(apiKey);
|
|
20863
22324
|
const effectiveConfig = {
|
|
20864
22325
|
...DEFAULT_CONFIG$1,
|
|
20865
22326
|
headers: {
|
|
20866
22327
|
...DEFAULT_CONFIG$1.headers,
|
|
20867
|
-
// Permissionless mode: no Authorization header when the
|
|
22328
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
20868
22329
|
...apiKey !== undefined && {
|
|
20869
22330
|
Authorization: `Bearer ${apiKey}`
|
|
20870
22331
|
}
|
|
@@ -21014,7 +22475,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21014
22475
|
}
|
|
21015
22476
|
// Use validated data
|
|
21016
22477
|
const validatedParams = result.data;
|
|
21017
|
-
// Never let a server-only
|
|
22478
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
21018
22479
|
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
21019
22480
|
// Build the API URL
|
|
21020
22481
|
const url = buildQuoteUrl(validatedParams);
|
|
@@ -21023,7 +22484,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21023
22484
|
...DEFAULT_CONFIG$1,
|
|
21024
22485
|
headers: {
|
|
21025
22486
|
...DEFAULT_CONFIG$1.headers,
|
|
21026
|
-
// Permissionless mode: no Authorization header when the
|
|
22487
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
21027
22488
|
...validatedParams.apiKey !== undefined && {
|
|
21028
22489
|
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
21029
22490
|
}
|
|
@@ -21088,14 +22549,14 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21088
22549
|
toChain: result.data.toChain
|
|
21089
22550
|
}
|
|
21090
22551
|
};
|
|
21091
|
-
// Never let a server-only
|
|
22552
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
21092
22553
|
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
21093
22554
|
const url = buildSwapStatusUrl(validatedParams);
|
|
21094
22555
|
const effectiveConfig = {
|
|
21095
22556
|
...DEFAULT_CONFIG$1,
|
|
21096
22557
|
headers: {
|
|
21097
22558
|
...DEFAULT_CONFIG$1.headers,
|
|
21098
|
-
// Permissionless mode: no Authorization header when the
|
|
22559
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
21099
22560
|
...validatedParams.apiKey !== undefined && {
|
|
21100
22561
|
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
21101
22562
|
}
|
|
@@ -21121,7 +22582,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21121
22582
|
* const url = buildTokenRatesUrl({
|
|
21122
22583
|
* chain: 'Ethereum',
|
|
21123
22584
|
* addresses: ['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'],
|
|
21124
|
-
* apiKey: '
|
|
22585
|
+
* apiKey: 'TEST_API_KEY:key:secret',
|
|
21125
22586
|
* })
|
|
21126
22587
|
* // => 'https://api.circle.com/v1/stablecoinKits/rates?chain=Ethereum&addresses=...'
|
|
21127
22588
|
* ```
|
|
@@ -21194,14 +22655,14 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21194
22655
|
addresses: result.data.addresses
|
|
21195
22656
|
}
|
|
21196
22657
|
};
|
|
21197
|
-
// Never let a server-only
|
|
22658
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
21198
22659
|
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
21199
22660
|
const url = buildTokenRatesUrl(validatedParams);
|
|
21200
22661
|
const effectiveConfig = {
|
|
21201
22662
|
...DEFAULT_CONFIG$1,
|
|
21202
22663
|
headers: {
|
|
21203
22664
|
...DEFAULT_CONFIG$1.headers,
|
|
21204
|
-
// Permissionless mode: no Authorization header when the
|
|
22665
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
21205
22666
|
...validatedParams.apiKey !== undefined && {
|
|
21206
22667
|
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
21207
22668
|
}
|
|
@@ -23179,7 +24640,7 @@ const abiParameterSchema = z.object({
|
|
|
23179
24640
|
*/ z.object({
|
|
23180
24641
|
type: z.literal('evm'),
|
|
23181
24642
|
abi: abiSchema,
|
|
23182
|
-
address: evmAddressSchema,
|
|
24643
|
+
address: evmAddressSchema$1,
|
|
23183
24644
|
functionName: z.string({
|
|
23184
24645
|
required_error: 'Function name is required',
|
|
23185
24646
|
invalid_type_error: 'Function name must be a string'
|
|
@@ -23216,7 +24677,7 @@ const abiParameterSchema = z.object({
|
|
|
23216
24677
|
* }
|
|
23217
24678
|
* ```
|
|
23218
24679
|
*/ z.object({
|
|
23219
|
-
address: evmAddressSchema,
|
|
24680
|
+
address: evmAddressSchema$1,
|
|
23220
24681
|
value: z.bigint({
|
|
23221
24682
|
required_error: 'Value is required for native transfers',
|
|
23222
24683
|
invalid_type_error: 'Value must be a bigint'
|
|
@@ -24450,6 +25911,23 @@ async function signUnits(group, signingUnits) {
|
|
|
24450
25911
|
}
|
|
24451
25912
|
return signedSets;
|
|
24452
25913
|
}
|
|
25914
|
+
/**
|
|
25915
|
+
* Fail loudly if a signing group carries an intent it does not own.
|
|
25916
|
+
*
|
|
25917
|
+
* Every intent in a group is signed by `group.address`, so that signer must own
|
|
25918
|
+
* each intent's `spec.sourceSigner`. If distinct spend sources ever collapse
|
|
25919
|
+
* into one group again (DEVX-3638), this throws before signing instead of
|
|
25920
|
+
* silently producing a wrong-signer signature. specs are bytes32 and
|
|
25921
|
+
* `group.address` is a native address, so normalize both before comparing.
|
|
25922
|
+
*/ function assertGroupSignerOwnsIntents(group) {
|
|
25923
|
+
if (group.address == null || group.address === '') return;
|
|
25924
|
+
const groupSigner = convertAddress(group.address, 'bytes32').toLowerCase();
|
|
25925
|
+
for (const intent of group.intents){
|
|
25926
|
+
if (intent.spec.sourceSigner.toLowerCase() !== groupSigner) {
|
|
25927
|
+
throw createValidationFailedError$1('group.address', group.address, `Signing group for ${group.address} carries an intent whose ` + `sourceSigner is ${intent.spec.sourceSigner}; a group must be signed ` + 'by the wallet that owns every intent it carries. This indicates ' + 'distinct spend sources collapsed into a single signing group.');
|
|
25928
|
+
}
|
|
25929
|
+
}
|
|
25930
|
+
}
|
|
24453
25931
|
/**
|
|
24454
25932
|
* Sign an EVM adapter group.
|
|
24455
25933
|
*
|
|
@@ -24492,22 +25970,42 @@ async function signUnits(group, signingUnits) {
|
|
|
24492
25970
|
// Validate the collection before doing bytecode reads or asking a wallet
|
|
24493
25971
|
// to sign. evmSigningData owns the canonical BurnIntent validation.
|
|
24494
25972
|
validateGroupIntents(group.intents);
|
|
25973
|
+
// Guard against a wrong-signer collapse: the group's signer must own every
|
|
25974
|
+
// intent it carries.
|
|
25975
|
+
assertGroupSignerOwnsIntents(group);
|
|
24495
25976
|
const chainsByDomain = collectChainsByDomain(group);
|
|
24496
25977
|
const signerTypeByDomain = await classifySignerTypes(group, chainsByDomain);
|
|
24497
25978
|
const signingUnits = createSigningUnits(group, signerTypeByDomain);
|
|
24498
25979
|
return await signUnits(group, signingUnits);
|
|
24499
25980
|
}
|
|
24500
25981
|
|
|
25982
|
+
// One adapter instance can serve several signer addresses (e.g. a single
|
|
25983
|
+
// dev-controlled adapter used for multiple wallets), so the group map must be
|
|
25984
|
+
// keyed on `(adapter, sourceSigner)` rather than the adapter reference alone.
|
|
25985
|
+
// Keying on the adapter alone collapsed two same-chain sources into one group,
|
|
25986
|
+
// signing the second wallet's intent with the first wallet (DEVX-3638 /
|
|
25987
|
+
// DEVX-3194). A Map needs a primitive key, so give each adapter instance a
|
|
25988
|
+
// stable token and combine it with the (case-normalized) signer address.
|
|
25989
|
+
const adapterTokens = new WeakMap();
|
|
25990
|
+
let nextAdapterToken = 0;
|
|
25991
|
+
function groupKey(alloc) {
|
|
25992
|
+
let token = adapterTokens.get(alloc.adapter);
|
|
25993
|
+
if (token === undefined) {
|
|
25994
|
+
token = nextAdapterToken++;
|
|
25995
|
+
adapterTokens.set(alloc.adapter, token);
|
|
25996
|
+
}
|
|
25997
|
+
return `${String(token)}:${alloc.sourceSigner.toLowerCase()}`;
|
|
25998
|
+
}
|
|
24501
25999
|
/**
|
|
24502
26000
|
* Add an EVM intent into the batched EVM group map.
|
|
24503
26001
|
*
|
|
24504
|
-
* On EVM, all intents for the same adapter are batched into a
|
|
24505
|
-
* group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
26002
|
+
* On EVM, all intents for the same adapter **and signer** are batched into a
|
|
26003
|
+
* single group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
24506
26004
|
* while signing ERC-1271 intents separately on their source chains.
|
|
24507
26005
|
*
|
|
24508
26006
|
* @param intent - The burn intent to group.
|
|
24509
26007
|
* @param alloc - The allocation that resolved to this intent.
|
|
24510
|
-
* @param evmGroups - Mutable map of adapter → EVM group.
|
|
26008
|
+
* @param evmGroups - Mutable map of `(adapter, sourceSigner)` key → EVM group.
|
|
24511
26009
|
*
|
|
24512
26010
|
* @example
|
|
24513
26011
|
* ```typescript
|
|
@@ -24517,12 +26015,13 @@ async function signUnits(group, signingUnits) {
|
|
|
24517
26015
|
* groupEvmIntents(intent, alloc, groups)
|
|
24518
26016
|
* ```
|
|
24519
26017
|
*/ function groupEvmIntents(intent, alloc, evmGroups) {
|
|
24520
|
-
const
|
|
26018
|
+
const key = groupKey(alloc);
|
|
26019
|
+
const existing = evmGroups.get(key);
|
|
24521
26020
|
if (existing) {
|
|
24522
26021
|
existing.intents.push(intent);
|
|
24523
26022
|
existing.chainsByDomain.set(alloc.chain.gateway.domain, alloc.chain);
|
|
24524
26023
|
} else {
|
|
24525
|
-
evmGroups.set(
|
|
26024
|
+
evmGroups.set(key, {
|
|
24526
26025
|
adapter: alloc.adapter,
|
|
24527
26026
|
chain: alloc.chain,
|
|
24528
26027
|
intents: [
|
|
@@ -24615,7 +26114,7 @@ z.object({
|
|
|
24615
26114
|
signature: evmSignatureSchema,
|
|
24616
26115
|
tokenInputs: z.array(z.object({
|
|
24617
26116
|
permitType: z.nativeEnum(PermitType),
|
|
24618
|
-
token: evmAddressSchema,
|
|
26117
|
+
token: evmAddressSchema$1,
|
|
24619
26118
|
amount: z.bigint().refine((value)=>value >= 0n, {
|
|
24620
26119
|
message: 'amount must be a non-negative bigint'
|
|
24621
26120
|
}),
|
|
@@ -25208,7 +26707,7 @@ function writeBytes32(buffer, hex, offset) {
|
|
|
25208
26707
|
});
|
|
25209
26708
|
|
|
25210
26709
|
/**
|
|
25211
|
-
* Assert that the
|
|
26710
|
+
* Assert that the credential is valid for swap operations.
|
|
25212
26711
|
*
|
|
25213
26712
|
* @remarks
|
|
25214
26713
|
* This validation is called when swap operations are executed, not at construction time.
|
|
@@ -25216,34 +26715,35 @@ function writeBytes32(buffer, hex, offset) {
|
|
|
25216
26715
|
* at usage time rather than construction time.
|
|
25217
26716
|
*
|
|
25218
26717
|
* Validates:
|
|
25219
|
-
* -
|
|
25220
|
-
*
|
|
25221
|
-
*
|
|
26718
|
+
* - The credential matches an accepted format when provided — a Circle API key
|
|
26719
|
+
* (`<ENV>_API_KEY:id:secret`) or a legacy kit key (`KIT_KEY:id:secret`).
|
|
26720
|
+
* An absent or empty credential is permitted (permissionless mode) — the swap
|
|
26721
|
+
* service treats the credential as optional.
|
|
25222
26722
|
*
|
|
25223
|
-
* @param
|
|
25224
|
-
* @throws KitError with VALIDATION_FAILED if a
|
|
25225
|
-
* match
|
|
26723
|
+
* @param apiKey - The inline credential from the swap operation config (optional)
|
|
26724
|
+
* @throws KitError with VALIDATION_FAILED if a credential is provided but does
|
|
26725
|
+
* not match an accepted format
|
|
25226
26726
|
*
|
|
25227
26727
|
* @example
|
|
25228
26728
|
* ```typescript
|
|
25229
|
-
* import {
|
|
26729
|
+
* import { assertApiKey } from './assertApiKey'
|
|
25230
26730
|
*
|
|
25231
|
-
* const
|
|
26731
|
+
* const apiKey = 'TEST_API_KEY:9d1bd085-bad2-4a2b-ae67-5814b85167c9:secretvalue'
|
|
25232
26732
|
*
|
|
25233
|
-
* // Validate
|
|
25234
|
-
*
|
|
26733
|
+
* // Validate the credential before use
|
|
26734
|
+
* assertApiKey(apiKey)
|
|
25235
26735
|
* ```
|
|
25236
|
-
*/ function
|
|
25237
|
-
// Permissionless mode: the swap service treats the
|
|
25238
|
-
// absent (or empty)
|
|
26736
|
+
*/ function assertApiKey(apiKey) {
|
|
26737
|
+
// Permissionless mode: the swap service treats the credential as optional, so
|
|
26738
|
+
// an absent (or empty) value is valid. Only validate the format when one is
|
|
25239
26739
|
// actually provided.
|
|
25240
|
-
if (!
|
|
26740
|
+
if (!apiKey) {
|
|
25241
26741
|
return;
|
|
25242
26742
|
}
|
|
25243
|
-
const apiKeyResult = apiKeySchema.safeParse(
|
|
26743
|
+
const apiKeyResult = apiKeySchema.safeParse(apiKey);
|
|
25244
26744
|
if (!apiKeyResult.success) {
|
|
25245
26745
|
const errors = apiKeyResult.error.issues.map((issue)=>issue.message).join(', ');
|
|
25246
|
-
throw createValidationFailedError$1('
|
|
26746
|
+
throw createValidationFailedError$1('apiKey', REDACTED_CREDENTIAL, `Invalid API key format. ${errors}. Expected format: <ENV>_API_KEY:<keyId>:<keySecret> (for example TEST_API_KEY:... or LIVE_API_KEY:...). Legacy KIT_KEY:<keyId>:<keySecret> values are still accepted. Get your key at: https://developers.circle.com/w3s/keys`);
|
|
25247
26747
|
}
|
|
25248
26748
|
}
|
|
25249
26749
|
|
|
@@ -25435,7 +26935,7 @@ const TOKEN_REGISTRY$4 = createTokenRegistry();
|
|
|
25435
26935
|
}
|
|
25436
26936
|
// Validate recipient address format matches the chain type
|
|
25437
26937
|
if (chain.type === 'evm') {
|
|
25438
|
-
const result = evmAddressSchema.safeParse(recipientAddress);
|
|
26938
|
+
const result = evmAddressSchema$1.safeParse(recipientAddress);
|
|
25439
26939
|
if (!result.success) {
|
|
25440
26940
|
throw createInvalidAddressError(recipientAddress, chain.name, '0x-prefixed 42-character hex string (0x + 40 hex characters)');
|
|
25441
26941
|
}
|
|
@@ -25497,7 +26997,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
25497
26997
|
* to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
|
|
25498
26998
|
* config: {
|
|
25499
26999
|
* slippageBps: 300,
|
|
25500
|
-
*
|
|
27000
|
+
* apiKey: 'TEST_API_KEY:...:...' // Inline credential (optional)
|
|
25501
27001
|
* }
|
|
25502
27002
|
* }
|
|
25503
27003
|
*
|
|
@@ -25535,18 +27035,17 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
25535
27035
|
validateResolvedAddress(resolvedTokenInAddress, chain);
|
|
25536
27036
|
validateResolvedAddress(resolvedTokenOutAddress, destinationChain);
|
|
25537
27037
|
validateResolvedAddress(to, destinationChain);
|
|
25538
|
-
const
|
|
25539
|
-
// Validate the
|
|
25540
|
-
|
|
27038
|
+
const apiKey = resolveApiKey(config ?? {});
|
|
27039
|
+
// Validate the credential format when one is provided (permissionless
|
|
27040
|
+
// otherwise).
|
|
27041
|
+
assertApiKey(apiKey);
|
|
25541
27042
|
// Validate custom fee configuration if present
|
|
25542
27043
|
const customFee = config?.customFee;
|
|
25543
27044
|
if (customFee !== undefined) {
|
|
25544
27045
|
const feePayoutChain = resolveFeePayoutChain(tokenIn, tokenOut, chain, destinationChain);
|
|
25545
27046
|
validateCustomFeeConfig(customFee, feePayoutChain);
|
|
25546
27047
|
}
|
|
25547
|
-
// Build CreateSwapParams for service client
|
|
25548
|
-
// Use object spread to conditionally include optional properties
|
|
25549
|
-
// Note: Service client expects 'apiKey' field, so we map kitKey → apiKey
|
|
27048
|
+
// Build CreateSwapParams for service client, spreading optional properties.
|
|
25550
27049
|
const chainName = chain.chain // The API expects the Blockchain enum value format like "World_Chain"
|
|
25551
27050
|
;
|
|
25552
27051
|
const destinationChainName = destinationChain.chain;
|
|
@@ -25581,9 +27080,9 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
25581
27080
|
}
|
|
25582
27081
|
}
|
|
25583
27082
|
},
|
|
25584
|
-
//
|
|
25585
|
-
...
|
|
25586
|
-
apiKey
|
|
27083
|
+
// Omitted in permissionless mode.
|
|
27084
|
+
...apiKey ? {
|
|
27085
|
+
apiKey
|
|
25587
27086
|
} : {}
|
|
25588
27087
|
};
|
|
25589
27088
|
}
|
|
@@ -27487,9 +28986,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
27487
28986
|
* ```typescript
|
|
27488
28987
|
* import { StablecoinServiceSwapProvider } from '@circle-fin/provider-stablecoin-service-swap'
|
|
27489
28988
|
*
|
|
27490
|
-
* // Create provider with developer fee (1 USDC)
|
|
28989
|
+
* // Create provider with developer fee (1 USDC).
|
|
28990
|
+
* // Credentials are supplied per operation via `config.apiKey`, not here.
|
|
27491
28991
|
* const provider = new StablecoinServiceSwapProvider({
|
|
27492
|
-
* kitKey: process.env.STABLECOIN_KIT_API_KEY,
|
|
27493
28992
|
* customFee: {
|
|
27494
28993
|
* amount: '1000000', // 1 USDC in base units (6 decimals)
|
|
27495
28994
|
* recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0'
|
|
@@ -28151,9 +29650,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
28151
29650
|
* import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
28152
29651
|
* import { Ethereum } from '@core/chains'
|
|
28153
29652
|
*
|
|
28154
|
-
* const provider = new StablecoinServiceSwapProvider(
|
|
28155
|
-
* kitKey: process.env.STABLECOIN_KIT_API_KEY
|
|
28156
|
-
* })
|
|
29653
|
+
* const provider = new StablecoinServiceSwapProvider()
|
|
28157
29654
|
*
|
|
28158
29655
|
* const adapter = createAdapterFromPrivateKey({
|
|
28159
29656
|
* privateKey: process.env.PRIVATE_KEY
|
|
@@ -28647,7 +30144,7 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
28647
30144
|
/**
|
|
28648
30145
|
* Fee recipient address (required).
|
|
28649
30146
|
* Must be a valid EVM address or Solana address.
|
|
28650
|
-
*/ recipientAddress: z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
30147
|
+
*/ recipientAddress: z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
28651
30148
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
28652
30149
|
})
|
|
28653
30150
|
}).strict();
|
|
@@ -28659,7 +30156,8 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
28659
30156
|
* - slippageBps: Optional positive number for slippage tolerance
|
|
28660
30157
|
* - stopLimit: Optional decimal string for minimum output
|
|
28661
30158
|
* - customFee: Optional fee configuration
|
|
28662
|
-
* -
|
|
30159
|
+
* - apiKey: Optional credential string
|
|
30160
|
+
* - kitKey: Optional credential string (deprecated alias for apiKey)
|
|
28663
30161
|
*/ const swapConfigSchema = z.object({
|
|
28664
30162
|
allowanceStrategy: allowanceStrategySchema$1.optional(),
|
|
28665
30163
|
slippageBps: z.number().int().min(0).optional(),
|
|
@@ -28669,11 +30167,12 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
28669
30167
|
attributeName: 'stopLimit'
|
|
28670
30168
|
})(z.string())).optional(),
|
|
28671
30169
|
customFee: swapCustomFeeSchema.optional(),
|
|
30170
|
+
apiKey: z.string().optional(),
|
|
28672
30171
|
kitKey: z.string().optional()
|
|
28673
30172
|
});
|
|
28674
30173
|
const swapDestinationSchema = z.object({
|
|
28675
30174
|
chain: optionalSwapChainIdentifierField,
|
|
28676
|
-
recipientAddress: z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
30175
|
+
recipientAddress: z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
28677
30176
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
28678
30177
|
}).optional()
|
|
28679
30178
|
}).strict();
|
|
@@ -30780,7 +32279,8 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
30780
32279
|
/**
|
|
30781
32280
|
* Helper to format the config section of a swap result, transforming fee amounts.
|
|
30782
32281
|
*
|
|
30783
|
-
* Strips
|
|
32282
|
+
* Strips the credential fields (`apiKey` and the deprecated `kitKey`) from the
|
|
32283
|
+
* config before returning, so a result is safe to log or serialize.
|
|
30784
32284
|
*
|
|
30785
32285
|
* @param config - The swap config from the provider.
|
|
30786
32286
|
* @param outputTokenTransform - The amount transformer for output-token-denominated values (fees and amountOut).
|
|
@@ -30789,11 +32289,11 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
30789
32289
|
if (!config) {
|
|
30790
32290
|
return undefined;
|
|
30791
32291
|
}
|
|
30792
|
-
//
|
|
30793
|
-
|
|
30794
|
-
|
|
30795
|
-
|
|
30796
|
-
|
|
32292
|
+
// Rebuild the config without any credential field. Filtering entries rather
|
|
32293
|
+
// than deleting them avoids reading the deprecated `kitKey` property
|
|
32294
|
+
// directly, and keeps the credential list in one place.
|
|
32295
|
+
const credentialFields = CREDENTIAL_CONFIG_FIELDS;
|
|
32296
|
+
const cloneConfig = Object.fromEntries(Object.entries(config).filter(([key])=>!credentialFields.includes(key)));
|
|
30797
32297
|
if (Object.keys(cloneConfig).length === 0) {
|
|
30798
32298
|
return undefined;
|
|
30799
32299
|
}
|
|
@@ -30994,9 +32494,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
30994
32494
|
try {
|
|
30995
32495
|
// Step 1: Build quote params directly (no need for buildServiceParams)
|
|
30996
32496
|
// Use chain.chain (Blockchain enum value like "World_Chain") not chain.name
|
|
30997
|
-
// The
|
|
30998
|
-
// fetched without an Authorization header.
|
|
30999
|
-
const
|
|
32497
|
+
// The credential is optional (permissionless mode); when absent the quote
|
|
32498
|
+
// is fetched without an Authorization header.
|
|
32499
|
+
const apiKey = resolveApiKey(params.config ?? {});
|
|
31000
32500
|
// Resolve token aliases to addresses for the quote API
|
|
31001
32501
|
// The quote endpoint requires resolved addresses, not aliases like 'USDC'
|
|
31002
32502
|
const chain = params.from.chain;
|
|
@@ -31021,8 +32521,8 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
31021
32521
|
...params.config?.slippageBps !== undefined && {
|
|
31022
32522
|
slippageBps: params.config.slippageBps
|
|
31023
32523
|
},
|
|
31024
|
-
...
|
|
31025
|
-
apiKey
|
|
32524
|
+
...apiKey ? {
|
|
32525
|
+
apiKey
|
|
31026
32526
|
} : {}
|
|
31027
32527
|
};
|
|
31028
32528
|
// Step 2: Get quote from service
|
|
@@ -31426,7 +32926,7 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31426
32926
|
* the service returns `receivingTokenDecimals`. When decimals are missing
|
|
31427
32927
|
* the field is omitted entirely (never raw base units).
|
|
31428
32928
|
*
|
|
31429
|
-
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `
|
|
32929
|
+
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `apiKey`.
|
|
31430
32930
|
* @returns A single snapshot of the swap's status.
|
|
31431
32931
|
* @throws \{KitError\} If `chainIn` or `chainOut` is malformed.
|
|
31432
32932
|
*
|
|
@@ -31442,7 +32942,7 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31442
32942
|
* txHash: result.txHash,
|
|
31443
32943
|
* chainIn: result.chainIn,
|
|
31444
32944
|
* chainOut: result.chainOut,
|
|
31445
|
-
*
|
|
32945
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31446
32946
|
* })
|
|
31447
32947
|
* while (status.progress.status === 'PENDING') {
|
|
31448
32948
|
* await new Promise((r) => setTimeout(r, 3_000))
|
|
@@ -31450,7 +32950,7 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31450
32950
|
* txHash: result.txHash,
|
|
31451
32951
|
* chainIn: result.chainIn,
|
|
31452
32952
|
* chainOut: result.chainOut,
|
|
31453
|
-
*
|
|
32953
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31454
32954
|
* })
|
|
31455
32955
|
* }
|
|
31456
32956
|
*
|
|
@@ -31468,14 +32968,15 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31468
32968
|
const chainIn = normalizeChain(params.chainIn, 'chainIn');
|
|
31469
32969
|
const chainOut = params.chainOut === undefined ? undefined : normalizeChain(params.chainOut, 'chainOut');
|
|
31470
32970
|
const isCrossChain = chainOut !== undefined && chainOut !== chainIn;
|
|
32971
|
+
const apiKey = resolveApiKey(params);
|
|
31471
32972
|
const request = {
|
|
31472
32973
|
txHash: params.txHash,
|
|
31473
32974
|
chain: chainIn,
|
|
31474
32975
|
...isCrossChain && {
|
|
31475
32976
|
toChain: chainOut
|
|
31476
32977
|
},
|
|
31477
|
-
...
|
|
31478
|
-
apiKey
|
|
32978
|
+
...apiKey ? {
|
|
32979
|
+
apiKey
|
|
31479
32980
|
} : {}
|
|
31480
32981
|
};
|
|
31481
32982
|
let raw = await getSwapStatus$2(request);
|
|
@@ -31554,9 +33055,9 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31554
33055
|
*
|
|
31555
33056
|
* Two parameter shapes are accepted:
|
|
31556
33057
|
*
|
|
31557
|
-
* - Pass the {@link SwapResult} directly: `{ result,
|
|
33058
|
+
* - Pass the {@link SwapResult} directly: `{ result, apiKey, ... }`.
|
|
31558
33059
|
* Cleanest when chaining off `kit.swap()`.
|
|
31559
|
-
* - Pass discrete fields: `{ txHash, chainIn, chainOut?,
|
|
33060
|
+
* - Pass discrete fields: `{ txHash, chainIn, chainOut?, apiKey, ... }`.
|
|
31560
33061
|
* Useful for resuming an in-flight swap from a persisted record.
|
|
31561
33062
|
*
|
|
31562
33063
|
* @param params - Wait configuration. See {@link WaitForSwapParams}.
|
|
@@ -31576,7 +33077,7 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31576
33077
|
*
|
|
31577
33078
|
* const final = await waitForSwap({
|
|
31578
33079
|
* result,
|
|
31579
|
-
*
|
|
33080
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31580
33081
|
* onProgress: (snapshot) => {
|
|
31581
33082
|
* console.log(snapshot.progress.substatus ?? snapshot.progress.status)
|
|
31582
33083
|
* },
|
|
@@ -31594,7 +33095,7 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31594
33095
|
* txHash: '0x…',
|
|
31595
33096
|
* chainIn: 'Ethereum',
|
|
31596
33097
|
* chainOut: 'Base',
|
|
31597
|
-
*
|
|
33098
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31598
33099
|
* })
|
|
31599
33100
|
* ```
|
|
31600
33101
|
*/ async function waitForSwap$1(params) {
|
|
@@ -31610,14 +33111,15 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31610
33111
|
const chainOut = isResultShape(params) ? params.result.chainOut : params.chainOut;
|
|
31611
33112
|
// Delegate parameter validation (txHash, chainIn, chainOut) to
|
|
31612
33113
|
// getSwapStatus so the same rules apply to both helpers.
|
|
33114
|
+
const apiKey = resolveApiKey(params);
|
|
31613
33115
|
const statusParams = {
|
|
31614
33116
|
txHash,
|
|
31615
33117
|
chainIn,
|
|
31616
33118
|
...chainOut !== undefined && {
|
|
31617
33119
|
chainOut
|
|
31618
33120
|
},
|
|
31619
|
-
...
|
|
31620
|
-
|
|
33121
|
+
...apiKey ? {
|
|
33122
|
+
apiKey
|
|
31621
33123
|
} : {}
|
|
31622
33124
|
};
|
|
31623
33125
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -31741,7 +33243,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31741
33243
|
*
|
|
31742
33244
|
* @param context - SwapKit context (provides the `TokenRegistry` used for
|
|
31743
33245
|
* symbol resolution).
|
|
31744
|
-
* @param params - `chain`, optional `tokens`, and `
|
|
33246
|
+
* @param params - `chain`, optional `tokens`, and `apiKey`.
|
|
31745
33247
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
31746
33248
|
* @throws \{KitError\} If `chain` cannot be resolved, `tokens` exceeds 100
|
|
31747
33249
|
* entries, or any entry is neither a registered symbol nor a
|
|
@@ -31756,7 +33258,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31756
33258
|
* const { rates } = await getTokenRates(context, {
|
|
31757
33259
|
* chain: Blockchain.Ethereum,
|
|
31758
33260
|
* tokens: ['USDC', 'EURC', 'NATIVE'],
|
|
31759
|
-
*
|
|
33261
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31760
33262
|
* })
|
|
31761
33263
|
*
|
|
31762
33264
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -31777,10 +33279,11 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31777
33279
|
// valid Blockchain enum string, so `getChainByEnum` is guaranteed to resolve.
|
|
31778
33280
|
const chainDef = getChainByEnum(chain);
|
|
31779
33281
|
const resolvedAddresses = params.tokens?.map((entry, index)=>resolveTokenEntry(entry, index, chain, chainDef, context));
|
|
33282
|
+
const apiKey = resolveApiKey(params);
|
|
31780
33283
|
return getTokenRates$2({
|
|
31781
33284
|
chain,
|
|
31782
|
-
...
|
|
31783
|
-
apiKey
|
|
33285
|
+
...apiKey ? {
|
|
33286
|
+
apiKey
|
|
31784
33287
|
} : {},
|
|
31785
33288
|
...resolvedAddresses !== undefined && {
|
|
31786
33289
|
addresses: resolvedAddresses
|
|
@@ -32411,7 +33914,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32411
33914
|
* is formatted as a human-readable decimal string; when decimals are
|
|
32412
33915
|
* missing the field is omitted entirely (never raw base units).
|
|
32413
33916
|
*
|
|
32414
|
-
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `
|
|
33917
|
+
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `apiKey`.
|
|
32415
33918
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
32416
33919
|
* @throws \{KitError\} If `chainIn` or `chainOut` is malformed.
|
|
32417
33920
|
*
|
|
@@ -32424,7 +33927,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32424
33927
|
* txHash: result.txHash,
|
|
32425
33928
|
* chainIn: result.chainIn,
|
|
32426
33929
|
* chainOut: result.chainOut,
|
|
32427
|
-
*
|
|
33930
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32428
33931
|
* })
|
|
32429
33932
|
* console.log(status.progress.status, status.progress.substatus)
|
|
32430
33933
|
* ```
|
|
@@ -32436,7 +33939,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32436
33939
|
* txHash: result.txHash,
|
|
32437
33940
|
* chainIn: result.chainIn,
|
|
32438
33941
|
* chainOut: result.chainOut,
|
|
32439
|
-
*
|
|
33942
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32440
33943
|
* })
|
|
32441
33944
|
* while (status.progress.status === 'PENDING') {
|
|
32442
33945
|
* await new Promise((r) => setTimeout(r, 3_000))
|
|
@@ -32444,7 +33947,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32444
33947
|
* txHash: result.txHash,
|
|
32445
33948
|
* chainIn: result.chainIn,
|
|
32446
33949
|
* chainOut: result.chainOut,
|
|
32447
|
-
*
|
|
33950
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32448
33951
|
* })
|
|
32449
33952
|
* }
|
|
32450
33953
|
*
|
|
@@ -32475,7 +33978,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32475
33978
|
* 5xx, etc.) surface unchanged so the caller can decide whether to
|
|
32476
33979
|
* retry the whole wait. The wait itself only emits a timeout error.
|
|
32477
33980
|
*
|
|
32478
|
-
* @param params - Wait configuration including identifiers,
|
|
33981
|
+
* @param params - Wait configuration including identifiers, API key,
|
|
32479
33982
|
* optional `timeoutMs`, and an optional `onProgress`
|
|
32480
33983
|
* callback fired on every successful poll.
|
|
32481
33984
|
* @returns The first terminal {@link SwapStatusResult} observed.
|
|
@@ -32494,7 +33997,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32494
33997
|
*
|
|
32495
33998
|
* const final = await kit.waitForSwap({
|
|
32496
33999
|
* result,
|
|
32497
|
-
*
|
|
34000
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32498
34001
|
* onProgress: (snap) => console.log(snap.progress.substatus ?? snap.progress.status),
|
|
32499
34002
|
* })
|
|
32500
34003
|
*
|
|
@@ -32512,7 +34015,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32512
34015
|
* txHash: persisted.txHash,
|
|
32513
34016
|
* chainIn: persisted.chainIn,
|
|
32514
34017
|
* chainOut: persisted.chainOut,
|
|
32515
|
-
*
|
|
34018
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32516
34019
|
* })
|
|
32517
34020
|
* ```
|
|
32518
34021
|
*/ async waitForSwap(params) {
|
|
@@ -32546,7 +34049,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32546
34049
|
* not cache a rate for the native asset on that chain, the response simply
|
|
32547
34050
|
* omits the address.
|
|
32548
34051
|
*
|
|
32549
|
-
* @param params - `chain`, optional `tokens`, and `
|
|
34052
|
+
* @param params - `chain`, optional `tokens`, and `apiKey`.
|
|
32550
34053
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
32551
34054
|
* @throws \{KitError\} If `chain` is malformed, `tokens` exceeds 100
|
|
32552
34055
|
* entries, or any entry is neither a registered symbol nor a
|
|
@@ -32558,7 +34061,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32558
34061
|
* const { rates } = await kit.getTokenRates({
|
|
32559
34062
|
* chain: 'Ethereum',
|
|
32560
34063
|
* tokens: ['USDC', 'EURC', 'NATIVE'],
|
|
32561
|
-
*
|
|
34064
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32562
34065
|
* })
|
|
32563
34066
|
*
|
|
32564
34067
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -32572,7 +34075,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32572
34075
|
* chain: 'Ethereum',
|
|
32573
34076
|
* // 'USDC' (registry symbol) + raw USDT address
|
|
32574
34077
|
* tokens: ['USDC', '0xdAC17F958D2ee523a2206206994597C13D831ec7'],
|
|
32575
|
-
*
|
|
34078
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32576
34079
|
* })
|
|
32577
34080
|
* ```
|
|
32578
34081
|
*
|
|
@@ -32581,7 +34084,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32581
34084
|
* ```typescript
|
|
32582
34085
|
* const { rates } = await kit.getTokenRates({
|
|
32583
34086
|
* chain: 'Base',
|
|
32584
|
-
*
|
|
34087
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32585
34088
|
* })
|
|
32586
34089
|
* ```
|
|
32587
34090
|
*/ async getTokenRates(params) {
|
|
@@ -32943,7 +34446,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
32943
34446
|
};
|
|
32944
34447
|
|
|
32945
34448
|
var name$2 = "@circle-fin/earn-kit";
|
|
32946
|
-
var version$2 = "1.
|
|
34449
|
+
var version$2 = "1.6.0";
|
|
32947
34450
|
var pkg$2 = {
|
|
32948
34451
|
name: name$2,
|
|
32949
34452
|
version: version$2};
|
|
@@ -33159,7 +34662,7 @@ function toSdkChain(chain) {
|
|
|
33159
34662
|
*
|
|
33160
34663
|
* @internal
|
|
33161
34664
|
*/ function assertHexAddress(field, value, message = `${field} must be a 0x-prefixed 20-byte hex address`) {
|
|
33162
|
-
const result = evmAddressSchema.safeParse(value);
|
|
34665
|
+
const result = evmAddressSchema$1.safeParse(value);
|
|
33163
34666
|
if (!result.success) {
|
|
33164
34667
|
throw createValidationFailedError$1(field, value, message);
|
|
33165
34668
|
}
|
|
@@ -34845,7 +36348,7 @@ const EARN_OPERATIONS = new Set([
|
|
|
34845
36348
|
// Store the original service params non-enumerably. `retry()` reads them by
|
|
34846
36349
|
// direct property access (`trace.params`), but JSON.stringify, console.log,
|
|
34847
36350
|
// and telemetry serializers skip non-enumerable properties — so a
|
|
34848
|
-
// permissioned call's `config.
|
|
36351
|
+
// permissioned call's `config.apiKey` (a credential value) and
|
|
34849
36352
|
// the in-memory adapter never leak through a logged error. defineProperty
|
|
34850
36353
|
// also redefines any enumerable `params` an upstream trace may have carried.
|
|
34851
36354
|
Object.defineProperty(trace, 'params', {
|
|
@@ -34916,7 +36419,7 @@ function isNonNegativeBigIntLike(value) {
|
|
|
34916
36419
|
}
|
|
34917
36420
|
}
|
|
34918
36421
|
const hexSignatureSchema = evmSignatureSchema;
|
|
34919
|
-
const hexAddressSchema = evmAddressSchema;
|
|
36422
|
+
const hexAddressSchema = evmAddressSchema$1;
|
|
34920
36423
|
// '0x' prefix + 32 bytes * 2 hex chars.
|
|
34921
36424
|
const BYTES32_HEX_LENGTH = 66;
|
|
34922
36425
|
const bridgeFeeTokenSchema = hexAddressSchema;
|
|
@@ -35885,7 +37388,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
35885
37388
|
}
|
|
35886
37389
|
|
|
35887
37390
|
var name$1 = "@circle-fin/provider-earn-service";
|
|
35888
|
-
var version$1 = "1.
|
|
37391
|
+
var version$1 = "1.5.0";
|
|
35889
37392
|
var pkg$1 = {
|
|
35890
37393
|
name: name$1,
|
|
35891
37394
|
version: version$1};
|
|
@@ -35947,10 +37450,12 @@ var pkg$1 = {
|
|
|
35947
37450
|
*
|
|
35948
37451
|
* @internal
|
|
35949
37452
|
*/ function buildConfig(serviceConfig) {
|
|
35950
|
-
|
|
35951
|
-
//
|
|
35952
|
-
|
|
35953
|
-
|
|
37453
|
+
const apiKey = resolveApiKey(serviceConfig ?? {});
|
|
37454
|
+
// The credential is a server-only secret. Reject it in the browser so it
|
|
37455
|
+
// cannot leak into a client bundle (no-op in Node.js). Keyless usage stays
|
|
37456
|
+
// allowed.
|
|
37457
|
+
if (apiKey !== undefined && isBrowserEnvironment()) {
|
|
37458
|
+
throw createValidationFailedError$1('apiKey', '[redacted]', 'apiKey must not be provided in a browser environment — it is a server-only secret. Run EarnKit operations that use an API key on your server, or omit apiKey (and the legacy kitKey) to use the permissionless (keyless) client path');
|
|
35954
37459
|
}
|
|
35955
37460
|
const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
|
|
35956
37461
|
// The API CORS policy does not allow this custom header. Keep the existing
|
|
@@ -35959,7 +37464,7 @@ var pkg$1 = {
|
|
|
35959
37464
|
const sdkVersionHeader = isNodeEnvironment() ? {
|
|
35960
37465
|
[SDK_VERSION_HEADER]: resolveSdkVersionHeader()
|
|
35961
37466
|
} : {};
|
|
35962
|
-
if (
|
|
37467
|
+
if (apiKey === undefined) {
|
|
35963
37468
|
return {
|
|
35964
37469
|
pollingConfig: {
|
|
35965
37470
|
...DEFAULT_CONFIG,
|
|
@@ -35971,11 +37476,11 @@ var pkg$1 = {
|
|
|
35971
37476
|
baseUrl
|
|
35972
37477
|
};
|
|
35973
37478
|
}
|
|
35974
|
-
if (!isValidApiKey(
|
|
37479
|
+
if (!isValidApiKey(apiKey)) {
|
|
35975
37480
|
throw new KitError({
|
|
35976
37481
|
...InputError.VALIDATION_FAILED,
|
|
35977
37482
|
recoverability: 'FATAL',
|
|
35978
|
-
message: 'Invalid
|
|
37483
|
+
message: 'Invalid apiKey format. Expected <ENV>_API_KEY:<keyId>:<keySecret> (for example TEST_API_KEY:...), or a legacy KIT_KEY:<keyId>:<keySecret>.'
|
|
35979
37484
|
});
|
|
35980
37485
|
}
|
|
35981
37486
|
return {
|
|
@@ -35984,7 +37489,7 @@ var pkg$1 = {
|
|
|
35984
37489
|
headers: {
|
|
35985
37490
|
...DEFAULT_CONFIG.headers,
|
|
35986
37491
|
...sdkVersionHeader,
|
|
35987
|
-
Authorization: `Bearer ${
|
|
37492
|
+
Authorization: `Bearer ${apiKey}`
|
|
35988
37493
|
}
|
|
35989
37494
|
},
|
|
35990
37495
|
baseUrl
|
|
@@ -37301,9 +38806,9 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
37301
38806
|
* // Permissionless mode
|
|
37302
38807
|
* const provider = new EarnServiceProvider()
|
|
37303
38808
|
*
|
|
37304
|
-
* // Permissioned mode with
|
|
38809
|
+
* // Permissioned mode with an API key
|
|
37305
38810
|
* const provider = new EarnServiceProvider({
|
|
37306
|
-
*
|
|
38811
|
+
* apiKey: 'TEST_API_KEY:keyId:keySecret',
|
|
37307
38812
|
* })
|
|
37308
38813
|
*
|
|
37309
38814
|
* // Custom base URL for testing
|
|
@@ -38530,7 +40035,7 @@ function findProvider(context, chain, operation = 'earn') {
|
|
|
38530
40035
|
* fast instead of round-tripping to the service.
|
|
38531
40036
|
*
|
|
38532
40037
|
* @internal
|
|
38533
|
-
*/ const recipientEvmAddressSchema = evmAddressSchema.refine((address)=>!ZERO_EVM_ADDRESS_REGEX.test(address), 'address must not be the zero address').refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
40038
|
+
*/ const recipientEvmAddressSchema = evmAddressSchema$1.refine((address)=>!ZERO_EVM_ADDRESS_REGEX.test(address), 'address must not be the zero address').refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
38534
40039
|
/**
|
|
38535
40040
|
* Schema for the adapter context within earn operations.
|
|
38536
40041
|
*
|
|
@@ -38562,19 +40067,39 @@ const sourceAdapterContextSchema = z.object({
|
|
|
38562
40067
|
/**
|
|
38563
40068
|
* Schema for the EarnConfig options.
|
|
38564
40069
|
*
|
|
38565
|
-
* Validate the
|
|
38566
|
-
*
|
|
38567
|
-
*
|
|
38568
|
-
*
|
|
38569
|
-
*
|
|
38570
|
-
*
|
|
40070
|
+
* Validate the *resolved* credential using the standard `apiKeySchema` format
|
|
40071
|
+
* (`<ENV>_API_KEY:<keyId>:<keySecret>`, or a legacy
|
|
40072
|
+
* `KIT_KEY:<keyId>:<keySecret>`). `apiKey` takes precedence over the deprecated
|
|
40073
|
+
* `kitKey`, so a malformed `kitKey` that is being ignored must not fail a config
|
|
40074
|
+
* that supplies a valid `apiKey` (and vice versa) — only the credential that
|
|
40075
|
+
* would actually be sent is format-checked. When neither is supplied the SDK
|
|
40076
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service endpoint
|
|
40077
|
+
* (e.g. staging); `batchTransactions: false` opts out of atomic batched
|
|
40078
|
+
* execution. All are forwarded to the provider, so this `.strict()` schema must
|
|
40079
|
+
* accept them or a valid config object is rejected.
|
|
38571
40080
|
*
|
|
38572
40081
|
* @internal
|
|
38573
40082
|
*/ const earnConfigSchema = z.object({
|
|
38574
|
-
|
|
40083
|
+
apiKey: z.string().optional(),
|
|
40084
|
+
kitKey: z.string().optional(),
|
|
38575
40085
|
baseUrl: z.string().optional(),
|
|
38576
40086
|
batchTransactions: z.boolean().optional()
|
|
38577
|
-
}).strict()
|
|
40087
|
+
}).strict().superRefine((config, ctx)=>{
|
|
40088
|
+
const credential = resolveApiKey(config);
|
|
40089
|
+
if (credential === undefined) {
|
|
40090
|
+
return;
|
|
40091
|
+
}
|
|
40092
|
+
const result = apiKeySchema.safeParse(credential);
|
|
40093
|
+
if (!result.success) {
|
|
40094
|
+
ctx.addIssue({
|
|
40095
|
+
code: z.ZodIssueCode.custom,
|
|
40096
|
+
path: [
|
|
40097
|
+
credential === config.apiKey ? 'apiKey' : 'kitKey'
|
|
40098
|
+
],
|
|
40099
|
+
message: result.error.issues[0]?.message ?? 'Invalid API key format'
|
|
40100
|
+
});
|
|
40101
|
+
}
|
|
40102
|
+
});
|
|
38578
40103
|
/**
|
|
38579
40104
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|
|
38580
40105
|
* '0' is only allowed immediately before the decimal point). Rejects the
|
|
@@ -38646,7 +40171,7 @@ const sourceAdapterContextSchema = z.object({
|
|
|
38646
40171
|
* currently supports EVM vault addresses on Arc Testnet.
|
|
38647
40172
|
*
|
|
38648
40173
|
* @internal
|
|
38649
|
-
*/ const vaultAddressSchema = evmAddressSchema.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
40174
|
+
*/ const vaultAddressSchema = evmAddressSchema$1.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
38650
40175
|
/**
|
|
38651
40176
|
* Validation schema for VaultQuery.
|
|
38652
40177
|
*
|
|
@@ -38921,7 +40446,7 @@ const sameChainGetDepositQuoteParamsSchema = z.object({
|
|
|
38921
40446
|
const crossChainGetDepositQuoteParamsSchema = z.object({
|
|
38922
40447
|
from: sourceAdapterContextSchema,
|
|
38923
40448
|
chain: earnBridgeDestinationChainIdentifierSchema,
|
|
38924
|
-
address: evmAddressSchema,
|
|
40449
|
+
address: evmAddressSchema$1,
|
|
38925
40450
|
vaultAddress: vaultAddressSchema,
|
|
38926
40451
|
amount: amountSchema$1,
|
|
38927
40452
|
transferSpeed: z.enum([
|
|
@@ -41237,7 +42762,7 @@ const tokens = createTokenRegistry();
|
|
|
41237
42762
|
*
|
|
41238
42763
|
* @param context - AppKit context.
|
|
41239
42764
|
* @param params - `txHash` and `chainIn`, plus optional `chainOut` and
|
|
41240
|
-
* `
|
|
42765
|
+
* `apiKey`.
|
|
41241
42766
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
41242
42767
|
*
|
|
41243
42768
|
* @example
|
|
@@ -41251,7 +42776,7 @@ const tokens = createTokenRegistry();
|
|
|
41251
42776
|
* txHash: result.txHash,
|
|
41252
42777
|
* chainIn: result.chainIn,
|
|
41253
42778
|
* chainOut: result.chainOut,
|
|
41254
|
-
*
|
|
42779
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
41255
42780
|
* })
|
|
41256
42781
|
*
|
|
41257
42782
|
* console.log(status.progress.status, status.progress.substatus)
|
|
@@ -41274,7 +42799,7 @@ const tokens = createTokenRegistry();
|
|
|
41274
42799
|
* `timeoutMs` elapses.
|
|
41275
42800
|
*
|
|
41276
42801
|
* @param context - AppKit context.
|
|
41277
|
-
* @param params - Wait configuration, including identifiers,
|
|
42802
|
+
* @param params - Wait configuration, including identifiers, API key,
|
|
41278
42803
|
* optional `timeoutMs`, and an optional `onProgress`
|
|
41279
42804
|
* callback.
|
|
41280
42805
|
* @returns The first terminal {@link SwapStatusResult} observed.
|
|
@@ -41290,7 +42815,7 @@ const tokens = createTokenRegistry();
|
|
|
41290
42815
|
* txHash: result.txHash,
|
|
41291
42816
|
* chainIn: result.chainIn,
|
|
41292
42817
|
* chainOut: result.chainOut,
|
|
41293
|
-
*
|
|
42818
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
41294
42819
|
* onProgress: (snap) => console.log(snap.progress.status),
|
|
41295
42820
|
* })
|
|
41296
42821
|
*
|
|
@@ -41326,7 +42851,7 @@ const tokens = createTokenRegistry();
|
|
|
41326
42851
|
* addresses before indexing into `result.rates[chain]`.
|
|
41327
42852
|
*
|
|
41328
42853
|
* @param context - AppKit context.
|
|
41329
|
-
* @param params - `chain`, plus optional `tokens` and `
|
|
42854
|
+
* @param params - `chain`, plus optional `tokens` and `apiKey`.
|
|
41330
42855
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
41331
42856
|
*
|
|
41332
42857
|
* @example
|
|
@@ -41337,7 +42862,7 @@ const tokens = createTokenRegistry();
|
|
|
41337
42862
|
* const { rates } = await kit.getTokenRates({
|
|
41338
42863
|
* chain: 'Ethereum',
|
|
41339
42864
|
* tokens: ['USDC'],
|
|
41340
|
-
*
|
|
42865
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
41341
42866
|
* })
|
|
41342
42867
|
*
|
|
41343
42868
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -41348,6 +42873,43 @@ const tokens = createTokenRegistry();
|
|
|
41348
42873
|
return kit.getTokenRates(params);
|
|
41349
42874
|
};
|
|
41350
42875
|
|
|
42876
|
+
const BRIDGE_FILTER_KEYS = new Set([
|
|
42877
|
+
'chainType',
|
|
42878
|
+
'isTestnet',
|
|
42879
|
+
'forwarderSupported',
|
|
42880
|
+
'sourceFeeSupported'
|
|
42881
|
+
]);
|
|
42882
|
+
function assertBridgeSupportedChainsOptions(options) {
|
|
42883
|
+
if (options === undefined) {
|
|
42884
|
+
return;
|
|
42885
|
+
}
|
|
42886
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options)) {
|
|
42887
|
+
throw createValidationFailedError$1('options', options, 'Bridge chain filters must be an object');
|
|
42888
|
+
}
|
|
42889
|
+
const entries = Object.entries(options);
|
|
42890
|
+
if (entries.length === 0 || entries.some(([key])=>!BRIDGE_FILTER_KEYS.has(key))) {
|
|
42891
|
+
throw createValidationFailedError$1('options', options, 'Bridge chain filters must contain only supported filter properties');
|
|
42892
|
+
}
|
|
42893
|
+
const record = options;
|
|
42894
|
+
for (const key of [
|
|
42895
|
+
'isTestnet',
|
|
42896
|
+
'forwarderSupported',
|
|
42897
|
+
'sourceFeeSupported'
|
|
42898
|
+
]){
|
|
42899
|
+
if (record[key] !== undefined && typeof record[key] !== 'boolean') {
|
|
42900
|
+
throw createValidationFailedError$1(`options.${key}`, record[key], 'Bridge chain boolean filters must be true or false');
|
|
42901
|
+
}
|
|
42902
|
+
}
|
|
42903
|
+
const chainType = record['chainType'];
|
|
42904
|
+
if (chainType !== undefined) {
|
|
42905
|
+
const chainTypes = Array.isArray(chainType) ? chainType : [
|
|
42906
|
+
chainType
|
|
42907
|
+
];
|
|
42908
|
+
if (chainTypes.length === 0 || chainTypes.some((value)=>value !== 'evm' && value !== 'solana')) {
|
|
42909
|
+
throw createValidationFailedError$1('options.chainType', chainType, "Bridge chain type must be 'evm', 'solana', or an array of those values");
|
|
42910
|
+
}
|
|
42911
|
+
}
|
|
42912
|
+
}
|
|
41351
42913
|
/**
|
|
41352
42914
|
* Get chains supported by AppKit operations.
|
|
41353
42915
|
*
|
|
@@ -41362,6 +42924,7 @@ const tokens = createTokenRegistry();
|
|
|
41362
42924
|
* @param context - AppKit context containing fee configuration.
|
|
41363
42925
|
* @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance').
|
|
41364
42926
|
* @param unifiedBalance - Persistent AppKitUnifiedBalance namespace instance.
|
|
42927
|
+
* @param bridgeOptions - Optional Bridge Kit chain filters. Requires `operationType: 'bridge'`.
|
|
41365
42928
|
* @returns Array of unique chain definitions supporting the specified operation(s)
|
|
41366
42929
|
*
|
|
41367
42930
|
* @example Get all supported chains via AppKit
|
|
@@ -41380,6 +42943,13 @@ const tokens = createTokenRegistry();
|
|
|
41380
42943
|
* console.log('Chains supporting bridge:', bridgeChains.map(c => c.name))
|
|
41381
42944
|
* ```
|
|
41382
42945
|
*
|
|
42946
|
+
* @example Get chains that support source-paid bridge fees
|
|
42947
|
+
* ```typescript
|
|
42948
|
+
* const sourceFeeChains = kit.getSupportedChains('bridge', {
|
|
42949
|
+
* sourceFeeSupported: true,
|
|
42950
|
+
* })
|
|
42951
|
+
* ```
|
|
42952
|
+
*
|
|
41383
42953
|
* @example Get swap-specific chains
|
|
41384
42954
|
* ```typescript
|
|
41385
42955
|
* const swapChains = kit.getSupportedChains('swap')
|
|
@@ -41400,7 +42970,8 @@ const tokens = createTokenRegistry();
|
|
|
41400
42970
|
* const supportsEthBridge = bridgeChains.some(c => c.chain === Ethereum.chain)
|
|
41401
42971
|
* console.log(`Ethereum supports bridge: ${supportsEthBridge}`)
|
|
41402
42972
|
* ```
|
|
41403
|
-
*/ const getSupportedChains$2 = (context, operationType, unifiedBalance)=>{
|
|
42973
|
+
*/ const getSupportedChains$2 = (context, operationType, unifiedBalance, bridgeOptions)=>{
|
|
42974
|
+
assertBridgeSupportedChainsOptions(bridgeOptions);
|
|
41404
42975
|
// Runtime validation when operationType is provided
|
|
41405
42976
|
if (operationType !== undefined && operationType !== 'bridge' && operationType !== 'swap' && operationType !== 'earn' && operationType !== 'unifiedBalance') {
|
|
41406
42977
|
throw new KitError({
|
|
@@ -41409,10 +42980,13 @@ const tokens = createTokenRegistry();
|
|
|
41409
42980
|
message: `Invalid operationType: "${String(operationType)}". Expected 'bridge', 'swap', 'earn', or 'unifiedBalance'.`
|
|
41410
42981
|
});
|
|
41411
42982
|
}
|
|
42983
|
+
if (bridgeOptions !== undefined && operationType !== 'bridge') {
|
|
42984
|
+
throw createValidationFailedError$1('options', bridgeOptions, "Bridge chain filters require operationType 'bridge'");
|
|
42985
|
+
}
|
|
41412
42986
|
// Return bridge-specific chains
|
|
41413
42987
|
if (operationType === 'bridge') {
|
|
41414
42988
|
const bridgeKit = createBridgeKit(context);
|
|
41415
|
-
return bridgeKit.getSupportedChains();
|
|
42989
|
+
return bridgeKit.getSupportedChains(bridgeOptions);
|
|
41416
42990
|
}
|
|
41417
42991
|
// Return swap-specific chains
|
|
41418
42992
|
if (operationType === 'swap') {
|
|
@@ -41750,7 +43324,7 @@ async function deposit$2(context, params) {
|
|
|
41750
43324
|
}
|
|
41751
43325
|
|
|
41752
43326
|
var name = "@circle-fin/unified-balance-kit";
|
|
41753
|
-
var version = "1.
|
|
43327
|
+
var version = "1.5.0";
|
|
41754
43328
|
var pkg = {
|
|
41755
43329
|
name: name,
|
|
41756
43330
|
version: version};
|
|
@@ -42583,6 +44157,8 @@ const gatewayInfoSchema = z.object({
|
|
|
42583
44157
|
* Fetch Gateway info from the `/v1/info` endpoint.
|
|
42584
44158
|
*
|
|
42585
44159
|
* @param isTestnet - Whether to query the testnet or mainnet API.
|
|
44160
|
+
* @param config - Optional polling config overrides, including custom
|
|
44161
|
+
* `headers` forwarded with the request.
|
|
42586
44162
|
* @returns Parsed Gateway info including per-domain processed heights.
|
|
42587
44163
|
* @throws KitError on HTTP failure or invalid response.
|
|
42588
44164
|
*
|
|
@@ -42591,11 +44167,11 @@ const gatewayInfoSchema = z.object({
|
|
|
42591
44167
|
* const info = await fetchGatewayInfo(true)
|
|
42592
44168
|
* console.log(info.domains.map(d => `${d.chain}: ${d.processedHeight}`))
|
|
42593
44169
|
* ```
|
|
42594
|
-
*/ async function fetchGatewayInfo(isTestnet) {
|
|
44170
|
+
*/ async function fetchGatewayInfo(isTestnet, config) {
|
|
42595
44171
|
const apiBaseUrl = getGatewayApiBaseUrl(isTestnet);
|
|
42596
44172
|
const url = `${apiBaseUrl}/v1/info`;
|
|
42597
44173
|
try {
|
|
42598
|
-
return await pollApiGet(url, assertGatewayInfoResponse);
|
|
44174
|
+
return await pollApiGet(url, assertGatewayInfoResponse, config);
|
|
42599
44175
|
} catch (error) {
|
|
42600
44176
|
if (error instanceof KitError) {
|
|
42601
44177
|
throw error;
|
|
@@ -42621,6 +44197,8 @@ const gatewayInfoSchema = z.object({
|
|
|
42621
44197
|
*
|
|
42622
44198
|
* @param isTestnet - Whether to query the testnet or mainnet API.
|
|
42623
44199
|
* @param domain - The Gateway domain number (e.g. 0 = Ethereum, 6 = Base).
|
|
44200
|
+
* @param config - Optional polling config overrides, including custom
|
|
44201
|
+
* `headers` forwarded with the `/v1/info` request.
|
|
42624
44202
|
* @returns The processed height as a bigint.
|
|
42625
44203
|
* @throws KitError when the domain is not found in the response.
|
|
42626
44204
|
*
|
|
@@ -42629,10 +44207,10 @@ const gatewayInfoSchema = z.object({
|
|
|
42629
44207
|
* const height = await getProcessedHeight(true, 6) // Base Sepolia
|
|
42630
44208
|
* console.log(`Gateway processed up to block ${height}`)
|
|
42631
44209
|
* ```
|
|
42632
|
-
*/ async function getProcessedHeight(isTestnet, domain) {
|
|
44210
|
+
*/ async function getProcessedHeight(isTestnet, domain, config) {
|
|
42633
44211
|
// No caching in v1 — /v1/info is cheap and delegate status checks
|
|
42634
44212
|
// are infrequent. Revisit if rate-limit or latency issues appear.
|
|
42635
|
-
const info = await fetchGatewayInfo(isTestnet);
|
|
44213
|
+
const info = await fetchGatewayInfo(isTestnet, config);
|
|
42636
44214
|
const entry = info.domains.find((d)=>d.domain === domain);
|
|
42637
44215
|
if (!entry) {
|
|
42638
44216
|
throw new KitError({
|
|
@@ -43098,12 +44676,22 @@ function throwNetworkMismatch(expected, actual) {
|
|
|
43098
44676
|
* @param allocations - Normalized allocations used to map domain → adapter/chain.
|
|
43099
44677
|
* @returns Array of groups, each with adapter, chain, and intents to sign.
|
|
43100
44678
|
*/ function groupIntentsByAdapter(intents, allocations) {
|
|
44679
|
+
// Keyed by `(adapter, sourceSigner)` (see groupEvmIntents), not by adapter
|
|
44680
|
+
// alone: two spend sources on the same chain — including two addresses behind
|
|
44681
|
+
// one shared adapter — must stay in separate signing groups.
|
|
43101
44682
|
const evmGroups = new Map();
|
|
43102
44683
|
const solanaGroups = [];
|
|
43103
44684
|
for (const intent of intents){
|
|
43104
|
-
|
|
44685
|
+
// Match each intent back to its source allocation by BOTH Gateway domain
|
|
44686
|
+
// and signer. Two spend sources on the same chain share a domain, so
|
|
44687
|
+
// matching on domain alone always returned the FIRST allocation — folding
|
|
44688
|
+
// the second wallet's intent into the first wallet's signing group so it
|
|
44689
|
+
// was signed by the wrong wallet (DEVX-3638). specs carry sourceSigner as
|
|
44690
|
+
// bytes32 and allocations carry native addresses, so normalize both.
|
|
44691
|
+
const intentSigner = intent.spec.sourceSigner.toLowerCase();
|
|
44692
|
+
const alloc = allocations.find((a)=>a.chain.gateway.domain === intent.spec.sourceDomain && convertAddress(a.sourceSigner, 'bytes32').toLowerCase() === intentSigner);
|
|
43105
44693
|
if (!alloc) {
|
|
43106
|
-
throw createValidationFailedError$1('intent.spec.sourceDomain', intent.spec.sourceDomain, `No allocation found for sourceDomain ${String(intent.spec.sourceDomain)}; ` + 'estimate response contains an intent that does not match any allocation');
|
|
44694
|
+
throw createValidationFailedError$1('intent.spec.sourceDomain', intent.spec.sourceDomain, `No allocation found for sourceDomain ${String(intent.spec.sourceDomain)} and sourceSigner ${String(intent.spec.sourceSigner)}; ` + 'estimate response contains an intent that does not match any allocation');
|
|
43107
44695
|
}
|
|
43108
44696
|
if (alloc.chain.type === 'solana') {
|
|
43109
44697
|
solanaGroups.push(groupSolanaIntents(intent, alloc));
|
|
@@ -45004,12 +46592,12 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45004
46592
|
* exceeds the 5-minute timeout without reaching a terminal status.
|
|
45005
46593
|
* @throws KitError Propagated from {@link pollApiGet} if the HTTP
|
|
45006
46594
|
* request itself fails or returns a non-OK response.
|
|
45007
|
-
*/ async function pollForwarderCompletion(apiBaseUrl, transferId) {
|
|
46595
|
+
*/ async function pollForwarderCompletion(apiBaseUrl, transferId, requestConfig) {
|
|
45008
46596
|
const startTime = Date.now();
|
|
45009
46597
|
while(Date.now() - startTime < FORWARDER_POLL_TIMEOUT_MS){
|
|
45010
46598
|
let status;
|
|
45011
46599
|
try {
|
|
45012
|
-
status = await pollApiGet(`${apiBaseUrl}/v1/transfer/${transferId}`, assertGatewayTransferDetails);
|
|
46600
|
+
status = await pollApiGet(`${apiBaseUrl}/v1/transfer/${transferId}`, assertGatewayTransferDetails, requestConfig);
|
|
45013
46601
|
} catch (error) {
|
|
45014
46602
|
throwGatewayApiError(error);
|
|
45015
46603
|
}
|
|
@@ -45127,7 +46715,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45127
46715
|
* @param params - Spend parameters (source(s) and token).
|
|
45128
46716
|
* @param destChain - Resolved destination chain (used for network type).
|
|
45129
46717
|
* @returns Confirmed balances tagged with their originating source index.
|
|
45130
|
-
*/ async function fetchChainBalances(params, destChain) {
|
|
46718
|
+
*/ async function fetchChainBalances(params, destChain, requestConfig) {
|
|
45131
46719
|
const rawSources = Array.isArray(params.from) ? params.from : [
|
|
45132
46720
|
params.from
|
|
45133
46721
|
];
|
|
@@ -45156,7 +46744,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45156
46744
|
token: params.token,
|
|
45157
46745
|
sources: querySource,
|
|
45158
46746
|
networkType
|
|
45159
|
-
});
|
|
46747
|
+
}, requestConfig);
|
|
45160
46748
|
}));
|
|
45161
46749
|
const chainBalances = [];
|
|
45162
46750
|
for(let i = 0; i < balanceResults.length; i++){
|
|
@@ -45229,9 +46817,9 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45229
46817
|
* @param recipientAddress - Resolved recipient address.
|
|
45230
46818
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45231
46819
|
* @returns Allocations, intents, and (auto-allocation only) confirmed balances.
|
|
45232
|
-
*/ async function resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder) {
|
|
46820
|
+
*/ async function resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder, requestConfig) {
|
|
45233
46821
|
if (params.amountIn) {
|
|
45234
|
-
const chainBalances = await fetchChainBalances(params, destChain);
|
|
46822
|
+
const chainBalances = await fetchChainBalances(params, destChain, requestConfig);
|
|
45235
46823
|
const { allocations, intents } = await buildAutoAllocatedFromBalances(params, destChain, recipientAddress, useForwarder, params.amountIn, chainBalances);
|
|
45236
46824
|
return {
|
|
45237
46825
|
allocations,
|
|
@@ -45254,11 +46842,11 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45254
46842
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45255
46843
|
* @param allocations - Normalised allocations (forwarded to error handler).
|
|
45256
46844
|
* @returns Estimate entries and optional forwarding fee.
|
|
45257
|
-
*/ async function fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations) {
|
|
46845
|
+
*/ async function fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations, requestConfig) {
|
|
45258
46846
|
if (useForwarder) {
|
|
45259
46847
|
let fwdResponse;
|
|
45260
46848
|
try {
|
|
45261
|
-
fwdResponse = await pollApiPost(`${apiBaseUrl}/v1/estimate?enableForwarder=true`, estimateBody, assertGatewayForwarderEstimateResponse);
|
|
46849
|
+
fwdResponse = await pollApiPost(`${apiBaseUrl}/v1/estimate?enableForwarder=true`, estimateBody, assertGatewayForwarderEstimateResponse, requestConfig);
|
|
45262
46850
|
} catch (error) {
|
|
45263
46851
|
throwGatewayApiError(error, allocations);
|
|
45264
46852
|
}
|
|
@@ -45269,7 +46857,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45269
46857
|
}
|
|
45270
46858
|
let entries;
|
|
45271
46859
|
try {
|
|
45272
|
-
entries = await pollApiPost(`${apiBaseUrl}/v1/estimate`, estimateBody, assertGatewayEstimateResponse);
|
|
46860
|
+
entries = await pollApiPost(`${apiBaseUrl}/v1/estimate`, estimateBody, assertGatewayEstimateResponse, requestConfig);
|
|
45273
46861
|
} catch (error) {
|
|
45274
46862
|
throwGatewayApiError(error, allocations);
|
|
45275
46863
|
}
|
|
@@ -45340,14 +46928,14 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45340
46928
|
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
45341
46929
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45342
46930
|
* @returns Estimated intents (with real maxFee) and optional forwarding fee.
|
|
45343
|
-
*/ async function validateAndEstimate(allocations, intents, destChain, useForwarder) {
|
|
46931
|
+
*/ async function validateAndEstimate(allocations, intents, destChain, useForwarder, requestConfig) {
|
|
45344
46932
|
assertNetworkCompatibility(allocations, destChain);
|
|
45345
46933
|
if (useForwarder) {
|
|
45346
46934
|
assertForwarderRouteSupport(destChain, allocations);
|
|
45347
46935
|
}
|
|
45348
46936
|
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
45349
46937
|
const estimateBody = buildEstimateRequestBody(intents);
|
|
45350
|
-
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations);
|
|
46938
|
+
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations, requestConfig);
|
|
45351
46939
|
const estimatedIntents = parseEstimateResponse(entries, intents);
|
|
45352
46940
|
return {
|
|
45353
46941
|
estimatedIntents,
|
|
@@ -45406,7 +46994,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45406
46994
|
}
|
|
45407
46995
|
throw err;
|
|
45408
46996
|
}
|
|
45409
|
-
const { estimatedIntents, forwardingFee } = await validateAndEstimate(corrected.allocations, corrected.intents, opts.destChain, opts.useForwarder);
|
|
46997
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(corrected.allocations, corrected.intents, opts.destChain, opts.useForwarder, opts.requestConfig);
|
|
45410
46998
|
const stillOverdrawn = findOverdrawnChains(estimatedIntents, corrected.allocations, opts.chainBalances);
|
|
45411
46999
|
if (stillOverdrawn.length === 0) {
|
|
45412
47000
|
return {
|
|
@@ -45440,12 +47028,12 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45440
47028
|
* @param recipientAddress - Resolved recipient address.
|
|
45441
47029
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45442
47030
|
* @returns Allocations, estimated intents, and optional forwarding fee.
|
|
45443
|
-
*/ async function resolveValidatedIntents(params, destChain, recipientAddress, useForwarder) {
|
|
47031
|
+
*/ async function resolveValidatedIntents(params, destChain, recipientAddress, useForwarder, requestConfig) {
|
|
45444
47032
|
if (useForwarder) {
|
|
45445
47033
|
assertForwarderRouteSupport(destChain);
|
|
45446
47034
|
}
|
|
45447
|
-
const { allocations, intents, chainBalances } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder);
|
|
45448
|
-
const { estimatedIntents, forwardingFee } = await validateAndEstimate(allocations, intents, destChain, useForwarder);
|
|
47035
|
+
const { allocations, intents, chainBalances } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder, requestConfig);
|
|
47036
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(allocations, intents, destChain, useForwarder, requestConfig);
|
|
45449
47037
|
if (params.amountIn && chainBalances) {
|
|
45450
47038
|
const overdrawn = findOverdrawnChains(estimatedIntents, allocations, chainBalances);
|
|
45451
47039
|
if (overdrawn.length > 0) {
|
|
@@ -45458,7 +47046,10 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45458
47046
|
chainBalances,
|
|
45459
47047
|
estimatedIntents,
|
|
45460
47048
|
allocations,
|
|
45461
|
-
overdrawn
|
|
47049
|
+
overdrawn,
|
|
47050
|
+
...requestConfig ? {
|
|
47051
|
+
requestConfig
|
|
47052
|
+
} : {}
|
|
45462
47053
|
});
|
|
45463
47054
|
}
|
|
45464
47055
|
}
|
|
@@ -45502,10 +47093,10 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45502
47093
|
attestationSignature: transferResponse.signature
|
|
45503
47094
|
};
|
|
45504
47095
|
}
|
|
45505
|
-
async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, steps) {
|
|
47096
|
+
async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, steps, requestConfig) {
|
|
45506
47097
|
const chainName = destChain.name;
|
|
45507
47098
|
const recipientAddress = await resolveRecipientAddress(params.to);
|
|
45508
|
-
const { allocations, estimatedIntents, forwardingFee: estimateFwdFee } = await resolveValidatedIntents(params, destChain, recipientAddress, useForwarder);
|
|
47099
|
+
const { allocations, estimatedIntents, forwardingFee: estimateFwdFee } = await resolveValidatedIntents(params, destChain, recipientAddress, useForwarder, requestConfig);
|
|
45509
47100
|
pushStep(steps, dispatcher, 'gateway.spend.step.buildBurnIntents', chainName, {
|
|
45510
47101
|
name: 'buildBurnIntents',
|
|
45511
47102
|
state: 'success',
|
|
@@ -45542,7 +47133,7 @@ async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, s
|
|
|
45542
47133
|
const forwarderParam = useForwarder ? '?enableForwarder=true' : '';
|
|
45543
47134
|
let transferResponse;
|
|
45544
47135
|
try {
|
|
45545
|
-
transferResponse = await pollApiPost(`${transferApiBaseUrl}/v1/transfer${forwarderParam}`, transferBody, assertGatewayTransferResponse);
|
|
47136
|
+
transferResponse = await pollApiPost(`${transferApiBaseUrl}/v1/transfer${forwarderParam}`, transferBody, assertGatewayTransferResponse, requestConfig);
|
|
45546
47137
|
} catch (error) {
|
|
45547
47138
|
throwGatewayApiError(error, allocations);
|
|
45548
47139
|
}
|
|
@@ -45622,10 +47213,11 @@ function updateFeesWithActual(fees, completionStatus) {
|
|
|
45622
47213
|
amount: actual
|
|
45623
47214
|
} : f);
|
|
45624
47215
|
}
|
|
45625
|
-
async function resolveDestinationTxHash(
|
|
47216
|
+
async function resolveDestinationTxHash(opts) {
|
|
47217
|
+
const { params, destChain, forwarder, transferId, attestation, attestationSignature, fees, requestConfig } = opts;
|
|
45626
47218
|
if (forwarder && transferId) {
|
|
45627
47219
|
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
45628
|
-
const completionStatus = await pollForwarderCompletion(apiBaseUrl, transferId);
|
|
47220
|
+
const completionStatus = await pollForwarderCompletion(apiBaseUrl, transferId, requestConfig);
|
|
45629
47221
|
if (!completionStatus.transactionHash) {
|
|
45630
47222
|
throw new KitError({
|
|
45631
47223
|
...OnchainError.TRANSACTION_REVERTED,
|
|
@@ -45708,9 +47300,20 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45708
47300
|
/**
|
|
45709
47301
|
* Execute the mint step, recording success/error as a spend step.
|
|
45710
47302
|
*/ async function executeMintStep(opts) {
|
|
45711
|
-
const { params, destChain, forwarder, transferId, attestation, attestationSignature, fees, steps, dispatcher } = opts;
|
|
47303
|
+
const { params, destChain, forwarder, transferId, attestation, attestationSignature, fees, steps, dispatcher, requestConfig } = opts;
|
|
45712
47304
|
try {
|
|
45713
|
-
const mintResult = await resolveDestinationTxHash(
|
|
47305
|
+
const mintResult = await resolveDestinationTxHash({
|
|
47306
|
+
params,
|
|
47307
|
+
destChain,
|
|
47308
|
+
forwarder,
|
|
47309
|
+
transferId,
|
|
47310
|
+
attestation,
|
|
47311
|
+
attestationSignature,
|
|
47312
|
+
fees,
|
|
47313
|
+
...requestConfig ? {
|
|
47314
|
+
requestConfig
|
|
47315
|
+
} : {}
|
|
47316
|
+
});
|
|
45714
47317
|
pushStep(steps, dispatcher, 'gateway.spend.step.mint', destChain.name, {
|
|
45715
47318
|
name: 'mint',
|
|
45716
47319
|
state: 'success',
|
|
@@ -45765,7 +47368,8 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45765
47368
|
assertSpendAddresses(params, destChain);
|
|
45766
47369
|
const forwarder = isForwarderEnabled(params.to);
|
|
45767
47370
|
const steps = [];
|
|
45768
|
-
const
|
|
47371
|
+
const requestConfig = options?.requestConfig;
|
|
47372
|
+
const context = params.config?.retry ? await resolveRetryContext(params) : await resolveNormalContext(params, destChain, forwarder, dispatcher, steps, requestConfig);
|
|
45769
47373
|
const fees = appendForwarderFee(context.fees, context.forwardingFee);
|
|
45770
47374
|
const mintResult = await executeMintStep({
|
|
45771
47375
|
params,
|
|
@@ -45776,7 +47380,10 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45776
47380
|
attestationSignature: context.attestationSignature,
|
|
45777
47381
|
fees,
|
|
45778
47382
|
steps,
|
|
45779
|
-
dispatcher
|
|
47383
|
+
dispatcher,
|
|
47384
|
+
...requestConfig ? {
|
|
47385
|
+
requestConfig
|
|
47386
|
+
} : {}
|
|
45780
47387
|
});
|
|
45781
47388
|
// A throwing `onBroadcast` must never strand the caller after a
|
|
45782
47389
|
// successful mint — the chain has moved. `safeInvokeCallback` swallows
|
|
@@ -45793,14 +47400,14 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45793
47400
|
/**
|
|
45794
47401
|
* Resolve the spend context via the normal (non-retry) path.
|
|
45795
47402
|
* Validates `from`, then delegates to `runSpendNormalPath`.
|
|
45796
|
-
*/ async function resolveNormalContext(params, destChain, forwarder, dispatcher, steps) {
|
|
47403
|
+
*/ async function resolveNormalContext(params, destChain, forwarder, dispatcher, steps, requestConfig) {
|
|
45797
47404
|
if (!params.from) {
|
|
45798
47405
|
throw createValidationFailedError$1('from', params.from, 'Source (from) is required when config.retry is not provided.');
|
|
45799
47406
|
}
|
|
45800
47407
|
return runSpendNormalPath({
|
|
45801
47408
|
...params,
|
|
45802
47409
|
from: params.from
|
|
45803
|
-
}, destChain, forwarder, dispatcher, steps);
|
|
47410
|
+
}, destChain, forwarder, dispatcher, steps, requestConfig);
|
|
45804
47411
|
}
|
|
45805
47412
|
/**
|
|
45806
47413
|
* Estimate fees for a Gateway spend without executing it.
|
|
@@ -45823,13 +47430,13 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45823
47430
|
* })
|
|
45824
47431
|
* fees.forEach((f) => console.log(f.type, f.amount, f.allocations))
|
|
45825
47432
|
* ```
|
|
45826
|
-
*/ async function estimateSpend$1(params) {
|
|
47433
|
+
*/ async function estimateSpend$1(params, requestConfig) {
|
|
45827
47434
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
45828
47435
|
assertGatewayV1(destChain);
|
|
45829
47436
|
assertSpendAddresses(params, destChain);
|
|
45830
47437
|
const forwarder = isForwarderEnabled(params.to);
|
|
45831
47438
|
const recipientAddress = await resolveRecipientAddress(params.to);
|
|
45832
|
-
const { allocations, estimatedIntents, forwardingFee } = await resolveValidatedIntents(params, destChain, recipientAddress, forwarder);
|
|
47439
|
+
const { allocations, estimatedIntents, forwardingFee } = await resolveValidatedIntents(params, destChain, recipientAddress, forwarder, requestConfig);
|
|
45833
47440
|
const fees = computeEstimateFees(estimatedIntents, allocations, params.config?.customFee);
|
|
45834
47441
|
if (forwardingFee) {
|
|
45835
47442
|
fees.push({
|
|
@@ -45953,6 +47560,9 @@ function assertNotSelfDelegation(chain, signerAddress, delegateAddress, action)
|
|
|
45953
47560
|
* the delegate is `'ready'` (finalized); otherwise it is `'pending'`.
|
|
45954
47561
|
*
|
|
45955
47562
|
* @param params - The adapter context (`from`), delegate address, and token.
|
|
47563
|
+
* @param requestConfig - Optional polling config overrides, including custom
|
|
47564
|
+
* `headers` forwarded with the `/v1/info` request used on the EVM finality
|
|
47565
|
+
* check.
|
|
45956
47566
|
* @returns Promise resolving to `'none'`, `'pending'`, or `'ready'`.
|
|
45957
47567
|
* @throws KitError if the chain is unsupported or `/v1/info` fails.
|
|
45958
47568
|
*
|
|
@@ -45972,7 +47582,7 @@ function assertNotSelfDelegation(chain, signerAddress, delegateAddress, action)
|
|
|
45972
47582
|
* const status = await getDelegateStatus(params)
|
|
45973
47583
|
* if (status === 'ready') { // safe to spend }
|
|
45974
47584
|
* ```
|
|
45975
|
-
*/ async function getDelegateStatus$1(params) {
|
|
47585
|
+
*/ async function getDelegateStatus$1(params, requestConfig) {
|
|
45976
47586
|
const { from, delegateAddress } = params;
|
|
45977
47587
|
const { adapter } = from;
|
|
45978
47588
|
const chain = resolveChainIdentifier(from.chain);
|
|
@@ -46012,7 +47622,7 @@ function assertNotSelfDelegation(chain, signerAddress, delegateAddress, action)
|
|
|
46012
47622
|
return String(finalizedResult).toLowerCase() === 'true' ? 'ready' : 'pending';
|
|
46013
47623
|
}
|
|
46014
47624
|
// EVM: use processedHeight from /v1/info
|
|
46015
|
-
const processedHeight = await getProcessedHeight(chain.isTestnet, chain.gateway.domain);
|
|
47625
|
+
const processedHeight = await getProcessedHeight(chain.isTestnet, chain.gateway.domain, requestConfig);
|
|
46016
47626
|
// If the RPC node lags Gateway's indexer view, the historical read at
|
|
46017
47627
|
// processedHeight may throw a block-range error. This is safe to treat
|
|
46018
47628
|
// as 'pending' because processedHeight comes from Gateway's /v1/info
|
|
@@ -46230,6 +47840,35 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46230
47840
|
dispatchEvent(dispatcher, opName, 'failed', chain, extractErrorInfo(error));
|
|
46231
47841
|
}
|
|
46232
47842
|
|
|
47843
|
+
// ---------------------------------------------------------------------------
|
|
47844
|
+
// Config validation
|
|
47845
|
+
// ---------------------------------------------------------------------------
|
|
47846
|
+
function isPlainObject(value) {
|
|
47847
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
47848
|
+
return false;
|
|
47849
|
+
}
|
|
47850
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47851
|
+
return prototype === Object.prototype || prototype === null;
|
|
47852
|
+
}
|
|
47853
|
+
function assertHeadersConfig(headers) {
|
|
47854
|
+
if (headers === undefined) {
|
|
47855
|
+
return;
|
|
47856
|
+
}
|
|
47857
|
+
if (!isPlainObject(headers)) {
|
|
47858
|
+
throw createValidationFailedError$1('config.headers', headers, 'config.headers must be a plain object with string header values when provided');
|
|
47859
|
+
}
|
|
47860
|
+
for (const [name, value] of Object.entries(headers)){
|
|
47861
|
+
if (typeof value !== 'string') {
|
|
47862
|
+
throw createValidationFailedError$1(`config.headers.${name}`, value, 'header values must be strings');
|
|
47863
|
+
}
|
|
47864
|
+
}
|
|
47865
|
+
}
|
|
47866
|
+
function assertGatewayV1Config(config) {
|
|
47867
|
+
if (!isPlainObject(config)) {
|
|
47868
|
+
throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
|
|
47869
|
+
}
|
|
47870
|
+
assertHeadersConfig(config['headers']);
|
|
47871
|
+
}
|
|
46233
47872
|
// ---------------------------------------------------------------------------
|
|
46234
47873
|
// Factory
|
|
46235
47874
|
// ---------------------------------------------------------------------------
|
|
@@ -46239,7 +47878,12 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46239
47878
|
* Returns a plain object that implements {@link IGatewayProvider} by
|
|
46240
47879
|
* delegating to the standalone functions in `operations/`.
|
|
46241
47880
|
*
|
|
47881
|
+
* @param config - Optional provider configuration. Supply `headers` to
|
|
47882
|
+
* forward custom HTTP headers with every Gateway API request. See
|
|
47883
|
+
* {@link GatewayV1Config}.
|
|
46242
47884
|
* @returns An {@link IGatewayProvider} instance.
|
|
47885
|
+
* @throws Throws when `config` or `config.headers` is not a plain object, or a
|
|
47886
|
+
* header value is not a string.
|
|
46243
47887
|
*
|
|
46244
47888
|
* @example
|
|
46245
47889
|
* ```typescript
|
|
@@ -46248,7 +47892,23 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46248
47892
|
* const provider = createGatewayV1Provider()
|
|
46249
47893
|
* const chains = provider.getSupportedChains()
|
|
46250
47894
|
* ```
|
|
46251
|
-
|
|
47895
|
+
*
|
|
47896
|
+
* @example
|
|
47897
|
+
* ```typescript
|
|
47898
|
+
* // Forward a custom header on every Gateway API request.
|
|
47899
|
+
* const provider = createGatewayV1Provider({
|
|
47900
|
+
* headers: { 'X-Access-Key': process.env.GATEWAY_ACCESS_KEY! },
|
|
47901
|
+
* })
|
|
47902
|
+
* ```
|
|
47903
|
+
*/ function createGatewayV1Provider(config = {}) {
|
|
47904
|
+
assertGatewayV1Config(config);
|
|
47905
|
+
// Captured once and threaded to every Gateway API call the operations make,
|
|
47906
|
+
// so a configured header reaches balances, estimate, transfer, forwarder
|
|
47907
|
+
// status, and /v1/info alike. Left undefined when no headers are configured
|
|
47908
|
+
// so the operations keep their existing defaults untouched.
|
|
47909
|
+
const requestConfig = config.headers ? {
|
|
47910
|
+
headers: config.headers
|
|
47911
|
+
} : undefined;
|
|
46252
47912
|
/**
|
|
46253
47913
|
* Execute an operation with started/succeeded/failed event dispatch.
|
|
46254
47914
|
*/ async function withEvents(opName, chain, startedData, params, fn) {
|
|
@@ -46279,21 +47939,25 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46279
47939
|
}, params, depositFor$1),
|
|
46280
47940
|
spend: async (params, options)=>{
|
|
46281
47941
|
const destChainName = extractChainInfo(params.to.chain).name;
|
|
47942
|
+
const mergedOptions = requestConfig ? {
|
|
47943
|
+
...options,
|
|
47944
|
+
requestConfig
|
|
47945
|
+
} : options;
|
|
46282
47946
|
return withEvents('spend', destChainName, {
|
|
46283
47947
|
destinationChain: destChainName,
|
|
46284
47948
|
token: params.token ?? 'USDC'
|
|
46285
|
-
}, params, async (p)=>spend$1(p, provider.actionDispatcher,
|
|
47949
|
+
}, params, async (p)=>spend$1(p, provider.actionDispatcher, mergedOptions));
|
|
46286
47950
|
},
|
|
46287
47951
|
estimateSpend: async (params)=>{
|
|
46288
47952
|
const destChainName = extractChainInfo(params.to.chain).name;
|
|
46289
47953
|
return withEvents('estimateSpend', destChainName, {
|
|
46290
47954
|
destinationChain: destChainName,
|
|
46291
47955
|
token: params.token ?? 'USDC'
|
|
46292
|
-
}, params, estimateSpend$1);
|
|
47956
|
+
}, params, async (p)=>estimateSpend$1(p, requestConfig));
|
|
46293
47957
|
},
|
|
46294
47958
|
getBalances: async (params)=>withEvents('getBalances', 'aggregate', {
|
|
46295
47959
|
token: params.token ?? 'USDC'
|
|
46296
|
-
}, params, getBalances$1),
|
|
47960
|
+
}, params, async (p)=>getBalances$1(p, requestConfig)),
|
|
46297
47961
|
addDelegate: async (params)=>withEvents('addDelegate', extractChainInfo(params.from.chain).name, {
|
|
46298
47962
|
delegateAddress: params.delegateAddress
|
|
46299
47963
|
}, params, addDelegate$1),
|
|
@@ -46311,7 +47975,9 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46311
47975
|
// in withEvents. getSupportedChains is synchronous. getDelegateStatus is a
|
|
46312
47976
|
// lightweight read that consumers treat as a utility check, unlike
|
|
46313
47977
|
// getBalances which involves API polling and benefits from lifecycle events.
|
|
46314
|
-
getDelegateStatus
|
|
47978
|
+
// getDelegateStatus is wrapped to forward the provider's request config to
|
|
47979
|
+
// its /v1/info call; getSupportedChains makes no API call.
|
|
47980
|
+
getDelegateStatus: async (params)=>getDelegateStatus$1(params, requestConfig),
|
|
46315
47981
|
getSupportedChains: getSupportedChains$1
|
|
46316
47982
|
};
|
|
46317
47983
|
return provider;
|
|
@@ -46321,6 +47987,7 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46321
47987
|
* The default providers that will be used in addition to the providers
|
|
46322
47988
|
* provided to the createUnifiedBalanceKitContext factory function.
|
|
46323
47989
|
*
|
|
47990
|
+
* @param config - Optional configuration forwarded to the default provider.
|
|
46324
47991
|
* @returns A readonly tuple with one element: the default Gateway v1 provider
|
|
46325
47992
|
* instance created via {@link createGatewayV1Provider}.
|
|
46326
47993
|
* @internal
|
|
@@ -46332,8 +47999,10 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46332
47999
|
* const { providers } = createUnifiedBalanceKitContext()
|
|
46333
48000
|
* // providers[0] is the default Gateway v1 instance from getDefaultProviders()
|
|
46334
48001
|
* ```
|
|
46335
|
-
*/ const getDefaultProviders = ()=>[
|
|
46336
|
-
createGatewayV1Provider(
|
|
48002
|
+
*/ const getDefaultProviders = (config = {})=>[
|
|
48003
|
+
createGatewayV1Provider(config.headers ? {
|
|
48004
|
+
headers: config.headers
|
|
48005
|
+
} : {})
|
|
46337
48006
|
];
|
|
46338
48007
|
function createUnifiedBalanceKitContext(config = {}) {
|
|
46339
48008
|
// Runtime guard: `excludeDefaultProviders: true` with no `providers` is
|
|
@@ -46347,7 +48016,10 @@ function createUnifiedBalanceKitContext(config = {}) {
|
|
|
46347
48016
|
// Initialize default providers — either the canonical singleton tuple or
|
|
46348
48017
|
// an empty list when the caller has opted out. Typed loosely here; each
|
|
46349
48018
|
// public overload narrows the return type for callers.
|
|
46350
|
-
const
|
|
48019
|
+
const defaultProvidersConfig = config.headers ? {
|
|
48020
|
+
headers: config.headers
|
|
48021
|
+
} : {};
|
|
48022
|
+
const defaultProviders = config.excludeDefaultProviders === true ? [] : getDefaultProviders(defaultProvidersConfig);
|
|
46351
48023
|
const providers = [
|
|
46352
48024
|
...defaultProviders,
|
|
46353
48025
|
...config.providers ?? []
|
|
@@ -47038,29 +48710,65 @@ function sumAllocationsFromSources(sources) {
|
|
|
47038
48710
|
}
|
|
47039
48711
|
});
|
|
47040
48712
|
|
|
48713
|
+
function normalizeAddressIdentity(value) {
|
|
48714
|
+
if (typeof value !== 'string') return undefined;
|
|
48715
|
+
try {
|
|
48716
|
+
return convertAddress(value, 'bytes32').toLowerCase();
|
|
48717
|
+
} catch {
|
|
48718
|
+
// Address validation below owns the structured error for invalid input.
|
|
48719
|
+
return value;
|
|
48720
|
+
}
|
|
48721
|
+
}
|
|
47041
48722
|
/**
|
|
47042
|
-
* Reject `from` arrays
|
|
47043
|
-
*
|
|
47044
|
-
*
|
|
48723
|
+
* Reject `from` arrays that describe the same spend source more than once.
|
|
48724
|
+
*
|
|
48725
|
+
* One adapter instance may legitimately be reused across distinct Gateway
|
|
48726
|
+
* source accounts. When `sourceAccount` is omitted, the signer address
|
|
48727
|
+
* identifies the source. Must run on the raw input (before Zod parsing)
|
|
48728
|
+
* because `z.object()` creates new wrapper objects and adapter identity
|
|
48729
|
+
* (`===`) is lost.
|
|
47045
48730
|
*
|
|
47046
48731
|
* @internal
|
|
47047
|
-
*/ function
|
|
48732
|
+
*/ function assertNoDuplicateSpendSources(params) {
|
|
47048
48733
|
if (params == null || typeof params !== 'object') return;
|
|
47049
48734
|
const from = params['from'];
|
|
47050
48735
|
if (!Array.isArray(from) || from.length < 2) return;
|
|
47051
|
-
const seen = new
|
|
48736
|
+
const seen = new Map();
|
|
47052
48737
|
for (const source of from){
|
|
47053
48738
|
if (source == null || typeof source !== 'object') continue;
|
|
47054
|
-
const
|
|
48739
|
+
const record = source;
|
|
48740
|
+
const adapter = record['adapter'];
|
|
47055
48741
|
if (adapter == null) continue;
|
|
47056
|
-
|
|
48742
|
+
const sourceAccount = normalizeAddressIdentity(record['sourceAccount']) ?? normalizeAddressIdentity(record['address']);
|
|
48743
|
+
let sourceAccounts = seen.get(adapter);
|
|
48744
|
+
if (sourceAccounts == null) {
|
|
48745
|
+
sourceAccounts = new Set();
|
|
48746
|
+
seen.set(adapter, sourceAccounts);
|
|
48747
|
+
}
|
|
48748
|
+
if (sourceAccounts.has(sourceAccount)) {
|
|
47057
48749
|
throw new KitError({
|
|
47058
48750
|
...InputError.VALIDATION_FAILED,
|
|
47059
48751
|
recoverability: 'FATAL',
|
|
47060
|
-
message: 'Duplicate
|
|
48752
|
+
message: 'Duplicate spend source detected in from array. The same adapter ' + 'and Gateway source account are reused more than once.'
|
|
47061
48753
|
});
|
|
47062
48754
|
}
|
|
47063
|
-
|
|
48755
|
+
sourceAccounts.add(sourceAccount);
|
|
48756
|
+
}
|
|
48757
|
+
}
|
|
48758
|
+
function assertSourceAddressContexts(from) {
|
|
48759
|
+
const sources = Array.isArray(from) ? from : [
|
|
48760
|
+
from
|
|
48761
|
+
];
|
|
48762
|
+
for (const raw of sources){
|
|
48763
|
+
if (raw == null || typeof raw !== 'object') continue;
|
|
48764
|
+
const source = raw;
|
|
48765
|
+
const addressContext = source.adapter?.capabilities?.addressContext;
|
|
48766
|
+
if (addressContext === 'developer-controlled' && (typeof source.address !== 'string' || source.address === '')) {
|
|
48767
|
+
throw createValidationFailedError$1('address', source.address, 'Address is required for developer-controlled adapters.');
|
|
48768
|
+
}
|
|
48769
|
+
if (addressContext === 'user-controlled' && source.address !== undefined) {
|
|
48770
|
+
throw createValidationFailedError$1('address', source.address, 'Address should not be provided for user-controlled adapters. ' + 'The address is automatically resolved from the connected wallet.');
|
|
48771
|
+
}
|
|
47064
48772
|
}
|
|
47065
48773
|
}
|
|
47066
48774
|
/**
|
|
@@ -47074,14 +48782,17 @@ function sumAllocationsFromSources(sources) {
|
|
|
47074
48782
|
* @throws \{KitError\} When validation fails; error code is
|
|
47075
48783
|
* {@link InputError.VALIDATION_FAILED}.
|
|
47076
48784
|
*/ function assertSpendParams(params) {
|
|
47077
|
-
|
|
48785
|
+
assertNoDuplicateSpendSources(params);
|
|
48786
|
+
if (params != null && typeof params === 'object' && 'from' in params) {
|
|
48787
|
+
assertSourceAddressContexts(params['from']);
|
|
48788
|
+
}
|
|
47078
48789
|
const result = spendParamsSchema.safeParse(params);
|
|
47079
48790
|
if (!result.success) {
|
|
47080
48791
|
throw convertZodErrorToStructured(result.error, params);
|
|
47081
48792
|
}
|
|
47082
48793
|
const data = result.data;
|
|
47083
48794
|
// Validate recipientAddress against the destination chain
|
|
47084
|
-
if (data.to.recipientAddress) {
|
|
48795
|
+
if (data.to.recipientAddress !== undefined && data.to.recipientAddress !== '') {
|
|
47085
48796
|
const toChain = data.to.chain;
|
|
47086
48797
|
if (!isValidAddressForChain(data.to.recipientAddress, toChain)) {
|
|
47087
48798
|
const chainInfo = extractChainInfo(toChain);
|
|
@@ -47089,7 +48800,7 @@ function sumAllocationsFromSources(sources) {
|
|
|
47089
48800
|
}
|
|
47090
48801
|
}
|
|
47091
48802
|
// Validate sourceAccount against each source's allocation chain
|
|
47092
|
-
if (data.from) {
|
|
48803
|
+
if (data.from !== undefined) {
|
|
47093
48804
|
assertSourceAccountAddresses(data.from);
|
|
47094
48805
|
}
|
|
47095
48806
|
}
|
|
@@ -47104,11 +48815,15 @@ function assertSourceAccountAddresses(from) {
|
|
|
47104
48815
|
from
|
|
47105
48816
|
];
|
|
47106
48817
|
for (const raw of sources){
|
|
47107
|
-
if (
|
|
48818
|
+
if (raw === null || typeof raw !== 'object' || !('sourceAccount' in raw)) {
|
|
48819
|
+
continue;
|
|
48820
|
+
}
|
|
47108
48821
|
const source = raw;
|
|
47109
|
-
if (
|
|
48822
|
+
if (source.sourceAccount === undefined || source.sourceAccount === '') {
|
|
48823
|
+
continue;
|
|
48824
|
+
}
|
|
47110
48825
|
const sourceChain = getSourceChainFromAllocations(source.allocations);
|
|
47111
|
-
if (
|
|
48826
|
+
if (sourceChain === undefined) continue;
|
|
47112
48827
|
if (!isValidAddressForChain(source.sourceAccount, sourceChain)) {
|
|
47113
48828
|
const chainInfo = extractChainInfo(sourceChain);
|
|
47114
48829
|
throw createInvalidAddressError(source.sourceAccount, String(chainInfo.name), chainInfo.expectedAddressFormat);
|
|
@@ -49308,6 +51023,11 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49308
51023
|
assertAppKitCustomFeePolicy(config.customFeePolicy);
|
|
49309
51024
|
}
|
|
49310
51025
|
const unifiedBalance = new AppKitUnifiedBalance({
|
|
51026
|
+
// Top-level headers act as the shared default; an explicit
|
|
51027
|
+
// `unifiedBalance.headers` overrides them by coming later in the spread.
|
|
51028
|
+
...config.headers != null && {
|
|
51029
|
+
headers: config.headers
|
|
51030
|
+
},
|
|
49311
51031
|
...config.unifiedBalance,
|
|
49312
51032
|
...config.disableErrorReporting != null && {
|
|
49313
51033
|
disableErrorReporting: config.disableErrorReporting
|
|
@@ -49410,30 +51130,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49410
51130
|
*/ async retryBridge(result, retryContext) {
|
|
49411
51131
|
return retryBridge(this.context, result, retryContext);
|
|
49412
51132
|
}
|
|
49413
|
-
|
|
49414
|
-
* Estimate the bridge operation.
|
|
49415
|
-
*
|
|
49416
|
-
* Calculates gas costs, protocol fees, and optional custom fees for a
|
|
49417
|
-
* cross-chain bridge transfer without executing the transaction. Useful
|
|
49418
|
-
* for displaying cost estimates to users before they confirm a transfer.
|
|
49419
|
-
*
|
|
49420
|
-
* @param params - Bridge parameters containing source, destination, amount, and token
|
|
49421
|
-
* @returns Promise resolving to the estimate result with fee information
|
|
49422
|
-
* @throws If bridge parameters are invalid
|
|
49423
|
-
* @throws If the bridge route is not supported
|
|
49424
|
-
*
|
|
49425
|
-
* @example
|
|
49426
|
-
* ```typescript
|
|
49427
|
-
* const estimate = await kit.estimateBridge({
|
|
49428
|
-
* from: { adapter: sourceAdapter, chain: 'Ethereum' },
|
|
49429
|
-
* to: { adapter: destAdapter, chain: 'Polygon' },
|
|
49430
|
-
* amount: '100.50',
|
|
49431
|
-
* token: 'USDC'
|
|
49432
|
-
* })
|
|
49433
|
-
*
|
|
49434
|
-
* console.log('Estimated fees:', estimate.fees)
|
|
49435
|
-
* ```
|
|
49436
|
-
*/ async estimateBridge(params) {
|
|
51133
|
+
async estimateBridge(params) {
|
|
49437
51134
|
return estimateBridge(this.context, params);
|
|
49438
51135
|
}
|
|
49439
51136
|
/**
|
|
@@ -49549,7 +51246,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49549
51246
|
* config: {
|
|
49550
51247
|
* slippageBps: 300, // 3% slippage
|
|
49551
51248
|
* allowanceStrategy: 'permit',
|
|
49552
|
-
*
|
|
51249
|
+
* apiKey: 'TEST_API_KEY:id:secret'
|
|
49553
51250
|
* }
|
|
49554
51251
|
* })
|
|
49555
51252
|
*
|
|
@@ -49578,7 +51275,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49578
51275
|
* amountIn: '100.50',
|
|
49579
51276
|
* config: {
|
|
49580
51277
|
* slippageBps: 300,
|
|
49581
|
-
*
|
|
51278
|
+
* apiKey: 'TEST_API_KEY:id:secret'
|
|
49582
51279
|
* }
|
|
49583
51280
|
* })
|
|
49584
51281
|
*
|
|
@@ -49602,7 +51299,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49602
51299
|
* not write the polling loop yourself.
|
|
49603
51300
|
*
|
|
49604
51301
|
* @param params - `txHash` and `chainIn`, plus optional `chainOut` and
|
|
49605
|
-
* `
|
|
51302
|
+
* `apiKey`.
|
|
49606
51303
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
49607
51304
|
* @throws \{KitError\} If `chainIn` or `chainOut` is malformed.
|
|
49608
51305
|
*
|
|
@@ -49615,7 +51312,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49615
51312
|
* txHash: result.txHash,
|
|
49616
51313
|
* chainIn: result.chainIn,
|
|
49617
51314
|
* chainOut: result.chainOut,
|
|
49618
|
-
*
|
|
51315
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49619
51316
|
* })
|
|
49620
51317
|
*
|
|
49621
51318
|
* console.log(status.progress.status, status.progress.substatus)
|
|
@@ -49628,7 +51325,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49628
51325
|
* txHash: result.txHash,
|
|
49629
51326
|
* chainIn: result.chainIn,
|
|
49630
51327
|
* chainOut: result.chainOut,
|
|
49631
|
-
*
|
|
51328
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49632
51329
|
* })
|
|
49633
51330
|
* while (status.progress.status === 'PENDING') {
|
|
49634
51331
|
* await new Promise((r) => setTimeout(r, 3_000))
|
|
@@ -49636,7 +51333,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49636
51333
|
* txHash: result.txHash,
|
|
49637
51334
|
* chainIn: result.chainIn,
|
|
49638
51335
|
* chainOut: result.chainOut,
|
|
49639
|
-
*
|
|
51336
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49640
51337
|
* })
|
|
49641
51338
|
* }
|
|
49642
51339
|
* ```
|
|
@@ -49655,7 +51352,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49655
51352
|
* expires. Timeouts surface as a RETRYABLE `KitError` so callers can
|
|
49656
51353
|
* re-invoke with the same `txHash`.
|
|
49657
51354
|
*
|
|
49658
|
-
* @param params - Wait configuration: identifiers,
|
|
51355
|
+
* @param params - Wait configuration: identifiers, API key, and optional
|
|
49659
51356
|
* `timeoutMs` / `onProgress`.
|
|
49660
51357
|
* @returns The first terminal {@link SwapStatusResult} observed.
|
|
49661
51358
|
* @throws \{KitError\} (`NETWORK.TIMEOUT`, RETRYABLE) when the wait
|
|
@@ -49668,7 +51365,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49668
51365
|
*
|
|
49669
51366
|
* const final = await kit.waitForSwap({
|
|
49670
51367
|
* result,
|
|
49671
|
-
*
|
|
51368
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49672
51369
|
* onProgress: (snap) => console.log(snap.progress.status),
|
|
49673
51370
|
* })
|
|
49674
51371
|
*
|
|
@@ -49684,7 +51381,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49684
51381
|
* txHash: persisted.txHash,
|
|
49685
51382
|
* chainIn: persisted.chainIn,
|
|
49686
51383
|
* chainOut: persisted.chainOut,
|
|
49687
|
-
*
|
|
51384
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49688
51385
|
* })
|
|
49689
51386
|
* ```
|
|
49690
51387
|
*/ async waitForSwap(params) {
|
|
@@ -49716,7 +51413,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49716
51413
|
* translates to the chain's native sentinel address — `0xEee…` for EVM,
|
|
49717
51414
|
* `1111…` for Solana — before querying the service.
|
|
49718
51415
|
*
|
|
49719
|
-
* @param params - `chain`, plus optional `tokens` and `
|
|
51416
|
+
* @param params - `chain`, plus optional `tokens` and `apiKey`.
|
|
49720
51417
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
49721
51418
|
* @throws \{KitError\} If `chain` is malformed, `tokens` exceeds 100
|
|
49722
51419
|
* entries, or any entry is neither a registered symbol nor a
|
|
@@ -49727,7 +51424,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49727
51424
|
* const { rates } = await kit.getTokenRates({
|
|
49728
51425
|
* chain: 'Ethereum',
|
|
49729
51426
|
* tokens: ['USDC', 'EURC'],
|
|
49730
|
-
*
|
|
51427
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49731
51428
|
* })
|
|
49732
51429
|
*
|
|
49733
51430
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -49736,38 +51433,8 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49736
51433
|
*/ async getTokenRates(params) {
|
|
49737
51434
|
return getTokenRates(this.context, params);
|
|
49738
51435
|
}
|
|
49739
|
-
|
|
49740
|
-
|
|
49741
|
-
*
|
|
49742
|
-
* Returns blockchain networks that support specific stablecoin operations.
|
|
49743
|
-
* When no operation type is specified, returns all chains supporting any
|
|
49744
|
-
* operation (bridge, swap, earn, or unified balance).
|
|
49745
|
-
*
|
|
49746
|
-
* @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance')
|
|
49747
|
-
* @returns Array of unique chain definitions supporting the specified operation(s)
|
|
49748
|
-
*
|
|
49749
|
-
* @example Get all supported chains
|
|
49750
|
-
* ```typescript
|
|
49751
|
-
* import { AppKit } from '@circle-fin/app-kit'
|
|
49752
|
-
*
|
|
49753
|
-
* const kit = new AppKit()
|
|
49754
|
-
* const allChains = kit.getSupportedChains()
|
|
49755
|
-
*
|
|
49756
|
-
* console.log(`Total supported chains: ${allChains.length}`)
|
|
49757
|
-
* allChains.forEach(chain => {
|
|
49758
|
-
* console.log(`- ${chain.name} (${chain.type})`)
|
|
49759
|
-
* })
|
|
49760
|
-
* ```
|
|
49761
|
-
*
|
|
49762
|
-
* @example Get bridge-specific chains
|
|
49763
|
-
* ```typescript
|
|
49764
|
-
* const kit = new AppKit()
|
|
49765
|
-
* const bridgeChains = kit.getSupportedChains('bridge')
|
|
49766
|
-
*
|
|
49767
|
-
* console.log('Chains supporting bridge:', bridgeChains.map(c => c.name))
|
|
49768
|
-
* ```
|
|
49769
|
-
*/ getSupportedChains(operationType) {
|
|
49770
|
-
return getSupportedChains$2(this.context, operationType, this.unifiedBalance);
|
|
51436
|
+
getSupportedChains(operationType, options) {
|
|
51437
|
+
return getSupportedChains$2(this.context, operationType, this.unifiedBalance, options);
|
|
49771
51438
|
}
|
|
49772
51439
|
/**
|
|
49773
51440
|
* Set operation-scoped custom fee policies.
|