@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.cjs
CHANGED
|
@@ -4071,6 +4071,8 @@ function getOptionalString(value) {
|
|
|
4071
4071
|
Blockchain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
4072
4072
|
Blockchain["Pharos"] = "Pharos";
|
|
4073
4073
|
Blockchain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
4074
|
+
Blockchain["Plasma"] = "Plasma";
|
|
4075
|
+
Blockchain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
4074
4076
|
Blockchain["Polkadot_Asset_Hub"] = "Polkadot_Asset_Hub";
|
|
4075
4077
|
Blockchain["Polkadot_Westmint"] = "Polkadot_Westmint";
|
|
4076
4078
|
Blockchain["Plume"] = "Plume";
|
|
@@ -4140,6 +4142,7 @@ exports.BridgeChain = void 0;
|
|
|
4140
4142
|
BridgeChain["Morph"] = "Morph";
|
|
4141
4143
|
BridgeChain["Optimism"] = "Optimism";
|
|
4142
4144
|
BridgeChain["Pharos"] = "Pharos";
|
|
4145
|
+
BridgeChain["Plasma"] = "Plasma";
|
|
4143
4146
|
BridgeChain["Plume"] = "Plume";
|
|
4144
4147
|
BridgeChain["Polygon"] = "Polygon";
|
|
4145
4148
|
BridgeChain["Sei"] = "Sei";
|
|
@@ -4166,6 +4169,7 @@ exports.BridgeChain = void 0;
|
|
|
4166
4169
|
BridgeChain["Morph_Testnet"] = "Morph_Testnet";
|
|
4167
4170
|
BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
|
|
4168
4171
|
BridgeChain["Pharos_Testnet"] = "Pharos_Testnet";
|
|
4172
|
+
BridgeChain["Plasma_Testnet"] = "Plasma_Testnet";
|
|
4169
4173
|
BridgeChain["Plume_Testnet"] = "Plume_Testnet";
|
|
4170
4174
|
BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
|
|
4171
4175
|
BridgeChain["Sei_Testnet"] = "Sei_Testnet";
|
|
@@ -4623,6 +4627,8 @@ exports.EarnChain = void 0;
|
|
|
4623
4627
|
* This program handles minting operations for Gateway transactions
|
|
4624
4628
|
* on Solana devnet.
|
|
4625
4629
|
*/ const GATEWAY_MINTER_SOLANA_DEVNET = 'GATEmKK2ECL1brEngQZWCgMWPbvrEYqsV6u29dAaHavr';
|
|
4630
|
+
/** TokenMessengerWithFees address shared by enabled EVM mainnet sources. */ const TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET = '0x71f54F818671cD0D7ea140Da213e5C8b5C92a408';
|
|
4631
|
+
/** TokenMessengerWithFees address shared by enabled EVM testnet sources. */ const TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET = '0x8745D906D67C346E5eb1aEEED38Eb87F34DF0C0A';
|
|
4626
4632
|
/**
|
|
4627
4633
|
* Circle's fee recipient address for EVM chains.
|
|
4628
4634
|
*
|
|
@@ -4670,6 +4676,7 @@ exports.EarnChain = void 0;
|
|
|
4670
4676
|
v2: {
|
|
4671
4677
|
type: 'split',
|
|
4672
4678
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4679
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4673
4680
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4674
4681
|
confirmations: 1,
|
|
4675
4682
|
fastConfirmations: 1
|
|
@@ -4737,6 +4744,7 @@ exports.EarnChain = void 0;
|
|
|
4737
4744
|
v2: {
|
|
4738
4745
|
type: 'split',
|
|
4739
4746
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4747
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4740
4748
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4741
4749
|
confirmations: 65,
|
|
4742
4750
|
fastConfirmations: 1
|
|
@@ -4801,6 +4809,7 @@ exports.EarnChain = void 0;
|
|
|
4801
4809
|
v2: {
|
|
4802
4810
|
type: 'split',
|
|
4803
4811
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
4812
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4804
4813
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
4805
4814
|
confirmations: 65,
|
|
4806
4815
|
fastConfirmations: 1
|
|
@@ -4865,6 +4874,7 @@ exports.EarnChain = void 0;
|
|
|
4865
4874
|
v2: {
|
|
4866
4875
|
type: 'split',
|
|
4867
4876
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
4877
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4868
4878
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4869
4879
|
confirmations: 1,
|
|
4870
4880
|
fastConfirmations: 1
|
|
@@ -4926,6 +4936,7 @@ exports.EarnChain = void 0;
|
|
|
4926
4936
|
v2: {
|
|
4927
4937
|
type: 'split',
|
|
4928
4938
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
4939
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
4929
4940
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
4930
4941
|
confirmations: 1,
|
|
4931
4942
|
fastConfirmations: 1
|
|
@@ -4993,6 +5004,7 @@ exports.EarnChain = void 0;
|
|
|
4993
5004
|
v2: {
|
|
4994
5005
|
type: 'split',
|
|
4995
5006
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5007
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
4996
5008
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
4997
5009
|
confirmations: 65,
|
|
4998
5010
|
fastConfirmations: 1
|
|
@@ -5057,6 +5069,7 @@ exports.EarnChain = void 0;
|
|
|
5057
5069
|
v2: {
|
|
5058
5070
|
type: 'split',
|
|
5059
5071
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5072
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5060
5073
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5061
5074
|
confirmations: 65,
|
|
5062
5075
|
fastConfirmations: 1
|
|
@@ -5167,6 +5180,7 @@ exports.EarnChain = void 0;
|
|
|
5167
5180
|
v2: {
|
|
5168
5181
|
type: 'split',
|
|
5169
5182
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5183
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5170
5184
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5171
5185
|
confirmations: 65,
|
|
5172
5186
|
fastConfirmations: 1
|
|
@@ -5211,6 +5225,7 @@ exports.EarnChain = void 0;
|
|
|
5211
5225
|
v2: {
|
|
5212
5226
|
type: 'split',
|
|
5213
5227
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5228
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5214
5229
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5215
5230
|
confirmations: 65,
|
|
5216
5231
|
fastConfirmations: 1
|
|
@@ -5442,6 +5457,7 @@ exports.EarnChain = void 0;
|
|
|
5442
5457
|
v2: {
|
|
5443
5458
|
type: 'split',
|
|
5444
5459
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5460
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5445
5461
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5446
5462
|
confirmations: 65,
|
|
5447
5463
|
fastConfirmations: 2
|
|
@@ -5506,6 +5522,7 @@ exports.EarnChain = void 0;
|
|
|
5506
5522
|
v2: {
|
|
5507
5523
|
type: 'split',
|
|
5508
5524
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5525
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5509
5526
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5510
5527
|
confirmations: 65,
|
|
5511
5528
|
fastConfirmations: 2
|
|
@@ -5616,6 +5633,7 @@ exports.EarnChain = void 0;
|
|
|
5616
5633
|
v2: {
|
|
5617
5634
|
type: 'split',
|
|
5618
5635
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5636
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5619
5637
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5620
5638
|
confirmations: 1,
|
|
5621
5639
|
fastConfirmations: 1
|
|
@@ -5675,6 +5693,7 @@ exports.EarnChain = void 0;
|
|
|
5675
5693
|
v2: {
|
|
5676
5694
|
type: 'split',
|
|
5677
5695
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5696
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5678
5697
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5679
5698
|
confirmations: 1,
|
|
5680
5699
|
fastConfirmations: 1
|
|
@@ -5829,6 +5848,7 @@ exports.EarnChain = void 0;
|
|
|
5829
5848
|
v2: {
|
|
5830
5849
|
type: 'split',
|
|
5831
5850
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5851
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5832
5852
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5833
5853
|
confirmations: 65,
|
|
5834
5854
|
fastConfirmations: 1
|
|
@@ -5876,6 +5896,7 @@ exports.EarnChain = void 0;
|
|
|
5876
5896
|
v2: {
|
|
5877
5897
|
type: 'split',
|
|
5878
5898
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5899
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5879
5900
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5880
5901
|
confirmations: 65,
|
|
5881
5902
|
fastConfirmations: 1
|
|
@@ -5920,6 +5941,7 @@ exports.EarnChain = void 0;
|
|
|
5920
5941
|
v2: {
|
|
5921
5942
|
type: 'split',
|
|
5922
5943
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5944
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
5923
5945
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5924
5946
|
confirmations: 1,
|
|
5925
5947
|
fastConfirmations: 1
|
|
@@ -5965,6 +5987,7 @@ exports.EarnChain = void 0;
|
|
|
5965
5987
|
v2: {
|
|
5966
5988
|
type: 'split',
|
|
5967
5989
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5990
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
5968
5991
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
5969
5992
|
confirmations: 1,
|
|
5970
5993
|
fastConfirmations: 1
|
|
@@ -6011,6 +6034,7 @@ exports.EarnChain = void 0;
|
|
|
6011
6034
|
v2: {
|
|
6012
6035
|
type: 'split',
|
|
6013
6036
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6037
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6014
6038
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6015
6039
|
confirmations: 1,
|
|
6016
6040
|
fastConfirmations: 1
|
|
@@ -6324,6 +6348,7 @@ exports.EarnChain = void 0;
|
|
|
6324
6348
|
v2: {
|
|
6325
6349
|
type: 'split',
|
|
6326
6350
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6351
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6327
6352
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6328
6353
|
confirmations: 65,
|
|
6329
6354
|
fastConfirmations: 1
|
|
@@ -6388,6 +6413,7 @@ exports.EarnChain = void 0;
|
|
|
6388
6413
|
v2: {
|
|
6389
6414
|
type: 'split',
|
|
6390
6415
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
6416
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6391
6417
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
6392
6418
|
confirmations: 65,
|
|
6393
6419
|
fastConfirmations: 1
|
|
@@ -6508,6 +6534,98 @@ exports.EarnChain = void 0;
|
|
|
6508
6534
|
}
|
|
6509
6535
|
});
|
|
6510
6536
|
|
|
6537
|
+
/**
|
|
6538
|
+
* Plasma Mainnet chain definition
|
|
6539
|
+
* @remarks
|
|
6540
|
+
* This represents the official production network for the Plasma blockchain.
|
|
6541
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
6542
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
6543
|
+
*/ const Plasma = defineChain({
|
|
6544
|
+
type: 'evm',
|
|
6545
|
+
chain: exports.Blockchain.Plasma,
|
|
6546
|
+
name: 'Plasma',
|
|
6547
|
+
title: 'Plasma Mainnet',
|
|
6548
|
+
nativeCurrency: {
|
|
6549
|
+
name: 'Plasma',
|
|
6550
|
+
symbol: 'XPL',
|
|
6551
|
+
decimals: 18
|
|
6552
|
+
},
|
|
6553
|
+
chainId: 9745,
|
|
6554
|
+
isTestnet: false,
|
|
6555
|
+
explorerUrl: 'https://plasmascan.to/tx/{hash}',
|
|
6556
|
+
rpcEndpoints: [
|
|
6557
|
+
'https://rpc.plasma.to'
|
|
6558
|
+
],
|
|
6559
|
+
eurcAddress: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
6560
|
+
usdcAddress: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
6561
|
+
usdtAddress: null,
|
|
6562
|
+
cctp: {
|
|
6563
|
+
domain: 33,
|
|
6564
|
+
contracts: {
|
|
6565
|
+
v2: {
|
|
6566
|
+
type: 'split',
|
|
6567
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6568
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6569
|
+
confirmations: 3,
|
|
6570
|
+
fastConfirmations: 1
|
|
6571
|
+
}
|
|
6572
|
+
},
|
|
6573
|
+
forwarderSupported: {
|
|
6574
|
+
source: false,
|
|
6575
|
+
destination: false
|
|
6576
|
+
}
|
|
6577
|
+
},
|
|
6578
|
+
kitContracts: {
|
|
6579
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
6580
|
+
}
|
|
6581
|
+
});
|
|
6582
|
+
|
|
6583
|
+
/**
|
|
6584
|
+
* Plasma Testnet chain definition
|
|
6585
|
+
* @remarks
|
|
6586
|
+
* This represents the official test network for the Plasma blockchain.
|
|
6587
|
+
* Plasma is an EVM-equivalent Layer 1 blockchain purpose-built for global
|
|
6588
|
+
* stablecoin payments, with deterministic BFT finality (PlasmaBFT/Fast-HotStuff).
|
|
6589
|
+
*/ const PlasmaTestnet = defineChain({
|
|
6590
|
+
type: 'evm',
|
|
6591
|
+
chain: exports.Blockchain.Plasma_Testnet,
|
|
6592
|
+
name: 'Plasma Testnet',
|
|
6593
|
+
title: 'Plasma Testnet',
|
|
6594
|
+
nativeCurrency: {
|
|
6595
|
+
name: 'Plasma',
|
|
6596
|
+
symbol: 'XPL',
|
|
6597
|
+
decimals: 18
|
|
6598
|
+
},
|
|
6599
|
+
chainId: 9746,
|
|
6600
|
+
isTestnet: true,
|
|
6601
|
+
explorerUrl: 'https://testnet.plasmascan.to/tx/{hash}',
|
|
6602
|
+
rpcEndpoints: [
|
|
6603
|
+
'https://testnet-rpc.plasma.to'
|
|
6604
|
+
],
|
|
6605
|
+
eurcAddress: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330',
|
|
6606
|
+
usdcAddress: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
6607
|
+
usdtAddress: null,
|
|
6608
|
+
cctp: {
|
|
6609
|
+
domain: 33,
|
|
6610
|
+
contracts: {
|
|
6611
|
+
v2: {
|
|
6612
|
+
type: 'split',
|
|
6613
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6614
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6615
|
+
confirmations: 3,
|
|
6616
|
+
fastConfirmations: 1
|
|
6617
|
+
}
|
|
6618
|
+
},
|
|
6619
|
+
forwarderSupported: {
|
|
6620
|
+
source: false,
|
|
6621
|
+
destination: false
|
|
6622
|
+
}
|
|
6623
|
+
},
|
|
6624
|
+
kitContracts: {
|
|
6625
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
6626
|
+
}
|
|
6627
|
+
});
|
|
6628
|
+
|
|
6511
6629
|
/**
|
|
6512
6630
|
* Plume Mainnet chain definition
|
|
6513
6631
|
* @remarks
|
|
@@ -6539,6 +6657,7 @@ exports.EarnChain = void 0;
|
|
|
6539
6657
|
v2: {
|
|
6540
6658
|
type: 'split',
|
|
6541
6659
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6660
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6542
6661
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6543
6662
|
confirmations: 65,
|
|
6544
6663
|
fastConfirmations: 1
|
|
@@ -6585,6 +6704,7 @@ exports.EarnChain = void 0;
|
|
|
6585
6704
|
v2: {
|
|
6586
6705
|
type: 'split',
|
|
6587
6706
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6707
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6588
6708
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6589
6709
|
confirmations: 65,
|
|
6590
6710
|
fastConfirmations: 1
|
|
@@ -6686,6 +6806,7 @@ exports.EarnChain = void 0;
|
|
|
6686
6806
|
v2: {
|
|
6687
6807
|
type: 'split',
|
|
6688
6808
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6809
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6689
6810
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6690
6811
|
confirmations: 33,
|
|
6691
6812
|
fastConfirmations: 13
|
|
@@ -6751,6 +6872,7 @@ exports.EarnChain = void 0;
|
|
|
6751
6872
|
v2: {
|
|
6752
6873
|
type: 'split',
|
|
6753
6874
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6875
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6754
6876
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6755
6877
|
confirmations: 33,
|
|
6756
6878
|
fastConfirmations: 13
|
|
@@ -6810,6 +6932,7 @@ exports.EarnChain = void 0;
|
|
|
6810
6932
|
v2: {
|
|
6811
6933
|
type: 'split',
|
|
6812
6934
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
6935
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6813
6936
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6814
6937
|
confirmations: 1,
|
|
6815
6938
|
fastConfirmations: 1
|
|
@@ -6869,6 +6992,7 @@ exports.EarnChain = void 0;
|
|
|
6869
6992
|
v2: {
|
|
6870
6993
|
type: 'split',
|
|
6871
6994
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
6995
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6872
6996
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6873
6997
|
confirmations: 1,
|
|
6874
6998
|
fastConfirmations: 1
|
|
@@ -6926,6 +7050,7 @@ exports.EarnChain = void 0;
|
|
|
6926
7050
|
v2: {
|
|
6927
7051
|
type: 'split',
|
|
6928
7052
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7053
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
6929
7054
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
6930
7055
|
confirmations: 1,
|
|
6931
7056
|
fastConfirmations: 1
|
|
@@ -6984,6 +7109,7 @@ exports.EarnChain = void 0;
|
|
|
6984
7109
|
v2: {
|
|
6985
7110
|
type: 'split',
|
|
6986
7111
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7112
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
6987
7113
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
6988
7114
|
confirmations: 1,
|
|
6989
7115
|
fastConfirmations: 1
|
|
@@ -7299,6 +7425,7 @@ exports.EarnChain = void 0;
|
|
|
7299
7425
|
v2: {
|
|
7300
7426
|
type: 'split',
|
|
7301
7427
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7428
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
7302
7429
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7303
7430
|
confirmations: 65,
|
|
7304
7431
|
fastConfirmations: 1
|
|
@@ -7363,6 +7490,7 @@ exports.EarnChain = void 0;
|
|
|
7363
7490
|
v2: {
|
|
7364
7491
|
type: 'split',
|
|
7365
7492
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7493
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
7366
7494
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
7367
7495
|
confirmations: 65,
|
|
7368
7496
|
fastConfirmations: 1
|
|
@@ -7420,6 +7548,7 @@ exports.EarnChain = void 0;
|
|
|
7420
7548
|
v2: {
|
|
7421
7549
|
type: 'split',
|
|
7422
7550
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cF5d',
|
|
7551
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
7423
7552
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7424
7553
|
confirmations: 65,
|
|
7425
7554
|
fastConfirmations: 1
|
|
@@ -7479,6 +7608,7 @@ exports.EarnChain = void 0;
|
|
|
7479
7608
|
v2: {
|
|
7480
7609
|
type: 'split',
|
|
7481
7610
|
tokenMessenger: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
7611
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
7482
7612
|
messageTransmitter: '0xe737e5cebeeba77efe34d4aa090756590b1ce275',
|
|
7483
7613
|
confirmations: 65,
|
|
7484
7614
|
fastConfirmations: 1
|
|
@@ -7539,6 +7669,7 @@ exports.EarnChain = void 0;
|
|
|
7539
7669
|
v2: {
|
|
7540
7670
|
type: 'split',
|
|
7541
7671
|
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7672
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_MAINNET,
|
|
7542
7673
|
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7543
7674
|
confirmations: 3,
|
|
7544
7675
|
fastConfirmations: 3
|
|
@@ -7584,6 +7715,7 @@ exports.EarnChain = void 0;
|
|
|
7584
7715
|
v2: {
|
|
7585
7716
|
type: 'split',
|
|
7586
7717
|
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7718
|
+
tokenMessengerWithFees: TOKEN_MESSENGER_WITH_FEES_EVM_TESTNET,
|
|
7587
7719
|
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
7588
7720
|
confirmations: 3,
|
|
7589
7721
|
fastConfirmations: 1
|
|
@@ -7794,6 +7926,8 @@ var Chains = {
|
|
|
7794
7926
|
OptimismSepolia: OptimismSepolia,
|
|
7795
7927
|
Pharos: Pharos,
|
|
7796
7928
|
PharosTestnet: PharosTestnet,
|
|
7929
|
+
Plasma: Plasma,
|
|
7930
|
+
PlasmaTestnet: PlasmaTestnet,
|
|
7797
7931
|
Plume: Plume,
|
|
7798
7932
|
PlumeTestnet: PlumeTestnet,
|
|
7799
7933
|
PolkadotAssetHub: PolkadotAssetHub,
|
|
@@ -7843,6 +7977,34 @@ var Chains = {
|
|
|
7843
7977
|
return chain.cctp?.contracts.v2 !== undefined;
|
|
7844
7978
|
}
|
|
7845
7979
|
|
|
7980
|
+
/**
|
|
7981
|
+
* Check whether a chain supports source-paid ("receive-exact") CCTP v2 fees.
|
|
7982
|
+
*
|
|
7983
|
+
* A chain supports source-paid fees when its CCTP v2 configuration carries a
|
|
7984
|
+
* deployed `TokenMessengerWithFees` wrapper address. Bridge Kit routes
|
|
7985
|
+
* `feePayment: 'source'` transfers through this wrapper via
|
|
7986
|
+
* `depositForBurnWithHookAndFees`, so a chain without the wrapper cannot be a
|
|
7987
|
+
* source for receive-exact bridging.
|
|
7988
|
+
*
|
|
7989
|
+
* @param chain - The chain definition to check.
|
|
7990
|
+
* @returns `true` when the chain has a `tokenMessengerWithFees` wrapper
|
|
7991
|
+
* configured, `false` otherwise.
|
|
7992
|
+
*
|
|
7993
|
+
* @example
|
|
7994
|
+
* ```typescript
|
|
7995
|
+
* import { Chains, hasSourceFeeSupport } from '@core/chains'
|
|
7996
|
+
*
|
|
7997
|
+
* hasSourceFeeSupport(Chains.Optimism) // true
|
|
7998
|
+
* hasSourceFeeSupport(Chains.Solana) // false
|
|
7999
|
+
* ```
|
|
8000
|
+
*/ function hasSourceFeeSupport(chain) {
|
|
8001
|
+
if (!isCCTPV2Supported(chain)) {
|
|
8002
|
+
return false;
|
|
8003
|
+
}
|
|
8004
|
+
const wrapper = chain.cctp.contracts.v2.tokenMessengerWithFees;
|
|
8005
|
+
return typeof wrapper === 'string' && wrapper.length > 0;
|
|
8006
|
+
}
|
|
8007
|
+
|
|
7846
8008
|
/**
|
|
7847
8009
|
* Check if a chain supports a specific type of custom smart contract logic.
|
|
7848
8010
|
*
|
|
@@ -10170,6 +10332,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10170
10332
|
[exports.Blockchain.Noble]: 'uusdc',
|
|
10171
10333
|
[exports.Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
|
|
10172
10334
|
[exports.Blockchain.Pharos]: '0xC879C018dB60520F4355C26eD1a6D572cdAC1815',
|
|
10335
|
+
[exports.Blockchain.Plasma]: '0x2d661C89D812261039AF9764eceaAee884f5F67F',
|
|
10173
10336
|
[exports.Blockchain.Plume]: '0x222365EF19F7947e5484218551B56bb3965Aa7aF',
|
|
10174
10337
|
[exports.Blockchain.Polkadot_Asset_Hub]: '1337',
|
|
10175
10338
|
[exports.Blockchain.Polygon]: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359',
|
|
@@ -10206,6 +10369,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10206
10369
|
[exports.Blockchain.Noble_Testnet]: 'uusdc',
|
|
10207
10370
|
[exports.Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
|
|
10208
10371
|
[exports.Blockchain.Pharos_Testnet]: '0xcfC8330f4BCAB529c625D12781b1C19466A9Fc8B',
|
|
10372
|
+
[exports.Blockchain.Plasma_Testnet]: '0xE67Fb267022cBA8064Dd388CC2FED724F3120D9D',
|
|
10209
10373
|
[exports.Blockchain.Plume_Testnet]: '0xcB5f30e335672893c7eb944B374c196392C19D18',
|
|
10210
10374
|
[exports.Blockchain.Polkadot_Westmint]: '31337',
|
|
10211
10375
|
[exports.Blockchain.Polygon_Amoy_Testnet]: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -10268,6 +10432,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10268
10432
|
[exports.Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
|
|
10269
10433
|
[exports.Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
|
|
10270
10434
|
[exports.Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
|
|
10435
|
+
[exports.Blockchain.Plasma]: '0x3EE196E78d4d4248b849B8E1C7F44C5457FAFD2C',
|
|
10271
10436
|
[exports.Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
|
|
10272
10437
|
[exports.Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
|
|
10273
10438
|
// =========================================================================
|
|
@@ -10276,7 +10441,8 @@ function parseOrThrow(value, schema, context) {
|
|
|
10276
10441
|
[exports.Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
|
|
10277
10442
|
[exports.Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
|
|
10278
10443
|
[exports.Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
|
|
10279
|
-
[exports.Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
|
|
10444
|
+
[exports.Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4',
|
|
10445
|
+
[exports.Blockchain.Plasma_Testnet]: '0x98AfA0F93Dd993B736399f9074eDcEBD1985A330'
|
|
10280
10446
|
}
|
|
10281
10447
|
};
|
|
10282
10448
|
|
|
@@ -11229,6 +11395,16 @@ function parseOrThrow(value, schema, context) {
|
|
|
11229
11395
|
*
|
|
11230
11396
|
* Set to 0 when no additional Circle-reserved data is needed.
|
|
11231
11397
|
*/ const CCTP_FORWARD_PAYLOAD_LENGTH = 0;
|
|
11398
|
+
/**
|
|
11399
|
+
* Length in bytes of a Solana owner (ed25519 / PDA) public key.
|
|
11400
|
+
*/ const SOLANA_PUBKEY_LENGTH = 32;
|
|
11401
|
+
/**
|
|
11402
|
+
* Byte length of the Solana ATA-creation forwarding payload appended after the
|
|
11403
|
+
* `cctp-forward` frame: `createAta` (1 byte) + `ataOwner` (32 bytes).
|
|
11404
|
+
*
|
|
11405
|
+
* Circle's Orbit relayer decodes exactly this many bytes; see
|
|
11406
|
+
* {@link buildSolanaAtaForwardingHookData}.
|
|
11407
|
+
*/ const SOLANA_ATA_FORWARD_PAYLOAD_LENGTH = 1 + SOLANA_PUBKEY_LENGTH;
|
|
11232
11408
|
/**
|
|
11233
11409
|
* Build the hookData bytes for CCTP forwarding.
|
|
11234
11410
|
*
|
|
@@ -11285,6 +11461,108 @@ function buildForwardingHookData() {
|
|
|
11285
11461
|
cachedHookDataHex = '0x' + Array.from(buffer).map((b)=>b.toString(16).padStart(2, '0')).join('');
|
|
11286
11462
|
return cachedHookDataHex;
|
|
11287
11463
|
}
|
|
11464
|
+
/**
|
|
11465
|
+
* Build a `cctp-forward` hookData frame that instructs Circle's Orbit relayer to
|
|
11466
|
+
* create the recipient's Associated Token Account (ATA) before minting on Solana.
|
|
11467
|
+
*
|
|
11468
|
+
* When an EVM→Solana bridge is forwarded, the destination mint targets the
|
|
11469
|
+
* recipient's USDC ATA — which does not exist for a fresh wallet. This frame
|
|
11470
|
+
* tells the relayer to prepend an idempotent `createAssociatedTokenAccount`
|
|
11471
|
+
* instruction (the relayer pays the rent) so the mint always succeeds.
|
|
11472
|
+
*
|
|
11473
|
+
* Unlike {@link buildForwardingHookData} (an empty version-0 frame), this emits
|
|
11474
|
+
* a version-0 frame whose 32-bit `dataLength` is set to
|
|
11475
|
+
* {@link SOLANA_ATA_FORWARD_PAYLOAD_LENGTH} (33), followed by the payload the
|
|
11476
|
+
* relayer decodes:
|
|
11477
|
+
* - Byte 0: `createAta` flag, always `1`
|
|
11478
|
+
* - Bytes 1-32: the recipient's 32-byte Solana owner public key (`ataOwner`)
|
|
11479
|
+
*
|
|
11480
|
+
* @remarks
|
|
11481
|
+
* `ataOwner` is the recipient's *wallet* public key, not the derived ATA. The
|
|
11482
|
+
* relayer re-derives the ATA from `ataOwner` and the USDC mint and requires it
|
|
11483
|
+
* to equal the burn's `mintRecipient`, so callers must pass the same owner used
|
|
11484
|
+
* to derive `mintRecipient`. The all-zero key is reserved as "absent owner" and
|
|
11485
|
+
* is rejected.
|
|
11486
|
+
*
|
|
11487
|
+
* @param ataOwner - The recipient's 32-byte Solana owner public key.
|
|
11488
|
+
* @returns A 0x-prefixed hex string: the 32-byte frame followed by the 33-byte
|
|
11489
|
+
* Solana ATA payload.
|
|
11490
|
+
* @throws {KitError} If `ataOwner` is not exactly 32 bytes, or is the all-zero
|
|
11491
|
+
* key (INPUT_VALIDATION_FAILED).
|
|
11492
|
+
*
|
|
11493
|
+
* @example
|
|
11494
|
+
* ```typescript
|
|
11495
|
+
* import { PublicKey } from '@solana/web3.js'
|
|
11496
|
+
* import { buildSolanaAtaForwardingHookData } from '@core/utils'
|
|
11497
|
+
*
|
|
11498
|
+
* const owner = new PublicKey('9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM')
|
|
11499
|
+
* const hookData = buildSolanaAtaForwardingHookData(owner.toBytes())
|
|
11500
|
+
*
|
|
11501
|
+
* // Use with the forwarded depositForBurnWithHook action so the relayer
|
|
11502
|
+
* // creates the recipient ATA before minting.
|
|
11503
|
+
* await adapter.prepareAction('cctp.v2.depositForBurnWithHook', {
|
|
11504
|
+
* amount: BigInt('1000000'),
|
|
11505
|
+
* mintRecipient: '0x...',
|
|
11506
|
+
* maxFee: BigInt('50000'),
|
|
11507
|
+
* minFinalityThreshold: 1000,
|
|
11508
|
+
* fromChain: ethereum,
|
|
11509
|
+
* toChain: solana,
|
|
11510
|
+
* hookData,
|
|
11511
|
+
* })
|
|
11512
|
+
* ```
|
|
11513
|
+
*/ function buildSolanaAtaForwardingHookData(ataOwner) {
|
|
11514
|
+
if (!(ataOwner instanceof Uint8Array) || ataOwner.length !== SOLANA_PUBKEY_LENGTH) {
|
|
11515
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, `Expected a ${String(SOLANA_PUBKEY_LENGTH)}-byte Solana owner public key`);
|
|
11516
|
+
}
|
|
11517
|
+
if (ataOwner.every((byte)=>byte === 0)) {
|
|
11518
|
+
throw createValidationFailedError$1('ataOwner', ataOwner, 'Expected a non-zero Solana owner public key; the all-zero key is reserved as "absent owner"');
|
|
11519
|
+
}
|
|
11520
|
+
// Inner payload: createAta(1) + ataOwner(32).
|
|
11521
|
+
const payload = new Uint8Array(SOLANA_ATA_FORWARD_PAYLOAD_LENGTH);
|
|
11522
|
+
payload[0] = 1 // createAta = true
|
|
11523
|
+
;
|
|
11524
|
+
payload.set(ataOwner, 1);
|
|
11525
|
+
// 32-byte header: 24-byte magic + uint32 version(0) + uint32 dataLength(33).
|
|
11526
|
+
// The relayer reads dataLength from the v0 frame to slice the inner payload,
|
|
11527
|
+
// so it MUST reflect the appended byte count (unlike the GenericExecutor path).
|
|
11528
|
+
const frame = new Uint8Array(32);
|
|
11529
|
+
frame.set(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX), 0);
|
|
11530
|
+
const view = new DataView(frame.buffer);
|
|
11531
|
+
view.setUint32(24, CCTP_FORWARD_VERSION, false) // big-endian, 0
|
|
11532
|
+
;
|
|
11533
|
+
view.setUint32(28, SOLANA_ATA_FORWARD_PAYLOAD_LENGTH, false) // big-endian, 33
|
|
11534
|
+
;
|
|
11535
|
+
return bytes.hexlify(bytes.concat([
|
|
11536
|
+
frame,
|
|
11537
|
+
payload
|
|
11538
|
+
]));
|
|
11539
|
+
}
|
|
11540
|
+
|
|
11541
|
+
/**
|
|
11542
|
+
* Left-pad a 20-byte EVM address to a 32-byte (`bytes32`) hex string.
|
|
11543
|
+
*
|
|
11544
|
+
* Mirrors viem's `pad(address, size 32)` and CCTP's `mintRecipient`
|
|
11545
|
+
* convention. Solana addresses are already 32 bytes and need no padding.
|
|
11546
|
+
*
|
|
11547
|
+
* @param address - A 0x-prefixed 20-byte EVM address.
|
|
11548
|
+
* @returns The address left-zero-padded to a 0x-prefixed 32-byte hex string.
|
|
11549
|
+
* @throws {KitError} If `address` is not a valid EVM address (INPUT_VALIDATION_FAILED).
|
|
11550
|
+
*
|
|
11551
|
+
* @example
|
|
11552
|
+
* ```typescript
|
|
11553
|
+
* import { padAddressToBytes32 } from '@core/utils'
|
|
11554
|
+
*
|
|
11555
|
+
* padAddressToBytes32('0x75275Aff2D01699D922f045b69ed291311209738')
|
|
11556
|
+
* // '0x00000000000000000000000075275aff2d01699d922f045b69ed291311209738'
|
|
11557
|
+
* ```
|
|
11558
|
+
*/ function padAddressToBytes32(address$1) {
|
|
11559
|
+
if (!address.isAddress(address$1)) {
|
|
11560
|
+
throw createValidationFailedError$1('address', address$1, 'Expected a valid 20-byte EVM address');
|
|
11561
|
+
}
|
|
11562
|
+
// bytes32 is raw bytes, not a checksummed address — emit lowercase so it
|
|
11563
|
+
// matches ABI-decoded output.
|
|
11564
|
+
return bytes.hexZeroPad(address.getAddress(address$1), 32).toLowerCase();
|
|
11565
|
+
}
|
|
11288
11566
|
|
|
11289
11567
|
/**
|
|
11290
11568
|
* Configuration for {@link retryAsync}.
|
|
@@ -11873,7 +12151,7 @@ function resolveOptions(options) {
|
|
|
11873
12151
|
}
|
|
11874
12152
|
|
|
11875
12153
|
var name$4 = "@circle-fin/bridge-kit";
|
|
11876
|
-
var version$5 = "1.
|
|
12154
|
+
var version$5 = "1.14.0";
|
|
11877
12155
|
var pkg$5 = {
|
|
11878
12156
|
name: name$4,
|
|
11879
12157
|
version: version$5};
|
|
@@ -12323,7 +12601,7 @@ const READ_ACTION_KEY_SET = new Set(READ_ACTION_KEYS);
|
|
|
12323
12601
|
* const result = evmAddressSchema.safeParse(validAddress)
|
|
12324
12602
|
* console.log(result.success) // true
|
|
12325
12603
|
* ```
|
|
12326
|
-
*/ const evmAddressSchema = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
12604
|
+
*/ const evmAddressSchema$1 = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
12327
12605
|
/**
|
|
12328
12606
|
* Schema for validating transaction hashes.
|
|
12329
12607
|
*
|
|
@@ -13012,6 +13290,10 @@ exports.TransferSpeed = void 0;
|
|
|
13012
13290
|
token: zod.z.literal('USDC').optional(),
|
|
13013
13291
|
config: zod.z.object({
|
|
13014
13292
|
transferSpeed: zod.z.nativeEnum(exports.TransferSpeed).optional(),
|
|
13293
|
+
feePayment: zod.z.enum([
|
|
13294
|
+
'source',
|
|
13295
|
+
'destination'
|
|
13296
|
+
]).optional(),
|
|
13015
13297
|
maxFee: zod.z.string().min(1, 'Required').pipe(createDecimalStringValidator({
|
|
13016
13298
|
allowZero: true,
|
|
13017
13299
|
regexMessage: MAX_FEE_FORMAT_ERROR_MESSAGE,
|
|
@@ -13019,7 +13301,8 @@ exports.TransferSpeed = void 0;
|
|
|
13019
13301
|
maxDecimals: 6
|
|
13020
13302
|
})(zod.z.string())).optional(),
|
|
13021
13303
|
customFee: customFeeSchema.optional()
|
|
13022
|
-
}).optional()
|
|
13304
|
+
}).optional(),
|
|
13305
|
+
quote: zod.z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex').optional()
|
|
13023
13306
|
});
|
|
13024
13307
|
|
|
13025
13308
|
/**
|
|
@@ -14233,16 +14516,73 @@ exports.TransferSpeed = void 0;
|
|
|
14233
14516
|
};
|
|
14234
14517
|
}
|
|
14235
14518
|
|
|
14519
|
+
/**
|
|
14520
|
+
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
14521
|
+
*
|
|
14522
|
+
* Constructs the appropriate action payload and dispatches it to any registered
|
|
14523
|
+
* event listeners. Handles type-safe dispatching for different step types.
|
|
14524
|
+
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
14525
|
+
*
|
|
14526
|
+
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
14527
|
+
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
14528
|
+
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
14529
|
+
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
14530
|
+
*
|
|
14531
|
+
* @example
|
|
14532
|
+
* ```typescript
|
|
14533
|
+
* const step: BridgeStep = {
|
|
14534
|
+
* name: 'burn',
|
|
14535
|
+
* state: 'success',
|
|
14536
|
+
* txHash: '0xabc...',
|
|
14537
|
+
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
14538
|
+
* data: { ... }
|
|
14539
|
+
* }
|
|
14540
|
+
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
14541
|
+
* ```
|
|
14542
|
+
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
14543
|
+
if (!provider.actionDispatcher) {
|
|
14544
|
+
return;
|
|
14545
|
+
}
|
|
14546
|
+
// Extract traceId from invocation context if provided
|
|
14547
|
+
const traceId = invocation?.traceId;
|
|
14548
|
+
const actionValues = {
|
|
14549
|
+
protocol: 'cctp',
|
|
14550
|
+
version: 'v2',
|
|
14551
|
+
...traceId !== undefined && {
|
|
14552
|
+
traceId
|
|
14553
|
+
},
|
|
14554
|
+
values: step
|
|
14555
|
+
};
|
|
14556
|
+
switch(name){
|
|
14557
|
+
case 'approve':
|
|
14558
|
+
case 'burn':
|
|
14559
|
+
case 'mint':
|
|
14560
|
+
provider.actionDispatcher.dispatch(name, {
|
|
14561
|
+
...actionValues,
|
|
14562
|
+
method: name
|
|
14563
|
+
});
|
|
14564
|
+
break;
|
|
14565
|
+
case 'fetchAttestation':
|
|
14566
|
+
case 'reAttest':
|
|
14567
|
+
provider.actionDispatcher.dispatch(name, {
|
|
14568
|
+
...actionValues,
|
|
14569
|
+
method: name,
|
|
14570
|
+
values: step
|
|
14571
|
+
});
|
|
14572
|
+
break;
|
|
14573
|
+
}
|
|
14574
|
+
}
|
|
14575
|
+
|
|
14236
14576
|
/**
|
|
14237
14577
|
* Base URL for Circle's IRIS API (mainnet/production).
|
|
14238
14578
|
*
|
|
14239
14579
|
* The IRIS API provides attestation services for CCTP cross-chain transfers.
|
|
14240
|
-
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
14580
|
+
*/ const IRIS_API_BASE_URL$1 = 'https://iris-api.circle.com';
|
|
14241
14581
|
/**
|
|
14242
14582
|
* Base URL for Circle's IRIS API (testnet/sandbox).
|
|
14243
14583
|
*
|
|
14244
14584
|
* Used for development and testing on testnet chains.
|
|
14245
|
-
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
14585
|
+
*/ const IRIS_API_SANDBOX_BASE_URL$1 = 'https://iris-api-sandbox.circle.com';
|
|
14246
14586
|
|
|
14247
14587
|
/**
|
|
14248
14588
|
* Type guard to validate the API response structure.
|
|
@@ -14285,7 +14625,7 @@ const isFastBurnFeeResponse = (data)=>{
|
|
|
14285
14625
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
14286
14626
|
* @returns The complete API URL
|
|
14287
14627
|
*/ function buildFastBurnFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
14288
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
14628
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
14289
14629
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}`;
|
|
14290
14630
|
}
|
|
14291
14631
|
const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
@@ -14496,6 +14836,77 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14496
14836
|
}
|
|
14497
14837
|
};
|
|
14498
14838
|
|
|
14839
|
+
/**
|
|
14840
|
+
* Build the forwarding `hookData` for a forwarded (Orbit-relayed) CCTP v2 burn,
|
|
14841
|
+
* tailored to the destination chain.
|
|
14842
|
+
*
|
|
14843
|
+
* For EVM destinations the recipient already holds ERC-20 USDC directly, so the
|
|
14844
|
+
* empty version-0 `cctp-forward` frame is sufficient. For Solana destinations
|
|
14845
|
+
* USDC is held in an Associated Token Account (ATA) that may not exist for a
|
|
14846
|
+
* fresh wallet, so this emits a frame carrying `createAta` + `ataOwner` that
|
|
14847
|
+
* instructs the relayer to create the recipient ATA (idempotently, at the
|
|
14848
|
+
* relayer's expense) before minting.
|
|
14849
|
+
*
|
|
14850
|
+
* `@solana/web3.js` is imported lazily so EVM-only consumers never load Solana
|
|
14851
|
+
* code, mirroring {@link getMintRecipientAccount}.
|
|
14852
|
+
*
|
|
14853
|
+
* @param chainType - The destination blockchain type ('evm' or 'solana').
|
|
14854
|
+
* @param ownerAddress - The recipient's wallet address on the destination chain
|
|
14855
|
+
* (base58 for Solana). Must be the same owner used to derive `mintRecipient`.
|
|
14856
|
+
* @returns A 0x-prefixed hookData hex string for the forwarded burn.
|
|
14857
|
+
* @throws {KitError} If `chainType` is neither 'evm' nor 'solana', if
|
|
14858
|
+
* `@solana/web3.js` cannot be loaded, or if `ownerAddress` is not a valid
|
|
14859
|
+
* Solana public key (all FATAL).
|
|
14860
|
+
*
|
|
14861
|
+
* @example
|
|
14862
|
+
* ```typescript
|
|
14863
|
+
* import { getForwarderHookData } from './getForwarderHookData'
|
|
14864
|
+
*
|
|
14865
|
+
* // EVM: empty forwarding frame
|
|
14866
|
+
* const evmHook = await getForwarderHookData('evm', '0x742d35Cc...')
|
|
14867
|
+
*
|
|
14868
|
+
* // Solana: frame instructing the relayer to create the recipient ATA
|
|
14869
|
+
* const solanaHook = await getForwarderHookData(
|
|
14870
|
+
* 'solana',
|
|
14871
|
+
* '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
14872
|
+
* )
|
|
14873
|
+
* ```
|
|
14874
|
+
*/ const getForwarderHookData = async (/** The destination blockchain type - determines the hookData shape */ chainType, /** The recipient's wallet address (hex for EVM, base58 for Solana) */ ownerAddress)=>{
|
|
14875
|
+
if (chainType === 'evm') {
|
|
14876
|
+
// EVM: the recipient holds USDC directly; no ATA setup is needed.
|
|
14877
|
+
return buildForwardingHookData();
|
|
14878
|
+
}
|
|
14879
|
+
// Fail closed: only EVM and Solana forwarding destinations are supported.
|
|
14880
|
+
// Without this guard any future non-EVM chain type would silently fall
|
|
14881
|
+
// through to the Solana path and mis-encode hookData on a money-movement path.
|
|
14882
|
+
if (chainType !== 'solana') {
|
|
14883
|
+
throw new KitError({
|
|
14884
|
+
...InputError.VALIDATION_FAILED,
|
|
14885
|
+
recoverability: 'FATAL',
|
|
14886
|
+
message: `Forwarded burns are not supported for destination chain type "${chainType}"`
|
|
14887
|
+
});
|
|
14888
|
+
}
|
|
14889
|
+
// Solana: encode the owner so the relayer creates the recipient ATA.
|
|
14890
|
+
// Resolve @solana/web3.js lazily so EVM-only consumers never load Solana code.
|
|
14891
|
+
const { PublicKey } = await import('@solana/web3.js').catch(()=>{
|
|
14892
|
+
throw new KitError({
|
|
14893
|
+
...InputError.VALIDATION_FAILED,
|
|
14894
|
+
recoverability: 'FATAL',
|
|
14895
|
+
message: 'Failed to load @solana/web3.js. Please ensure it is installed: npm install @solana/web3.js'
|
|
14896
|
+
});
|
|
14897
|
+
});
|
|
14898
|
+
try {
|
|
14899
|
+
const owner = new PublicKey(ownerAddress);
|
|
14900
|
+
return buildSolanaAtaForwardingHookData(owner.toBytes());
|
|
14901
|
+
} catch (error) {
|
|
14902
|
+
throw new KitError({
|
|
14903
|
+
...InputError.INVALID_ADDRESS,
|
|
14904
|
+
recoverability: 'FATAL',
|
|
14905
|
+
message: `Failed to build Solana forwarder hookData for recipient "${ownerAddress}": ${error instanceof Error ? error.message : String(error)}`
|
|
14906
|
+
});
|
|
14907
|
+
}
|
|
14908
|
+
};
|
|
14909
|
+
|
|
14499
14910
|
/**
|
|
14500
14911
|
* Validates and converts a fee value to bigint.
|
|
14501
14912
|
*
|
|
@@ -14598,7 +15009,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14598
15009
|
|
|
14599
15010
|
/**
|
|
14600
15011
|
* The zero address, denoting a native-currency fee in a signed quote.
|
|
14601
|
-
*/ const ZERO_ADDRESS$
|
|
15012
|
+
*/ const ZERO_ADDRESS$2 = '0x0000000000000000000000000000000000000000';
|
|
14602
15013
|
/**
|
|
14603
15014
|
* Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
|
|
14604
15015
|
*
|
|
@@ -14639,7 +15050,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14639
15050
|
if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
|
|
14640
15051
|
throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
|
|
14641
15052
|
}
|
|
14642
|
-
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS$
|
|
15053
|
+
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS$2;
|
|
14643
15054
|
const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
|
|
14644
15055
|
if (isNativeFee) {
|
|
14645
15056
|
return {
|
|
@@ -14777,7 +15188,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14777
15188
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
14778
15189
|
* @returns The complete API URL with forward=true query parameter
|
|
14779
15190
|
*/ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
14780
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
15191
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
14781
15192
|
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true`;
|
|
14782
15193
|
}
|
|
14783
15194
|
/**
|
|
@@ -15838,7 +16249,7 @@ function hasPendingState(analysis, result) {
|
|
|
15838
16249
|
* // => 'https://iris-api-sandbox.circle.com/v2/messages/1?transactionHash=0xdef...'
|
|
15839
16250
|
* ```
|
|
15840
16251
|
*/ const buildIrisUrl = (sourceDomainId, transactionHash, isTestnet)=>{
|
|
15841
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
16252
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
15842
16253
|
const url = new URL(`${baseUrl}/v2/messages/${String(sourceDomainId)}`);
|
|
15843
16254
|
url.searchParams.set('transactionHash', transactionHash);
|
|
15844
16255
|
return url.toString();
|
|
@@ -16006,7 +16417,7 @@ function hasPendingState(analysis, result) {
|
|
|
16006
16417
|
* // => 'https://iris-api-sandbox.circle.com/v2/reattest/0xabc'
|
|
16007
16418
|
* ```
|
|
16008
16419
|
*/ const buildReAttestUrl = (nonce, isTestnet)=>{
|
|
16009
|
-
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL;
|
|
16420
|
+
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
16010
16421
|
const url = new URL(`${baseUrl}/v2/reattest/${nonce}`);
|
|
16011
16422
|
return url.toString();
|
|
16012
16423
|
};
|
|
@@ -16461,7 +16872,8 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16461
16872
|
* - `destinationChain` — present and supports CCTP v2
|
|
16462
16873
|
* - source and destination chains must both be testnet or both mainnet
|
|
16463
16874
|
* - source and destination chains must differ
|
|
16464
|
-
* - `executor
|
|
16875
|
+
* - destination — either `executor`, or both `mintRecipient` and
|
|
16876
|
+
* `destinationCaller`; not both
|
|
16465
16877
|
* - `amount` — bigint or non-empty string coercible to bigint
|
|
16466
16878
|
* - `feeTotalAmount` — bigint or non-empty string coercible to bigint
|
|
16467
16879
|
* - `feeToken` — valid EVM address (`0x` + 40 hex chars)
|
|
@@ -16503,10 +16915,17 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16503
16915
|
if (source.chain.name === dest.name) {
|
|
16504
16916
|
throw createUnsupportedRouteError(source.chain.name, dest.name);
|
|
16505
16917
|
}
|
|
16506
|
-
//
|
|
16918
|
+
// Destination: GenericExecutor shorthand or explicit recipient + caller.
|
|
16507
16919
|
const executor = p['executor'];
|
|
16508
|
-
|
|
16509
|
-
|
|
16920
|
+
const mintRecipient = p['mintRecipient'];
|
|
16921
|
+
const destinationCaller = p['destinationCaller'];
|
|
16922
|
+
const hasExecutor = typeof executor === 'string' && executor !== '';
|
|
16923
|
+
const hasDirectDestination = typeof mintRecipient === 'string' && mintRecipient !== '' && typeof destinationCaller === 'string' && destinationCaller !== '';
|
|
16924
|
+
if (!hasExecutor && !hasDirectDestination) {
|
|
16925
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor, or both mintRecipient and destinationCaller');
|
|
16926
|
+
}
|
|
16927
|
+
if (hasExecutor && hasDirectDestination) {
|
|
16928
|
+
throw createValidationFailedError$1('destination', undefined, 'Provide executor or direct destination fields, not both');
|
|
16510
16929
|
}
|
|
16511
16930
|
// amount
|
|
16512
16931
|
const rawAmount = p['amount'];
|
|
@@ -16529,7 +16948,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16529
16948
|
throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
|
|
16530
16949
|
}
|
|
16531
16950
|
// feeToken
|
|
16532
|
-
if (!evmAddressSchema.safeParse(p['feeToken']).success) {
|
|
16951
|
+
if (!evmAddressSchema$1.safeParse(p['feeToken']).success) {
|
|
16533
16952
|
throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
|
|
16534
16953
|
}
|
|
16535
16954
|
// claim
|
|
@@ -16541,7 +16960,7 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
16541
16960
|
if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
|
|
16542
16961
|
throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
|
|
16543
16962
|
}
|
|
16544
|
-
if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
|
|
16963
|
+
if (!evmAddressSchema$1.safeParse(claim['refundAddress']).success) {
|
|
16545
16964
|
throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
|
|
16546
16965
|
}
|
|
16547
16966
|
// hookData (optional)
|
|
@@ -16923,63 +17342,6 @@ const mockAttestationMessage = {
|
|
|
16923
17342
|
});
|
|
16924
17343
|
}
|
|
16925
17344
|
|
|
16926
|
-
/**
|
|
16927
|
-
* Dispatch a bridge step event through the provider's action dispatcher.
|
|
16928
|
-
*
|
|
16929
|
-
* Constructs the appropriate action payload and dispatches it to any registered
|
|
16930
|
-
* event listeners. Handles type-safe dispatching for different step types.
|
|
16931
|
-
* When provided, traceId from the invocation context is included for end-to-end correlation.
|
|
16932
|
-
*
|
|
16933
|
-
* @param name - The step name (approve, burn, fetchAttestation, or mint).
|
|
16934
|
-
* @param step - The completed bridge step containing transaction details and explorerUrl.
|
|
16935
|
-
* @param provider - The CCTP v2 provider with action dispatcher.
|
|
16936
|
-
* @param invocation - Optional invocation context containing traceId for correlation.
|
|
16937
|
-
*
|
|
16938
|
-
* @example
|
|
16939
|
-
* ```typescript
|
|
16940
|
-
* const step: BridgeStep = {
|
|
16941
|
-
* name: 'burn',
|
|
16942
|
-
* state: 'success',
|
|
16943
|
-
* txHash: '0xabc...',
|
|
16944
|
-
* explorerUrl: 'https://sepolia.etherscan.io/tx/0xabc...',
|
|
16945
|
-
* data: { ... }
|
|
16946
|
-
* }
|
|
16947
|
-
* dispatchStepEvent('burn', step, provider, invocationContext)
|
|
16948
|
-
* ```
|
|
16949
|
-
*/ function dispatchStepEvent(name, step, provider, invocation) {
|
|
16950
|
-
if (!provider.actionDispatcher) {
|
|
16951
|
-
return;
|
|
16952
|
-
}
|
|
16953
|
-
// Extract traceId from invocation context if provided
|
|
16954
|
-
const traceId = invocation?.traceId;
|
|
16955
|
-
const actionValues = {
|
|
16956
|
-
protocol: 'cctp',
|
|
16957
|
-
version: 'v2',
|
|
16958
|
-
...traceId !== undefined && {
|
|
16959
|
-
traceId
|
|
16960
|
-
},
|
|
16961
|
-
values: step
|
|
16962
|
-
};
|
|
16963
|
-
switch(name){
|
|
16964
|
-
case 'approve':
|
|
16965
|
-
case 'burn':
|
|
16966
|
-
case 'mint':
|
|
16967
|
-
provider.actionDispatcher.dispatch(name, {
|
|
16968
|
-
...actionValues,
|
|
16969
|
-
method: name
|
|
16970
|
-
});
|
|
16971
|
-
break;
|
|
16972
|
-
case 'fetchAttestation':
|
|
16973
|
-
case 'reAttest':
|
|
16974
|
-
provider.actionDispatcher.dispatch(name, {
|
|
16975
|
-
...actionValues,
|
|
16976
|
-
method: name,
|
|
16977
|
-
values: step
|
|
16978
|
-
});
|
|
16979
|
-
break;
|
|
16980
|
-
}
|
|
16981
|
-
}
|
|
16982
|
-
|
|
16983
17345
|
/**
|
|
16984
17346
|
* Check whether the source adapter supports EIP-5792 atomic batching and
|
|
16985
17347
|
* the consumer has not explicitly opted out via `config.batchTransactions`.
|
|
@@ -17226,7 +17588,7 @@ const mockAttestationMessage = {
|
|
|
17226
17588
|
return step;
|
|
17227
17589
|
}
|
|
17228
17590
|
|
|
17229
|
-
var version$4 = "1.
|
|
17591
|
+
var version$4 = "1.12.0";
|
|
17230
17592
|
var pkg$4 = {
|
|
17231
17593
|
version: version$4};
|
|
17232
17594
|
|
|
@@ -17932,18 +18294,18 @@ var pkg$4 = {
|
|
|
17932
18294
|
}
|
|
17933
18295
|
}
|
|
17934
18296
|
|
|
17935
|
-
function isPlainObject(value) {
|
|
18297
|
+
function isPlainObject$1(value) {
|
|
17936
18298
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
17937
18299
|
return false;
|
|
17938
18300
|
}
|
|
17939
18301
|
const prototype = Object.getPrototypeOf(value);
|
|
17940
18302
|
return prototype === Object.prototype || prototype === null;
|
|
17941
18303
|
}
|
|
17942
|
-
function assertHeadersConfig(headers, field) {
|
|
18304
|
+
function assertHeadersConfig$1(headers, field) {
|
|
17943
18305
|
if (headers === undefined) {
|
|
17944
18306
|
return;
|
|
17945
18307
|
}
|
|
17946
|
-
if (!isPlainObject(headers)) {
|
|
18308
|
+
if (!isPlainObject$1(headers)) {
|
|
17947
18309
|
throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
|
|
17948
18310
|
}
|
|
17949
18311
|
for (const [name, value] of Object.entries(headers)){
|
|
@@ -17953,16 +18315,16 @@ function assertHeadersConfig(headers, field) {
|
|
|
17953
18315
|
}
|
|
17954
18316
|
}
|
|
17955
18317
|
function assertCCTPV2Config(config) {
|
|
17956
|
-
if (!isPlainObject(config)) {
|
|
18318
|
+
if (!isPlainObject$1(config)) {
|
|
17957
18319
|
throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
|
|
17958
18320
|
}
|
|
17959
|
-
assertHeadersConfig(config['headers'], 'config.headers');
|
|
18321
|
+
assertHeadersConfig$1(config['headers'], 'config.headers');
|
|
17960
18322
|
const attestation = config['attestation'];
|
|
17961
18323
|
if (attestation !== undefined) {
|
|
17962
|
-
if (!isPlainObject(attestation)) {
|
|
18324
|
+
if (!isPlainObject$1(attestation)) {
|
|
17963
18325
|
throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
|
|
17964
18326
|
}
|
|
17965
|
-
assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
|
|
18327
|
+
assertHeadersConfig$1(attestation['headers'], 'config.attestation.headers');
|
|
17966
18328
|
}
|
|
17967
18329
|
}
|
|
17968
18330
|
/**
|
|
@@ -18037,6 +18399,34 @@ function assertCCTPV2Config(config) {
|
|
|
18037
18399
|
this.config = config;
|
|
18038
18400
|
}
|
|
18039
18401
|
/**
|
|
18402
|
+
* Emit a bridge step event through the provider's registered action
|
|
18403
|
+
* dispatcher.
|
|
18404
|
+
*
|
|
18405
|
+
* Kit-level orchestration that drives the burn primitives directly instead
|
|
18406
|
+
* of {@link CCTPV2BridgingProvider.bridge} (for example the receive-exact
|
|
18407
|
+
* source-fee flow) uses this to surface the same `approve`/`burn`/`mint`
|
|
18408
|
+
* events as the standard bridge path. It is a no-op when no dispatcher is
|
|
18409
|
+
* registered.
|
|
18410
|
+
*
|
|
18411
|
+
* @param name - The step name (`approve`, `burn`, `mint`, ...).
|
|
18412
|
+
* @param step - The completed bridge step to broadcast.
|
|
18413
|
+
* @param invocation - Optional invocation context carrying a `traceId` for
|
|
18414
|
+
* end-to-end correlation.
|
|
18415
|
+
* @returns Nothing.
|
|
18416
|
+
*
|
|
18417
|
+
* @example
|
|
18418
|
+
* ```typescript
|
|
18419
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
18420
|
+
* provider.emitBridgeStep('burn', {
|
|
18421
|
+
* name: 'burn',
|
|
18422
|
+
* state: 'success',
|
|
18423
|
+
* txHash: '0xabc...',
|
|
18424
|
+
* })
|
|
18425
|
+
* ```
|
|
18426
|
+
*/ emitBridgeStep(name, step, invocation) {
|
|
18427
|
+
dispatchStepEvent(name, step, this, invocation);
|
|
18428
|
+
}
|
|
18429
|
+
/**
|
|
18040
18430
|
* Resolves the effective polling configuration for an attestation request.
|
|
18041
18431
|
*
|
|
18042
18432
|
* Precedence (lowest to highest): provider `config.attestation`, then the
|
|
@@ -18862,8 +19252,11 @@ function assertCCTPV2Config(config) {
|
|
|
18862
19252
|
// 2. Forwarder: Does the user want Circle's relayer to handle attestation/mint?
|
|
18863
19253
|
const useCustomBurn = hasCustomContractSupport(source.chain, 'bridge');
|
|
18864
19254
|
const useForwarder = destination.useForwarder === true;
|
|
18865
|
-
// Build hookData once if forwarder is enabled
|
|
18866
|
-
|
|
19255
|
+
// Build hookData once if forwarder is enabled. EVM destinations get the
|
|
19256
|
+
// empty forwarding frame; Solana destinations get a frame instructing the
|
|
19257
|
+
// relayer to create the recipient's ATA (using the same owner that derived
|
|
19258
|
+
// `mintRecipient`) so the mint succeeds even for a fresh wallet.
|
|
19259
|
+
const hookData = useForwarder ? await getForwarderHookData(destination.chain.type, destinationAddressForMint) : undefined;
|
|
18867
19260
|
if (useCustomBurn) {
|
|
18868
19261
|
// Custom burn path: use bridge contract (with or without hook)
|
|
18869
19262
|
const customBurnParams = {
|
|
@@ -18891,13 +19284,14 @@ function assertCCTPV2Config(config) {
|
|
|
18891
19284
|
/**
|
|
18892
19285
|
* Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
|
|
18893
19286
|
*
|
|
18894
|
-
*
|
|
18895
|
-
*
|
|
18896
|
-
*
|
|
18897
|
-
*
|
|
19287
|
+
* Build the source-chain `depositForBurnWithHookAndFees` call. Fees are
|
|
19288
|
+
* collected up front on the source chain against a signed quote. The
|
|
19289
|
+
* destination may use the GenericExecutor shorthand, or an explicit mint
|
|
19290
|
+
* recipient and destination caller for direct forwarding.
|
|
18898
19291
|
*
|
|
18899
|
-
* This is the low-level on-chain primitive behind the
|
|
18900
|
-
*
|
|
19292
|
+
* This is the low-level on-chain primitive behind the Unified Balance Kit
|
|
19293
|
+
* `fastCrossChainDeposit` and the Bridge Kit source-fee
|
|
19294
|
+
* (`feePayment: 'source'`) flow. The `hookData` and signed-quote
|
|
18901
19295
|
* `claim` are produced elsewhere and passed in here:
|
|
18902
19296
|
* - `hookData`: `buildForwardingHookDataWithPayload(version,
|
|
18903
19297
|
* buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
|
|
@@ -18914,32 +19308,48 @@ function assertCCTPV2Config(config) {
|
|
|
18914
19308
|
* approval covers both; the redundant second approval is skipped.
|
|
18915
19309
|
*
|
|
18916
19310
|
* @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
|
|
18917
|
-
* @param params - The burn amount,
|
|
19311
|
+
* @param params - The burn amount, destination, hook data, signed quote, and fee.
|
|
18918
19312
|
* @returns The prepared approvals, the prepared burn, and the resolved fee plan.
|
|
18919
19313
|
* @throws {KitError} If the wallet context is invalid, `destinationChain` does not
|
|
18920
|
-
* support CCTP v2, the
|
|
18921
|
-
* a bigint or a numeric string coercible to bigint,
|
|
18922
|
-
* `cctp-forward` frame (guaranteed
|
|
18923
|
-
* context cannot be resolved.
|
|
19314
|
+
* support CCTP v2, the destination fields are missing, `amount` or
|
|
19315
|
+
* `feeTotalAmount` is not a bigint or a numeric string coercible to bigint,
|
|
19316
|
+
* the hook data lacks a `cctp-forward` frame (guaranteed
|
|
19317
|
+
* `ForwardFeeWithoutHook`), or the operation context cannot be resolved.
|
|
18924
19318
|
*
|
|
18925
19319
|
* @example
|
|
18926
19320
|
* ```typescript
|
|
19321
|
+
* import {
|
|
19322
|
+
* CCTPV2BridgingProvider,
|
|
19323
|
+
* type BurnWithFeesParams,
|
|
19324
|
+
* } from '@circle-fin/provider-cctp-v2'
|
|
19325
|
+
*
|
|
19326
|
+
* declare const source: BurnWithFeesParams['source']
|
|
19327
|
+
* declare const destinationChain: BurnWithFeesParams['destinationChain']
|
|
19328
|
+
* declare const recipient: string
|
|
19329
|
+
* declare const hookData: string
|
|
19330
|
+
* declare const claim: BurnWithFeesParams['claim']
|
|
19331
|
+
*
|
|
19332
|
+
* const provider = new CCTPV2BridgingProvider()
|
|
18927
19333
|
* const { approvals, burn } = await provider.burnWithFees({
|
|
18928
19334
|
* source,
|
|
18929
|
-
* destinationChain
|
|
19335
|
+
* destinationChain,
|
|
18930
19336
|
* amount: 1_000_000n,
|
|
18931
|
-
*
|
|
18932
|
-
*
|
|
18933
|
-
*
|
|
18934
|
-
*
|
|
18935
|
-
*
|
|
19337
|
+
* mintRecipient: recipient,
|
|
19338
|
+
* destinationCaller: '0x0000000000000000000000000000000000000000',
|
|
19339
|
+
* hookData,
|
|
19340
|
+
* claim,
|
|
19341
|
+
* feeToken: source.chain.usdcAddress,
|
|
19342
|
+
* feeTotalAmount: 10_000n,
|
|
18936
19343
|
* })
|
|
18937
19344
|
* for (const approval of approvals) await approval.execute()
|
|
18938
19345
|
* const txHash = await burn.execute()
|
|
18939
19346
|
* ```
|
|
18940
19347
|
*/ async burnWithFees(params) {
|
|
18941
19348
|
assertBurnWithFeesParams(params);
|
|
18942
|
-
const { source, destinationChain,
|
|
19349
|
+
const { source, destinationChain, hookData, claim, feeToken } = params;
|
|
19350
|
+
const hasExecutor = 'executor' in params && params.executor !== undefined;
|
|
19351
|
+
const mintRecipient = hasExecutor ? params.executor : params.mintRecipient;
|
|
19352
|
+
const destinationCaller = hasExecutor ? params.executor : params.destinationCaller;
|
|
18943
19353
|
const amount = BigInt(params.amount);
|
|
18944
19354
|
const feeTotalAmount = BigInt(params.feeTotalAmount);
|
|
18945
19355
|
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
|
|
@@ -18970,13 +19380,14 @@ function assertCCTPV2Config(config) {
|
|
|
18970
19380
|
delegate: wrapperAddress,
|
|
18971
19381
|
amount: approval.amount
|
|
18972
19382
|
}, context)));
|
|
18973
|
-
// Build the burn
|
|
19383
|
+
// Build the burn with either the GenericExecutor shorthand or the explicit
|
|
19384
|
+
// direct-forwarding recipient and caller.
|
|
18974
19385
|
const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
18975
19386
|
fromChain: source.chain,
|
|
18976
19387
|
toChain: destinationChain,
|
|
18977
19388
|
amount,
|
|
18978
|
-
mintRecipient
|
|
18979
|
-
destinationCaller
|
|
19389
|
+
mintRecipient,
|
|
19390
|
+
destinationCaller,
|
|
18980
19391
|
hookData,
|
|
18981
19392
|
claim,
|
|
18982
19393
|
feeToken,
|
|
@@ -19133,6 +19544,948 @@ function assertCCTPV2Config(config) {
|
|
|
19133
19544
|
]
|
|
19134
19545
|
];
|
|
19135
19546
|
|
|
19547
|
+
/**
|
|
19548
|
+
* Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
|
|
19549
|
+
*/ const IRIS_API_BASE_URL = 'https://iris-api.circle.com';
|
|
19550
|
+
/**
|
|
19551
|
+
* Base URL for Circle's Quote API (hosted in Iris) on testnet/sandbox.
|
|
19552
|
+
*/ const IRIS_API_SANDBOX_BASE_URL = 'https://iris-api-sandbox.circle.com';
|
|
19553
|
+
/**
|
|
19554
|
+
* Native fee-token sentinel (the zero address).
|
|
19555
|
+
*
|
|
19556
|
+
* When `feeToken` is the zero address the quote prices fees in the source
|
|
19557
|
+
* chain's native gas token (paid as `msg.value` on-chain). Pass a USDC token
|
|
19558
|
+
* address instead to denominate fees in USDC.
|
|
19559
|
+
*/ const NATIVE_FEE_TOKEN = '0x0000000000000000000000000000000000000000';
|
|
19560
|
+
/**
|
|
19561
|
+
* API path prefix for the CCTP v2 USDC burn quote endpoint.
|
|
19562
|
+
*
|
|
19563
|
+
* The full path is `${QUOTE_BURN_USDC_PATH}/{sourceDomain}/{destinationDomain}`;
|
|
19564
|
+
* `usdc` is a fixed literal, not a token parameter.
|
|
19565
|
+
*/ const QUOTE_BURN_USDC_PATH = '/v2/quote/burn/usdc';
|
|
19566
|
+
/**
|
|
19567
|
+
* API path prefix for the CCTP v2 USDC quote validate endpoint.
|
|
19568
|
+
*
|
|
19569
|
+
* The full path is `${QUOTE_VALIDATE_USDC_PATH}/{sourceDomain}`; accepts a
|
|
19570
|
+
* `POST { abiSignature, args }` body and returns whether the signed quote is
|
|
19571
|
+
* currently claimable together with its authoritative expiry status.
|
|
19572
|
+
*/ const QUOTE_VALIDATE_USDC_PATH = '/v2/quote/validate/usdc';
|
|
19573
|
+
/**
|
|
19574
|
+
* Default polling configuration for Quote API calls.
|
|
19575
|
+
*
|
|
19576
|
+
* A signed quote is short-lived (typically ~2 minutes, varying per chain) and
|
|
19577
|
+
* a feature-flag-disabled source chain returns a
|
|
19578
|
+
* permanent `503 SERVICE_NOT_ENABLED`, so retrying buys little and risks
|
|
19579
|
+
* outliving the quote. The client therefore makes a single attempt
|
|
19580
|
+
* (`maxRetries: 1`) with a 15s timeout, mirroring the reference
|
|
19581
|
+
* implementation; callers refresh by requesting a new quote rather than
|
|
19582
|
+
* relying on transport retries.
|
|
19583
|
+
*
|
|
19584
|
+
* No `headers` are set here: `pollApiWithValidation` always injects
|
|
19585
|
+
* `Content-Type: application/json` and adds `User-Agent` in Node. Browser
|
|
19586
|
+
* requests omit a user-agent header to avoid a CORS preflight, so duplicating
|
|
19587
|
+
* either header here would be dead configuration.
|
|
19588
|
+
*/ const FEE_QUOTE_DEFAULT_CONFIG = {
|
|
19589
|
+
timeout: 15_000,
|
|
19590
|
+
maxRetries: 1,
|
|
19591
|
+
retryDelay: 200
|
|
19592
|
+
};
|
|
19593
|
+
|
|
19594
|
+
/** Decimal string in token minor units, constrained to be strictly positive. */ const positiveAmountSchema = zod.z.string().regex(/^\d+$/, 'must be a non-negative integer string')// Re-check the digit shape here: zod still runs this refinement when the
|
|
19595
|
+
// regex check above fails ("dirty"), so guard BigInt() against throwing on a
|
|
19596
|
+
// non-numeric value before comparing.
|
|
19597
|
+
.refine((value)=>/^\d+$/.test(value) && BigInt(value) > 0n, 'must be greater than zero');
|
|
19598
|
+
/**
|
|
19599
|
+
* A 20-byte EVM address in `0x` hex.
|
|
19600
|
+
*
|
|
19601
|
+
* The MVP prepaid-`FORWARD` `burn/usdc` path targets EVM contracts
|
|
19602
|
+
* (`TokenMessengerWithFees` / `GenericExecutor`), so `feeToken` and
|
|
19603
|
+
* `destinationCaller` are constrained to EVM addresses by design. This is an
|
|
19604
|
+
* intentional scope limit, not a permanent one: it can be widened to other
|
|
19605
|
+
* address formats as the fee service expands to more chains.
|
|
19606
|
+
*/ const evmAddressSchema = zod.z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'must be a 20-byte 0x address');
|
|
19607
|
+
/** Even-length `0x` hex (the empty `0x` is allowed). */ const hexSchema = zod.z.string().regex(/^0x([a-fA-F0-9]{2})*$/, 'must be even-length 0x hex');
|
|
19608
|
+
/** Non-empty, even-length `0x` hex. */ const nonEmptyHexSchema = zod.z.string().regex(/^0x([a-fA-F0-9]{2})+$/, 'must be non-empty 0x hex');
|
|
19609
|
+
/** A 32-byte `0x` hex hash. */ const bytes32Schema = zod.z.string().regex(/^0x[a-fA-F0-9]{64}$/, 'must be a 32-byte 0x hash');
|
|
19610
|
+
/** Decimal string in minor units, allowing zero. */ const numericStringSchema$1 = zod.z.string().regex(/^\d+$/, 'must be a non-negative integer string');
|
|
19611
|
+
/** An `https:` URL, used for the optional base-URL override. */ const httpsUrlSchema = zod.z.string().refine((value)=>{
|
|
19612
|
+
try {
|
|
19613
|
+
return new URL(value).protocol === 'https:';
|
|
19614
|
+
} catch {
|
|
19615
|
+
return false;
|
|
19616
|
+
}
|
|
19617
|
+
}, 'must be an https URL');
|
|
19618
|
+
const forwardParamsSchema = zod.z.object({
|
|
19619
|
+
hookData: hexSchema.optional(),
|
|
19620
|
+
destinationCaller: evmAddressSchema.optional()
|
|
19621
|
+
}).strict();
|
|
19622
|
+
const forwardRequestSchema = zod.z.object({
|
|
19623
|
+
type: zod.z.literal('FORWARD'),
|
|
19624
|
+
params: forwardParamsSchema.optional()
|
|
19625
|
+
}).strict();
|
|
19626
|
+
const preFinalityRequestSchema = zod.z.object({
|
|
19627
|
+
type: zod.z.literal('PRE_FINALITY')
|
|
19628
|
+
}).strict();
|
|
19629
|
+
/** A single quote request item (`FORWARD` or `PRE_FINALITY`). */ const feeQuoteRequestSchema = zod.z.discriminatedUnion('type', [
|
|
19630
|
+
forwardRequestSchema,
|
|
19631
|
+
preFinalityRequestSchema
|
|
19632
|
+
]);
|
|
19633
|
+
/** A non-empty list of quote request items with unique types. */ const feeQuoteRequestsSchema = zod.z.array(feeQuoteRequestSchema).min(1, 'at least one request item is required').refine((items)=>new Set(items.map((item)=>item.type)).size === items.length, 'request item types must be unique');
|
|
19634
|
+
/**
|
|
19635
|
+
* A structured `Partial<ApiPollingConfig>` polling override.
|
|
19636
|
+
*
|
|
19637
|
+
* Validates the field types callers actually set, so a plain-JS caller passing
|
|
19638
|
+
* `{ timeout: 'soon' }` is rejected at the boundary rather than failing opaquely
|
|
19639
|
+
* inside the transport. Unknown keys pass through so a future `ApiPollingConfig`
|
|
19640
|
+
* field is forwarded rather than silently dropped.
|
|
19641
|
+
*/ const apiPollingConfigSchema = zod.z.object({
|
|
19642
|
+
timeout: zod.z.number().int().positive().optional(),
|
|
19643
|
+
maxRetries: zod.z.number().int().nonnegative().optional(),
|
|
19644
|
+
retryDelay: zod.z.number().int().nonnegative().optional(),
|
|
19645
|
+
backoff: zod.z.enum([
|
|
19646
|
+
'fixed',
|
|
19647
|
+
'exponential'
|
|
19648
|
+
]).optional(),
|
|
19649
|
+
maxRetryDelayMs: zod.z.number().int().positive().optional(),
|
|
19650
|
+
headers: zod.z.record(zod.z.string()).optional()
|
|
19651
|
+
}).passthrough();
|
|
19652
|
+
/**
|
|
19653
|
+
* The validatable input for {@link fetchFeeQuote}.
|
|
19654
|
+
*
|
|
19655
|
+
* This is the single source of truth for input validation, including the CCTP
|
|
19656
|
+
* domains and the `isTestnet` environment flag. Validating `isTestnet` at
|
|
19657
|
+
* runtime matters because a plain-JS caller who omits it would otherwise leave
|
|
19658
|
+
* it `undefined`, which is falsy and silently selects the production base URL.
|
|
19659
|
+
* (`buildFeeQuoteUrl` independently re-validates the domains for standalone
|
|
19660
|
+
* callers.)
|
|
19661
|
+
*/ const fetchFeeQuoteInputSchema = zod.z.object({
|
|
19662
|
+
sourceDomain: zod.z.number().int().nonnegative(),
|
|
19663
|
+
destinationDomain: zod.z.number().int().nonnegative(),
|
|
19664
|
+
amount: positiveAmountSchema,
|
|
19665
|
+
feeToken: evmAddressSchema.optional(),
|
|
19666
|
+
requests: feeQuoteRequestsSchema,
|
|
19667
|
+
isTestnet: zod.z.boolean(),
|
|
19668
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
19669
|
+
config: apiPollingConfigSchema.optional()
|
|
19670
|
+
}).strict();
|
|
19671
|
+
const feeQuoteItemSchema = zod.z.object({
|
|
19672
|
+
type: zod.z.string().min(1),
|
|
19673
|
+
amount: numericStringSchema$1,
|
|
19674
|
+
args: zod.z.array(zod.z.string()),
|
|
19675
|
+
argsHash: bytes32Schema
|
|
19676
|
+
}).passthrough();
|
|
19677
|
+
const exchangeRatesSchema = zod.z.object({
|
|
19678
|
+
feeTokenUsd: zod.z.string(),
|
|
19679
|
+
destinationTokenUsd: zod.z.string()
|
|
19680
|
+
}).passthrough();
|
|
19681
|
+
const metadataSchema = zod.z.object({
|
|
19682
|
+
destinationGasPrice: zod.z.string().optional(),
|
|
19683
|
+
exchangeRates: exchangeRatesSchema.optional()
|
|
19684
|
+
}).passthrough();
|
|
19685
|
+
/** The `expiry` object the Quote API nests the quote TTL under. */ const expirySchema = zod.z.discriminatedUnion('mode', [
|
|
19686
|
+
zod.z.object({
|
|
19687
|
+
mode: zod.z.literal('TIMESTAMP'),
|
|
19688
|
+
expiresAt: zod.z.number().int().nonnegative()
|
|
19689
|
+
}).passthrough(),
|
|
19690
|
+
zod.z.object({
|
|
19691
|
+
mode: zod.z.literal('BLOCK_NUMBER'),
|
|
19692
|
+
expiresAtBlock: zod.z.number().int().nonnegative(),
|
|
19693
|
+
blockEstimatedAt: zod.z.number().int().nonnegative().optional()
|
|
19694
|
+
}).passthrough()
|
|
19695
|
+
]);
|
|
19696
|
+
/** Schema for a signed fee quote returned by the Quote API. */ const signedFeeQuoteSchema = zod.z.object({
|
|
19697
|
+
// The runtime YAML spec maps signedQuote to a looser `hex` (which allows
|
|
19698
|
+
// an empty `0x`); we keep the stricter non-empty form. Do not relax
|
|
19699
|
+
// without a reason.
|
|
19700
|
+
signedQuote: nonEmptyHexSchema,
|
|
19701
|
+
issuedAt: zod.z.number().int().nonnegative(),
|
|
19702
|
+
// The API returns a mode-specific timestamp or source-block deadline.
|
|
19703
|
+
expiry: expirySchema,
|
|
19704
|
+
feeTotalAmount: numericStringSchema$1,
|
|
19705
|
+
feeToken: evmAddressSchema,
|
|
19706
|
+
nonce: numericStringSchema$1,
|
|
19707
|
+
items: zod.z.array(feeQuoteItemSchema),
|
|
19708
|
+
metadata: metadataSchema.optional()
|
|
19709
|
+
}).passthrough();
|
|
19710
|
+
/**
|
|
19711
|
+
* Validate that an unknown value is a signed fee quote.
|
|
19712
|
+
*
|
|
19713
|
+
* @param value - The unknown value to validate.
|
|
19714
|
+
* @returns `true` when the value matches the signed-quote response shape.
|
|
19715
|
+
*
|
|
19716
|
+
* @example
|
|
19717
|
+
* ```typescript
|
|
19718
|
+
* import { isSignedFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
19719
|
+
*
|
|
19720
|
+
* declare const payload: unknown
|
|
19721
|
+
* if (isSignedFeeQuote(payload)) {
|
|
19722
|
+
* console.log(payload.feeTotalAmount)
|
|
19723
|
+
* }
|
|
19724
|
+
* ```
|
|
19725
|
+
*/ function isSignedFeeQuote(value) {
|
|
19726
|
+
return signedFeeQuoteSchema.safeParse(value).success;
|
|
19727
|
+
}
|
|
19728
|
+
/** Validate input to {@link validateQuote}. */ const validateQuoteInputSchema = zod.z.object({
|
|
19729
|
+
sourceDomain: zod.z.number().int().nonnegative(),
|
|
19730
|
+
abiSignature: zod.z.string().min(1),
|
|
19731
|
+
args: zod.z.array(zod.z.union([
|
|
19732
|
+
zod.z.string(),
|
|
19733
|
+
zod.z.array(zod.z.string())
|
|
19734
|
+
])),
|
|
19735
|
+
isTestnet: zod.z.boolean(),
|
|
19736
|
+
baseUrl: httpsUrlSchema.optional(),
|
|
19737
|
+
config: apiPollingConfigSchema.optional()
|
|
19738
|
+
}).strict();
|
|
19739
|
+
const quoteExpiryStatusSchema = zod.z.discriminatedUnion('mode', [
|
|
19740
|
+
zod.z.object({
|
|
19741
|
+
mode: zod.z.literal('TIMESTAMP'),
|
|
19742
|
+
expired: zod.z.boolean(),
|
|
19743
|
+
secondsRemaining: zod.z.number().int().nonnegative(),
|
|
19744
|
+
expiresAt: zod.z.number().int().nonnegative()
|
|
19745
|
+
}).passthrough(),
|
|
19746
|
+
zod.z.object({
|
|
19747
|
+
mode: zod.z.literal('BLOCK_NUMBER'),
|
|
19748
|
+
expired: zod.z.boolean(),
|
|
19749
|
+
secondsRemaining: zod.z.number().int().nonnegative(),
|
|
19750
|
+
expiresAtBlock: zod.z.number().int().nonnegative(),
|
|
19751
|
+
blockEstimatedAt: zod.z.number().int().nonnegative().optional()
|
|
19752
|
+
}).passthrough()
|
|
19753
|
+
]);
|
|
19754
|
+
const validateQuoteItemSchema = zod.z.object({
|
|
19755
|
+
type: zod.z.string().min(1),
|
|
19756
|
+
argsMatch: zod.z.boolean(),
|
|
19757
|
+
amount: numericStringSchema$1.optional(),
|
|
19758
|
+
args: zod.z.array(zod.z.string()).optional(),
|
|
19759
|
+
argsHash: bytes32Schema.optional(),
|
|
19760
|
+
computedArgsHash: bytes32Schema.optional()
|
|
19761
|
+
}).passthrough();
|
|
19762
|
+
/**
|
|
19763
|
+
* Schema for a response from the Quote API validation endpoint.
|
|
19764
|
+
*
|
|
19765
|
+
* The endpoint takes the source domain as a URL path parameter and does not
|
|
19766
|
+
* return it in the response body, so `sourceDomain` is intentionally not part
|
|
19767
|
+
* of this schema.
|
|
19768
|
+
*/ const validateQuoteResultSchema = zod.z.object({
|
|
19769
|
+
signedQuote: nonEmptyHexSchema,
|
|
19770
|
+
expiry: quoteExpiryStatusSchema,
|
|
19771
|
+
feeTotalAmount: numericStringSchema$1,
|
|
19772
|
+
feeToken: evmAddressSchema,
|
|
19773
|
+
nonce: numericStringSchema$1,
|
|
19774
|
+
claimable: zod.z.boolean(),
|
|
19775
|
+
// Preserve newly introduced server-side reasons as opaque strings rather
|
|
19776
|
+
// than rejecting the entire safety response before the SDK is updated.
|
|
19777
|
+
failedChecks: zod.z.array(zod.z.string().min(1)),
|
|
19778
|
+
items: zod.z.array(validateQuoteItemSchema)
|
|
19779
|
+
}).passthrough();
|
|
19780
|
+
/**
|
|
19781
|
+
* Validate a Quote API validation response.
|
|
19782
|
+
*
|
|
19783
|
+
* @param value - The unknown response value.
|
|
19784
|
+
* @returns `true` when the value has the expected validation response shape.
|
|
19785
|
+
*
|
|
19786
|
+
* @example
|
|
19787
|
+
* ```typescript
|
|
19788
|
+
* import { isValidateQuoteResult } from '@circle-fin/provider-fee-v1'
|
|
19789
|
+
*
|
|
19790
|
+
* declare const response: unknown
|
|
19791
|
+
* if (isValidateQuoteResult(response)) {
|
|
19792
|
+
* console.log(response.claimable)
|
|
19793
|
+
* }
|
|
19794
|
+
* ```
|
|
19795
|
+
*/ function isValidateQuoteResult(value) {
|
|
19796
|
+
return validateQuoteResultSchema.safeParse(value).success;
|
|
19797
|
+
}
|
|
19798
|
+
|
|
19799
|
+
/**
|
|
19800
|
+
* Validate that a CCTP domain id is a non-negative integer.
|
|
19801
|
+
*
|
|
19802
|
+
* @param value - The domain id to validate.
|
|
19803
|
+
* @param label - The parameter name, used in the error message.
|
|
19804
|
+
* @returns Nothing.
|
|
19805
|
+
* @throws {@link KitError} When the value is not a non-negative integer.
|
|
19806
|
+
* @internal
|
|
19807
|
+
*/ function assertDomain(value, label) {
|
|
19808
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
19809
|
+
throw new KitError({
|
|
19810
|
+
...InputError.VALIDATION_FAILED,
|
|
19811
|
+
recoverability: 'FATAL',
|
|
19812
|
+
message: `Quote API getFeeQuote failed: ${label} must be a ` + `non-negative integer, received ${String(value)}`,
|
|
19813
|
+
cause: {
|
|
19814
|
+
trace: {
|
|
19815
|
+
[label]: value
|
|
19816
|
+
}
|
|
19817
|
+
}
|
|
19818
|
+
});
|
|
19819
|
+
}
|
|
19820
|
+
}
|
|
19821
|
+
/**
|
|
19822
|
+
* Build the Quote API URL for a CCTP v2 USDC burn quote.
|
|
19823
|
+
*
|
|
19824
|
+
* Resolves the environment base URL (or an explicit `baseUrl` override) and
|
|
19825
|
+
* appends the burn/usdc path with the source and destination CCTP domains.
|
|
19826
|
+
* `usdc` is a fixed path literal, not a token parameter.
|
|
19827
|
+
*
|
|
19828
|
+
* @param params - The domains and environment selector.
|
|
19829
|
+
* @returns The fully-qualified Quote API URL.
|
|
19830
|
+
* @throws {@link KitError} When either domain is not a non-negative integer.
|
|
19831
|
+
*
|
|
19832
|
+
* @example
|
|
19833
|
+
* ```typescript
|
|
19834
|
+
* import { buildFeeQuoteUrl } from '@circle-fin/provider-fee-v1'
|
|
19835
|
+
*
|
|
19836
|
+
* const url = buildFeeQuoteUrl({
|
|
19837
|
+
* sourceDomain: 3,
|
|
19838
|
+
* destinationDomain: 26,
|
|
19839
|
+
* isTestnet: false,
|
|
19840
|
+
* })
|
|
19841
|
+
* // => 'https://iris-api.circle.com/v2/quote/burn/usdc/3/26'
|
|
19842
|
+
* ```
|
|
19843
|
+
*/ function buildFeeQuoteUrl(params) {
|
|
19844
|
+
const { sourceDomain, destinationDomain, isTestnet, baseUrl } = params;
|
|
19845
|
+
assertDomain(sourceDomain, 'sourceDomain');
|
|
19846
|
+
assertDomain(destinationDomain, 'destinationDomain');
|
|
19847
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
19848
|
+
return new URL(`${QUOTE_BURN_USDC_PATH}/${String(sourceDomain)}/${String(destinationDomain)}`, resolvedBaseUrl).toString();
|
|
19849
|
+
}
|
|
19850
|
+
|
|
19851
|
+
/**
|
|
19852
|
+
* Assert that a quote's per-item fee amounts sum to its `feeTotalAmount`.
|
|
19853
|
+
*
|
|
19854
|
+
* A defensive integrity check on the Quote API response, enforced internally
|
|
19855
|
+
* by `fetchFeeQuote`. It does not compare against the destination-side
|
|
19856
|
+
* `feeExecuted`, which is expected to be zero for prepaid forward-only burns.
|
|
19857
|
+
*
|
|
19858
|
+
* @param quote - The signed fee quote to check.
|
|
19859
|
+
* @returns Nothing.
|
|
19860
|
+
* @throws {@link KitError} When the item amounts do not sum to `feeTotalAmount`.
|
|
19861
|
+
* @internal
|
|
19862
|
+
*/ function assertFeeItemsSumToTotal(quote) {
|
|
19863
|
+
const itemsTotal = quote.items.reduce((sum, item)=>sum + BigInt(item.amount), 0n);
|
|
19864
|
+
const declaredTotal = BigInt(quote.feeTotalAmount);
|
|
19865
|
+
if (itemsTotal !== declaredTotal) {
|
|
19866
|
+
throw new KitError({
|
|
19867
|
+
...InputError.VALIDATION_FAILED,
|
|
19868
|
+
recoverability: 'FATAL',
|
|
19869
|
+
message: `Quote API getFeeQuote failed: fee items sum ` + `(${itemsTotal.toString()}) does not equal feeTotalAmount ` + `(${declaredTotal.toString()})`,
|
|
19870
|
+
cause: {
|
|
19871
|
+
trace: {
|
|
19872
|
+
itemsTotal: itemsTotal.toString(),
|
|
19873
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
19874
|
+
}
|
|
19875
|
+
}
|
|
19876
|
+
});
|
|
19877
|
+
}
|
|
19878
|
+
}
|
|
19879
|
+
|
|
19880
|
+
/**
|
|
19881
|
+
* Determine whether a Quote API error represents a disabled source chain.
|
|
19882
|
+
*
|
|
19883
|
+
* @param error - The error thrown by the HTTP layer.
|
|
19884
|
+
* @returns `true` when the error contains the `SERVICE_NOT_ENABLED` marker.
|
|
19885
|
+
* @internal
|
|
19886
|
+
*/ function isServiceNotEnabled(error) {
|
|
19887
|
+
const body = typeof error === 'object' && error !== null && 'responseBody' in error ? error.responseBody : undefined;
|
|
19888
|
+
if (typeof body === 'object' && body !== null) {
|
|
19889
|
+
const fields = body;
|
|
19890
|
+
const candidates = [
|
|
19891
|
+
fields['errorCode'],
|
|
19892
|
+
fields['code'],
|
|
19893
|
+
fields['message'],
|
|
19894
|
+
fields['externalMessage'],
|
|
19895
|
+
fields['error']
|
|
19896
|
+
];
|
|
19897
|
+
if (candidates.some((value)=>typeof value === 'string' && value.toUpperCase().includes('SERVICE_NOT_ENABLED'))) {
|
|
19898
|
+
return true;
|
|
19899
|
+
}
|
|
19900
|
+
}
|
|
19901
|
+
return getErrorMessage(error).toUpperCase().includes('SERVICE_NOT_ENABLED');
|
|
19902
|
+
}
|
|
19903
|
+
|
|
19904
|
+
const SERVICE$1 = 'Quote API';
|
|
19905
|
+
const OPERATION$1 = 'getFeeQuote';
|
|
19906
|
+
/**
|
|
19907
|
+
* Serialize request items for the wire body.
|
|
19908
|
+
*
|
|
19909
|
+
* `PRE_FINALITY` is emitted with no `params` key, and a `FORWARD` item only
|
|
19910
|
+
* carries the binding fields that are present.
|
|
19911
|
+
*
|
|
19912
|
+
* @param requests - The request items to serialize.
|
|
19913
|
+
* @returns The serialized request items.
|
|
19914
|
+
* @internal
|
|
19915
|
+
*/ function serializeRequests(requests) {
|
|
19916
|
+
return requests.map((request)=>{
|
|
19917
|
+
if (request.type === 'PRE_FINALITY') {
|
|
19918
|
+
return {
|
|
19919
|
+
type: 'PRE_FINALITY'
|
|
19920
|
+
};
|
|
19921
|
+
}
|
|
19922
|
+
const params = request.params;
|
|
19923
|
+
if (params === undefined) {
|
|
19924
|
+
return {
|
|
19925
|
+
type: 'FORWARD'
|
|
19926
|
+
};
|
|
19927
|
+
}
|
|
19928
|
+
const forwardParams = {};
|
|
19929
|
+
if (params.hookData !== undefined) {
|
|
19930
|
+
forwardParams.hookData = params.hookData;
|
|
19931
|
+
}
|
|
19932
|
+
if (params.destinationCaller !== undefined) {
|
|
19933
|
+
forwardParams.destinationCaller = params.destinationCaller;
|
|
19934
|
+
}
|
|
19935
|
+
return {
|
|
19936
|
+
type: 'FORWARD',
|
|
19937
|
+
params: forwardParams
|
|
19938
|
+
};
|
|
19939
|
+
});
|
|
19940
|
+
}
|
|
19941
|
+
/**
|
|
19942
|
+
* Fetch a signed fee quote from Circle's Quote API for a CCTP v2 USDC burn.
|
|
19943
|
+
*
|
|
19944
|
+
* Validates inputs, POSTs to
|
|
19945
|
+
* `/v2/quote/burn/usdc/{sourceDomain}/{destinationDomain}` with a single
|
|
19946
|
+
* attempt (the signed quote is short-lived), and returns the typed quote. A
|
|
19947
|
+
* disabled source chain (`503 SERVICE_NOT_ENABLED`) surfaces as a fatal,
|
|
19948
|
+
* non-retryable error; other failures are mapped to a {@link KitError} via the
|
|
19949
|
+
* shared API error parser.
|
|
19950
|
+
*
|
|
19951
|
+
* @param params - The domains, amount, request items, and environment.
|
|
19952
|
+
* @returns The signed fee quote.
|
|
19953
|
+
* @throws {@link KitError} On invalid input, a disabled source chain, an HTTP
|
|
19954
|
+
* error, or an invalid response shape.
|
|
19955
|
+
*
|
|
19956
|
+
* @example
|
|
19957
|
+
* ```typescript
|
|
19958
|
+
* import { fetchFeeQuote } from '@circle-fin/provider-fee-v1'
|
|
19959
|
+
*
|
|
19960
|
+
* const quote = await fetchFeeQuote({
|
|
19961
|
+
* sourceDomain: 3,
|
|
19962
|
+
* destinationDomain: 26,
|
|
19963
|
+
* amount: '1000000',
|
|
19964
|
+
* requests: [{ type: 'FORWARD' }, { type: 'PRE_FINALITY' }],
|
|
19965
|
+
* isTestnet: false,
|
|
19966
|
+
* })
|
|
19967
|
+
* console.log(quote.feeTotalAmount, quote.expiry)
|
|
19968
|
+
* ```
|
|
19969
|
+
*/ async function fetchFeeQuote(params) {
|
|
19970
|
+
const { sourceDomain, destinationDomain, amount, requests, feeToken, isTestnet, baseUrl, config } = params;
|
|
19971
|
+
const parsed = fetchFeeQuoteInputSchema.safeParse({
|
|
19972
|
+
sourceDomain,
|
|
19973
|
+
destinationDomain,
|
|
19974
|
+
amount,
|
|
19975
|
+
feeToken,
|
|
19976
|
+
requests,
|
|
19977
|
+
isTestnet,
|
|
19978
|
+
baseUrl,
|
|
19979
|
+
config
|
|
19980
|
+
});
|
|
19981
|
+
if (!parsed.success) {
|
|
19982
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
19983
|
+
throw new KitError({
|
|
19984
|
+
...InputError.VALIDATION_FAILED,
|
|
19985
|
+
recoverability: 'FATAL',
|
|
19986
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: ${detail}`,
|
|
19987
|
+
cause: {
|
|
19988
|
+
trace: parsed.error.issues
|
|
19989
|
+
}
|
|
19990
|
+
});
|
|
19991
|
+
}
|
|
19992
|
+
const url = baseUrl === undefined ? buildFeeQuoteUrl({
|
|
19993
|
+
sourceDomain,
|
|
19994
|
+
destinationDomain,
|
|
19995
|
+
isTestnet
|
|
19996
|
+
}) : buildFeeQuoteUrl({
|
|
19997
|
+
sourceDomain,
|
|
19998
|
+
destinationDomain,
|
|
19999
|
+
isTestnet,
|
|
20000
|
+
baseUrl
|
|
20001
|
+
});
|
|
20002
|
+
const body = {
|
|
20003
|
+
amount,
|
|
20004
|
+
feeToken: feeToken ?? NATIVE_FEE_TOKEN,
|
|
20005
|
+
requests: serializeRequests(requests)
|
|
20006
|
+
};
|
|
20007
|
+
const pollingConfig = {
|
|
20008
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
20009
|
+
...config
|
|
20010
|
+
};
|
|
20011
|
+
let quote;
|
|
20012
|
+
try {
|
|
20013
|
+
quote = await pollApiPost(url, body, isSignedFeeQuote, pollingConfig);
|
|
20014
|
+
} catch (error) {
|
|
20015
|
+
// Only one service-specific code (SERVICE_NOT_ENABLED) needs bespoke
|
|
20016
|
+
// mapping, so it is detected inline rather than via a dedicated
|
|
20017
|
+
// `parseFeeQuoteApiError` parser; everything else flows through the shared
|
|
20018
|
+
// `parseApiError`. Promote to a parser if more coded errors appear.
|
|
20019
|
+
if (isServiceNotEnabled(error)) {
|
|
20020
|
+
throw new KitError({
|
|
20021
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
20022
|
+
recoverability: 'FATAL',
|
|
20023
|
+
message: `${SERVICE$1} ${OPERATION$1} failed: source chain not enabled for fee ` + `quotes (SERVICE_NOT_ENABLED)`,
|
|
20024
|
+
cause: {
|
|
20025
|
+
trace: error
|
|
20026
|
+
}
|
|
20027
|
+
});
|
|
20028
|
+
}
|
|
20029
|
+
throw parseApiError(error, {
|
|
20030
|
+
service: SERVICE$1,
|
|
20031
|
+
operation: OPERATION$1
|
|
20032
|
+
});
|
|
20033
|
+
}
|
|
20034
|
+
// Defense-in-depth: a self-consistent quote's per-item fees sum to the
|
|
20035
|
+
// declared total. Enforced here so callers cannot forget the check.
|
|
20036
|
+
assertFeeItemsSumToTotal(quote);
|
|
20037
|
+
return quote;
|
|
20038
|
+
}
|
|
20039
|
+
|
|
20040
|
+
const SERVICE = 'Quote API';
|
|
20041
|
+
const OPERATION = 'validateQuote';
|
|
20042
|
+
/**
|
|
20043
|
+
* Validate a signed quote against a complete source-chain contract call.
|
|
20044
|
+
*
|
|
20045
|
+
* @param params - The source domain, ABI signature, call arguments, and environment.
|
|
20046
|
+
* @returns The claimability, binding checks, and authoritative expiry status.
|
|
20047
|
+
* @throws {@link KitError} When input, transport, or response validation fails.
|
|
20048
|
+
*
|
|
20049
|
+
* @example
|
|
20050
|
+
* ```typescript
|
|
20051
|
+
* import { validateQuote } from '@circle-fin/provider-fee-v1'
|
|
20052
|
+
*
|
|
20053
|
+
* const result = await validateQuote({
|
|
20054
|
+
* sourceDomain: 3,
|
|
20055
|
+
* // The exact function + arguments, in ABI order, that will be burned on-chain.
|
|
20056
|
+
* abiSignature:
|
|
20057
|
+
* 'depositForBurnWithHookAndFees(uint256,uint32,bytes32,address,bytes32,bytes,(bytes,address))',
|
|
20058
|
+
* args: [
|
|
20059
|
+
* '1000000',
|
|
20060
|
+
* '26',
|
|
20061
|
+
* '0x0000000000000000000000001111111111111111111111111111111111111111',
|
|
20062
|
+
* '0x2222222222222222222222222222222222222222',
|
|
20063
|
+
* '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
20064
|
+
* '0x636374702d666f72776172640000000000000000000000000000000000000000',
|
|
20065
|
+
* ['0x01abcd', '0x3333333333333333333333333333333333333333'],
|
|
20066
|
+
* ],
|
|
20067
|
+
* isTestnet: false,
|
|
20068
|
+
* })
|
|
20069
|
+
* console.log(result.claimable, result.expiry.secondsRemaining)
|
|
20070
|
+
* ```
|
|
20071
|
+
*/ async function validateQuote(params) {
|
|
20072
|
+
const parsed = validateQuoteInputSchema.safeParse(params);
|
|
20073
|
+
if (!parsed.success) {
|
|
20074
|
+
const detail = parsed.error.issues.map((issue)=>`${issue.path.join('.')}: ${issue.message}`).join('; ');
|
|
20075
|
+
throw new KitError({
|
|
20076
|
+
...InputError.VALIDATION_FAILED,
|
|
20077
|
+
recoverability: 'FATAL',
|
|
20078
|
+
message: `${SERVICE} ${OPERATION} failed: ${detail}`,
|
|
20079
|
+
cause: {
|
|
20080
|
+
trace: parsed.error.issues
|
|
20081
|
+
}
|
|
20082
|
+
});
|
|
20083
|
+
}
|
|
20084
|
+
const { sourceDomain, abiSignature, args, isTestnet, baseUrl } = parsed.data;
|
|
20085
|
+
// `config` was validated by the schema above; spread the caller's original,
|
|
20086
|
+
// precisely typed `Partial<ApiPollingConfig>` so the merged polling config
|
|
20087
|
+
// stays assignable under `exactOptionalPropertyTypes`.
|
|
20088
|
+
const pollingConfig = {
|
|
20089
|
+
...FEE_QUOTE_DEFAULT_CONFIG,
|
|
20090
|
+
...params.config
|
|
20091
|
+
};
|
|
20092
|
+
const resolvedBaseUrl = baseUrl ?? (isTestnet ? IRIS_API_SANDBOX_BASE_URL : IRIS_API_BASE_URL);
|
|
20093
|
+
const url = new URL(`${QUOTE_VALIDATE_USDC_PATH}/${String(sourceDomain)}`, resolvedBaseUrl).toString();
|
|
20094
|
+
try {
|
|
20095
|
+
return await pollApiPost(url, {
|
|
20096
|
+
abiSignature,
|
|
20097
|
+
args
|
|
20098
|
+
}, isValidateQuoteResult, pollingConfig);
|
|
20099
|
+
} catch (error) {
|
|
20100
|
+
if (isServiceNotEnabled(error)) {
|
|
20101
|
+
throw new KitError({
|
|
20102
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
20103
|
+
recoverability: 'FATAL',
|
|
20104
|
+
message: `${SERVICE} ${OPERATION} failed: source chain not enabled for ` + `quote validation (SERVICE_NOT_ENABLED)`,
|
|
20105
|
+
cause: {
|
|
20106
|
+
trace: error
|
|
20107
|
+
}
|
|
20108
|
+
});
|
|
20109
|
+
}
|
|
20110
|
+
throw parseApiError(error, {
|
|
20111
|
+
service: SERVICE,
|
|
20112
|
+
operation: OPERATION
|
|
20113
|
+
});
|
|
20114
|
+
}
|
|
20115
|
+
}
|
|
20116
|
+
|
|
20117
|
+
/** Refresh timestamp quotes this many seconds before submission. */ const QUOTE_EXPIRY_SAFETY_SECONDS = 30;
|
|
20118
|
+
/** 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))';
|
|
20119
|
+
/** Unrestricted CCTP destination caller used by the forwarding relayer. */ const ZERO_ADDRESS$1 = '0x0000000000000000000000000000000000000000';
|
|
20120
|
+
function isQuoteNearEstimatedExpiry(quote) {
|
|
20121
|
+
const currentSeconds = Math.floor(Date.now() / 1_000);
|
|
20122
|
+
const expiresAt = quote.expiry.mode === 'TIMESTAMP' ? quote.expiry.expiresAt : quote.expiry.blockEstimatedAt;
|
|
20123
|
+
// A BLOCK_NUMBER quote may omit the advisory `blockEstimatedAt` estimate; when
|
|
20124
|
+
// it is absent, skip this wall-clock pre-check and defer to the authoritative
|
|
20125
|
+
// source-chain-tip validation performed downstream.
|
|
20126
|
+
if (expiresAt === undefined) {
|
|
20127
|
+
return false;
|
|
20128
|
+
}
|
|
20129
|
+
return expiresAt <= currentSeconds + QUOTE_EXPIRY_SAFETY_SECONDS;
|
|
20130
|
+
}
|
|
20131
|
+
function assertSourceFeeRoute(params) {
|
|
20132
|
+
const { source, destination, config } = params;
|
|
20133
|
+
if (source.chain.type !== 'evm' || destination.chain.type !== 'evm' || !isCCTPV2Supported(source.chain) || !isCCTPV2Supported(destination.chain)) {
|
|
20134
|
+
throw createUnsupportedRouteError(source.chain.name, destination.chain.name);
|
|
20135
|
+
}
|
|
20136
|
+
const useForwarder = destination.useForwarder;
|
|
20137
|
+
if (useForwarder !== true) {
|
|
20138
|
+
throw createValidationFailedError$1('to.useForwarder', useForwarder, "feePayment: 'source' requires useForwarder: true");
|
|
20139
|
+
}
|
|
20140
|
+
if (config.customFee !== undefined) {
|
|
20141
|
+
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.');
|
|
20142
|
+
}
|
|
20143
|
+
}
|
|
20144
|
+
function buildQuoteBinding(params) {
|
|
20145
|
+
assertSourceFeeRoute(params);
|
|
20146
|
+
const mintRecipient = params.destination.recipientAddress ?? params.destination.address;
|
|
20147
|
+
const hookData = buildForwardingHookData();
|
|
20148
|
+
const requests = [
|
|
20149
|
+
{
|
|
20150
|
+
type: 'FORWARD',
|
|
20151
|
+
params: {
|
|
20152
|
+
hookData,
|
|
20153
|
+
destinationCaller: ZERO_ADDRESS$1
|
|
20154
|
+
}
|
|
20155
|
+
}
|
|
20156
|
+
];
|
|
20157
|
+
if ((params.config.transferSpeed ?? exports.TransferSpeed.FAST) === exports.TransferSpeed.FAST) {
|
|
20158
|
+
requests.push({
|
|
20159
|
+
type: 'PRE_FINALITY'
|
|
20160
|
+
});
|
|
20161
|
+
}
|
|
20162
|
+
return {
|
|
20163
|
+
sourceDomain: params.source.chain.cctp.domain,
|
|
20164
|
+
destinationDomain: params.destination.chain.cctp.domain,
|
|
20165
|
+
isTestnet: params.source.chain.isTestnet,
|
|
20166
|
+
amount: params.amount,
|
|
20167
|
+
mintRecipient,
|
|
20168
|
+
hookData,
|
|
20169
|
+
destinationCaller: ZERO_ADDRESS$1,
|
|
20170
|
+
feeToken: params.source.chain.usdcAddress,
|
|
20171
|
+
requests
|
|
20172
|
+
};
|
|
20173
|
+
}
|
|
20174
|
+
async function fetchBoundQuote(binding) {
|
|
20175
|
+
try {
|
|
20176
|
+
const quote = await fetchFeeQuote({
|
|
20177
|
+
sourceDomain: binding.sourceDomain,
|
|
20178
|
+
destinationDomain: binding.destinationDomain,
|
|
20179
|
+
amount: binding.amount,
|
|
20180
|
+
feeToken: binding.feeToken,
|
|
20181
|
+
requests: binding.requests,
|
|
20182
|
+
isTestnet: binding.isTestnet
|
|
20183
|
+
});
|
|
20184
|
+
if (quote.feeToken.toLowerCase() !== binding.feeToken.toLowerCase()) {
|
|
20185
|
+
throw createValidationFailedError$1('feeToken', quote.feeToken, 'Fee Service must return source-chain USDC for source-fee bridging');
|
|
20186
|
+
}
|
|
20187
|
+
return quote;
|
|
20188
|
+
} catch (error) {
|
|
20189
|
+
if (isRateLimitError(error)) {
|
|
20190
|
+
throw new KitError({
|
|
20191
|
+
...RateLimitError.RATE_LIMIT_EXCEEDED,
|
|
20192
|
+
recoverability: 'RETRYABLE',
|
|
20193
|
+
message: 'Fee Service rate limit exceeded. Retry with caller-managed exponential backoff; Bridge Kit does not retry signed quote requests automatically.',
|
|
20194
|
+
cause: {
|
|
20195
|
+
trace: error
|
|
20196
|
+
}
|
|
20197
|
+
});
|
|
20198
|
+
}
|
|
20199
|
+
throw error;
|
|
20200
|
+
}
|
|
20201
|
+
}
|
|
20202
|
+
async function fetchSubmissionQuote(binding) {
|
|
20203
|
+
let quote = await fetchBoundQuote(binding);
|
|
20204
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
20205
|
+
quote = await fetchBoundQuote(binding);
|
|
20206
|
+
}
|
|
20207
|
+
if (isQuoteNearEstimatedExpiry(quote)) {
|
|
20208
|
+
throw createValidationFailedError$1('quote', undefined, 'Fee Service returned a quote too close to expiry for safe submission');
|
|
20209
|
+
}
|
|
20210
|
+
return quote;
|
|
20211
|
+
}
|
|
20212
|
+
async function validateBoundQuote(binding, signedQuote, refundAddress) {
|
|
20213
|
+
return validateQuote({
|
|
20214
|
+
sourceDomain: binding.sourceDomain,
|
|
20215
|
+
abiSignature: BURN_WITH_FEES_ABI_SIGNATURE,
|
|
20216
|
+
args: [
|
|
20217
|
+
binding.amount,
|
|
20218
|
+
String(binding.destinationDomain),
|
|
20219
|
+
padAddressToBytes32(binding.mintRecipient),
|
|
20220
|
+
binding.feeToken,
|
|
20221
|
+
padAddressToBytes32(binding.destinationCaller),
|
|
20222
|
+
binding.hookData,
|
|
20223
|
+
[
|
|
20224
|
+
signedQuote,
|
|
20225
|
+
refundAddress
|
|
20226
|
+
]
|
|
20227
|
+
],
|
|
20228
|
+
isTestnet: binding.isTestnet
|
|
20229
|
+
});
|
|
20230
|
+
}
|
|
20231
|
+
function isValidationSafe(binding, signedQuote, validation, expectedQuote) {
|
|
20232
|
+
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);
|
|
20233
|
+
}
|
|
20234
|
+
function unsafeQuoteError(validation) {
|
|
20235
|
+
const detail = validation.failedChecks.length > 0 ? ` (${validation.failedChecks.join(', ')})` : '';
|
|
20236
|
+
return createValidationFailedError$1('quote', undefined, `The fee quote is not safe for submission${detail}; ` + 'call estimate again to obtain a valid quote');
|
|
20237
|
+
}
|
|
20238
|
+
function toExecutionFeeQuote(quote) {
|
|
20239
|
+
return {
|
|
20240
|
+
signedQuote: quote.signedQuote,
|
|
20241
|
+
feeToken: quote.feeToken,
|
|
20242
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
20243
|
+
};
|
|
20244
|
+
}
|
|
20245
|
+
function toFeeItems(quote) {
|
|
20246
|
+
return quote.items.map((item)=>({
|
|
20247
|
+
type: item.type,
|
|
20248
|
+
amount: formatUnits(item.amount, 6),
|
|
20249
|
+
args: item.args,
|
|
20250
|
+
argsHash: item.argsHash
|
|
20251
|
+
}));
|
|
20252
|
+
}
|
|
20253
|
+
/**
|
|
20254
|
+
* Estimate a source-fee bridge using a source-denominated signed fee quote.
|
|
20255
|
+
*
|
|
20256
|
+
* @internal
|
|
20257
|
+
*/ async function estimateSourceFeeBridge(params) {
|
|
20258
|
+
const binding = buildQuoteBinding(params);
|
|
20259
|
+
const quote = await fetchSubmissionQuote(binding);
|
|
20260
|
+
const feeTotal = formatUnits(quote.feeTotalAmount, 6);
|
|
20261
|
+
return {
|
|
20262
|
+
token: 'USDC',
|
|
20263
|
+
amount: formatUnits(params.amount, 6),
|
|
20264
|
+
source: {
|
|
20265
|
+
address: params.source.address,
|
|
20266
|
+
chain: params.source.chain.chain
|
|
20267
|
+
},
|
|
20268
|
+
destination: {
|
|
20269
|
+
address: params.destination.address,
|
|
20270
|
+
chain: params.destination.chain.chain,
|
|
20271
|
+
...params.destination.recipientAddress !== undefined && {
|
|
20272
|
+
recipientAddress: params.destination.recipientAddress
|
|
20273
|
+
}
|
|
20274
|
+
},
|
|
20275
|
+
gasFees: [],
|
|
20276
|
+
fees: quote.items.map((item)=>({
|
|
20277
|
+
type: item.type === 'FORWARD' ? 'forwarder' : 'provider',
|
|
20278
|
+
token: 'USDC',
|
|
20279
|
+
amount: formatUnits(item.amount, 6)
|
|
20280
|
+
})),
|
|
20281
|
+
amountReceived: formatUnits(params.amount, 6),
|
|
20282
|
+
feeTotal,
|
|
20283
|
+
feeItems: toFeeItems(quote),
|
|
20284
|
+
totalDebit: formatUnits((BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString(), 6),
|
|
20285
|
+
quoteExpiry: quote.expiry,
|
|
20286
|
+
quote: quote.signedQuote
|
|
20287
|
+
};
|
|
20288
|
+
}
|
|
20289
|
+
async function readAllowance(params, delegate) {
|
|
20290
|
+
const operationContext = {
|
|
20291
|
+
chain: params.source.chain,
|
|
20292
|
+
address: params.source.address
|
|
20293
|
+
};
|
|
20294
|
+
const prepared = await params.source.adapter.prepareAction('usdc.allowance', {
|
|
20295
|
+
walletAddress: params.source.address,
|
|
20296
|
+
delegate
|
|
20297
|
+
}, operationContext);
|
|
20298
|
+
return BigInt(String(await prepared.execute()));
|
|
20299
|
+
}
|
|
20300
|
+
async function executeAndConfirm(request, params, provider) {
|
|
20301
|
+
const txHash = await request.execute();
|
|
20302
|
+
const data = await provider.waitForTransaction(params.source.adapter, txHash, params.source.chain);
|
|
20303
|
+
return {
|
|
20304
|
+
txHash,
|
|
20305
|
+
data
|
|
20306
|
+
};
|
|
20307
|
+
}
|
|
20308
|
+
async function prepareAndPreflight(params, binding, quote, provider) {
|
|
20309
|
+
assertSourceFeeRoute(params);
|
|
20310
|
+
const totalDebit = (BigInt(params.amount) + BigInt(quote.feeTotalAmount)).toString();
|
|
20311
|
+
const operationContext = {
|
|
20312
|
+
chain: params.source.chain,
|
|
20313
|
+
address: params.source.address
|
|
20314
|
+
};
|
|
20315
|
+
await validateBalanceForTransaction({
|
|
20316
|
+
adapter: params.source.adapter,
|
|
20317
|
+
amount: totalDebit,
|
|
20318
|
+
token: 'USDC',
|
|
20319
|
+
tokenAddress: params.source.chain.usdcAddress,
|
|
20320
|
+
operationContext
|
|
20321
|
+
});
|
|
20322
|
+
const prepared = await provider.burnWithFees({
|
|
20323
|
+
source: params.source,
|
|
20324
|
+
destinationChain: params.destination.chain,
|
|
20325
|
+
amount: params.amount,
|
|
20326
|
+
mintRecipient: binding.mintRecipient,
|
|
20327
|
+
destinationCaller: binding.destinationCaller,
|
|
20328
|
+
hookData: binding.hookData,
|
|
20329
|
+
claim: {
|
|
20330
|
+
signedQuote: quote.signedQuote,
|
|
20331
|
+
refundAddress: params.source.address
|
|
20332
|
+
},
|
|
20333
|
+
feeToken: quote.feeToken,
|
|
20334
|
+
feeTotalAmount: quote.feeTotalAmount
|
|
20335
|
+
});
|
|
20336
|
+
const wrapper = resolveCCTPV2ContractAddress(params.source.chain, 'tokenMessengerWithFees');
|
|
20337
|
+
const allowance = await readAllowance(params, wrapper);
|
|
20338
|
+
if (allowance >= BigInt(totalDebit)) {
|
|
20339
|
+
return {
|
|
20340
|
+
prepared,
|
|
20341
|
+
approveStep: {
|
|
20342
|
+
name: 'approve',
|
|
20343
|
+
state: 'noop'
|
|
20344
|
+
}
|
|
20345
|
+
};
|
|
20346
|
+
}
|
|
20347
|
+
let lastApproval;
|
|
20348
|
+
for (const approval of prepared.approvals){
|
|
20349
|
+
lastApproval = await executeAndConfirm(approval, params, provider);
|
|
20350
|
+
}
|
|
20351
|
+
return {
|
|
20352
|
+
prepared,
|
|
20353
|
+
approveStep: {
|
|
20354
|
+
name: 'approve',
|
|
20355
|
+
state: 'success',
|
|
20356
|
+
data: lastApproval?.data,
|
|
20357
|
+
...lastApproval?.txHash !== undefined && {
|
|
20358
|
+
txHash: lastApproval.txHash,
|
|
20359
|
+
explorerUrl: buildExplorerUrl(params.source.chain, lastApproval.txHash)
|
|
20360
|
+
}
|
|
20361
|
+
}
|
|
20362
|
+
};
|
|
20363
|
+
}
|
|
20364
|
+
async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
20365
|
+
if (suppliedQuote === undefined) {
|
|
20366
|
+
const fetchedQuote = await fetchSubmissionQuote(binding);
|
|
20367
|
+
return {
|
|
20368
|
+
quote: toExecutionFeeQuote(fetchedQuote),
|
|
20369
|
+
fetchedQuote
|
|
20370
|
+
};
|
|
20371
|
+
}
|
|
20372
|
+
const validation = await validateBoundQuote(binding, suppliedQuote, refundAddress);
|
|
20373
|
+
if (!isValidationSafe(binding, suppliedQuote, validation)) {
|
|
20374
|
+
throw unsafeQuoteError(validation);
|
|
20375
|
+
}
|
|
20376
|
+
return {
|
|
20377
|
+
quote: toExecutionFeeQuote(validation),
|
|
20378
|
+
fetchedQuote: undefined
|
|
20379
|
+
};
|
|
20380
|
+
}
|
|
20381
|
+
/**
|
|
20382
|
+
* Execute a source-fee bridge while preserving receive-exact semantics.
|
|
20383
|
+
*
|
|
20384
|
+
* @internal
|
|
20385
|
+
*/ async function executeSourceFeeBridge(rawParams, params, provider) {
|
|
20386
|
+
// Narrows `params` to the CCTP v2 route type for the rest of this function.
|
|
20387
|
+
// buildQuoteBinding asserts too, but that narrows its own scope, not this one.
|
|
20388
|
+
assertSourceFeeRoute(params);
|
|
20389
|
+
const binding = buildQuoteBinding(params);
|
|
20390
|
+
const suppliedQuote = rawParams.quote;
|
|
20391
|
+
let { quote, fetchedQuote } = await resolveExecutionQuote(binding, suppliedQuote, params.source.address);
|
|
20392
|
+
let { prepared, approveStep } = await prepareAndPreflight(params, binding, quote, provider);
|
|
20393
|
+
// Validate against the current source-chain tip after approval confirmation.
|
|
20394
|
+
// BLOCK_NUMBER expiries cannot be checked safely with wall-clock time alone.
|
|
20395
|
+
let validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
20396
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
20397
|
+
if (suppliedQuote === undefined) {
|
|
20398
|
+
fetchedQuote = await fetchSubmissionQuote(binding);
|
|
20399
|
+
quote = toExecutionFeeQuote(fetchedQuote);
|
|
20400
|
+
const refreshedPreparation = await prepareAndPreflight(params, binding, quote, provider);
|
|
20401
|
+
prepared = refreshedPreparation.prepared;
|
|
20402
|
+
if (refreshedPreparation.approveStep.state !== 'noop') {
|
|
20403
|
+
approveStep = refreshedPreparation.approveStep;
|
|
20404
|
+
}
|
|
20405
|
+
validation = await validateBoundQuote(binding, quote.signedQuote, params.source.address);
|
|
20406
|
+
if (!isValidationSafe(binding, quote.signedQuote, validation, fetchedQuote)) {
|
|
20407
|
+
throw unsafeQuoteError(validation);
|
|
20408
|
+
}
|
|
20409
|
+
} else {
|
|
20410
|
+
throw unsafeQuoteError(validation);
|
|
20411
|
+
}
|
|
20412
|
+
}
|
|
20413
|
+
// Surface the same step events as the standard bridge() path so
|
|
20414
|
+
// kit.on('approve'|'burn'|'mint', ...) handlers fire for source-fee bridges.
|
|
20415
|
+
if (approveStep.state !== 'noop') {
|
|
20416
|
+
provider.emitBridgeStep('approve', approveStep);
|
|
20417
|
+
}
|
|
20418
|
+
const burn = await executeAndConfirm(prepared.burn, params, provider);
|
|
20419
|
+
const burnStep = {
|
|
20420
|
+
name: 'burn',
|
|
20421
|
+
state: 'success',
|
|
20422
|
+
txHash: burn.txHash,
|
|
20423
|
+
data: burn.data,
|
|
20424
|
+
explorerUrl: buildExplorerUrl(params.source.chain, burn.txHash)
|
|
20425
|
+
};
|
|
20426
|
+
provider.emitBridgeStep('burn', burnStep);
|
|
20427
|
+
const resultBase = {
|
|
20428
|
+
amount: params.amount,
|
|
20429
|
+
token: 'USDC',
|
|
20430
|
+
config: params.config,
|
|
20431
|
+
provider: provider.name,
|
|
20432
|
+
source: {
|
|
20433
|
+
address: params.source.address,
|
|
20434
|
+
chain: params.source.chain
|
|
20435
|
+
},
|
|
20436
|
+
destination: {
|
|
20437
|
+
address: params.destination.address,
|
|
20438
|
+
chain: params.destination.chain,
|
|
20439
|
+
...params.destination.recipientAddress !== undefined && {
|
|
20440
|
+
recipientAddress: params.destination.recipientAddress
|
|
20441
|
+
},
|
|
20442
|
+
useForwarder: true
|
|
20443
|
+
}
|
|
20444
|
+
};
|
|
20445
|
+
const attestation = await provider.fetchRelayerMint(params.source, burn.txHash);
|
|
20446
|
+
const forwardTxHash = attestation.forwardTxHash;
|
|
20447
|
+
// The burn already moved funds. If the relayer confirms without a
|
|
20448
|
+
// destination hash, surface an error-state result that preserves the burn
|
|
20449
|
+
// step (so `retry()` can resume the mint) instead of throwing and discarding
|
|
20450
|
+
// the completed burn.
|
|
20451
|
+
if (typeof forwardTxHash !== 'string' || forwardTxHash.trim() === '') {
|
|
20452
|
+
const mintStep = {
|
|
20453
|
+
name: 'mint',
|
|
20454
|
+
state: 'error',
|
|
20455
|
+
forwarded: true,
|
|
20456
|
+
errorCategory: 'failed_offchain',
|
|
20457
|
+
errorMessage: 'Relayer confirmation did not include a destination transaction hash'
|
|
20458
|
+
};
|
|
20459
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
20460
|
+
return {
|
|
20461
|
+
...resultBase,
|
|
20462
|
+
state: 'error',
|
|
20463
|
+
steps: [
|
|
20464
|
+
approveStep,
|
|
20465
|
+
burnStep,
|
|
20466
|
+
mintStep
|
|
20467
|
+
]
|
|
20468
|
+
};
|
|
20469
|
+
}
|
|
20470
|
+
const mintStep = {
|
|
20471
|
+
name: 'mint',
|
|
20472
|
+
state: 'success',
|
|
20473
|
+
forwarded: true,
|
|
20474
|
+
txHash: forwardTxHash,
|
|
20475
|
+
explorerUrl: buildExplorerUrl(params.destination.chain, forwardTxHash)
|
|
20476
|
+
};
|
|
20477
|
+
provider.emitBridgeStep('mint', mintStep);
|
|
20478
|
+
return {
|
|
20479
|
+
...resultBase,
|
|
20480
|
+
state: 'success',
|
|
20481
|
+
steps: [
|
|
20482
|
+
approveStep,
|
|
20483
|
+
burnStep,
|
|
20484
|
+
mintStep
|
|
20485
|
+
]
|
|
20486
|
+
};
|
|
20487
|
+
}
|
|
20488
|
+
|
|
19136
20489
|
/** SDK name used in telemetry payloads. */ const SDK_NAME$3 = resolveKitSdkName(pkg$5.name);
|
|
19137
20490
|
/**
|
|
19138
20491
|
* Pick the most-relevant `txHash` to attach to an error telemetry payload.
|
|
@@ -19360,11 +20713,18 @@ function assertCCTPV2Config(config) {
|
|
|
19360
20713
|
this.validateNetworkCompatibility(resolvedParams);
|
|
19361
20714
|
// Merge the custom fee config into the resolved params
|
|
19362
20715
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
//
|
|
19366
|
-
|
|
19367
|
-
|
|
20716
|
+
let result;
|
|
20717
|
+
// Execute the explicit source-fee path without changing legacy
|
|
20718
|
+
// useForwarder behavior for callers that did not opt in.
|
|
20719
|
+
if (params.config?.feePayment === 'source') {
|
|
20720
|
+
const sourceFeeProvider = this.findSourceFeeProvider(finalResolvedParams);
|
|
20721
|
+
result = formatBridgeResult(await executeSourceFeeBridge(params, finalResolvedParams, sourceFeeProvider), 'to-human-readable');
|
|
20722
|
+
} else {
|
|
20723
|
+
// Find a provider that supports this route
|
|
20724
|
+
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
20725
|
+
// Execute the transfer using the provider and format the result.
|
|
20726
|
+
result = formatBridgeResult(await provider.bridge(finalResolvedParams), 'to-human-readable');
|
|
20727
|
+
}
|
|
19368
20728
|
// Emit error telemetry when the provider returns an error state
|
|
19369
20729
|
// (provider records step failures in the result instead of throwing).
|
|
19370
20730
|
if (result.state === 'error') {
|
|
@@ -19487,42 +20847,7 @@ function assertCCTPV2Config(config) {
|
|
|
19487
20847
|
tokenIn: result.token
|
|
19488
20848
|
});
|
|
19489
20849
|
}
|
|
19490
|
-
|
|
19491
|
-
* Estimate the cost and fees for a cross-chain USDC bridge operation.
|
|
19492
|
-
*
|
|
19493
|
-
* This method calculates the expected gas fees and protocol costs for bridging
|
|
19494
|
-
* without actually executing the transaction. It performs the same validation
|
|
19495
|
-
* as the bridge method but stops before execution.
|
|
19496
|
-
*
|
|
19497
|
-
* @param params - The bridge parameters for cost estimation, including optional invocation metadata
|
|
19498
|
-
* @returns Promise resolving to detailed cost breakdown including gas estimates
|
|
19499
|
-
* @throws {KitError} When the parameters are invalid.
|
|
19500
|
-
* @throws {UnsupportedRouteError} When the route is not supported.
|
|
19501
|
-
*
|
|
19502
|
-
* @example
|
|
19503
|
-
* ```typescript
|
|
19504
|
-
* // Basic usage
|
|
19505
|
-
* const estimate = await kit.estimate({
|
|
19506
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
19507
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
19508
|
-
* amount: '10.50',
|
|
19509
|
-
* token: 'USDC'
|
|
19510
|
-
* })
|
|
19511
|
-
* console.log('Estimated cost:', estimate.totalCost)
|
|
19512
|
-
*
|
|
19513
|
-
* // With custom invocation metadata
|
|
19514
|
-
* const estimate = await kit.estimate({
|
|
19515
|
-
* from: { adapter: adapter, chain: 'Ethereum' },
|
|
19516
|
-
* to: { adapter: adapter, chain: 'Base' },
|
|
19517
|
-
* amount: '10.50',
|
|
19518
|
-
* token: 'USDC',
|
|
19519
|
-
* invocationMeta: {
|
|
19520
|
-
* traceId: 'custom-trace-id',
|
|
19521
|
-
* callers: [{ type: 'app', name: 'MyDApp', version: '1.0.0' }],
|
|
19522
|
-
* },
|
|
19523
|
-
* })
|
|
19524
|
-
* ```
|
|
19525
|
-
*/ async estimate(params) {
|
|
20850
|
+
async estimate(params) {
|
|
19526
20851
|
return withErrorTelemetry(async ()=>{
|
|
19527
20852
|
// First validate the parameters
|
|
19528
20853
|
assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
|
|
@@ -19532,6 +20857,10 @@ function assertCCTPV2Config(config) {
|
|
|
19532
20857
|
this.validateNetworkCompatibility(resolvedParams);
|
|
19533
20858
|
// Merge the custom fee config into the resolved params
|
|
19534
20859
|
const finalResolvedParams = await this.mergeCustomFeeConfig(resolvedParams);
|
|
20860
|
+
if (params.config?.feePayment === 'source') {
|
|
20861
|
+
this.findSourceFeeProvider(finalResolvedParams);
|
|
20862
|
+
return estimateSourceFeeBridge(finalResolvedParams);
|
|
20863
|
+
}
|
|
19535
20864
|
// Find a provider that supports this route
|
|
19536
20865
|
const provider = this.findProviderForRoute(finalResolvedParams);
|
|
19537
20866
|
// Estimate the transfer using the provider and format amounts to human-readable strings
|
|
@@ -19580,6 +20909,9 @@ function assertCCTPV2Config(config) {
|
|
|
19580
20909
|
* // Get only chains that support forwarding
|
|
19581
20910
|
* const forwarderChains = kit.getSupportedChains({ forwarderSupported: true })
|
|
19582
20911
|
*
|
|
20912
|
+
* // Get only chains that can pay fees on the source chain (receive-exact)
|
|
20913
|
+
* const sourceFeeChains = kit.getSupportedChains({ sourceFeeSupported: true })
|
|
20914
|
+
*
|
|
19583
20915
|
* console.log('Supported chains:')
|
|
19584
20916
|
* allChains.forEach(chain => {
|
|
19585
20917
|
* console.log(`- ${chain.name} (${chain.type})`)
|
|
@@ -19628,6 +20960,10 @@ function assertCCTPV2Config(config) {
|
|
|
19628
20960
|
return options.forwarderSupported ? fs.source || fs.destination : !fs.source && !fs.destination;
|
|
19629
20961
|
});
|
|
19630
20962
|
}
|
|
20963
|
+
// Apply source-paid ("receive-exact") fee support filter if provided
|
|
20964
|
+
if (options?.sourceFeeSupported !== undefined) {
|
|
20965
|
+
chains = chains.filter((chain)=>hasSourceFeeSupport(chain) === options.sourceFeeSupported);
|
|
20966
|
+
}
|
|
19631
20967
|
return chains;
|
|
19632
20968
|
}
|
|
19633
20969
|
/**
|
|
@@ -19662,6 +20998,20 @@ function assertCCTPV2Config(config) {
|
|
|
19662
20998
|
return provider;
|
|
19663
20999
|
}
|
|
19664
21000
|
/**
|
|
21001
|
+
* Find the default CCTP v2 provider for a source-fee forwarding route.
|
|
21002
|
+
*
|
|
21003
|
+
* @param params - The resolved provider parameters.
|
|
21004
|
+
* @returns The CCTP v2 provider that supports the forwarded route.
|
|
21005
|
+
* @throws {UnsupportedRouteError} When no source-fee provider supports the route.
|
|
21006
|
+
* @internal
|
|
21007
|
+
*/ findSourceFeeProvider(params) {
|
|
21008
|
+
const provider = this.providers.find((candidate)=>candidate instanceof CCTPV2BridgingProvider && candidate.supportsRoute(params.source.chain, params.destination.chain, params.token, true));
|
|
21009
|
+
if (!(provider instanceof CCTPV2BridgingProvider)) {
|
|
21010
|
+
throw createUnsupportedRouteError(params.source.chain.name, params.destination.chain.name);
|
|
21011
|
+
}
|
|
21012
|
+
return provider;
|
|
21013
|
+
}
|
|
21014
|
+
/**
|
|
19665
21015
|
* Merge custom fee configuration into provider parameters.
|
|
19666
21016
|
*
|
|
19667
21017
|
* Prioritizes any custom fee configuration already present on the
|
|
@@ -19801,7 +21151,7 @@ function assertCCTPV2Config(config) {
|
|
|
19801
21151
|
registerKit(`${pkg$5.name}/${pkg$5.version}`);
|
|
19802
21152
|
|
|
19803
21153
|
var name$3 = "@circle-fin/swap-kit";
|
|
19804
|
-
var version$3 = "1.
|
|
21154
|
+
var version$3 = "1.6.0";
|
|
19805
21155
|
var pkg$3 = {
|
|
19806
21156
|
name: name$3,
|
|
19807
21157
|
version: version$3};
|
|
@@ -19815,7 +21165,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
19815
21165
|
* Catches obviously malformed addresses at parse time; chain-specific validation
|
|
19816
21166
|
* is performed in buildServiceParams.
|
|
19817
21167
|
*/ const destinationAddressSchema = zod.z.union([
|
|
19818
|
-
evmAddressSchema,
|
|
21168
|
+
evmAddressSchema$1,
|
|
19819
21169
|
solanaAddressSchema
|
|
19820
21170
|
]);
|
|
19821
21171
|
/**
|
|
@@ -19861,9 +21211,16 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
19861
21211
|
message: 'stopLimit must be greater than 0'
|
|
19862
21212
|
}).optional(),
|
|
19863
21213
|
customFee: serviceSwapCustomFeeSchema.optional(),
|
|
21214
|
+
apiKey: zod.z.string({
|
|
21215
|
+
invalid_type_error: 'apiKey must be a string'
|
|
21216
|
+
})// Tolerate '' so the `process.env.CIRCLE_API_KEY ?? ''` idiom falls back to
|
|
21217
|
+
// kitKey via resolveApiKey instead of being rejected here.
|
|
21218
|
+
.optional(),
|
|
19864
21219
|
kitKey: zod.z.string({
|
|
19865
21220
|
invalid_type_error: 'kitKey must be a string'
|
|
19866
|
-
})
|
|
21221
|
+
})// Tolerate '' so an unset kit-key env var yields the permissionless path,
|
|
21222
|
+
// matching buildServiceParams (which already omits an empty credential).
|
|
21223
|
+
.optional(),
|
|
19867
21224
|
provider: zod.z.string({
|
|
19868
21225
|
invalid_type_error: 'provider must be a string'
|
|
19869
21226
|
}).min(1, 'provider must be a non-empty string').optional(),
|
|
@@ -20028,23 +21385,49 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20028
21385
|
fees: zod.z.array(serviceSwapFeeSchema).optional()
|
|
20029
21386
|
}).passthrough();
|
|
20030
21387
|
|
|
20031
|
-
/** Placeholder used wherever a
|
|
21388
|
+
/** Placeholder used wherever a credential is scrubbed from logs, traces, or errors. */ const REDACTED_CREDENTIAL = '[redacted]';
|
|
21389
|
+
/**
|
|
21390
|
+
* Config fields that carry a Stablecoin Service credential.
|
|
21391
|
+
*
|
|
21392
|
+
* `apiKey` is the supported field; `kitKey` is the deprecated alias. Redaction
|
|
21393
|
+
* and credential resolution both read this list so a newly added field is
|
|
21394
|
+
* scrubbed everywhere it needs to be.
|
|
21395
|
+
*/ const CREDENTIAL_CONFIG_FIELDS = [
|
|
21396
|
+
'apiKey',
|
|
21397
|
+
'kitKey'
|
|
21398
|
+
];
|
|
20032
21399
|
|
|
21400
|
+
/**
|
|
21401
|
+
* Replaces any credential field present on a config with a redaction marker.
|
|
21402
|
+
*
|
|
21403
|
+
* Covers `apiKey` and the deprecated `kitKey`, so adding a credential field
|
|
21404
|
+
* without extending this list cannot silently leak it into a trace. Returns the
|
|
21405
|
+
* config unchanged when it carries no credential.
|
|
21406
|
+
*/ const redactCredentialFields = (config)=>{
|
|
21407
|
+
const present = CREDENTIAL_CONFIG_FIELDS.filter((field)=>field in config);
|
|
21408
|
+
if (present.length === 0) {
|
|
21409
|
+
return config;
|
|
21410
|
+
}
|
|
21411
|
+
return present.reduce((acc, field)=>({
|
|
21412
|
+
...acc,
|
|
21413
|
+
[field]: REDACTED_CREDENTIAL
|
|
21414
|
+
}), {
|
|
21415
|
+
...config
|
|
21416
|
+
});
|
|
21417
|
+
};
|
|
20033
21418
|
/**
|
|
20034
21419
|
* Builds a redacted projection of swap params that is safe to embed in error
|
|
20035
21420
|
* traces (which may be logged downstream). Allow-lists only non-sensitive
|
|
20036
21421
|
* fields and drops `from` entirely — `from.adapter` can carry signer/private-key
|
|
20037
|
-
* material for signer-backed (viem/ethers) flows — and redacts
|
|
21422
|
+
* material for signer-backed (viem/ethers) flows — and redacts every credential
|
|
21423
|
+
* field on `config`.
|
|
20038
21424
|
*/ const redactSwapParamsForTrace = (params)=>{
|
|
20039
21425
|
if (typeof params !== 'object' || params === null) {
|
|
20040
21426
|
return params;
|
|
20041
21427
|
}
|
|
20042
21428
|
const record = params;
|
|
20043
21429
|
const config = record['config'];
|
|
20044
|
-
const redactedConfig = typeof config === 'object' && config !== null
|
|
20045
|
-
...config,
|
|
20046
|
-
kitKey: REDACTED_KIT_KEY
|
|
20047
|
-
} : config;
|
|
21430
|
+
const redactedConfig = typeof config === 'object' && config !== null ? redactCredentialFields(config) : config;
|
|
20048
21431
|
return {
|
|
20049
21432
|
tokenIn: record['tokenIn'],
|
|
20050
21433
|
tokenOut: record['tokenOut'],
|
|
@@ -20128,6 +21511,41 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20128
21511
|
*
|
|
20129
21512
|
* @internal
|
|
20130
21513
|
*/ const MAX_RATE_ADDRESSES_PER_REQUEST = 100;
|
|
21514
|
+
/**
|
|
21515
|
+
* Environment prefixes carried by Circle platform API keys.
|
|
21516
|
+
*
|
|
21517
|
+
* A Circle API key is `<ENV>_API_KEY:<keyId>:<keySecret>`, where `<ENV>` is one
|
|
21518
|
+
* of these prefixes.
|
|
21519
|
+
*
|
|
21520
|
+
* @internal
|
|
21521
|
+
*/ const API_KEY_ENV_PREFIXES = [
|
|
21522
|
+
'TEST',
|
|
21523
|
+
'LIVE',
|
|
21524
|
+
'SAND',
|
|
21525
|
+
'SANDBOX',
|
|
21526
|
+
'SMOK',
|
|
21527
|
+
'PROD',
|
|
21528
|
+
'STAG',
|
|
21529
|
+
'DEV'
|
|
21530
|
+
];
|
|
21531
|
+
/**
|
|
21532
|
+
* Accepted credential formats for Stablecoin Service authentication.
|
|
21533
|
+
*
|
|
21534
|
+
* Matches a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
21535
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`). API keys are the
|
|
21536
|
+
* recommended credential; kit keys remain accepted as the legacy path.
|
|
21537
|
+
*
|
|
21538
|
+
* @remarks
|
|
21539
|
+
* This is a local pre-flight check, not the authority — the Stablecoin Service
|
|
21540
|
+
* validates the credential and answers 401 when it rejects one. The prefix list
|
|
21541
|
+
* is therefore deliberately permissive: a valid key carrying a prefix this SDK
|
|
21542
|
+
* has not been taught about should reach the service and be judged there rather
|
|
21543
|
+
* than be refused locally, since refusing locally is indistinguishable from an
|
|
21544
|
+
* outage to the caller. Kept as the single source of truth so the pattern is
|
|
21545
|
+
* not restated per call site.
|
|
21546
|
+
*
|
|
21547
|
+
* @internal
|
|
21548
|
+
*/ const API_KEY_PATTERN = new RegExp(`^(?:KIT_KEY|(?:${API_KEY_ENV_PREFIXES.join('|')})_API_KEY)` + ':[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$');
|
|
20131
21549
|
|
|
20132
21550
|
/**
|
|
20133
21551
|
* Zod schema for validating stop limits.
|
|
@@ -20196,13 +21614,14 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20196
21614
|
/**
|
|
20197
21615
|
* Zod schema for validating API keys.
|
|
20198
21616
|
*
|
|
20199
|
-
*
|
|
21617
|
+
* Accepts a Circle platform API key (`<ENV>_API_KEY:<keyId>:<keySecret>`) and
|
|
21618
|
+
* the legacy kit key (`KIT_KEY:<keyId>:<keySecret>`).
|
|
20200
21619
|
*
|
|
20201
21620
|
* @example
|
|
20202
21621
|
* ```typescript
|
|
20203
21622
|
* import { apiKeySchema } from '@core/service-client'
|
|
20204
21623
|
*
|
|
20205
|
-
* const result = apiKeySchema.safeParse('
|
|
21624
|
+
* const result = apiKeySchema.safeParse('TEST_API_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
|
|
20206
21625
|
* if (!result.success) {
|
|
20207
21626
|
* console.error('Invalid API key format:', result.error.issues)
|
|
20208
21627
|
* }
|
|
@@ -20210,7 +21629,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
20210
21629
|
*/ const apiKeySchema = zod.z.string({
|
|
20211
21630
|
required_error: 'API key is required',
|
|
20212
21631
|
invalid_type_error: 'Invalid API key format'
|
|
20213
|
-
}).regex(
|
|
21632
|
+
}).regex(API_KEY_PATTERN, 'Invalid API key format');
|
|
20214
21633
|
/**
|
|
20215
21634
|
* Zod schema for platform fees configuration.
|
|
20216
21635
|
*
|
|
@@ -20734,8 +22153,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20734
22153
|
* Validates that an API key is properly formatted for Circle Stablecoin Service.
|
|
20735
22154
|
*
|
|
20736
22155
|
* This function performs validation on API keys to ensure they conform to the
|
|
20737
|
-
* expected format before making API requests. The key must follow the structure
|
|
20738
|
-
* `KIT_KEY:keyId:keySecret
|
|
22156
|
+
* expected format before making API requests. The key must follow the structure
|
|
22157
|
+
* `<ENV>_API_KEY:keyId:keySecret`, or the legacy `KIT_KEY:keyId:keySecret`.
|
|
20739
22158
|
*
|
|
20740
22159
|
* @param apiKey - The API key to validate (can be any type for graceful handling).
|
|
20741
22160
|
* @returns True if the API key is valid, false otherwise.
|
|
@@ -20743,7 +22162,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20743
22162
|
* @remarks
|
|
20744
22163
|
* This function handles invalid inputs gracefully by returning false rather than
|
|
20745
22164
|
* throwing errors. It validates:
|
|
20746
|
-
* - The key
|
|
22165
|
+
* - The key carries a recognized prefix — `<ENV>_API_KEY:` for a Circle platform
|
|
22166
|
+
* API key, or `KIT_KEY:` for the legacy kit key
|
|
20747
22167
|
* - Contains exactly three colon-separated parts
|
|
20748
22168
|
* - keyId and keySecret contain only valid characters (alphanumeric, `-`, `_`, `.`)
|
|
20749
22169
|
* - keyId and keySecret are non-empty
|
|
@@ -20751,40 +22171,80 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20751
22171
|
*
|
|
20752
22172
|
* Valid characters in keyId and keySecret: `a-z`, `A-Z`, `0-9`, `-`, `_`, `.`
|
|
20753
22173
|
*
|
|
22174
|
+
* Format is checked locally only so an obviously malformed credential fails
|
|
22175
|
+
* before a request is made. The Stablecoin Service remains the authority on
|
|
22176
|
+
* whether a well-formed key is actually accepted.
|
|
22177
|
+
*
|
|
20754
22178
|
* @example
|
|
20755
22179
|
* ```typescript
|
|
20756
22180
|
* import { isValidApiKey } from '@core/service-client'
|
|
20757
22181
|
*
|
|
20758
22182
|
* // Valid API key
|
|
20759
|
-
* const isValid = isValidApiKey('
|
|
22183
|
+
* const isValid = isValidApiKey('TEST_API_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
|
|
20760
22184
|
* console.log(isValid) // true
|
|
20761
22185
|
*
|
|
22186
|
+
* // Legacy kit keys remain valid
|
|
22187
|
+
* console.log(isValidApiKey('KIT_KEY:my-key-id:my-key-secret')) // true
|
|
22188
|
+
*
|
|
20762
22189
|
* ```
|
|
20763
22190
|
*/ const isValidApiKey = (apiKey)=>{
|
|
20764
22191
|
// Handle invalid input types
|
|
20765
22192
|
if (typeof apiKey !== 'string') {
|
|
20766
22193
|
return false;
|
|
20767
22194
|
}
|
|
20768
|
-
const apiKeyPattern = /^KIT_KEY:[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$/;
|
|
20769
22195
|
// Validate without trimming - any whitespace will cause validation to fail
|
|
20770
|
-
return
|
|
22196
|
+
return API_KEY_PATTERN.test(apiKey);
|
|
20771
22197
|
};
|
|
20772
22198
|
/**
|
|
20773
|
-
*
|
|
22199
|
+
* Resolve the credential to authenticate a Stablecoin Service request with.
|
|
22200
|
+
*
|
|
22201
|
+
* `apiKey` is the supported field; `kitKey` is the deprecated alias kept for
|
|
22202
|
+
* existing integrations. When both are supplied `apiKey` wins, so a caller
|
|
22203
|
+
* migrating field-by-field cannot be silently pinned to a stale credential.
|
|
20774
22204
|
*
|
|
20775
|
-
*
|
|
20776
|
-
*
|
|
20777
|
-
*
|
|
20778
|
-
*
|
|
20779
|
-
*
|
|
20780
|
-
*
|
|
20781
|
-
*
|
|
20782
|
-
*
|
|
22205
|
+
* An empty-string value is treated as absent on either field. Without this, the
|
|
22206
|
+
* common `process.env.CIRCLE_API_KEY ?? ''` idiom (which yields `''` when the
|
|
22207
|
+
* variable is unset) would either shadow a working `kitKey` or, on a bare
|
|
22208
|
+
* `kitKey: ''`, reach downstream validation as an invalid credential instead of
|
|
22209
|
+
* falling through to the permissionless path.
|
|
22210
|
+
*
|
|
22211
|
+
* @param source - Object carrying either credential field, or neither.
|
|
22212
|
+
* @returns The credential to use, or `undefined` for the permissionless
|
|
22213
|
+
* (keyless) path.
|
|
22214
|
+
*
|
|
22215
|
+
* @example
|
|
22216
|
+
* ```typescript
|
|
22217
|
+
* import { resolveApiKey } from '@core/service-client'
|
|
20783
22218
|
*
|
|
20784
|
-
*
|
|
20785
|
-
*
|
|
22219
|
+
* resolveApiKey({ apiKey: 'TEST_API_KEY:id:secret' }) // 'TEST_API_KEY:id:secret'
|
|
22220
|
+
* resolveApiKey({ kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
22221
|
+
* resolveApiKey({ apiKey: '', kitKey: 'KIT_KEY:id:secret' }) // 'KIT_KEY:id:secret'
|
|
22222
|
+
* resolveApiKey({ kitKey: '' }) // undefined
|
|
22223
|
+
* resolveApiKey({}) // undefined
|
|
22224
|
+
* ```
|
|
22225
|
+
*/ const resolveApiKey = (source)=>{
|
|
22226
|
+
// Treat an empty-string value as absent on either field so the
|
|
22227
|
+
// `env ?? ''` idiom falls through to the next credential (or permissionless).
|
|
22228
|
+
const normalize = (value)=>value !== undefined && value !== '' ? value : undefined;
|
|
22229
|
+
return normalize(source.apiKey) ?? normalize(source.kitKey);
|
|
22230
|
+
};
|
|
22231
|
+
/**
|
|
22232
|
+
* Assert that a Stablecoin Service credential is not supplied from a browser.
|
|
22233
|
+
*
|
|
22234
|
+
* An API key (`<ENV>_API_KEY:<id>:<secret>`) and a legacy kit key
|
|
22235
|
+
* (`KIT_KEY:<id>:<secret>`) are both server-only secrets. Every Stablecoin
|
|
22236
|
+
* Service request that attaches an `Authorization: Bearer` header funnels
|
|
22237
|
+
* through this package, so calling this guard before that header is built
|
|
22238
|
+
* prevents the secret from being sent from — and thus bundled into — a client
|
|
22239
|
+
* application. In Node.js the check is a no-op, preserving the legitimate "hold
|
|
22240
|
+
* the credential on the server, forward the prepared transaction to the client"
|
|
22241
|
+
* flow. When no credential is supplied the permissionless (keyless) client path
|
|
22242
|
+
* remains fully allowed.
|
|
22243
|
+
*
|
|
22244
|
+
* @param apiKey - The inline credential for the request, or `undefined` when
|
|
22245
|
+
* none was supplied (permissionless mode).
|
|
20786
22246
|
* @returns Nothing.
|
|
20787
|
-
* @throws KitError with VALIDATION_FAILED when a
|
|
22247
|
+
* @throws KitError with VALIDATION_FAILED when a credential is supplied while
|
|
20788
22248
|
* running in a browser environment. The secret value is never echoed.
|
|
20789
22249
|
*
|
|
20790
22250
|
* @example
|
|
@@ -20792,10 +22252,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20792
22252
|
* import { assertBrowserSafeApiKey } from '@core/service-client'
|
|
20793
22253
|
*
|
|
20794
22254
|
* // Server (Node.js): no-op, request proceeds with the Authorization header.
|
|
20795
|
-
* assertBrowserSafeApiKey('
|
|
22255
|
+
* assertBrowserSafeApiKey('TEST_API_KEY:my-id:my-secret')
|
|
20796
22256
|
*
|
|
20797
22257
|
* // Browser: throws to stop the secret from leaking into the client bundle.
|
|
20798
|
-
* assertBrowserSafeApiKey('
|
|
22258
|
+
* assertBrowserSafeApiKey('TEST_API_KEY:my-id:my-secret')
|
|
20799
22259
|
*
|
|
20800
22260
|
* // Browser, permissionless: allowed.
|
|
20801
22261
|
* assertBrowserSafeApiKey(undefined)
|
|
@@ -20805,7 +22265,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20805
22265
|
return;
|
|
20806
22266
|
}
|
|
20807
22267
|
if (isBrowserEnvironment()) {
|
|
20808
|
-
throw createValidationFailedError$1('
|
|
22268
|
+
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');
|
|
20809
22269
|
}
|
|
20810
22270
|
};
|
|
20811
22271
|
|
|
@@ -20817,7 +22277,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20817
22277
|
*
|
|
20818
22278
|
* @remarks
|
|
20819
22279
|
* - Input validation leverages Zod schemas to surface actionable KitErrors.
|
|
20820
|
-
* - API key format must match
|
|
22280
|
+
* - API key format must match `<ENV>_API_KEY:<keyId>:<keySecret>`, or the
|
|
22281
|
+
* legacy `KIT_KEY:<keyId>:<keySecret>`
|
|
20821
22282
|
*
|
|
20822
22283
|
* @param params - The create transaction request parameters with API key.
|
|
20823
22284
|
* @returns A promise that resolves to a typed {@link CreateSwapResponse}.
|
|
@@ -20846,7 +22307,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20846
22307
|
* recipientAddress: '0x050928e1f9086b20539c767c96909663ff7fbdf2',
|
|
20847
22308
|
* },
|
|
20848
22309
|
* },
|
|
20849
|
-
* apiKey: '
|
|
22310
|
+
* apiKey: 'TEST_API_KEY:example-key:example-secret',
|
|
20850
22311
|
* })
|
|
20851
22312
|
*
|
|
20852
22313
|
* console.log(`Transaction target: ${transaction.transaction.target}`)
|
|
@@ -20865,13 +22326,13 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20865
22326
|
const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
|
|
20866
22327
|
// Remove the API key from the request body
|
|
20867
22328
|
const { apiKey, ...requestBody } = validatedParams;
|
|
20868
|
-
// Never let a server-only
|
|
22329
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
20869
22330
|
assertBrowserSafeApiKey(apiKey);
|
|
20870
22331
|
const effectiveConfig = {
|
|
20871
22332
|
...DEFAULT_CONFIG$1,
|
|
20872
22333
|
headers: {
|
|
20873
22334
|
...DEFAULT_CONFIG$1.headers,
|
|
20874
|
-
// Permissionless mode: no Authorization header when the
|
|
22335
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
20875
22336
|
...apiKey !== undefined && {
|
|
20876
22337
|
Authorization: `Bearer ${apiKey}`
|
|
20877
22338
|
}
|
|
@@ -21021,7 +22482,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21021
22482
|
}
|
|
21022
22483
|
// Use validated data
|
|
21023
22484
|
const validatedParams = result.data;
|
|
21024
|
-
// Never let a server-only
|
|
22485
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
21025
22486
|
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
21026
22487
|
// Build the API URL
|
|
21027
22488
|
const url = buildQuoteUrl(validatedParams);
|
|
@@ -21030,7 +22491,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21030
22491
|
...DEFAULT_CONFIG$1,
|
|
21031
22492
|
headers: {
|
|
21032
22493
|
...DEFAULT_CONFIG$1.headers,
|
|
21033
|
-
// Permissionless mode: no Authorization header when the
|
|
22494
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
21034
22495
|
...validatedParams.apiKey !== undefined && {
|
|
21035
22496
|
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
21036
22497
|
}
|
|
@@ -21095,14 +22556,14 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21095
22556
|
toChain: result.data.toChain
|
|
21096
22557
|
}
|
|
21097
22558
|
};
|
|
21098
|
-
// Never let a server-only
|
|
22559
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
21099
22560
|
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
21100
22561
|
const url = buildSwapStatusUrl(validatedParams);
|
|
21101
22562
|
const effectiveConfig = {
|
|
21102
22563
|
...DEFAULT_CONFIG$1,
|
|
21103
22564
|
headers: {
|
|
21104
22565
|
...DEFAULT_CONFIG$1.headers,
|
|
21105
|
-
// Permissionless mode: no Authorization header when the
|
|
22566
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
21106
22567
|
...validatedParams.apiKey !== undefined && {
|
|
21107
22568
|
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
21108
22569
|
}
|
|
@@ -21128,7 +22589,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21128
22589
|
* const url = buildTokenRatesUrl({
|
|
21129
22590
|
* chain: 'Ethereum',
|
|
21130
22591
|
* addresses: ['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'],
|
|
21131
|
-
* apiKey: '
|
|
22592
|
+
* apiKey: 'TEST_API_KEY:key:secret',
|
|
21132
22593
|
* })
|
|
21133
22594
|
* // => 'https://api.circle.com/v1/stablecoinKits/rates?chain=Ethereum&addresses=...'
|
|
21134
22595
|
* ```
|
|
@@ -21201,14 +22662,14 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21201
22662
|
addresses: result.data.addresses
|
|
21202
22663
|
}
|
|
21203
22664
|
};
|
|
21204
|
-
// Never let a server-only
|
|
22665
|
+
// Never let a server-only credential leave a browser (no-op in Node.js).
|
|
21205
22666
|
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
21206
22667
|
const url = buildTokenRatesUrl(validatedParams);
|
|
21207
22668
|
const effectiveConfig = {
|
|
21208
22669
|
...DEFAULT_CONFIG$1,
|
|
21209
22670
|
headers: {
|
|
21210
22671
|
...DEFAULT_CONFIG$1.headers,
|
|
21211
|
-
// Permissionless mode: no Authorization header when the
|
|
22672
|
+
// Permissionless mode: no Authorization header when the credential is absent.
|
|
21212
22673
|
...validatedParams.apiKey !== undefined && {
|
|
21213
22674
|
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
21214
22675
|
}
|
|
@@ -23186,7 +24647,7 @@ const abiParameterSchema = zod.z.object({
|
|
|
23186
24647
|
*/ zod.z.object({
|
|
23187
24648
|
type: zod.z.literal('evm'),
|
|
23188
24649
|
abi: abiSchema,
|
|
23189
|
-
address: evmAddressSchema,
|
|
24650
|
+
address: evmAddressSchema$1,
|
|
23190
24651
|
functionName: zod.z.string({
|
|
23191
24652
|
required_error: 'Function name is required',
|
|
23192
24653
|
invalid_type_error: 'Function name must be a string'
|
|
@@ -23223,7 +24684,7 @@ const abiParameterSchema = zod.z.object({
|
|
|
23223
24684
|
* }
|
|
23224
24685
|
* ```
|
|
23225
24686
|
*/ zod.z.object({
|
|
23226
|
-
address: evmAddressSchema,
|
|
24687
|
+
address: evmAddressSchema$1,
|
|
23227
24688
|
value: zod.z.bigint({
|
|
23228
24689
|
required_error: 'Value is required for native transfers',
|
|
23229
24690
|
invalid_type_error: 'Value must be a bigint'
|
|
@@ -24457,6 +25918,23 @@ async function signUnits(group, signingUnits) {
|
|
|
24457
25918
|
}
|
|
24458
25919
|
return signedSets;
|
|
24459
25920
|
}
|
|
25921
|
+
/**
|
|
25922
|
+
* Fail loudly if a signing group carries an intent it does not own.
|
|
25923
|
+
*
|
|
25924
|
+
* Every intent in a group is signed by `group.address`, so that signer must own
|
|
25925
|
+
* each intent's `spec.sourceSigner`. If distinct spend sources ever collapse
|
|
25926
|
+
* into one group again (DEVX-3638), this throws before signing instead of
|
|
25927
|
+
* silently producing a wrong-signer signature. specs are bytes32 and
|
|
25928
|
+
* `group.address` is a native address, so normalize both before comparing.
|
|
25929
|
+
*/ function assertGroupSignerOwnsIntents(group) {
|
|
25930
|
+
if (group.address == null || group.address === '') return;
|
|
25931
|
+
const groupSigner = convertAddress(group.address, 'bytes32').toLowerCase();
|
|
25932
|
+
for (const intent of group.intents){
|
|
25933
|
+
if (intent.spec.sourceSigner.toLowerCase() !== groupSigner) {
|
|
25934
|
+
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.');
|
|
25935
|
+
}
|
|
25936
|
+
}
|
|
25937
|
+
}
|
|
24460
25938
|
/**
|
|
24461
25939
|
* Sign an EVM adapter group.
|
|
24462
25940
|
*
|
|
@@ -24499,22 +25977,42 @@ async function signUnits(group, signingUnits) {
|
|
|
24499
25977
|
// Validate the collection before doing bytecode reads or asking a wallet
|
|
24500
25978
|
// to sign. evmSigningData owns the canonical BurnIntent validation.
|
|
24501
25979
|
validateGroupIntents(group.intents);
|
|
25980
|
+
// Guard against a wrong-signer collapse: the group's signer must own every
|
|
25981
|
+
// intent it carries.
|
|
25982
|
+
assertGroupSignerOwnsIntents(group);
|
|
24502
25983
|
const chainsByDomain = collectChainsByDomain(group);
|
|
24503
25984
|
const signerTypeByDomain = await classifySignerTypes(group, chainsByDomain);
|
|
24504
25985
|
const signingUnits = createSigningUnits(group, signerTypeByDomain);
|
|
24505
25986
|
return await signUnits(group, signingUnits);
|
|
24506
25987
|
}
|
|
24507
25988
|
|
|
25989
|
+
// One adapter instance can serve several signer addresses (e.g. a single
|
|
25990
|
+
// dev-controlled adapter used for multiple wallets), so the group map must be
|
|
25991
|
+
// keyed on `(adapter, sourceSigner)` rather than the adapter reference alone.
|
|
25992
|
+
// Keying on the adapter alone collapsed two same-chain sources into one group,
|
|
25993
|
+
// signing the second wallet's intent with the first wallet (DEVX-3638 /
|
|
25994
|
+
// DEVX-3194). A Map needs a primitive key, so give each adapter instance a
|
|
25995
|
+
// stable token and combine it with the (case-normalized) signer address.
|
|
25996
|
+
const adapterTokens = new WeakMap();
|
|
25997
|
+
let nextAdapterToken = 0;
|
|
25998
|
+
function groupKey(alloc) {
|
|
25999
|
+
let token = adapterTokens.get(alloc.adapter);
|
|
26000
|
+
if (token === undefined) {
|
|
26001
|
+
token = nextAdapterToken++;
|
|
26002
|
+
adapterTokens.set(alloc.adapter, token);
|
|
26003
|
+
}
|
|
26004
|
+
return `${String(token)}:${alloc.sourceSigner.toLowerCase()}`;
|
|
26005
|
+
}
|
|
24508
26006
|
/**
|
|
24509
26007
|
* Add an EVM intent into the batched EVM group map.
|
|
24510
26008
|
*
|
|
24511
|
-
* On EVM, all intents for the same adapter are batched into a
|
|
24512
|
-
* group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
26009
|
+
* On EVM, all intents for the same adapter **and signer** are batched into a
|
|
26010
|
+
* single group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
24513
26011
|
* while signing ERC-1271 intents separately on their source chains.
|
|
24514
26012
|
*
|
|
24515
26013
|
* @param intent - The burn intent to group.
|
|
24516
26014
|
* @param alloc - The allocation that resolved to this intent.
|
|
24517
|
-
* @param evmGroups - Mutable map of adapter → EVM group.
|
|
26015
|
+
* @param evmGroups - Mutable map of `(adapter, sourceSigner)` key → EVM group.
|
|
24518
26016
|
*
|
|
24519
26017
|
* @example
|
|
24520
26018
|
* ```typescript
|
|
@@ -24524,12 +26022,13 @@ async function signUnits(group, signingUnits) {
|
|
|
24524
26022
|
* groupEvmIntents(intent, alloc, groups)
|
|
24525
26023
|
* ```
|
|
24526
26024
|
*/ function groupEvmIntents(intent, alloc, evmGroups) {
|
|
24527
|
-
const
|
|
26025
|
+
const key = groupKey(alloc);
|
|
26026
|
+
const existing = evmGroups.get(key);
|
|
24528
26027
|
if (existing) {
|
|
24529
26028
|
existing.intents.push(intent);
|
|
24530
26029
|
existing.chainsByDomain.set(alloc.chain.gateway.domain, alloc.chain);
|
|
24531
26030
|
} else {
|
|
24532
|
-
evmGroups.set(
|
|
26031
|
+
evmGroups.set(key, {
|
|
24533
26032
|
adapter: alloc.adapter,
|
|
24534
26033
|
chain: alloc.chain,
|
|
24535
26034
|
intents: [
|
|
@@ -24622,7 +26121,7 @@ zod.z.object({
|
|
|
24622
26121
|
signature: evmSignatureSchema,
|
|
24623
26122
|
tokenInputs: zod.z.array(zod.z.object({
|
|
24624
26123
|
permitType: zod.z.nativeEnum(PermitType),
|
|
24625
|
-
token: evmAddressSchema,
|
|
26124
|
+
token: evmAddressSchema$1,
|
|
24626
26125
|
amount: zod.z.bigint().refine((value)=>value >= 0n, {
|
|
24627
26126
|
message: 'amount must be a non-negative bigint'
|
|
24628
26127
|
}),
|
|
@@ -25215,7 +26714,7 @@ function writeBytes32(buffer, hex, offset) {
|
|
|
25215
26714
|
});
|
|
25216
26715
|
|
|
25217
26716
|
/**
|
|
25218
|
-
* Assert that the
|
|
26717
|
+
* Assert that the credential is valid for swap operations.
|
|
25219
26718
|
*
|
|
25220
26719
|
* @remarks
|
|
25221
26720
|
* This validation is called when swap operations are executed, not at construction time.
|
|
@@ -25223,34 +26722,35 @@ function writeBytes32(buffer, hex, offset) {
|
|
|
25223
26722
|
* at usage time rather than construction time.
|
|
25224
26723
|
*
|
|
25225
26724
|
* Validates:
|
|
25226
|
-
* -
|
|
25227
|
-
*
|
|
25228
|
-
*
|
|
26725
|
+
* - The credential matches an accepted format when provided — a Circle API key
|
|
26726
|
+
* (`<ENV>_API_KEY:id:secret`) or a legacy kit key (`KIT_KEY:id:secret`).
|
|
26727
|
+
* An absent or empty credential is permitted (permissionless mode) — the swap
|
|
26728
|
+
* service treats the credential as optional.
|
|
25229
26729
|
*
|
|
25230
|
-
* @param
|
|
25231
|
-
* @throws KitError with VALIDATION_FAILED if a
|
|
25232
|
-
* match
|
|
26730
|
+
* @param apiKey - The inline credential from the swap operation config (optional)
|
|
26731
|
+
* @throws KitError with VALIDATION_FAILED if a credential is provided but does
|
|
26732
|
+
* not match an accepted format
|
|
25233
26733
|
*
|
|
25234
26734
|
* @example
|
|
25235
26735
|
* ```typescript
|
|
25236
|
-
* import {
|
|
26736
|
+
* import { assertApiKey } from './assertApiKey'
|
|
25237
26737
|
*
|
|
25238
|
-
* const
|
|
26738
|
+
* const apiKey = 'TEST_API_KEY:9d1bd085-bad2-4a2b-ae67-5814b85167c9:secretvalue'
|
|
25239
26739
|
*
|
|
25240
|
-
* // Validate
|
|
25241
|
-
*
|
|
26740
|
+
* // Validate the credential before use
|
|
26741
|
+
* assertApiKey(apiKey)
|
|
25242
26742
|
* ```
|
|
25243
|
-
*/ function
|
|
25244
|
-
// Permissionless mode: the swap service treats the
|
|
25245
|
-
// absent (or empty)
|
|
26743
|
+
*/ function assertApiKey(apiKey) {
|
|
26744
|
+
// Permissionless mode: the swap service treats the credential as optional, so
|
|
26745
|
+
// an absent (or empty) value is valid. Only validate the format when one is
|
|
25246
26746
|
// actually provided.
|
|
25247
|
-
if (!
|
|
26747
|
+
if (!apiKey) {
|
|
25248
26748
|
return;
|
|
25249
26749
|
}
|
|
25250
|
-
const apiKeyResult = apiKeySchema.safeParse(
|
|
26750
|
+
const apiKeyResult = apiKeySchema.safeParse(apiKey);
|
|
25251
26751
|
if (!apiKeyResult.success) {
|
|
25252
26752
|
const errors = apiKeyResult.error.issues.map((issue)=>issue.message).join(', ');
|
|
25253
|
-
throw createValidationFailedError$1('
|
|
26753
|
+
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`);
|
|
25254
26754
|
}
|
|
25255
26755
|
}
|
|
25256
26756
|
|
|
@@ -25442,7 +26942,7 @@ const TOKEN_REGISTRY$4 = createTokenRegistry();
|
|
|
25442
26942
|
}
|
|
25443
26943
|
// Validate recipient address format matches the chain type
|
|
25444
26944
|
if (chain.type === 'evm') {
|
|
25445
|
-
const result = evmAddressSchema.safeParse(recipientAddress);
|
|
26945
|
+
const result = evmAddressSchema$1.safeParse(recipientAddress);
|
|
25446
26946
|
if (!result.success) {
|
|
25447
26947
|
throw createInvalidAddressError(recipientAddress, chain.name, '0x-prefixed 42-character hex string (0x + 40 hex characters)');
|
|
25448
26948
|
}
|
|
@@ -25504,7 +27004,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
25504
27004
|
* to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
|
|
25505
27005
|
* config: {
|
|
25506
27006
|
* slippageBps: 300,
|
|
25507
|
-
*
|
|
27007
|
+
* apiKey: 'TEST_API_KEY:...:...' // Inline credential (optional)
|
|
25508
27008
|
* }
|
|
25509
27009
|
* }
|
|
25510
27010
|
*
|
|
@@ -25542,18 +27042,17 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
25542
27042
|
validateResolvedAddress(resolvedTokenInAddress, chain);
|
|
25543
27043
|
validateResolvedAddress(resolvedTokenOutAddress, destinationChain);
|
|
25544
27044
|
validateResolvedAddress(to, destinationChain);
|
|
25545
|
-
const
|
|
25546
|
-
// Validate the
|
|
25547
|
-
|
|
27045
|
+
const apiKey = resolveApiKey(config ?? {});
|
|
27046
|
+
// Validate the credential format when one is provided (permissionless
|
|
27047
|
+
// otherwise).
|
|
27048
|
+
assertApiKey(apiKey);
|
|
25548
27049
|
// Validate custom fee configuration if present
|
|
25549
27050
|
const customFee = config?.customFee;
|
|
25550
27051
|
if (customFee !== undefined) {
|
|
25551
27052
|
const feePayoutChain = resolveFeePayoutChain(tokenIn, tokenOut, chain, destinationChain);
|
|
25552
27053
|
validateCustomFeeConfig(customFee, feePayoutChain);
|
|
25553
27054
|
}
|
|
25554
|
-
// Build CreateSwapParams for service client
|
|
25555
|
-
// Use object spread to conditionally include optional properties
|
|
25556
|
-
// Note: Service client expects 'apiKey' field, so we map kitKey → apiKey
|
|
27055
|
+
// Build CreateSwapParams for service client, spreading optional properties.
|
|
25557
27056
|
const chainName = chain.chain // The API expects the Blockchain enum value format like "World_Chain"
|
|
25558
27057
|
;
|
|
25559
27058
|
const destinationChainName = destinationChain.chain;
|
|
@@ -25588,9 +27087,9 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
25588
27087
|
}
|
|
25589
27088
|
}
|
|
25590
27089
|
},
|
|
25591
|
-
//
|
|
25592
|
-
...
|
|
25593
|
-
apiKey
|
|
27090
|
+
// Omitted in permissionless mode.
|
|
27091
|
+
...apiKey ? {
|
|
27092
|
+
apiKey
|
|
25594
27093
|
} : {}
|
|
25595
27094
|
};
|
|
25596
27095
|
}
|
|
@@ -27494,9 +28993,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
27494
28993
|
* ```typescript
|
|
27495
28994
|
* import { StablecoinServiceSwapProvider } from '@circle-fin/provider-stablecoin-service-swap'
|
|
27496
28995
|
*
|
|
27497
|
-
* // Create provider with developer fee (1 USDC)
|
|
28996
|
+
* // Create provider with developer fee (1 USDC).
|
|
28997
|
+
* // Credentials are supplied per operation via `config.apiKey`, not here.
|
|
27498
28998
|
* const provider = new StablecoinServiceSwapProvider({
|
|
27499
|
-
* kitKey: process.env.STABLECOIN_KIT_API_KEY,
|
|
27500
28999
|
* customFee: {
|
|
27501
29000
|
* amount: '1000000', // 1 USDC in base units (6 decimals)
|
|
27502
29001
|
* recipientAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0'
|
|
@@ -28158,9 +29657,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
28158
29657
|
* import { createAdapterFromPrivateKey } from '@circle-fin/adapter-viem-v2'
|
|
28159
29658
|
* import { Ethereum } from '@core/chains'
|
|
28160
29659
|
*
|
|
28161
|
-
* const provider = new StablecoinServiceSwapProvider(
|
|
28162
|
-
* kitKey: process.env.STABLECOIN_KIT_API_KEY
|
|
28163
|
-
* })
|
|
29660
|
+
* const provider = new StablecoinServiceSwapProvider()
|
|
28164
29661
|
*
|
|
28165
29662
|
* const adapter = createAdapterFromPrivateKey({
|
|
28166
29663
|
* privateKey: process.env.PRIVATE_KEY
|
|
@@ -28654,7 +30151,7 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
28654
30151
|
/**
|
|
28655
30152
|
* Fee recipient address (required).
|
|
28656
30153
|
* Must be a valid EVM address or Solana address.
|
|
28657
|
-
*/ recipientAddress: zod.z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
30154
|
+
*/ recipientAddress: zod.z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
28658
30155
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
28659
30156
|
})
|
|
28660
30157
|
}).strict();
|
|
@@ -28666,7 +30163,8 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
28666
30163
|
* - slippageBps: Optional positive number for slippage tolerance
|
|
28667
30164
|
* - stopLimit: Optional decimal string for minimum output
|
|
28668
30165
|
* - customFee: Optional fee configuration
|
|
28669
|
-
* -
|
|
30166
|
+
* - apiKey: Optional credential string
|
|
30167
|
+
* - kitKey: Optional credential string (deprecated alias for apiKey)
|
|
28670
30168
|
*/ const swapConfigSchema = zod.z.object({
|
|
28671
30169
|
allowanceStrategy: allowanceStrategySchema$1.optional(),
|
|
28672
30170
|
slippageBps: zod.z.number().int().min(0).optional(),
|
|
@@ -28676,11 +30174,12 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
28676
30174
|
attributeName: 'stopLimit'
|
|
28677
30175
|
})(zod.z.string())).optional(),
|
|
28678
30176
|
customFee: swapCustomFeeSchema.optional(),
|
|
30177
|
+
apiKey: zod.z.string().optional(),
|
|
28679
30178
|
kitKey: zod.z.string().optional()
|
|
28680
30179
|
});
|
|
28681
30180
|
const swapDestinationSchema = zod.z.object({
|
|
28682
30181
|
chain: optionalSwapChainIdentifierField,
|
|
28683
|
-
recipientAddress: zod.z.string().refine((value)=>evmAddressSchema.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
30182
|
+
recipientAddress: zod.z.string().refine((value)=>evmAddressSchema$1.safeParse(value).success || solanaAddressSchema.safeParse(value).success, {
|
|
28684
30183
|
message: 'recipientAddress must be a valid blockchain address: EVM (0x + 40 hex chars) or Solana (base58, 32-44 chars)'
|
|
28685
30184
|
}).optional()
|
|
28686
30185
|
}).strict();
|
|
@@ -30787,7 +32286,8 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
30787
32286
|
/**
|
|
30788
32287
|
* Helper to format the config section of a swap result, transforming fee amounts.
|
|
30789
32288
|
*
|
|
30790
|
-
* Strips
|
|
32289
|
+
* Strips the credential fields (`apiKey` and the deprecated `kitKey`) from the
|
|
32290
|
+
* config before returning, so a result is safe to log or serialize.
|
|
30791
32291
|
*
|
|
30792
32292
|
* @param config - The swap config from the provider.
|
|
30793
32293
|
* @param outputTokenTransform - The amount transformer for output-token-denominated values (fees and amountOut).
|
|
@@ -30796,11 +32296,11 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
30796
32296
|
if (!config) {
|
|
30797
32297
|
return undefined;
|
|
30798
32298
|
}
|
|
30799
|
-
//
|
|
30800
|
-
|
|
30801
|
-
|
|
30802
|
-
|
|
30803
|
-
|
|
32299
|
+
// Rebuild the config without any credential field. Filtering entries rather
|
|
32300
|
+
// than deleting them avoids reading the deprecated `kitKey` property
|
|
32301
|
+
// directly, and keeps the credential list in one place.
|
|
32302
|
+
const credentialFields = CREDENTIAL_CONFIG_FIELDS;
|
|
32303
|
+
const cloneConfig = Object.fromEntries(Object.entries(config).filter(([key])=>!credentialFields.includes(key)));
|
|
30804
32304
|
if (Object.keys(cloneConfig).length === 0) {
|
|
30805
32305
|
return undefined;
|
|
30806
32306
|
}
|
|
@@ -31001,9 +32501,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
31001
32501
|
try {
|
|
31002
32502
|
// Step 1: Build quote params directly (no need for buildServiceParams)
|
|
31003
32503
|
// Use chain.chain (Blockchain enum value like "World_Chain") not chain.name
|
|
31004
|
-
// The
|
|
31005
|
-
// fetched without an Authorization header.
|
|
31006
|
-
const
|
|
32504
|
+
// The credential is optional (permissionless mode); when absent the quote
|
|
32505
|
+
// is fetched without an Authorization header.
|
|
32506
|
+
const apiKey = resolveApiKey(params.config ?? {});
|
|
31007
32507
|
// Resolve token aliases to addresses for the quote API
|
|
31008
32508
|
// The quote endpoint requires resolved addresses, not aliases like 'USDC'
|
|
31009
32509
|
const chain = params.from.chain;
|
|
@@ -31028,8 +32528,8 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
31028
32528
|
...params.config?.slippageBps !== undefined && {
|
|
31029
32529
|
slippageBps: params.config.slippageBps
|
|
31030
32530
|
},
|
|
31031
|
-
...
|
|
31032
|
-
apiKey
|
|
32531
|
+
...apiKey ? {
|
|
32532
|
+
apiKey
|
|
31033
32533
|
} : {}
|
|
31034
32534
|
};
|
|
31035
32535
|
// Step 2: Get quote from service
|
|
@@ -31433,7 +32933,7 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31433
32933
|
* the service returns `receivingTokenDecimals`. When decimals are missing
|
|
31434
32934
|
* the field is omitted entirely (never raw base units).
|
|
31435
32935
|
*
|
|
31436
|
-
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `
|
|
32936
|
+
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `apiKey`.
|
|
31437
32937
|
* @returns A single snapshot of the swap's status.
|
|
31438
32938
|
* @throws \{KitError\} If `chainIn` or `chainOut` is malformed.
|
|
31439
32939
|
*
|
|
@@ -31449,7 +32949,7 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31449
32949
|
* txHash: result.txHash,
|
|
31450
32950
|
* chainIn: result.chainIn,
|
|
31451
32951
|
* chainOut: result.chainOut,
|
|
31452
|
-
*
|
|
32952
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31453
32953
|
* })
|
|
31454
32954
|
* while (status.progress.status === 'PENDING') {
|
|
31455
32955
|
* await new Promise((r) => setTimeout(r, 3_000))
|
|
@@ -31457,7 +32957,7 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31457
32957
|
* txHash: result.txHash,
|
|
31458
32958
|
* chainIn: result.chainIn,
|
|
31459
32959
|
* chainOut: result.chainOut,
|
|
31460
|
-
*
|
|
32960
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31461
32961
|
* })
|
|
31462
32962
|
* }
|
|
31463
32963
|
*
|
|
@@ -31475,14 +32975,15 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
31475
32975
|
const chainIn = normalizeChain(params.chainIn, 'chainIn');
|
|
31476
32976
|
const chainOut = params.chainOut === undefined ? undefined : normalizeChain(params.chainOut, 'chainOut');
|
|
31477
32977
|
const isCrossChain = chainOut !== undefined && chainOut !== chainIn;
|
|
32978
|
+
const apiKey = resolveApiKey(params);
|
|
31478
32979
|
const request = {
|
|
31479
32980
|
txHash: params.txHash,
|
|
31480
32981
|
chain: chainIn,
|
|
31481
32982
|
...isCrossChain && {
|
|
31482
32983
|
toChain: chainOut
|
|
31483
32984
|
},
|
|
31484
|
-
...
|
|
31485
|
-
apiKey
|
|
32985
|
+
...apiKey ? {
|
|
32986
|
+
apiKey
|
|
31486
32987
|
} : {}
|
|
31487
32988
|
};
|
|
31488
32989
|
let raw = await getSwapStatus$2(request);
|
|
@@ -31561,9 +33062,9 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31561
33062
|
*
|
|
31562
33063
|
* Two parameter shapes are accepted:
|
|
31563
33064
|
*
|
|
31564
|
-
* - Pass the {@link SwapResult} directly: `{ result,
|
|
33065
|
+
* - Pass the {@link SwapResult} directly: `{ result, apiKey, ... }`.
|
|
31565
33066
|
* Cleanest when chaining off `kit.swap()`.
|
|
31566
|
-
* - Pass discrete fields: `{ txHash, chainIn, chainOut?,
|
|
33067
|
+
* - Pass discrete fields: `{ txHash, chainIn, chainOut?, apiKey, ... }`.
|
|
31567
33068
|
* Useful for resuming an in-flight swap from a persisted record.
|
|
31568
33069
|
*
|
|
31569
33070
|
* @param params - Wait configuration. See {@link WaitForSwapParams}.
|
|
@@ -31583,7 +33084,7 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31583
33084
|
*
|
|
31584
33085
|
* const final = await waitForSwap({
|
|
31585
33086
|
* result,
|
|
31586
|
-
*
|
|
33087
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31587
33088
|
* onProgress: (snapshot) => {
|
|
31588
33089
|
* console.log(snapshot.progress.substatus ?? snapshot.progress.status)
|
|
31589
33090
|
* },
|
|
@@ -31601,7 +33102,7 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31601
33102
|
* txHash: '0x…',
|
|
31602
33103
|
* chainIn: 'Ethereum',
|
|
31603
33104
|
* chainOut: 'Base',
|
|
31604
|
-
*
|
|
33105
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31605
33106
|
* })
|
|
31606
33107
|
* ```
|
|
31607
33108
|
*/ async function waitForSwap$1(params) {
|
|
@@ -31617,14 +33118,15 @@ const isResultShape = (params)=>'result' in params;
|
|
|
31617
33118
|
const chainOut = isResultShape(params) ? params.result.chainOut : params.chainOut;
|
|
31618
33119
|
// Delegate parameter validation (txHash, chainIn, chainOut) to
|
|
31619
33120
|
// getSwapStatus so the same rules apply to both helpers.
|
|
33121
|
+
const apiKey = resolveApiKey(params);
|
|
31620
33122
|
const statusParams = {
|
|
31621
33123
|
txHash,
|
|
31622
33124
|
chainIn,
|
|
31623
33125
|
...chainOut !== undefined && {
|
|
31624
33126
|
chainOut
|
|
31625
33127
|
},
|
|
31626
|
-
...
|
|
31627
|
-
|
|
33128
|
+
...apiKey ? {
|
|
33129
|
+
apiKey
|
|
31628
33130
|
} : {}
|
|
31629
33131
|
};
|
|
31630
33132
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -31748,7 +33250,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31748
33250
|
*
|
|
31749
33251
|
* @param context - SwapKit context (provides the `TokenRegistry` used for
|
|
31750
33252
|
* symbol resolution).
|
|
31751
|
-
* @param params - `chain`, optional `tokens`, and `
|
|
33253
|
+
* @param params - `chain`, optional `tokens`, and `apiKey`.
|
|
31752
33254
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
31753
33255
|
* @throws \{KitError\} If `chain` cannot be resolved, `tokens` exceeds 100
|
|
31754
33256
|
* entries, or any entry is neither a registered symbol nor a
|
|
@@ -31763,7 +33265,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31763
33265
|
* const { rates } = await getTokenRates(context, {
|
|
31764
33266
|
* chain: Blockchain.Ethereum,
|
|
31765
33267
|
* tokens: ['USDC', 'EURC', 'NATIVE'],
|
|
31766
|
-
*
|
|
33268
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
31767
33269
|
* })
|
|
31768
33270
|
*
|
|
31769
33271
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -31784,10 +33286,11 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31784
33286
|
// valid Blockchain enum string, so `getChainByEnum` is guaranteed to resolve.
|
|
31785
33287
|
const chainDef = getChainByEnum(chain);
|
|
31786
33288
|
const resolvedAddresses = params.tokens?.map((entry, index)=>resolveTokenEntry(entry, index, chain, chainDef, context));
|
|
33289
|
+
const apiKey = resolveApiKey(params);
|
|
31787
33290
|
return getTokenRates$2({
|
|
31788
33291
|
chain,
|
|
31789
|
-
...
|
|
31790
|
-
apiKey
|
|
33292
|
+
...apiKey ? {
|
|
33293
|
+
apiKey
|
|
31791
33294
|
} : {},
|
|
31792
33295
|
...resolvedAddresses !== undefined && {
|
|
31793
33296
|
addresses: resolvedAddresses
|
|
@@ -32418,7 +33921,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32418
33921
|
* is formatted as a human-readable decimal string; when decimals are
|
|
32419
33922
|
* missing the field is omitted entirely (never raw base units).
|
|
32420
33923
|
*
|
|
32421
|
-
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `
|
|
33924
|
+
* @param params - `txHash`, `chainIn`, optional `chainOut`, and `apiKey`.
|
|
32422
33925
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
32423
33926
|
* @throws \{KitError\} If `chainIn` or `chainOut` is malformed.
|
|
32424
33927
|
*
|
|
@@ -32431,7 +33934,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32431
33934
|
* txHash: result.txHash,
|
|
32432
33935
|
* chainIn: result.chainIn,
|
|
32433
33936
|
* chainOut: result.chainOut,
|
|
32434
|
-
*
|
|
33937
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32435
33938
|
* })
|
|
32436
33939
|
* console.log(status.progress.status, status.progress.substatus)
|
|
32437
33940
|
* ```
|
|
@@ -32443,7 +33946,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32443
33946
|
* txHash: result.txHash,
|
|
32444
33947
|
* chainIn: result.chainIn,
|
|
32445
33948
|
* chainOut: result.chainOut,
|
|
32446
|
-
*
|
|
33949
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32447
33950
|
* })
|
|
32448
33951
|
* while (status.progress.status === 'PENDING') {
|
|
32449
33952
|
* await new Promise((r) => setTimeout(r, 3_000))
|
|
@@ -32451,7 +33954,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32451
33954
|
* txHash: result.txHash,
|
|
32452
33955
|
* chainIn: result.chainIn,
|
|
32453
33956
|
* chainOut: result.chainOut,
|
|
32454
|
-
*
|
|
33957
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32455
33958
|
* })
|
|
32456
33959
|
* }
|
|
32457
33960
|
*
|
|
@@ -32482,7 +33985,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32482
33985
|
* 5xx, etc.) surface unchanged so the caller can decide whether to
|
|
32483
33986
|
* retry the whole wait. The wait itself only emits a timeout error.
|
|
32484
33987
|
*
|
|
32485
|
-
* @param params - Wait configuration including identifiers,
|
|
33988
|
+
* @param params - Wait configuration including identifiers, API key,
|
|
32486
33989
|
* optional `timeoutMs`, and an optional `onProgress`
|
|
32487
33990
|
* callback fired on every successful poll.
|
|
32488
33991
|
* @returns The first terminal {@link SwapStatusResult} observed.
|
|
@@ -32501,7 +34004,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32501
34004
|
*
|
|
32502
34005
|
* const final = await kit.waitForSwap({
|
|
32503
34006
|
* result,
|
|
32504
|
-
*
|
|
34007
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32505
34008
|
* onProgress: (snap) => console.log(snap.progress.substatus ?? snap.progress.status),
|
|
32506
34009
|
* })
|
|
32507
34010
|
*
|
|
@@ -32519,7 +34022,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32519
34022
|
* txHash: persisted.txHash,
|
|
32520
34023
|
* chainIn: persisted.chainIn,
|
|
32521
34024
|
* chainOut: persisted.chainOut,
|
|
32522
|
-
*
|
|
34025
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32523
34026
|
* })
|
|
32524
34027
|
* ```
|
|
32525
34028
|
*/ async waitForSwap(params) {
|
|
@@ -32553,7 +34056,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32553
34056
|
* not cache a rate for the native asset on that chain, the response simply
|
|
32554
34057
|
* omits the address.
|
|
32555
34058
|
*
|
|
32556
|
-
* @param params - `chain`, optional `tokens`, and `
|
|
34059
|
+
* @param params - `chain`, optional `tokens`, and `apiKey`.
|
|
32557
34060
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
32558
34061
|
* @throws \{KitError\} If `chain` is malformed, `tokens` exceeds 100
|
|
32559
34062
|
* entries, or any entry is neither a registered symbol nor a
|
|
@@ -32565,7 +34068,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32565
34068
|
* const { rates } = await kit.getTokenRates({
|
|
32566
34069
|
* chain: 'Ethereum',
|
|
32567
34070
|
* tokens: ['USDC', 'EURC', 'NATIVE'],
|
|
32568
|
-
*
|
|
34071
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32569
34072
|
* })
|
|
32570
34073
|
*
|
|
32571
34074
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -32579,7 +34082,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32579
34082
|
* chain: 'Ethereum',
|
|
32580
34083
|
* // 'USDC' (registry symbol) + raw USDT address
|
|
32581
34084
|
* tokens: ['USDC', '0xdAC17F958D2ee523a2206206994597C13D831ec7'],
|
|
32582
|
-
*
|
|
34085
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32583
34086
|
* })
|
|
32584
34087
|
* ```
|
|
32585
34088
|
*
|
|
@@ -32588,7 +34091,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
32588
34091
|
* ```typescript
|
|
32589
34092
|
* const { rates } = await kit.getTokenRates({
|
|
32590
34093
|
* chain: 'Base',
|
|
32591
|
-
*
|
|
34094
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
32592
34095
|
* })
|
|
32593
34096
|
* ```
|
|
32594
34097
|
*/ async getTokenRates(params) {
|
|
@@ -32950,7 +34453,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
32950
34453
|
};
|
|
32951
34454
|
|
|
32952
34455
|
var name$2 = "@circle-fin/earn-kit";
|
|
32953
|
-
var version$2 = "1.
|
|
34456
|
+
var version$2 = "1.6.0";
|
|
32954
34457
|
var pkg$2 = {
|
|
32955
34458
|
name: name$2,
|
|
32956
34459
|
version: version$2};
|
|
@@ -33166,7 +34669,7 @@ function toSdkChain(chain) {
|
|
|
33166
34669
|
*
|
|
33167
34670
|
* @internal
|
|
33168
34671
|
*/ function assertHexAddress(field, value, message = `${field} must be a 0x-prefixed 20-byte hex address`) {
|
|
33169
|
-
const result = evmAddressSchema.safeParse(value);
|
|
34672
|
+
const result = evmAddressSchema$1.safeParse(value);
|
|
33170
34673
|
if (!result.success) {
|
|
33171
34674
|
throw createValidationFailedError$1(field, value, message);
|
|
33172
34675
|
}
|
|
@@ -34852,7 +36355,7 @@ const EARN_OPERATIONS = new Set([
|
|
|
34852
36355
|
// Store the original service params non-enumerably. `retry()` reads them by
|
|
34853
36356
|
// direct property access (`trace.params`), but JSON.stringify, console.log,
|
|
34854
36357
|
// and telemetry serializers skip non-enumerable properties — so a
|
|
34855
|
-
// permissioned call's `config.
|
|
36358
|
+
// permissioned call's `config.apiKey` (a credential value) and
|
|
34856
36359
|
// the in-memory adapter never leak through a logged error. defineProperty
|
|
34857
36360
|
// also redefines any enumerable `params` an upstream trace may have carried.
|
|
34858
36361
|
Object.defineProperty(trace, 'params', {
|
|
@@ -34923,7 +36426,7 @@ function isNonNegativeBigIntLike(value) {
|
|
|
34923
36426
|
}
|
|
34924
36427
|
}
|
|
34925
36428
|
const hexSignatureSchema = evmSignatureSchema;
|
|
34926
|
-
const hexAddressSchema = evmAddressSchema;
|
|
36429
|
+
const hexAddressSchema = evmAddressSchema$1;
|
|
34927
36430
|
// '0x' prefix + 32 bytes * 2 hex chars.
|
|
34928
36431
|
const BYTES32_HEX_LENGTH = 66;
|
|
34929
36432
|
const bridgeFeeTokenSchema = hexAddressSchema;
|
|
@@ -35892,7 +37395,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
35892
37395
|
}
|
|
35893
37396
|
|
|
35894
37397
|
var name$1 = "@circle-fin/provider-earn-service";
|
|
35895
|
-
var version$1 = "1.
|
|
37398
|
+
var version$1 = "1.5.0";
|
|
35896
37399
|
var pkg$1 = {
|
|
35897
37400
|
name: name$1,
|
|
35898
37401
|
version: version$1};
|
|
@@ -35954,10 +37457,12 @@ var pkg$1 = {
|
|
|
35954
37457
|
*
|
|
35955
37458
|
* @internal
|
|
35956
37459
|
*/ function buildConfig(serviceConfig) {
|
|
35957
|
-
|
|
35958
|
-
//
|
|
35959
|
-
|
|
35960
|
-
|
|
37460
|
+
const apiKey = resolveApiKey(serviceConfig ?? {});
|
|
37461
|
+
// The credential is a server-only secret. Reject it in the browser so it
|
|
37462
|
+
// cannot leak into a client bundle (no-op in Node.js). Keyless usage stays
|
|
37463
|
+
// allowed.
|
|
37464
|
+
if (apiKey !== undefined && isBrowserEnvironment()) {
|
|
37465
|
+
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');
|
|
35961
37466
|
}
|
|
35962
37467
|
const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
|
|
35963
37468
|
// The API CORS policy does not allow this custom header. Keep the existing
|
|
@@ -35966,7 +37471,7 @@ var pkg$1 = {
|
|
|
35966
37471
|
const sdkVersionHeader = isNodeEnvironment() ? {
|
|
35967
37472
|
[SDK_VERSION_HEADER]: resolveSdkVersionHeader()
|
|
35968
37473
|
} : {};
|
|
35969
|
-
if (
|
|
37474
|
+
if (apiKey === undefined) {
|
|
35970
37475
|
return {
|
|
35971
37476
|
pollingConfig: {
|
|
35972
37477
|
...DEFAULT_CONFIG,
|
|
@@ -35978,11 +37483,11 @@ var pkg$1 = {
|
|
|
35978
37483
|
baseUrl
|
|
35979
37484
|
};
|
|
35980
37485
|
}
|
|
35981
|
-
if (!isValidApiKey(
|
|
37486
|
+
if (!isValidApiKey(apiKey)) {
|
|
35982
37487
|
throw new KitError({
|
|
35983
37488
|
...InputError.VALIDATION_FAILED,
|
|
35984
37489
|
recoverability: 'FATAL',
|
|
35985
|
-
message: 'Invalid
|
|
37490
|
+
message: 'Invalid apiKey format. Expected <ENV>_API_KEY:<keyId>:<keySecret> (for example TEST_API_KEY:...), or a legacy KIT_KEY:<keyId>:<keySecret>.'
|
|
35986
37491
|
});
|
|
35987
37492
|
}
|
|
35988
37493
|
return {
|
|
@@ -35991,7 +37496,7 @@ var pkg$1 = {
|
|
|
35991
37496
|
headers: {
|
|
35992
37497
|
...DEFAULT_CONFIG.headers,
|
|
35993
37498
|
...sdkVersionHeader,
|
|
35994
|
-
Authorization: `Bearer ${
|
|
37499
|
+
Authorization: `Bearer ${apiKey}`
|
|
35995
37500
|
}
|
|
35996
37501
|
},
|
|
35997
37502
|
baseUrl
|
|
@@ -37308,9 +38813,9 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
37308
38813
|
* // Permissionless mode
|
|
37309
38814
|
* const provider = new EarnServiceProvider()
|
|
37310
38815
|
*
|
|
37311
|
-
* // Permissioned mode with
|
|
38816
|
+
* // Permissioned mode with an API key
|
|
37312
38817
|
* const provider = new EarnServiceProvider({
|
|
37313
|
-
*
|
|
38818
|
+
* apiKey: 'TEST_API_KEY:keyId:keySecret',
|
|
37314
38819
|
* })
|
|
37315
38820
|
*
|
|
37316
38821
|
* // Custom base URL for testing
|
|
@@ -38537,7 +40042,7 @@ function findProvider(context, chain, operation = 'earn') {
|
|
|
38537
40042
|
* fast instead of round-tripping to the service.
|
|
38538
40043
|
*
|
|
38539
40044
|
* @internal
|
|
38540
|
-
*/ 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');
|
|
40045
|
+
*/ 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');
|
|
38541
40046
|
/**
|
|
38542
40047
|
* Schema for the adapter context within earn operations.
|
|
38543
40048
|
*
|
|
@@ -38569,19 +40074,39 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
38569
40074
|
/**
|
|
38570
40075
|
* Schema for the EarnConfig options.
|
|
38571
40076
|
*
|
|
38572
|
-
* Validate the
|
|
38573
|
-
*
|
|
38574
|
-
*
|
|
38575
|
-
*
|
|
38576
|
-
*
|
|
38577
|
-
*
|
|
40077
|
+
* Validate the *resolved* credential using the standard `apiKeySchema` format
|
|
40078
|
+
* (`<ENV>_API_KEY:<keyId>:<keySecret>`, or a legacy
|
|
40079
|
+
* `KIT_KEY:<keyId>:<keySecret>`). `apiKey` takes precedence over the deprecated
|
|
40080
|
+
* `kitKey`, so a malformed `kitKey` that is being ignored must not fail a config
|
|
40081
|
+
* that supplies a valid `apiKey` (and vice versa) — only the credential that
|
|
40082
|
+
* would actually be sent is format-checked. When neither is supplied the SDK
|
|
40083
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service endpoint
|
|
40084
|
+
* (e.g. staging); `batchTransactions: false` opts out of atomic batched
|
|
40085
|
+
* execution. All are forwarded to the provider, so this `.strict()` schema must
|
|
40086
|
+
* accept them or a valid config object is rejected.
|
|
38578
40087
|
*
|
|
38579
40088
|
* @internal
|
|
38580
40089
|
*/ const earnConfigSchema = zod.z.object({
|
|
38581
|
-
|
|
40090
|
+
apiKey: zod.z.string().optional(),
|
|
40091
|
+
kitKey: zod.z.string().optional(),
|
|
38582
40092
|
baseUrl: zod.z.string().optional(),
|
|
38583
40093
|
batchTransactions: zod.z.boolean().optional()
|
|
38584
|
-
}).strict()
|
|
40094
|
+
}).strict().superRefine((config, ctx)=>{
|
|
40095
|
+
const credential = resolveApiKey(config);
|
|
40096
|
+
if (credential === undefined) {
|
|
40097
|
+
return;
|
|
40098
|
+
}
|
|
40099
|
+
const result = apiKeySchema.safeParse(credential);
|
|
40100
|
+
if (!result.success) {
|
|
40101
|
+
ctx.addIssue({
|
|
40102
|
+
code: zod.z.ZodIssueCode.custom,
|
|
40103
|
+
path: [
|
|
40104
|
+
credential === config.apiKey ? 'apiKey' : 'kitKey'
|
|
40105
|
+
],
|
|
40106
|
+
message: result.error.issues[0]?.message ?? 'Invalid API key format'
|
|
40107
|
+
});
|
|
40108
|
+
}
|
|
40109
|
+
});
|
|
38585
40110
|
/**
|
|
38586
40111
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|
|
38587
40112
|
* '0' is only allowed immediately before the decimal point). Rejects the
|
|
@@ -38653,7 +40178,7 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
38653
40178
|
* currently supports EVM vault addresses on Arc Testnet.
|
|
38654
40179
|
*
|
|
38655
40180
|
* @internal
|
|
38656
|
-
*/ const vaultAddressSchema = evmAddressSchema.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
40181
|
+
*/ const vaultAddressSchema = evmAddressSchema$1.refine(isValidEip55Checksum, 'address has an invalid EIP-55 checksum');
|
|
38657
40182
|
/**
|
|
38658
40183
|
* Validation schema for VaultQuery.
|
|
38659
40184
|
*
|
|
@@ -38928,7 +40453,7 @@ const sameChainGetDepositQuoteParamsSchema = zod.z.object({
|
|
|
38928
40453
|
const crossChainGetDepositQuoteParamsSchema = zod.z.object({
|
|
38929
40454
|
from: sourceAdapterContextSchema,
|
|
38930
40455
|
chain: earnBridgeDestinationChainIdentifierSchema,
|
|
38931
|
-
address: evmAddressSchema,
|
|
40456
|
+
address: evmAddressSchema$1,
|
|
38932
40457
|
vaultAddress: vaultAddressSchema,
|
|
38933
40458
|
amount: amountSchema$1,
|
|
38934
40459
|
transferSpeed: zod.z.enum([
|
|
@@ -41244,7 +42769,7 @@ const tokens = createTokenRegistry();
|
|
|
41244
42769
|
*
|
|
41245
42770
|
* @param context - AppKit context.
|
|
41246
42771
|
* @param params - `txHash` and `chainIn`, plus optional `chainOut` and
|
|
41247
|
-
* `
|
|
42772
|
+
* `apiKey`.
|
|
41248
42773
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
41249
42774
|
*
|
|
41250
42775
|
* @example
|
|
@@ -41258,7 +42783,7 @@ const tokens = createTokenRegistry();
|
|
|
41258
42783
|
* txHash: result.txHash,
|
|
41259
42784
|
* chainIn: result.chainIn,
|
|
41260
42785
|
* chainOut: result.chainOut,
|
|
41261
|
-
*
|
|
42786
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
41262
42787
|
* })
|
|
41263
42788
|
*
|
|
41264
42789
|
* console.log(status.progress.status, status.progress.substatus)
|
|
@@ -41281,7 +42806,7 @@ const tokens = createTokenRegistry();
|
|
|
41281
42806
|
* `timeoutMs` elapses.
|
|
41282
42807
|
*
|
|
41283
42808
|
* @param context - AppKit context.
|
|
41284
|
-
* @param params - Wait configuration, including identifiers,
|
|
42809
|
+
* @param params - Wait configuration, including identifiers, API key,
|
|
41285
42810
|
* optional `timeoutMs`, and an optional `onProgress`
|
|
41286
42811
|
* callback.
|
|
41287
42812
|
* @returns The first terminal {@link SwapStatusResult} observed.
|
|
@@ -41297,7 +42822,7 @@ const tokens = createTokenRegistry();
|
|
|
41297
42822
|
* txHash: result.txHash,
|
|
41298
42823
|
* chainIn: result.chainIn,
|
|
41299
42824
|
* chainOut: result.chainOut,
|
|
41300
|
-
*
|
|
42825
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
41301
42826
|
* onProgress: (snap) => console.log(snap.progress.status),
|
|
41302
42827
|
* })
|
|
41303
42828
|
*
|
|
@@ -41333,7 +42858,7 @@ const tokens = createTokenRegistry();
|
|
|
41333
42858
|
* addresses before indexing into `result.rates[chain]`.
|
|
41334
42859
|
*
|
|
41335
42860
|
* @param context - AppKit context.
|
|
41336
|
-
* @param params - `chain`, plus optional `tokens` and `
|
|
42861
|
+
* @param params - `chain`, plus optional `tokens` and `apiKey`.
|
|
41337
42862
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
41338
42863
|
*
|
|
41339
42864
|
* @example
|
|
@@ -41344,7 +42869,7 @@ const tokens = createTokenRegistry();
|
|
|
41344
42869
|
* const { rates } = await kit.getTokenRates({
|
|
41345
42870
|
* chain: 'Ethereum',
|
|
41346
42871
|
* tokens: ['USDC'],
|
|
41347
|
-
*
|
|
42872
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
41348
42873
|
* })
|
|
41349
42874
|
*
|
|
41350
42875
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -41355,6 +42880,43 @@ const tokens = createTokenRegistry();
|
|
|
41355
42880
|
return kit.getTokenRates(params);
|
|
41356
42881
|
};
|
|
41357
42882
|
|
|
42883
|
+
const BRIDGE_FILTER_KEYS = new Set([
|
|
42884
|
+
'chainType',
|
|
42885
|
+
'isTestnet',
|
|
42886
|
+
'forwarderSupported',
|
|
42887
|
+
'sourceFeeSupported'
|
|
42888
|
+
]);
|
|
42889
|
+
function assertBridgeSupportedChainsOptions(options) {
|
|
42890
|
+
if (options === undefined) {
|
|
42891
|
+
return;
|
|
42892
|
+
}
|
|
42893
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options)) {
|
|
42894
|
+
throw createValidationFailedError$1('options', options, 'Bridge chain filters must be an object');
|
|
42895
|
+
}
|
|
42896
|
+
const entries = Object.entries(options);
|
|
42897
|
+
if (entries.length === 0 || entries.some(([key])=>!BRIDGE_FILTER_KEYS.has(key))) {
|
|
42898
|
+
throw createValidationFailedError$1('options', options, 'Bridge chain filters must contain only supported filter properties');
|
|
42899
|
+
}
|
|
42900
|
+
const record = options;
|
|
42901
|
+
for (const key of [
|
|
42902
|
+
'isTestnet',
|
|
42903
|
+
'forwarderSupported',
|
|
42904
|
+
'sourceFeeSupported'
|
|
42905
|
+
]){
|
|
42906
|
+
if (record[key] !== undefined && typeof record[key] !== 'boolean') {
|
|
42907
|
+
throw createValidationFailedError$1(`options.${key}`, record[key], 'Bridge chain boolean filters must be true or false');
|
|
42908
|
+
}
|
|
42909
|
+
}
|
|
42910
|
+
const chainType = record['chainType'];
|
|
42911
|
+
if (chainType !== undefined) {
|
|
42912
|
+
const chainTypes = Array.isArray(chainType) ? chainType : [
|
|
42913
|
+
chainType
|
|
42914
|
+
];
|
|
42915
|
+
if (chainTypes.length === 0 || chainTypes.some((value)=>value !== 'evm' && value !== 'solana')) {
|
|
42916
|
+
throw createValidationFailedError$1('options.chainType', chainType, "Bridge chain type must be 'evm', 'solana', or an array of those values");
|
|
42917
|
+
}
|
|
42918
|
+
}
|
|
42919
|
+
}
|
|
41358
42920
|
/**
|
|
41359
42921
|
* Get chains supported by AppKit operations.
|
|
41360
42922
|
*
|
|
@@ -41369,6 +42931,7 @@ const tokens = createTokenRegistry();
|
|
|
41369
42931
|
* @param context - AppKit context containing fee configuration.
|
|
41370
42932
|
* @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance').
|
|
41371
42933
|
* @param unifiedBalance - Persistent AppKitUnifiedBalance namespace instance.
|
|
42934
|
+
* @param bridgeOptions - Optional Bridge Kit chain filters. Requires `operationType: 'bridge'`.
|
|
41372
42935
|
* @returns Array of unique chain definitions supporting the specified operation(s)
|
|
41373
42936
|
*
|
|
41374
42937
|
* @example Get all supported chains via AppKit
|
|
@@ -41387,6 +42950,13 @@ const tokens = createTokenRegistry();
|
|
|
41387
42950
|
* console.log('Chains supporting bridge:', bridgeChains.map(c => c.name))
|
|
41388
42951
|
* ```
|
|
41389
42952
|
*
|
|
42953
|
+
* @example Get chains that support source-paid bridge fees
|
|
42954
|
+
* ```typescript
|
|
42955
|
+
* const sourceFeeChains = kit.getSupportedChains('bridge', {
|
|
42956
|
+
* sourceFeeSupported: true,
|
|
42957
|
+
* })
|
|
42958
|
+
* ```
|
|
42959
|
+
*
|
|
41390
42960
|
* @example Get swap-specific chains
|
|
41391
42961
|
* ```typescript
|
|
41392
42962
|
* const swapChains = kit.getSupportedChains('swap')
|
|
@@ -41407,7 +42977,8 @@ const tokens = createTokenRegistry();
|
|
|
41407
42977
|
* const supportsEthBridge = bridgeChains.some(c => c.chain === Ethereum.chain)
|
|
41408
42978
|
* console.log(`Ethereum supports bridge: ${supportsEthBridge}`)
|
|
41409
42979
|
* ```
|
|
41410
|
-
*/ const getSupportedChains$2 = (context, operationType, unifiedBalance)=>{
|
|
42980
|
+
*/ const getSupportedChains$2 = (context, operationType, unifiedBalance, bridgeOptions)=>{
|
|
42981
|
+
assertBridgeSupportedChainsOptions(bridgeOptions);
|
|
41411
42982
|
// Runtime validation when operationType is provided
|
|
41412
42983
|
if (operationType !== undefined && operationType !== 'bridge' && operationType !== 'swap' && operationType !== 'earn' && operationType !== 'unifiedBalance') {
|
|
41413
42984
|
throw new KitError({
|
|
@@ -41416,10 +42987,13 @@ const tokens = createTokenRegistry();
|
|
|
41416
42987
|
message: `Invalid operationType: "${String(operationType)}". Expected 'bridge', 'swap', 'earn', or 'unifiedBalance'.`
|
|
41417
42988
|
});
|
|
41418
42989
|
}
|
|
42990
|
+
if (bridgeOptions !== undefined && operationType !== 'bridge') {
|
|
42991
|
+
throw createValidationFailedError$1('options', bridgeOptions, "Bridge chain filters require operationType 'bridge'");
|
|
42992
|
+
}
|
|
41419
42993
|
// Return bridge-specific chains
|
|
41420
42994
|
if (operationType === 'bridge') {
|
|
41421
42995
|
const bridgeKit = createBridgeKit(context);
|
|
41422
|
-
return bridgeKit.getSupportedChains();
|
|
42996
|
+
return bridgeKit.getSupportedChains(bridgeOptions);
|
|
41423
42997
|
}
|
|
41424
42998
|
// Return swap-specific chains
|
|
41425
42999
|
if (operationType === 'swap') {
|
|
@@ -41757,7 +43331,7 @@ async function deposit$2(context, params) {
|
|
|
41757
43331
|
}
|
|
41758
43332
|
|
|
41759
43333
|
var name = "@circle-fin/unified-balance-kit";
|
|
41760
|
-
var version = "1.
|
|
43334
|
+
var version = "1.5.0";
|
|
41761
43335
|
var pkg = {
|
|
41762
43336
|
name: name,
|
|
41763
43337
|
version: version};
|
|
@@ -42590,6 +44164,8 @@ const gatewayInfoSchema = zod.z.object({
|
|
|
42590
44164
|
* Fetch Gateway info from the `/v1/info` endpoint.
|
|
42591
44165
|
*
|
|
42592
44166
|
* @param isTestnet - Whether to query the testnet or mainnet API.
|
|
44167
|
+
* @param config - Optional polling config overrides, including custom
|
|
44168
|
+
* `headers` forwarded with the request.
|
|
42593
44169
|
* @returns Parsed Gateway info including per-domain processed heights.
|
|
42594
44170
|
* @throws KitError on HTTP failure or invalid response.
|
|
42595
44171
|
*
|
|
@@ -42598,11 +44174,11 @@ const gatewayInfoSchema = zod.z.object({
|
|
|
42598
44174
|
* const info = await fetchGatewayInfo(true)
|
|
42599
44175
|
* console.log(info.domains.map(d => `${d.chain}: ${d.processedHeight}`))
|
|
42600
44176
|
* ```
|
|
42601
|
-
*/ async function fetchGatewayInfo(isTestnet) {
|
|
44177
|
+
*/ async function fetchGatewayInfo(isTestnet, config) {
|
|
42602
44178
|
const apiBaseUrl = getGatewayApiBaseUrl(isTestnet);
|
|
42603
44179
|
const url = `${apiBaseUrl}/v1/info`;
|
|
42604
44180
|
try {
|
|
42605
|
-
return await pollApiGet(url, assertGatewayInfoResponse);
|
|
44181
|
+
return await pollApiGet(url, assertGatewayInfoResponse, config);
|
|
42606
44182
|
} catch (error) {
|
|
42607
44183
|
if (error instanceof KitError) {
|
|
42608
44184
|
throw error;
|
|
@@ -42628,6 +44204,8 @@ const gatewayInfoSchema = zod.z.object({
|
|
|
42628
44204
|
*
|
|
42629
44205
|
* @param isTestnet - Whether to query the testnet or mainnet API.
|
|
42630
44206
|
* @param domain - The Gateway domain number (e.g. 0 = Ethereum, 6 = Base).
|
|
44207
|
+
* @param config - Optional polling config overrides, including custom
|
|
44208
|
+
* `headers` forwarded with the `/v1/info` request.
|
|
42631
44209
|
* @returns The processed height as a bigint.
|
|
42632
44210
|
* @throws KitError when the domain is not found in the response.
|
|
42633
44211
|
*
|
|
@@ -42636,10 +44214,10 @@ const gatewayInfoSchema = zod.z.object({
|
|
|
42636
44214
|
* const height = await getProcessedHeight(true, 6) // Base Sepolia
|
|
42637
44215
|
* console.log(`Gateway processed up to block ${height}`)
|
|
42638
44216
|
* ```
|
|
42639
|
-
*/ async function getProcessedHeight(isTestnet, domain) {
|
|
44217
|
+
*/ async function getProcessedHeight(isTestnet, domain, config) {
|
|
42640
44218
|
// No caching in v1 — /v1/info is cheap and delegate status checks
|
|
42641
44219
|
// are infrequent. Revisit if rate-limit or latency issues appear.
|
|
42642
|
-
const info = await fetchGatewayInfo(isTestnet);
|
|
44220
|
+
const info = await fetchGatewayInfo(isTestnet, config);
|
|
42643
44221
|
const entry = info.domains.find((d)=>d.domain === domain);
|
|
42644
44222
|
if (!entry) {
|
|
42645
44223
|
throw new KitError({
|
|
@@ -43105,12 +44683,22 @@ function throwNetworkMismatch(expected, actual) {
|
|
|
43105
44683
|
* @param allocations - Normalized allocations used to map domain → adapter/chain.
|
|
43106
44684
|
* @returns Array of groups, each with adapter, chain, and intents to sign.
|
|
43107
44685
|
*/ function groupIntentsByAdapter(intents, allocations) {
|
|
44686
|
+
// Keyed by `(adapter, sourceSigner)` (see groupEvmIntents), not by adapter
|
|
44687
|
+
// alone: two spend sources on the same chain — including two addresses behind
|
|
44688
|
+
// one shared adapter — must stay in separate signing groups.
|
|
43108
44689
|
const evmGroups = new Map();
|
|
43109
44690
|
const solanaGroups = [];
|
|
43110
44691
|
for (const intent of intents){
|
|
43111
|
-
|
|
44692
|
+
// Match each intent back to its source allocation by BOTH Gateway domain
|
|
44693
|
+
// and signer. Two spend sources on the same chain share a domain, so
|
|
44694
|
+
// matching on domain alone always returned the FIRST allocation — folding
|
|
44695
|
+
// the second wallet's intent into the first wallet's signing group so it
|
|
44696
|
+
// was signed by the wrong wallet (DEVX-3638). specs carry sourceSigner as
|
|
44697
|
+
// bytes32 and allocations carry native addresses, so normalize both.
|
|
44698
|
+
const intentSigner = intent.spec.sourceSigner.toLowerCase();
|
|
44699
|
+
const alloc = allocations.find((a)=>a.chain.gateway.domain === intent.spec.sourceDomain && convertAddress(a.sourceSigner, 'bytes32').toLowerCase() === intentSigner);
|
|
43112
44700
|
if (!alloc) {
|
|
43113
|
-
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');
|
|
44701
|
+
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');
|
|
43114
44702
|
}
|
|
43115
44703
|
if (alloc.chain.type === 'solana') {
|
|
43116
44704
|
solanaGroups.push(groupSolanaIntents(intent, alloc));
|
|
@@ -45011,12 +46599,12 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45011
46599
|
* exceeds the 5-minute timeout without reaching a terminal status.
|
|
45012
46600
|
* @throws KitError Propagated from {@link pollApiGet} if the HTTP
|
|
45013
46601
|
* request itself fails or returns a non-OK response.
|
|
45014
|
-
*/ async function pollForwarderCompletion(apiBaseUrl, transferId) {
|
|
46602
|
+
*/ async function pollForwarderCompletion(apiBaseUrl, transferId, requestConfig) {
|
|
45015
46603
|
const startTime = Date.now();
|
|
45016
46604
|
while(Date.now() - startTime < FORWARDER_POLL_TIMEOUT_MS){
|
|
45017
46605
|
let status;
|
|
45018
46606
|
try {
|
|
45019
|
-
status = await pollApiGet(`${apiBaseUrl}/v1/transfer/${transferId}`, assertGatewayTransferDetails);
|
|
46607
|
+
status = await pollApiGet(`${apiBaseUrl}/v1/transfer/${transferId}`, assertGatewayTransferDetails, requestConfig);
|
|
45020
46608
|
} catch (error) {
|
|
45021
46609
|
throwGatewayApiError(error);
|
|
45022
46610
|
}
|
|
@@ -45134,7 +46722,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45134
46722
|
* @param params - Spend parameters (source(s) and token).
|
|
45135
46723
|
* @param destChain - Resolved destination chain (used for network type).
|
|
45136
46724
|
* @returns Confirmed balances tagged with their originating source index.
|
|
45137
|
-
*/ async function fetchChainBalances(params, destChain) {
|
|
46725
|
+
*/ async function fetchChainBalances(params, destChain, requestConfig) {
|
|
45138
46726
|
const rawSources = Array.isArray(params.from) ? params.from : [
|
|
45139
46727
|
params.from
|
|
45140
46728
|
];
|
|
@@ -45163,7 +46751,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45163
46751
|
token: params.token,
|
|
45164
46752
|
sources: querySource,
|
|
45165
46753
|
networkType
|
|
45166
|
-
});
|
|
46754
|
+
}, requestConfig);
|
|
45167
46755
|
}));
|
|
45168
46756
|
const chainBalances = [];
|
|
45169
46757
|
for(let i = 0; i < balanceResults.length; i++){
|
|
@@ -45236,9 +46824,9 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45236
46824
|
* @param recipientAddress - Resolved recipient address.
|
|
45237
46825
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45238
46826
|
* @returns Allocations, intents, and (auto-allocation only) confirmed balances.
|
|
45239
|
-
*/ async function resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder) {
|
|
46827
|
+
*/ async function resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder, requestConfig) {
|
|
45240
46828
|
if (params.amountIn) {
|
|
45241
|
-
const chainBalances = await fetchChainBalances(params, destChain);
|
|
46829
|
+
const chainBalances = await fetchChainBalances(params, destChain, requestConfig);
|
|
45242
46830
|
const { allocations, intents } = await buildAutoAllocatedFromBalances(params, destChain, recipientAddress, useForwarder, params.amountIn, chainBalances);
|
|
45243
46831
|
return {
|
|
45244
46832
|
allocations,
|
|
@@ -45261,11 +46849,11 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45261
46849
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45262
46850
|
* @param allocations - Normalised allocations (forwarded to error handler).
|
|
45263
46851
|
* @returns Estimate entries and optional forwarding fee.
|
|
45264
|
-
*/ async function fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations) {
|
|
46852
|
+
*/ async function fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations, requestConfig) {
|
|
45265
46853
|
if (useForwarder) {
|
|
45266
46854
|
let fwdResponse;
|
|
45267
46855
|
try {
|
|
45268
|
-
fwdResponse = await pollApiPost(`${apiBaseUrl}/v1/estimate?enableForwarder=true`, estimateBody, assertGatewayForwarderEstimateResponse);
|
|
46856
|
+
fwdResponse = await pollApiPost(`${apiBaseUrl}/v1/estimate?enableForwarder=true`, estimateBody, assertGatewayForwarderEstimateResponse, requestConfig);
|
|
45269
46857
|
} catch (error) {
|
|
45270
46858
|
throwGatewayApiError(error, allocations);
|
|
45271
46859
|
}
|
|
@@ -45276,7 +46864,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45276
46864
|
}
|
|
45277
46865
|
let entries;
|
|
45278
46866
|
try {
|
|
45279
|
-
entries = await pollApiPost(`${apiBaseUrl}/v1/estimate`, estimateBody, assertGatewayEstimateResponse);
|
|
46867
|
+
entries = await pollApiPost(`${apiBaseUrl}/v1/estimate`, estimateBody, assertGatewayEstimateResponse, requestConfig);
|
|
45280
46868
|
} catch (error) {
|
|
45281
46869
|
throwGatewayApiError(error, allocations);
|
|
45282
46870
|
}
|
|
@@ -45347,14 +46935,14 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45347
46935
|
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
45348
46936
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45349
46937
|
* @returns Estimated intents (with real maxFee) and optional forwarding fee.
|
|
45350
|
-
*/ async function validateAndEstimate(allocations, intents, destChain, useForwarder) {
|
|
46938
|
+
*/ async function validateAndEstimate(allocations, intents, destChain, useForwarder, requestConfig) {
|
|
45351
46939
|
assertNetworkCompatibility(allocations, destChain);
|
|
45352
46940
|
if (useForwarder) {
|
|
45353
46941
|
assertForwarderRouteSupport(destChain, allocations);
|
|
45354
46942
|
}
|
|
45355
46943
|
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
45356
46944
|
const estimateBody = buildEstimateRequestBody(intents);
|
|
45357
|
-
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations);
|
|
46945
|
+
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations, requestConfig);
|
|
45358
46946
|
const estimatedIntents = parseEstimateResponse(entries, intents);
|
|
45359
46947
|
return {
|
|
45360
46948
|
estimatedIntents,
|
|
@@ -45413,7 +47001,7 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45413
47001
|
}
|
|
45414
47002
|
throw err;
|
|
45415
47003
|
}
|
|
45416
|
-
const { estimatedIntents, forwardingFee } = await validateAndEstimate(corrected.allocations, corrected.intents, opts.destChain, opts.useForwarder);
|
|
47004
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(corrected.allocations, corrected.intents, opts.destChain, opts.useForwarder, opts.requestConfig);
|
|
45417
47005
|
const stillOverdrawn = findOverdrawnChains(estimatedIntents, corrected.allocations, opts.chainBalances);
|
|
45418
47006
|
if (stillOverdrawn.length === 0) {
|
|
45419
47007
|
return {
|
|
@@ -45447,12 +47035,12 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45447
47035
|
* @param recipientAddress - Resolved recipient address.
|
|
45448
47036
|
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
45449
47037
|
* @returns Allocations, estimated intents, and optional forwarding fee.
|
|
45450
|
-
*/ async function resolveValidatedIntents(params, destChain, recipientAddress, useForwarder) {
|
|
47038
|
+
*/ async function resolveValidatedIntents(params, destChain, recipientAddress, useForwarder, requestConfig) {
|
|
45451
47039
|
if (useForwarder) {
|
|
45452
47040
|
assertForwarderRouteSupport(destChain);
|
|
45453
47041
|
}
|
|
45454
|
-
const { allocations, intents, chainBalances } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder);
|
|
45455
|
-
const { estimatedIntents, forwardingFee } = await validateAndEstimate(allocations, intents, destChain, useForwarder);
|
|
47042
|
+
const { allocations, intents, chainBalances } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder, requestConfig);
|
|
47043
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(allocations, intents, destChain, useForwarder, requestConfig);
|
|
45456
47044
|
if (params.amountIn && chainBalances) {
|
|
45457
47045
|
const overdrawn = findOverdrawnChains(estimatedIntents, allocations, chainBalances);
|
|
45458
47046
|
if (overdrawn.length > 0) {
|
|
@@ -45465,7 +47053,10 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45465
47053
|
chainBalances,
|
|
45466
47054
|
estimatedIntents,
|
|
45467
47055
|
allocations,
|
|
45468
|
-
overdrawn
|
|
47056
|
+
overdrawn,
|
|
47057
|
+
...requestConfig ? {
|
|
47058
|
+
requestConfig
|
|
47059
|
+
} : {}
|
|
45469
47060
|
});
|
|
45470
47061
|
}
|
|
45471
47062
|
}
|
|
@@ -45509,10 +47100,10 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
45509
47100
|
attestationSignature: transferResponse.signature
|
|
45510
47101
|
};
|
|
45511
47102
|
}
|
|
45512
|
-
async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, steps) {
|
|
47103
|
+
async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, steps, requestConfig) {
|
|
45513
47104
|
const chainName = destChain.name;
|
|
45514
47105
|
const recipientAddress = await resolveRecipientAddress(params.to);
|
|
45515
|
-
const { allocations, estimatedIntents, forwardingFee: estimateFwdFee } = await resolveValidatedIntents(params, destChain, recipientAddress, useForwarder);
|
|
47106
|
+
const { allocations, estimatedIntents, forwardingFee: estimateFwdFee } = await resolveValidatedIntents(params, destChain, recipientAddress, useForwarder, requestConfig);
|
|
45516
47107
|
pushStep(steps, dispatcher, 'gateway.spend.step.buildBurnIntents', chainName, {
|
|
45517
47108
|
name: 'buildBurnIntents',
|
|
45518
47109
|
state: 'success',
|
|
@@ -45549,7 +47140,7 @@ async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, s
|
|
|
45549
47140
|
const forwarderParam = useForwarder ? '?enableForwarder=true' : '';
|
|
45550
47141
|
let transferResponse;
|
|
45551
47142
|
try {
|
|
45552
|
-
transferResponse = await pollApiPost(`${transferApiBaseUrl}/v1/transfer${forwarderParam}`, transferBody, assertGatewayTransferResponse);
|
|
47143
|
+
transferResponse = await pollApiPost(`${transferApiBaseUrl}/v1/transfer${forwarderParam}`, transferBody, assertGatewayTransferResponse, requestConfig);
|
|
45553
47144
|
} catch (error) {
|
|
45554
47145
|
throwGatewayApiError(error, allocations);
|
|
45555
47146
|
}
|
|
@@ -45629,10 +47220,11 @@ function updateFeesWithActual(fees, completionStatus) {
|
|
|
45629
47220
|
amount: actual
|
|
45630
47221
|
} : f);
|
|
45631
47222
|
}
|
|
45632
|
-
async function resolveDestinationTxHash(
|
|
47223
|
+
async function resolveDestinationTxHash(opts) {
|
|
47224
|
+
const { params, destChain, forwarder, transferId, attestation, attestationSignature, fees, requestConfig } = opts;
|
|
45633
47225
|
if (forwarder && transferId) {
|
|
45634
47226
|
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
45635
|
-
const completionStatus = await pollForwarderCompletion(apiBaseUrl, transferId);
|
|
47227
|
+
const completionStatus = await pollForwarderCompletion(apiBaseUrl, transferId, requestConfig);
|
|
45636
47228
|
if (!completionStatus.transactionHash) {
|
|
45637
47229
|
throw new KitError({
|
|
45638
47230
|
...OnchainError.TRANSACTION_REVERTED,
|
|
@@ -45715,9 +47307,20 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45715
47307
|
/**
|
|
45716
47308
|
* Execute the mint step, recording success/error as a spend step.
|
|
45717
47309
|
*/ async function executeMintStep(opts) {
|
|
45718
|
-
const { params, destChain, forwarder, transferId, attestation, attestationSignature, fees, steps, dispatcher } = opts;
|
|
47310
|
+
const { params, destChain, forwarder, transferId, attestation, attestationSignature, fees, steps, dispatcher, requestConfig } = opts;
|
|
45719
47311
|
try {
|
|
45720
|
-
const mintResult = await resolveDestinationTxHash(
|
|
47312
|
+
const mintResult = await resolveDestinationTxHash({
|
|
47313
|
+
params,
|
|
47314
|
+
destChain,
|
|
47315
|
+
forwarder,
|
|
47316
|
+
transferId,
|
|
47317
|
+
attestation,
|
|
47318
|
+
attestationSignature,
|
|
47319
|
+
fees,
|
|
47320
|
+
...requestConfig ? {
|
|
47321
|
+
requestConfig
|
|
47322
|
+
} : {}
|
|
47323
|
+
});
|
|
45721
47324
|
pushStep(steps, dispatcher, 'gateway.spend.step.mint', destChain.name, {
|
|
45722
47325
|
name: 'mint',
|
|
45723
47326
|
state: 'success',
|
|
@@ -45772,7 +47375,8 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45772
47375
|
assertSpendAddresses(params, destChain);
|
|
45773
47376
|
const forwarder = isForwarderEnabled(params.to);
|
|
45774
47377
|
const steps = [];
|
|
45775
|
-
const
|
|
47378
|
+
const requestConfig = options?.requestConfig;
|
|
47379
|
+
const context = params.config?.retry ? await resolveRetryContext(params) : await resolveNormalContext(params, destChain, forwarder, dispatcher, steps, requestConfig);
|
|
45776
47380
|
const fees = appendForwarderFee(context.fees, context.forwardingFee);
|
|
45777
47381
|
const mintResult = await executeMintStep({
|
|
45778
47382
|
params,
|
|
@@ -45783,7 +47387,10 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45783
47387
|
attestationSignature: context.attestationSignature,
|
|
45784
47388
|
fees,
|
|
45785
47389
|
steps,
|
|
45786
|
-
dispatcher
|
|
47390
|
+
dispatcher,
|
|
47391
|
+
...requestConfig ? {
|
|
47392
|
+
requestConfig
|
|
47393
|
+
} : {}
|
|
45787
47394
|
});
|
|
45788
47395
|
// A throwing `onBroadcast` must never strand the caller after a
|
|
45789
47396
|
// successful mint — the chain has moved. `safeInvokeCallback` swallows
|
|
@@ -45800,14 +47407,14 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45800
47407
|
/**
|
|
45801
47408
|
* Resolve the spend context via the normal (non-retry) path.
|
|
45802
47409
|
* Validates `from`, then delegates to `runSpendNormalPath`.
|
|
45803
|
-
*/ async function resolveNormalContext(params, destChain, forwarder, dispatcher, steps) {
|
|
47410
|
+
*/ async function resolveNormalContext(params, destChain, forwarder, dispatcher, steps, requestConfig) {
|
|
45804
47411
|
if (!params.from) {
|
|
45805
47412
|
throw createValidationFailedError$1('from', params.from, 'Source (from) is required when config.retry is not provided.');
|
|
45806
47413
|
}
|
|
45807
47414
|
return runSpendNormalPath({
|
|
45808
47415
|
...params,
|
|
45809
47416
|
from: params.from
|
|
45810
|
-
}, destChain, forwarder, dispatcher, steps);
|
|
47417
|
+
}, destChain, forwarder, dispatcher, steps, requestConfig);
|
|
45811
47418
|
}
|
|
45812
47419
|
/**
|
|
45813
47420
|
* Estimate fees for a Gateway spend without executing it.
|
|
@@ -45830,13 +47437,13 @@ function buildSpendResult(recipientAddress, destChain, txHash, allocationResults
|
|
|
45830
47437
|
* })
|
|
45831
47438
|
* fees.forEach((f) => console.log(f.type, f.amount, f.allocations))
|
|
45832
47439
|
* ```
|
|
45833
|
-
*/ async function estimateSpend$1(params) {
|
|
47440
|
+
*/ async function estimateSpend$1(params, requestConfig) {
|
|
45834
47441
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
45835
47442
|
assertGatewayV1(destChain);
|
|
45836
47443
|
assertSpendAddresses(params, destChain);
|
|
45837
47444
|
const forwarder = isForwarderEnabled(params.to);
|
|
45838
47445
|
const recipientAddress = await resolveRecipientAddress(params.to);
|
|
45839
|
-
const { allocations, estimatedIntents, forwardingFee } = await resolveValidatedIntents(params, destChain, recipientAddress, forwarder);
|
|
47446
|
+
const { allocations, estimatedIntents, forwardingFee } = await resolveValidatedIntents(params, destChain, recipientAddress, forwarder, requestConfig);
|
|
45840
47447
|
const fees = computeEstimateFees(estimatedIntents, allocations, params.config?.customFee);
|
|
45841
47448
|
if (forwardingFee) {
|
|
45842
47449
|
fees.push({
|
|
@@ -45960,6 +47567,9 @@ function assertNotSelfDelegation(chain, signerAddress, delegateAddress, action)
|
|
|
45960
47567
|
* the delegate is `'ready'` (finalized); otherwise it is `'pending'`.
|
|
45961
47568
|
*
|
|
45962
47569
|
* @param params - The adapter context (`from`), delegate address, and token.
|
|
47570
|
+
* @param requestConfig - Optional polling config overrides, including custom
|
|
47571
|
+
* `headers` forwarded with the `/v1/info` request used on the EVM finality
|
|
47572
|
+
* check.
|
|
45963
47573
|
* @returns Promise resolving to `'none'`, `'pending'`, or `'ready'`.
|
|
45964
47574
|
* @throws KitError if the chain is unsupported or `/v1/info` fails.
|
|
45965
47575
|
*
|
|
@@ -45979,7 +47589,7 @@ function assertNotSelfDelegation(chain, signerAddress, delegateAddress, action)
|
|
|
45979
47589
|
* const status = await getDelegateStatus(params)
|
|
45980
47590
|
* if (status === 'ready') { // safe to spend }
|
|
45981
47591
|
* ```
|
|
45982
|
-
*/ async function getDelegateStatus$1(params) {
|
|
47592
|
+
*/ async function getDelegateStatus$1(params, requestConfig) {
|
|
45983
47593
|
const { from, delegateAddress } = params;
|
|
45984
47594
|
const { adapter } = from;
|
|
45985
47595
|
const chain = resolveChainIdentifier(from.chain);
|
|
@@ -46019,7 +47629,7 @@ function assertNotSelfDelegation(chain, signerAddress, delegateAddress, action)
|
|
|
46019
47629
|
return String(finalizedResult).toLowerCase() === 'true' ? 'ready' : 'pending';
|
|
46020
47630
|
}
|
|
46021
47631
|
// EVM: use processedHeight from /v1/info
|
|
46022
|
-
const processedHeight = await getProcessedHeight(chain.isTestnet, chain.gateway.domain);
|
|
47632
|
+
const processedHeight = await getProcessedHeight(chain.isTestnet, chain.gateway.domain, requestConfig);
|
|
46023
47633
|
// If the RPC node lags Gateway's indexer view, the historical read at
|
|
46024
47634
|
// processedHeight may throw a block-range error. This is safe to treat
|
|
46025
47635
|
// as 'pending' because processedHeight comes from Gateway's /v1/info
|
|
@@ -46237,6 +47847,35 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46237
47847
|
dispatchEvent(dispatcher, opName, 'failed', chain, extractErrorInfo(error));
|
|
46238
47848
|
}
|
|
46239
47849
|
|
|
47850
|
+
// ---------------------------------------------------------------------------
|
|
47851
|
+
// Config validation
|
|
47852
|
+
// ---------------------------------------------------------------------------
|
|
47853
|
+
function isPlainObject(value) {
|
|
47854
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
47855
|
+
return false;
|
|
47856
|
+
}
|
|
47857
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47858
|
+
return prototype === Object.prototype || prototype === null;
|
|
47859
|
+
}
|
|
47860
|
+
function assertHeadersConfig(headers) {
|
|
47861
|
+
if (headers === undefined) {
|
|
47862
|
+
return;
|
|
47863
|
+
}
|
|
47864
|
+
if (!isPlainObject(headers)) {
|
|
47865
|
+
throw createValidationFailedError$1('config.headers', headers, 'config.headers must be a plain object with string header values when provided');
|
|
47866
|
+
}
|
|
47867
|
+
for (const [name, value] of Object.entries(headers)){
|
|
47868
|
+
if (typeof value !== 'string') {
|
|
47869
|
+
throw createValidationFailedError$1(`config.headers.${name}`, value, 'header values must be strings');
|
|
47870
|
+
}
|
|
47871
|
+
}
|
|
47872
|
+
}
|
|
47873
|
+
function assertGatewayV1Config(config) {
|
|
47874
|
+
if (!isPlainObject(config)) {
|
|
47875
|
+
throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
|
|
47876
|
+
}
|
|
47877
|
+
assertHeadersConfig(config['headers']);
|
|
47878
|
+
}
|
|
46240
47879
|
// ---------------------------------------------------------------------------
|
|
46241
47880
|
// Factory
|
|
46242
47881
|
// ---------------------------------------------------------------------------
|
|
@@ -46246,7 +47885,12 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46246
47885
|
* Returns a plain object that implements {@link IGatewayProvider} by
|
|
46247
47886
|
* delegating to the standalone functions in `operations/`.
|
|
46248
47887
|
*
|
|
47888
|
+
* @param config - Optional provider configuration. Supply `headers` to
|
|
47889
|
+
* forward custom HTTP headers with every Gateway API request. See
|
|
47890
|
+
* {@link GatewayV1Config}.
|
|
46249
47891
|
* @returns An {@link IGatewayProvider} instance.
|
|
47892
|
+
* @throws Throws when `config` or `config.headers` is not a plain object, or a
|
|
47893
|
+
* header value is not a string.
|
|
46250
47894
|
*
|
|
46251
47895
|
* @example
|
|
46252
47896
|
* ```typescript
|
|
@@ -46255,7 +47899,23 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46255
47899
|
* const provider = createGatewayV1Provider()
|
|
46256
47900
|
* const chains = provider.getSupportedChains()
|
|
46257
47901
|
* ```
|
|
46258
|
-
|
|
47902
|
+
*
|
|
47903
|
+
* @example
|
|
47904
|
+
* ```typescript
|
|
47905
|
+
* // Forward a custom header on every Gateway API request.
|
|
47906
|
+
* const provider = createGatewayV1Provider({
|
|
47907
|
+
* headers: { 'X-Access-Key': process.env.GATEWAY_ACCESS_KEY! },
|
|
47908
|
+
* })
|
|
47909
|
+
* ```
|
|
47910
|
+
*/ function createGatewayV1Provider(config = {}) {
|
|
47911
|
+
assertGatewayV1Config(config);
|
|
47912
|
+
// Captured once and threaded to every Gateway API call the operations make,
|
|
47913
|
+
// so a configured header reaches balances, estimate, transfer, forwarder
|
|
47914
|
+
// status, and /v1/info alike. Left undefined when no headers are configured
|
|
47915
|
+
// so the operations keep their existing defaults untouched.
|
|
47916
|
+
const requestConfig = config.headers ? {
|
|
47917
|
+
headers: config.headers
|
|
47918
|
+
} : undefined;
|
|
46259
47919
|
/**
|
|
46260
47920
|
* Execute an operation with started/succeeded/failed event dispatch.
|
|
46261
47921
|
*/ async function withEvents(opName, chain, startedData, params, fn) {
|
|
@@ -46286,21 +47946,25 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46286
47946
|
}, params, depositFor$1),
|
|
46287
47947
|
spend: async (params, options)=>{
|
|
46288
47948
|
const destChainName = extractChainInfo(params.to.chain).name;
|
|
47949
|
+
const mergedOptions = requestConfig ? {
|
|
47950
|
+
...options,
|
|
47951
|
+
requestConfig
|
|
47952
|
+
} : options;
|
|
46289
47953
|
return withEvents('spend', destChainName, {
|
|
46290
47954
|
destinationChain: destChainName,
|
|
46291
47955
|
token: params.token ?? 'USDC'
|
|
46292
|
-
}, params, async (p)=>spend$1(p, provider.actionDispatcher,
|
|
47956
|
+
}, params, async (p)=>spend$1(p, provider.actionDispatcher, mergedOptions));
|
|
46293
47957
|
},
|
|
46294
47958
|
estimateSpend: async (params)=>{
|
|
46295
47959
|
const destChainName = extractChainInfo(params.to.chain).name;
|
|
46296
47960
|
return withEvents('estimateSpend', destChainName, {
|
|
46297
47961
|
destinationChain: destChainName,
|
|
46298
47962
|
token: params.token ?? 'USDC'
|
|
46299
|
-
}, params, estimateSpend$1);
|
|
47963
|
+
}, params, async (p)=>estimateSpend$1(p, requestConfig));
|
|
46300
47964
|
},
|
|
46301
47965
|
getBalances: async (params)=>withEvents('getBalances', 'aggregate', {
|
|
46302
47966
|
token: params.token ?? 'USDC'
|
|
46303
|
-
}, params, getBalances$1),
|
|
47967
|
+
}, params, async (p)=>getBalances$1(p, requestConfig)),
|
|
46304
47968
|
addDelegate: async (params)=>withEvents('addDelegate', extractChainInfo(params.from.chain).name, {
|
|
46305
47969
|
delegateAddress: params.delegateAddress
|
|
46306
47970
|
}, params, addDelegate$1),
|
|
@@ -46318,7 +47982,9 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46318
47982
|
// in withEvents. getSupportedChains is synchronous. getDelegateStatus is a
|
|
46319
47983
|
// lightweight read that consumers treat as a utility check, unlike
|
|
46320
47984
|
// getBalances which involves API polling and benefits from lifecycle events.
|
|
46321
|
-
getDelegateStatus
|
|
47985
|
+
// getDelegateStatus is wrapped to forward the provider's request config to
|
|
47986
|
+
// its /v1/info call; getSupportedChains makes no API call.
|
|
47987
|
+
getDelegateStatus: async (params)=>getDelegateStatus$1(params, requestConfig),
|
|
46322
47988
|
getSupportedChains: getSupportedChains$1
|
|
46323
47989
|
};
|
|
46324
47990
|
return provider;
|
|
@@ -46328,6 +47994,7 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46328
47994
|
* The default providers that will be used in addition to the providers
|
|
46329
47995
|
* provided to the createUnifiedBalanceKitContext factory function.
|
|
46330
47996
|
*
|
|
47997
|
+
* @param config - Optional configuration forwarded to the default provider.
|
|
46331
47998
|
* @returns A readonly tuple with one element: the default Gateway v1 provider
|
|
46332
47999
|
* instance created via {@link createGatewayV1Provider}.
|
|
46333
48000
|
* @internal
|
|
@@ -46339,8 +48006,10 @@ function dispatchEvent(dispatcher, opName, stage, chain, data) {
|
|
|
46339
48006
|
* const { providers } = createUnifiedBalanceKitContext()
|
|
46340
48007
|
* // providers[0] is the default Gateway v1 instance from getDefaultProviders()
|
|
46341
48008
|
* ```
|
|
46342
|
-
*/ const getDefaultProviders = ()=>[
|
|
46343
|
-
createGatewayV1Provider(
|
|
48009
|
+
*/ const getDefaultProviders = (config = {})=>[
|
|
48010
|
+
createGatewayV1Provider(config.headers ? {
|
|
48011
|
+
headers: config.headers
|
|
48012
|
+
} : {})
|
|
46344
48013
|
];
|
|
46345
48014
|
function createUnifiedBalanceKitContext(config = {}) {
|
|
46346
48015
|
// Runtime guard: `excludeDefaultProviders: true` with no `providers` is
|
|
@@ -46354,7 +48023,10 @@ function createUnifiedBalanceKitContext(config = {}) {
|
|
|
46354
48023
|
// Initialize default providers — either the canonical singleton tuple or
|
|
46355
48024
|
// an empty list when the caller has opted out. Typed loosely here; each
|
|
46356
48025
|
// public overload narrows the return type for callers.
|
|
46357
|
-
const
|
|
48026
|
+
const defaultProvidersConfig = config.headers ? {
|
|
48027
|
+
headers: config.headers
|
|
48028
|
+
} : {};
|
|
48029
|
+
const defaultProviders = config.excludeDefaultProviders === true ? [] : getDefaultProviders(defaultProvidersConfig);
|
|
46358
48030
|
const providers = [
|
|
46359
48031
|
...defaultProviders,
|
|
46360
48032
|
...config.providers ?? []
|
|
@@ -47045,29 +48717,65 @@ function sumAllocationsFromSources(sources) {
|
|
|
47045
48717
|
}
|
|
47046
48718
|
});
|
|
47047
48719
|
|
|
48720
|
+
function normalizeAddressIdentity(value) {
|
|
48721
|
+
if (typeof value !== 'string') return undefined;
|
|
48722
|
+
try {
|
|
48723
|
+
return convertAddress(value, 'bytes32').toLowerCase();
|
|
48724
|
+
} catch {
|
|
48725
|
+
// Address validation below owns the structured error for invalid input.
|
|
48726
|
+
return value;
|
|
48727
|
+
}
|
|
48728
|
+
}
|
|
47048
48729
|
/**
|
|
47049
|
-
* Reject `from` arrays
|
|
47050
|
-
*
|
|
47051
|
-
*
|
|
48730
|
+
* Reject `from` arrays that describe the same spend source more than once.
|
|
48731
|
+
*
|
|
48732
|
+
* One adapter instance may legitimately be reused across distinct Gateway
|
|
48733
|
+
* source accounts. When `sourceAccount` is omitted, the signer address
|
|
48734
|
+
* identifies the source. Must run on the raw input (before Zod parsing)
|
|
48735
|
+
* because `z.object()` creates new wrapper objects and adapter identity
|
|
48736
|
+
* (`===`) is lost.
|
|
47052
48737
|
*
|
|
47053
48738
|
* @internal
|
|
47054
|
-
*/ function
|
|
48739
|
+
*/ function assertNoDuplicateSpendSources(params) {
|
|
47055
48740
|
if (params == null || typeof params !== 'object') return;
|
|
47056
48741
|
const from = params['from'];
|
|
47057
48742
|
if (!Array.isArray(from) || from.length < 2) return;
|
|
47058
|
-
const seen = new
|
|
48743
|
+
const seen = new Map();
|
|
47059
48744
|
for (const source of from){
|
|
47060
48745
|
if (source == null || typeof source !== 'object') continue;
|
|
47061
|
-
const
|
|
48746
|
+
const record = source;
|
|
48747
|
+
const adapter = record['adapter'];
|
|
47062
48748
|
if (adapter == null) continue;
|
|
47063
|
-
|
|
48749
|
+
const sourceAccount = normalizeAddressIdentity(record['sourceAccount']) ?? normalizeAddressIdentity(record['address']);
|
|
48750
|
+
let sourceAccounts = seen.get(adapter);
|
|
48751
|
+
if (sourceAccounts == null) {
|
|
48752
|
+
sourceAccounts = new Set();
|
|
48753
|
+
seen.set(adapter, sourceAccounts);
|
|
48754
|
+
}
|
|
48755
|
+
if (sourceAccounts.has(sourceAccount)) {
|
|
47064
48756
|
throw new KitError({
|
|
47065
48757
|
...InputError.VALIDATION_FAILED,
|
|
47066
48758
|
recoverability: 'FATAL',
|
|
47067
|
-
message: 'Duplicate
|
|
48759
|
+
message: 'Duplicate spend source detected in from array. The same adapter ' + 'and Gateway source account are reused more than once.'
|
|
47068
48760
|
});
|
|
47069
48761
|
}
|
|
47070
|
-
|
|
48762
|
+
sourceAccounts.add(sourceAccount);
|
|
48763
|
+
}
|
|
48764
|
+
}
|
|
48765
|
+
function assertSourceAddressContexts(from) {
|
|
48766
|
+
const sources = Array.isArray(from) ? from : [
|
|
48767
|
+
from
|
|
48768
|
+
];
|
|
48769
|
+
for (const raw of sources){
|
|
48770
|
+
if (raw == null || typeof raw !== 'object') continue;
|
|
48771
|
+
const source = raw;
|
|
48772
|
+
const addressContext = source.adapter?.capabilities?.addressContext;
|
|
48773
|
+
if (addressContext === 'developer-controlled' && (typeof source.address !== 'string' || source.address === '')) {
|
|
48774
|
+
throw createValidationFailedError$1('address', source.address, 'Address is required for developer-controlled adapters.');
|
|
48775
|
+
}
|
|
48776
|
+
if (addressContext === 'user-controlled' && source.address !== undefined) {
|
|
48777
|
+
throw createValidationFailedError$1('address', source.address, 'Address should not be provided for user-controlled adapters. ' + 'The address is automatically resolved from the connected wallet.');
|
|
48778
|
+
}
|
|
47071
48779
|
}
|
|
47072
48780
|
}
|
|
47073
48781
|
/**
|
|
@@ -47081,14 +48789,17 @@ function sumAllocationsFromSources(sources) {
|
|
|
47081
48789
|
* @throws \{KitError\} When validation fails; error code is
|
|
47082
48790
|
* {@link InputError.VALIDATION_FAILED}.
|
|
47083
48791
|
*/ function assertSpendParams(params) {
|
|
47084
|
-
|
|
48792
|
+
assertNoDuplicateSpendSources(params);
|
|
48793
|
+
if (params != null && typeof params === 'object' && 'from' in params) {
|
|
48794
|
+
assertSourceAddressContexts(params['from']);
|
|
48795
|
+
}
|
|
47085
48796
|
const result = spendParamsSchema.safeParse(params);
|
|
47086
48797
|
if (!result.success) {
|
|
47087
48798
|
throw convertZodErrorToStructured(result.error, params);
|
|
47088
48799
|
}
|
|
47089
48800
|
const data = result.data;
|
|
47090
48801
|
// Validate recipientAddress against the destination chain
|
|
47091
|
-
if (data.to.recipientAddress) {
|
|
48802
|
+
if (data.to.recipientAddress !== undefined && data.to.recipientAddress !== '') {
|
|
47092
48803
|
const toChain = data.to.chain;
|
|
47093
48804
|
if (!isValidAddressForChain(data.to.recipientAddress, toChain)) {
|
|
47094
48805
|
const chainInfo = extractChainInfo(toChain);
|
|
@@ -47096,7 +48807,7 @@ function sumAllocationsFromSources(sources) {
|
|
|
47096
48807
|
}
|
|
47097
48808
|
}
|
|
47098
48809
|
// Validate sourceAccount against each source's allocation chain
|
|
47099
|
-
if (data.from) {
|
|
48810
|
+
if (data.from !== undefined) {
|
|
47100
48811
|
assertSourceAccountAddresses(data.from);
|
|
47101
48812
|
}
|
|
47102
48813
|
}
|
|
@@ -47111,11 +48822,15 @@ function assertSourceAccountAddresses(from) {
|
|
|
47111
48822
|
from
|
|
47112
48823
|
];
|
|
47113
48824
|
for (const raw of sources){
|
|
47114
|
-
if (
|
|
48825
|
+
if (raw === null || typeof raw !== 'object' || !('sourceAccount' in raw)) {
|
|
48826
|
+
continue;
|
|
48827
|
+
}
|
|
47115
48828
|
const source = raw;
|
|
47116
|
-
if (
|
|
48829
|
+
if (source.sourceAccount === undefined || source.sourceAccount === '') {
|
|
48830
|
+
continue;
|
|
48831
|
+
}
|
|
47117
48832
|
const sourceChain = getSourceChainFromAllocations(source.allocations);
|
|
47118
|
-
if (
|
|
48833
|
+
if (sourceChain === undefined) continue;
|
|
47119
48834
|
if (!isValidAddressForChain(source.sourceAccount, sourceChain)) {
|
|
47120
48835
|
const chainInfo = extractChainInfo(sourceChain);
|
|
47121
48836
|
throw createInvalidAddressError(source.sourceAccount, String(chainInfo.name), chainInfo.expectedAddressFormat);
|
|
@@ -49315,6 +51030,11 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49315
51030
|
assertAppKitCustomFeePolicy(config.customFeePolicy);
|
|
49316
51031
|
}
|
|
49317
51032
|
const unifiedBalance = new AppKitUnifiedBalance({
|
|
51033
|
+
// Top-level headers act as the shared default; an explicit
|
|
51034
|
+
// `unifiedBalance.headers` overrides them by coming later in the spread.
|
|
51035
|
+
...config.headers != null && {
|
|
51036
|
+
headers: config.headers
|
|
51037
|
+
},
|
|
49318
51038
|
...config.unifiedBalance,
|
|
49319
51039
|
...config.disableErrorReporting != null && {
|
|
49320
51040
|
disableErrorReporting: config.disableErrorReporting
|
|
@@ -49417,30 +51137,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49417
51137
|
*/ async retryBridge(result, retryContext) {
|
|
49418
51138
|
return retryBridge(this.context, result, retryContext);
|
|
49419
51139
|
}
|
|
49420
|
-
|
|
49421
|
-
* Estimate the bridge operation.
|
|
49422
|
-
*
|
|
49423
|
-
* Calculates gas costs, protocol fees, and optional custom fees for a
|
|
49424
|
-
* cross-chain bridge transfer without executing the transaction. Useful
|
|
49425
|
-
* for displaying cost estimates to users before they confirm a transfer.
|
|
49426
|
-
*
|
|
49427
|
-
* @param params - Bridge parameters containing source, destination, amount, and token
|
|
49428
|
-
* @returns Promise resolving to the estimate result with fee information
|
|
49429
|
-
* @throws If bridge parameters are invalid
|
|
49430
|
-
* @throws If the bridge route is not supported
|
|
49431
|
-
*
|
|
49432
|
-
* @example
|
|
49433
|
-
* ```typescript
|
|
49434
|
-
* const estimate = await kit.estimateBridge({
|
|
49435
|
-
* from: { adapter: sourceAdapter, chain: 'Ethereum' },
|
|
49436
|
-
* to: { adapter: destAdapter, chain: 'Polygon' },
|
|
49437
|
-
* amount: '100.50',
|
|
49438
|
-
* token: 'USDC'
|
|
49439
|
-
* })
|
|
49440
|
-
*
|
|
49441
|
-
* console.log('Estimated fees:', estimate.fees)
|
|
49442
|
-
* ```
|
|
49443
|
-
*/ async estimateBridge(params) {
|
|
51140
|
+
async estimateBridge(params) {
|
|
49444
51141
|
return estimateBridge(this.context, params);
|
|
49445
51142
|
}
|
|
49446
51143
|
/**
|
|
@@ -49556,7 +51253,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49556
51253
|
* config: {
|
|
49557
51254
|
* slippageBps: 300, // 3% slippage
|
|
49558
51255
|
* allowanceStrategy: 'permit',
|
|
49559
|
-
*
|
|
51256
|
+
* apiKey: 'TEST_API_KEY:id:secret'
|
|
49560
51257
|
* }
|
|
49561
51258
|
* })
|
|
49562
51259
|
*
|
|
@@ -49585,7 +51282,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49585
51282
|
* amountIn: '100.50',
|
|
49586
51283
|
* config: {
|
|
49587
51284
|
* slippageBps: 300,
|
|
49588
|
-
*
|
|
51285
|
+
* apiKey: 'TEST_API_KEY:id:secret'
|
|
49589
51286
|
* }
|
|
49590
51287
|
* })
|
|
49591
51288
|
*
|
|
@@ -49609,7 +51306,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49609
51306
|
* not write the polling loop yourself.
|
|
49610
51307
|
*
|
|
49611
51308
|
* @param params - `txHash` and `chainIn`, plus optional `chainOut` and
|
|
49612
|
-
* `
|
|
51309
|
+
* `apiKey`.
|
|
49613
51310
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
49614
51311
|
* @throws \{KitError\} If `chainIn` or `chainOut` is malformed.
|
|
49615
51312
|
*
|
|
@@ -49622,7 +51319,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49622
51319
|
* txHash: result.txHash,
|
|
49623
51320
|
* chainIn: result.chainIn,
|
|
49624
51321
|
* chainOut: result.chainOut,
|
|
49625
|
-
*
|
|
51322
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49626
51323
|
* })
|
|
49627
51324
|
*
|
|
49628
51325
|
* console.log(status.progress.status, status.progress.substatus)
|
|
@@ -49635,7 +51332,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49635
51332
|
* txHash: result.txHash,
|
|
49636
51333
|
* chainIn: result.chainIn,
|
|
49637
51334
|
* chainOut: result.chainOut,
|
|
49638
|
-
*
|
|
51335
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49639
51336
|
* })
|
|
49640
51337
|
* while (status.progress.status === 'PENDING') {
|
|
49641
51338
|
* await new Promise((r) => setTimeout(r, 3_000))
|
|
@@ -49643,7 +51340,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49643
51340
|
* txHash: result.txHash,
|
|
49644
51341
|
* chainIn: result.chainIn,
|
|
49645
51342
|
* chainOut: result.chainOut,
|
|
49646
|
-
*
|
|
51343
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49647
51344
|
* })
|
|
49648
51345
|
* }
|
|
49649
51346
|
* ```
|
|
@@ -49662,7 +51359,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49662
51359
|
* expires. Timeouts surface as a RETRYABLE `KitError` so callers can
|
|
49663
51360
|
* re-invoke with the same `txHash`.
|
|
49664
51361
|
*
|
|
49665
|
-
* @param params - Wait configuration: identifiers,
|
|
51362
|
+
* @param params - Wait configuration: identifiers, API key, and optional
|
|
49666
51363
|
* `timeoutMs` / `onProgress`.
|
|
49667
51364
|
* @returns The first terminal {@link SwapStatusResult} observed.
|
|
49668
51365
|
* @throws \{KitError\} (`NETWORK.TIMEOUT`, RETRYABLE) when the wait
|
|
@@ -49675,7 +51372,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49675
51372
|
*
|
|
49676
51373
|
* const final = await kit.waitForSwap({
|
|
49677
51374
|
* result,
|
|
49678
|
-
*
|
|
51375
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49679
51376
|
* onProgress: (snap) => console.log(snap.progress.status),
|
|
49680
51377
|
* })
|
|
49681
51378
|
*
|
|
@@ -49691,7 +51388,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49691
51388
|
* txHash: persisted.txHash,
|
|
49692
51389
|
* chainIn: persisted.chainIn,
|
|
49693
51390
|
* chainOut: persisted.chainOut,
|
|
49694
|
-
*
|
|
51391
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49695
51392
|
* })
|
|
49696
51393
|
* ```
|
|
49697
51394
|
*/ async waitForSwap(params) {
|
|
@@ -49723,7 +51420,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49723
51420
|
* translates to the chain's native sentinel address — `0xEee…` for EVM,
|
|
49724
51421
|
* `1111…` for Solana — before querying the service.
|
|
49725
51422
|
*
|
|
49726
|
-
* @param params - `chain`, plus optional `tokens` and `
|
|
51423
|
+
* @param params - `chain`, plus optional `tokens` and `apiKey`.
|
|
49727
51424
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
49728
51425
|
* @throws \{KitError\} If `chain` is malformed, `tokens` exceeds 100
|
|
49729
51426
|
* entries, or any entry is neither a registered symbol nor a
|
|
@@ -49734,7 +51431,7 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49734
51431
|
* const { rates } = await kit.getTokenRates({
|
|
49735
51432
|
* chain: 'Ethereum',
|
|
49736
51433
|
* tokens: ['USDC', 'EURC'],
|
|
49737
|
-
*
|
|
51434
|
+
* apiKey: process.env.CIRCLE_API_KEY,
|
|
49738
51435
|
* })
|
|
49739
51436
|
*
|
|
49740
51437
|
* const usdc = rates['Ethereum']?.['0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48']
|
|
@@ -49743,38 +51440,8 @@ function assertAppKitCustomFeePolicyScope(operation) {
|
|
|
49743
51440
|
*/ async getTokenRates(params) {
|
|
49744
51441
|
return getTokenRates(this.context, params);
|
|
49745
51442
|
}
|
|
49746
|
-
|
|
49747
|
-
|
|
49748
|
-
*
|
|
49749
|
-
* Returns blockchain networks that support specific stablecoin operations.
|
|
49750
|
-
* When no operation type is specified, returns all chains supporting any
|
|
49751
|
-
* operation (bridge, swap, earn, or unified balance).
|
|
49752
|
-
*
|
|
49753
|
-
* @param operationType - Optional operation type to filter chains ('bridge' | 'swap' | 'earn' | 'unifiedBalance')
|
|
49754
|
-
* @returns Array of unique chain definitions supporting the specified operation(s)
|
|
49755
|
-
*
|
|
49756
|
-
* @example Get all supported chains
|
|
49757
|
-
* ```typescript
|
|
49758
|
-
* import { AppKit } from '@circle-fin/app-kit'
|
|
49759
|
-
*
|
|
49760
|
-
* const kit = new AppKit()
|
|
49761
|
-
* const allChains = kit.getSupportedChains()
|
|
49762
|
-
*
|
|
49763
|
-
* console.log(`Total supported chains: ${allChains.length}`)
|
|
49764
|
-
* allChains.forEach(chain => {
|
|
49765
|
-
* console.log(`- ${chain.name} (${chain.type})`)
|
|
49766
|
-
* })
|
|
49767
|
-
* ```
|
|
49768
|
-
*
|
|
49769
|
-
* @example Get bridge-specific chains
|
|
49770
|
-
* ```typescript
|
|
49771
|
-
* const kit = new AppKit()
|
|
49772
|
-
* const bridgeChains = kit.getSupportedChains('bridge')
|
|
49773
|
-
*
|
|
49774
|
-
* console.log('Chains supporting bridge:', bridgeChains.map(c => c.name))
|
|
49775
|
-
* ```
|
|
49776
|
-
*/ getSupportedChains(operationType) {
|
|
49777
|
-
return getSupportedChains$2(this.context, operationType, this.unifiedBalance);
|
|
51443
|
+
getSupportedChains(operationType, options) {
|
|
51444
|
+
return getSupportedChains$2(this.context, operationType, this.unifiedBalance, options);
|
|
49778
51445
|
}
|
|
49779
51446
|
/**
|
|
49780
51447
|
* Set operation-scoped custom fee policies.
|