@circle-fin/app-kit 1.16.0 → 1.18.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 +79 -0
- package/borrow.cjs +150 -32
- package/borrow.d.cts +38 -9
- package/borrow.d.mts +38 -9
- package/borrow.d.ts +38 -9
- package/borrow.mjs +150 -32
- package/bridge.cjs +454 -53
- package/bridge.d.cts +38 -9
- package/bridge.d.mts +38 -9
- package/bridge.d.ts +38 -9
- package/bridge.mjs +454 -53
- package/chains.cjs +10 -6
- package/chains.d.cts +1 -1
- package/chains.d.mts +1 -1
- package/chains.d.ts +1 -1
- package/chains.mjs +10 -6
- package/context.d.cts +38 -9
- package/context.d.mts +38 -9
- package/context.d.ts +38 -9
- package/earn.cjs +830 -43
- package/earn.d.cts +38 -9
- package/earn.d.mts +38 -9
- package/earn.d.ts +38 -9
- package/earn.mjs +830 -43
- package/estimateBridge.cjs +454 -53
- package/estimateBridge.d.cts +38 -9
- package/estimateBridge.d.mts +38 -9
- package/estimateBridge.d.ts +38 -9
- package/estimateBridge.mjs +454 -53
- package/estimateSwap.cjs +265 -45
- package/estimateSwap.d.cts +86 -9
- package/estimateSwap.d.mts +86 -9
- package/estimateSwap.d.ts +86 -9
- package/estimateSwap.mjs +265 -45
- package/index.cjs +1019 -73
- package/index.d.cts +373 -210
- package/index.d.mts +373 -210
- package/index.d.ts +373 -210
- package/index.mjs +1019 -73
- package/package.json +8 -8
- package/server.cjs +35 -9
- package/server.d.cts +29 -10
- package/server.d.mts +29 -10
- package/server.d.ts +29 -10
- package/server.mjs +35 -9
- package/swap.cjs +265 -45
- package/swap.d.cts +86 -9
- package/swap.d.mts +86 -9
- package/swap.d.ts +86 -9
- package/swap.mjs +265 -45
- package/unifiedBalance.cjs +139 -20
- package/unifiedBalance.mjs +139 -20
package/index.mjs
CHANGED
|
@@ -2804,22 +2804,96 @@ let activeRedactor = defaultMessageRedactor;
|
|
|
2804
2804
|
'0x9147d463': 'Final balance is less than the initial balance',
|
|
2805
2805
|
'0xf4b3b1bc': 'Native token transfer to the beneficiary failed'
|
|
2806
2806
|
};
|
|
2807
|
+
/**
|
|
2808
|
+
* Matches revert data: a 4-byte selector followed by whole ABI-encoded bytes.
|
|
2809
|
+
*/ const REVERT_DATA_PATTERN = /^0x[0-9a-f]{8}(?:[0-9a-f]{2})*$/i;
|
|
2810
|
+
/**
|
|
2811
|
+
* Returns the revert data held by one error in a `cause` chain.
|
|
2812
|
+
*
|
|
2813
|
+
* Browser wallets (EIP-1193) wrap the node's error, so the revert data sits
|
|
2814
|
+
* one level down as `data.data`. viem's `getContractError` reads it the same
|
|
2815
|
+
* way, then keeps it on `ContractFunctionRevertedError.raw` and drops the
|
|
2816
|
+
* node's error from the chain.
|
|
2817
|
+
*
|
|
2818
|
+
* @param link - One error in the `cause` chain
|
|
2819
|
+
* @returns The `raw` or `data` string, or undefined if there is none
|
|
2820
|
+
*/ function getRevertData(link) {
|
|
2821
|
+
if ('raw' in link && typeof link.raw === 'string' && REVERT_DATA_PATTERN.test(link.raw)) {
|
|
2822
|
+
return link.raw;
|
|
2823
|
+
}
|
|
2824
|
+
if (!('data' in link)) {
|
|
2825
|
+
return undefined;
|
|
2826
|
+
}
|
|
2827
|
+
const { data } = link;
|
|
2828
|
+
if (typeof data === 'string') {
|
|
2829
|
+
return data;
|
|
2830
|
+
}
|
|
2831
|
+
if (data !== null && typeof data === 'object' && 'data' in data && typeof data.data === 'string') {
|
|
2832
|
+
return data.data;
|
|
2833
|
+
}
|
|
2834
|
+
return undefined;
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Finds the first revert data on the error or in its `cause` chain that
|
|
2838
|
+
* `accept` takes.
|
|
2839
|
+
*
|
|
2840
|
+
* viem keeps the node's revert data on an inner `RpcRequestError`, not on
|
|
2841
|
+
* the error it throws. The `seen` set stops a self-referencing `cause` chain.
|
|
2842
|
+
*
|
|
2843
|
+
* @param error - The error object
|
|
2844
|
+
* @param accept - Returns true for the data shape the caller wants
|
|
2845
|
+
* @returns The data as written on the error, or undefined if not found
|
|
2846
|
+
*/ function findRevertData(error, accept) {
|
|
2847
|
+
const seen = new Set();
|
|
2848
|
+
let current = error;
|
|
2849
|
+
while(current !== null && typeof current === 'object' && !seen.has(current)){
|
|
2850
|
+
seen.add(current);
|
|
2851
|
+
const data = getRevertData(current);
|
|
2852
|
+
if (data !== undefined && accept(data)) {
|
|
2853
|
+
return data;
|
|
2854
|
+
}
|
|
2855
|
+
current = 'cause' in current ? current.cause : undefined;
|
|
2856
|
+
}
|
|
2857
|
+
return undefined;
|
|
2858
|
+
}
|
|
2859
|
+
/**
|
|
2860
|
+
* Finds the raw revert data on an error or anywhere in its `cause` chain.
|
|
2861
|
+
*
|
|
2862
|
+
* Reads `data`, the nested `data.data` from browser wallets, and viem's
|
|
2863
|
+
* `raw`. Only hex of at least 4 bytes counts, so decoded `data` objects and
|
|
2864
|
+
* empty `0x` data are skipped.
|
|
2865
|
+
*
|
|
2866
|
+
* Revert data is public: it is what the contract returned. Keep it when the
|
|
2867
|
+
* rest of a raw error is redacted, so callers can still decode the revert.
|
|
2868
|
+
*
|
|
2869
|
+
* @param error - The raw error from the blockchain library
|
|
2870
|
+
* @returns The revert data in lowercase, or undefined if there is none
|
|
2871
|
+
*
|
|
2872
|
+
* @example
|
|
2873
|
+
* ```typescript
|
|
2874
|
+
* import { extractRevertData } from '@core/errors'
|
|
2875
|
+
*
|
|
2876
|
+
* extractRevertData({ cause: { data: { data: '0xF64059DB' } } })
|
|
2877
|
+
* // Returns: '0xf64059db'
|
|
2878
|
+
* ```
|
|
2879
|
+
*/ function extractRevertData(error) {
|
|
2880
|
+
return findRevertData(error, (data)=>REVERT_DATA_PATTERN.test(data))?.toLowerCase();
|
|
2881
|
+
}
|
|
2807
2882
|
/**
|
|
2808
2883
|
* Attempts to extract an error selector from an error object or message.
|
|
2809
2884
|
*
|
|
2810
2885
|
* Checks multiple sources in priority order:
|
|
2811
|
-
* 1. `
|
|
2886
|
+
* 1. `raw` or `data` field (or nested `data.data`) on the error or anywhere
|
|
2887
|
+
* in its `cause` chain
|
|
2812
2888
|
* 2. Message pattern: "custom error 0x8baa579f"
|
|
2813
2889
|
*
|
|
2814
2890
|
* @param msg - The error message
|
|
2815
2891
|
* @param error - The error object (optional)
|
|
2816
2892
|
* @returns The 4 bytes selector , or undefined if not found
|
|
2817
2893
|
*/ function extractErrorSelector(msg, error) {
|
|
2818
|
-
|
|
2819
|
-
if (
|
|
2820
|
-
|
|
2821
|
-
return error.data.toLowerCase();
|
|
2822
|
-
}
|
|
2894
|
+
const data = findRevertData(error, (value)=>/^0x[0-9a-f]{8}$/i.test(value));
|
|
2895
|
+
if (data !== undefined) {
|
|
2896
|
+
return data.toLowerCase();
|
|
2823
2897
|
}
|
|
2824
2898
|
// Check for "custom error 0x8baa579f" in the message
|
|
2825
2899
|
const match = /custom error (0x[0-9a-f]{8})\b/i.exec(msg);
|
|
@@ -4356,7 +4430,7 @@ let activeRedactor = defaultMessageRedactor;
|
|
|
4356
4430
|
* between input errors (fix your request) and service errors (retry later).
|
|
4357
4431
|
*
|
|
4358
4432
|
* Error code ranges:
|
|
4359
|
-
* - 1100-
|
|
4433
|
+
* - 1100-1107: INPUT errors — invalid, unsupported, or stale request state
|
|
4360
4434
|
* - 8100-8105: SERVICE errors — retryable backend/provider failures
|
|
4361
4435
|
*
|
|
4362
4436
|
* @example
|
|
@@ -4417,6 +4491,15 @@ let activeRedactor = defaultMessageRedactor;
|
|
|
4417
4491
|
name: 'EARN_BRIDGE_QUOTE_EXPIRED',
|
|
4418
4492
|
type: 'INPUT'
|
|
4419
4493
|
},
|
|
4494
|
+
/**
|
|
4495
|
+
* The wallet's vault shares are inside the vault's share lock period, which
|
|
4496
|
+
* starts at deposit. Surfaced as FATAL rather than RETRYABLE because the
|
|
4497
|
+
* lock can last hours, so an immediate retry fails the same way.
|
|
4498
|
+
*/ SHARES_LOCKED: {
|
|
4499
|
+
code: 1107,
|
|
4500
|
+
name: 'EARN_SHARES_LOCKED',
|
|
4501
|
+
type: 'INPUT'
|
|
4502
|
+
},
|
|
4420
4503
|
/** The proxy signing call failed — retryable. */ SIGNING_FAILED: {
|
|
4421
4504
|
code: 8100,
|
|
4422
4505
|
name: 'EARN_SIGNING_FAILED',
|
|
@@ -4658,6 +4741,15 @@ let activeRedactor = defaultMessageRedactor;
|
|
|
4658
4741
|
recoverability: 'RETRYABLE'
|
|
4659
4742
|
}
|
|
4660
4743
|
],
|
|
4744
|
+
// DEPOSIT_INTENT_INVALID: the Veda deposit intent is missing, expired, or
|
|
4745
|
+
// not signed by the depositing wallet.
|
|
4746
|
+
[
|
|
4747
|
+
380418,
|
|
4748
|
+
{
|
|
4749
|
+
errorDef: EarnError.SIGNATURE_REJECTED,
|
|
4750
|
+
recoverability: 'FATAL'
|
|
4751
|
+
}
|
|
4752
|
+
],
|
|
4661
4753
|
// Bridge (380_5XX)
|
|
4662
4754
|
[
|
|
4663
4755
|
380500,
|
|
@@ -5563,6 +5655,8 @@ var SwapChain;
|
|
|
5563
5655
|
SwapChain["HyperEVM"] = "HyperEVM";
|
|
5564
5656
|
SwapChain["Monad"] = "Monad";
|
|
5565
5657
|
SwapChain["Arc"] = "Arc";
|
|
5658
|
+
SwapChain["Cronos"] = "Cronos";
|
|
5659
|
+
SwapChain["Morph"] = "Morph";
|
|
5566
5660
|
// Testnet chains with swap support
|
|
5567
5661
|
SwapChain["Arc_Testnet"] = "Arc_Testnet";
|
|
5568
5662
|
})(SwapChain || (SwapChain = {}));
|
|
@@ -6264,14 +6358,14 @@ var BorrowChain;
|
|
|
6264
6358
|
symbol: 'USDC',
|
|
6265
6359
|
// Arc uses native USDC with 18 decimals for gas payments (EVM standard).
|
|
6266
6360
|
// Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
|
|
6267
|
-
// See: https://docs.arc.
|
|
6361
|
+
// See: https://docs.arc.io/arc/references/contract-addresses
|
|
6268
6362
|
decimals: 18
|
|
6269
6363
|
},
|
|
6270
6364
|
chainId: 5042002,
|
|
6271
6365
|
isTestnet: true,
|
|
6272
6366
|
explorerUrl: 'https://testnet.arcscan.app/tx/{hash}',
|
|
6273
6367
|
rpcEndpoints: [
|
|
6274
|
-
'https://rpc.testnet.arc.
|
|
6368
|
+
'https://rpc.testnet.arc.io'
|
|
6275
6369
|
],
|
|
6276
6370
|
eurcAddress: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
|
|
6277
6371
|
usdcAddress: '0x3600000000000000000000000000000000000000',
|
|
@@ -6916,7 +7010,8 @@ var BorrowChain;
|
|
|
6916
7010
|
}
|
|
6917
7011
|
},
|
|
6918
7012
|
kitContracts: {
|
|
6919
|
-
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
7013
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
7014
|
+
adapter: ADAPTER_CONTRACT_EVM_MAINNET
|
|
6920
7015
|
}
|
|
6921
7016
|
});
|
|
6922
7017
|
|
|
@@ -7785,7 +7880,8 @@ var BorrowChain;
|
|
|
7785
7880
|
}
|
|
7786
7881
|
},
|
|
7787
7882
|
kitContracts: {
|
|
7788
|
-
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
7883
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET,
|
|
7884
|
+
adapter: ADAPTER_CONTRACT_EVM_MAINNET
|
|
7789
7885
|
}
|
|
7790
7886
|
});
|
|
7791
7887
|
|
|
@@ -10156,8 +10252,8 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
10156
10252
|
* Zod schema for validating swap-specific chain identifiers.
|
|
10157
10253
|
*
|
|
10158
10254
|
* Validates chains based on:
|
|
10159
|
-
* -
|
|
10160
|
-
* - Mainnet
|
|
10255
|
+
* - Membership in the SwapChain enum
|
|
10256
|
+
* - Mainnet or explicitly allowlisted testnet
|
|
10161
10257
|
* - At least one supported token available
|
|
10162
10258
|
*
|
|
10163
10259
|
*/ const swapChainIdentifierSchema = z.union([
|
|
@@ -10799,7 +10895,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
10799
10895
|
*
|
|
10800
10896
|
* A chain supports swaps if ALL conditions are met:
|
|
10801
10897
|
* 1. Is a member of the {@link SwapChain} enum
|
|
10802
|
-
* 2. Has
|
|
10898
|
+
* 2. Has an Adapter contract configured directly or through CCTP v2
|
|
10803
10899
|
*
|
|
10804
10900
|
* Testnets are allowed only when explicitly listed in the
|
|
10805
10901
|
* {@link SwapChain} enum (e.g., Arc_Testnet).
|
|
@@ -10820,14 +10916,14 @@ const swapTokenEnumSchema = z.enum([
|
|
|
10820
10916
|
* @remarks
|
|
10821
10917
|
* Chains automatically become swap-enabled when:
|
|
10822
10918
|
* - Added to the {@link SwapChain} enum
|
|
10823
|
-
* - Adapter contract is deployed
|
|
10919
|
+
* - An Adapter contract is deployed and configured
|
|
10824
10920
|
*
|
|
10825
10921
|
* No code changes needed when new chains are added!
|
|
10826
10922
|
*/ function isSwapSupportedChain(chain) {
|
|
10827
10923
|
if (!(chain.chain in SwapChain)) {
|
|
10828
10924
|
return false;
|
|
10829
10925
|
}
|
|
10830
|
-
if (!isCCTPV2Supported(chain)) {
|
|
10926
|
+
if (!isCCTPV2Supported(chain) && !hasCustomContractSupport(chain, 'adapter')) {
|
|
10831
10927
|
return false;
|
|
10832
10928
|
}
|
|
10833
10929
|
return true;
|
|
@@ -10838,7 +10934,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
10838
10934
|
*
|
|
10839
10935
|
* Dynamically filter chain definitions to include only those that:
|
|
10840
10936
|
* - Are members of the {@link SwapChain} enum
|
|
10841
|
-
* - Have
|
|
10937
|
+
* - Have an Adapter contract configured directly or through CCTP v2
|
|
10842
10938
|
*
|
|
10843
10939
|
* This function is pure and accepts chains as parameter to avoid
|
|
10844
10940
|
* circular dependencies.
|
|
@@ -12363,11 +12459,13 @@ function parseOrThrow(value, schema, context) {
|
|
|
12363
12459
|
[Blockchain.Arbitrum]: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
|
|
12364
12460
|
[Blockchain.Avalanche]: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7',
|
|
12365
12461
|
[Blockchain.Celo]: '0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e',
|
|
12462
|
+
[Blockchain.Cronos]: '0x66e428c3f67a68878562e79A0234c1F83c208770',
|
|
12366
12463
|
[Blockchain.Ethereum]: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
|
12367
12464
|
[Blockchain.HyperEVM]: '0xb8ce59fc3717ada4c02eadf9682a9e934f625ebb',
|
|
12368
12465
|
[Blockchain.Ink]: '0x0200C29006150606B650577BBE7B6248F58470c1',
|
|
12369
12466
|
[Blockchain.Linea]: '0xA219439258ca9da29E9Cc4cE5596924745e12B93',
|
|
12370
12467
|
[Blockchain.Monad]: '0xe7cd86e13AC4309349F30B3435a9d337750fC82D',
|
|
12468
|
+
[Blockchain.Morph]: '0xe7cd86e13AC4309349F30B3435a9d337750fC82D',
|
|
12371
12469
|
[Blockchain.NEAR]: 'usdt.tether-token.near',
|
|
12372
12470
|
[Blockchain.Optimism]: '0x94b008aA00579c1307B0EF2c499aD98a8ce58e58',
|
|
12373
12471
|
[Blockchain.Polkadot_Asset_Hub]: '1984',
|
|
@@ -12426,6 +12524,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
12426
12524
|
[Blockchain.Arbitrum]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
|
|
12427
12525
|
[Blockchain.Avalanche]: '0xd586E7F844cEa2F87f50152665BCbc2C279D8d70',
|
|
12428
12526
|
[Blockchain.Base]: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb',
|
|
12527
|
+
[Blockchain.Cronos]: '0xF2001B145b43032AAF5Ee2884e456CCd805F677D',
|
|
12429
12528
|
[Blockchain.Ethereum]: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
|
|
12430
12529
|
[Blockchain.Linea]: '0x4AF15ec2A0BD43Db75dd04E62FAA3B8EF36b00d5',
|
|
12431
12530
|
[Blockchain.Optimism]: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
|
|
@@ -12488,7 +12587,9 @@ function parseOrThrow(value, schema, context) {
|
|
|
12488
12587
|
[Blockchain.Arbitrum]: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
|
|
12489
12588
|
[Blockchain.Avalanche]: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB',
|
|
12490
12589
|
[Blockchain.Base]: '0x4200000000000000000000000000000000000006',
|
|
12590
|
+
[Blockchain.Cronos]: '0xe44Fd7fCb2b1581822D0c862B68222998a0c299a',
|
|
12491
12591
|
[Blockchain.Ethereum]: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
|
12592
|
+
[Blockchain.Morph]: '0x5300000000000000000000000000000000000011',
|
|
12492
12593
|
[Blockchain.Optimism]: '0x4200000000000000000000000000000000000006',
|
|
12493
12594
|
[Blockchain.Polygon]: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
|
|
12494
12595
|
[Blockchain.Solana]: '7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs'
|
|
@@ -12507,6 +12608,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
12507
12608
|
locators: {
|
|
12508
12609
|
[Blockchain.Ethereum]: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
|
|
12509
12610
|
[Blockchain.Arbitrum]: '0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f',
|
|
12611
|
+
[Blockchain.Cronos]: '0x062E66477Faf219F25D27dCED647BF57C3107d52',
|
|
12510
12612
|
[Blockchain.Avalanche]: '0x50b7545627a5162F82A992c33b87aDc75187B218'
|
|
12511
12613
|
}
|
|
12512
12614
|
};
|
|
@@ -14581,7 +14683,7 @@ function resolveOptions(options) {
|
|
|
14581
14683
|
}
|
|
14582
14684
|
|
|
14583
14685
|
var name$5 = "@circle-fin/bridge-kit";
|
|
14584
|
-
var version$6 = "1.
|
|
14686
|
+
var version$6 = "1.16.0";
|
|
14585
14687
|
var pkg$6 = {
|
|
14586
14688
|
name: name$5,
|
|
14587
14689
|
version: version$6};
|
|
@@ -16303,6 +16405,53 @@ const EURC_MAINNET_TOKEN_ID = '0x6ca9e29fa53becc29becaf4a90b9ca7a995ad4d2234880d
|
|
|
16303
16405
|
}
|
|
16304
16406
|
};
|
|
16305
16407
|
|
|
16408
|
+
/**
|
|
16409
|
+
* Map a CCTPx bytes32 token id back to its known display symbol.
|
|
16410
|
+
*
|
|
16411
|
+
* The inverse of the provider's internal symbol -> id resolution: a bridge
|
|
16412
|
+
* resolves a symbol such as `'cirBTC'` to its canonical bytes32 id and reports
|
|
16413
|
+
* that id on `result.token`, dropping the symbol. This helper recovers the
|
|
16414
|
+
* symbol for display. It is scoped to {@link KNOWN_TOKEN_SYMBOLS} — the same
|
|
16415
|
+
* known set the forward resolution covers — and is keyed by network, because a
|
|
16416
|
+
* symbol names a separate registration on each network, whose ids are
|
|
16417
|
+
* unrelated.
|
|
16418
|
+
*
|
|
16419
|
+
* The lookup is best-effort by design: an arbitrary registry token, a
|
|
16420
|
+
* non-canonical bridge of a known symbol, or any unrecognized id has no known
|
|
16421
|
+
* symbol and yields `undefined`. The id is matched case-insensitively (bytes32
|
|
16422
|
+
* ids have no checksum casing).
|
|
16423
|
+
*
|
|
16424
|
+
* @param id - The bytes32 token id to decode (e.g. a `result.token` value).
|
|
16425
|
+
* @param network - The network the id belongs to (`'testnet'` or `'mainnet'`);
|
|
16426
|
+
* derive it from the source chain, e.g. `chain.isTestnet ? 'testnet' :
|
|
16427
|
+
* 'mainnet'`.
|
|
16428
|
+
* @returns The matching {@link KnownTokenSymbol}, or `undefined` when `id` is
|
|
16429
|
+
* not one of the known canonical ids for `network`.
|
|
16430
|
+
* @throws A `KitError` (`INPUT_VALIDATION_FAILED`, FATAL) when `id` is not a
|
|
16431
|
+
* string or `network` is not a recognized network.
|
|
16432
|
+
*
|
|
16433
|
+
* @example
|
|
16434
|
+
* ```typescript
|
|
16435
|
+
* import { resolveToKnownTokenSymbol } from '@circle-fin/provider-cctpx'
|
|
16436
|
+
*
|
|
16437
|
+
* const result = await provider.bridge(params) // params.token === 'cirBTC'
|
|
16438
|
+
* const network = result.source.chain.isTestnet ? 'testnet' : 'mainnet'
|
|
16439
|
+
*
|
|
16440
|
+
* resolveToKnownTokenSymbol(result.token, network) // 'cirBTC'
|
|
16441
|
+
* resolveToKnownTokenSymbol('0xdeadbeef…', network) // undefined (not known)
|
|
16442
|
+
* ```
|
|
16443
|
+
*/ function resolveToKnownTokenSymbol(id, network) {
|
|
16444
|
+
if (typeof id !== 'string') {
|
|
16445
|
+
throw createValidationFailedError$1('id', id, 'must be a string');
|
|
16446
|
+
}
|
|
16447
|
+
if (network !== 'testnet' && network !== 'mainnet') {
|
|
16448
|
+
throw createValidationFailedError$1('network', network, "must be 'testnet' or 'mainnet'");
|
|
16449
|
+
}
|
|
16450
|
+
const row = KNOWN_TOKEN_IDS_BY_NETWORK[network];
|
|
16451
|
+
const target = id.toLowerCase();
|
|
16452
|
+
return KNOWN_TOKEN_SYMBOLS.find((symbol)=>row[symbol].toLowerCase() === target);
|
|
16453
|
+
}
|
|
16454
|
+
|
|
16306
16455
|
/**
|
|
16307
16456
|
* Default clock implementation.
|
|
16308
16457
|
*
|
|
@@ -17646,7 +17795,11 @@ function createNoopLogger() {
|
|
|
17646
17795
|
tokenId: bytes32Hex,
|
|
17647
17796
|
domainId: nonNegativeInteger,
|
|
17648
17797
|
tokenAddress: z.string(),
|
|
17649
|
-
tokenManagerAddress: z.string()
|
|
17798
|
+
tokenManagerAddress: z.string(),
|
|
17799
|
+
// Operator-controlled per-chain kill switch (CCTPE-3979). Optional for
|
|
17800
|
+
// back-compat with older IRIS versions that do not surface it. Downstream
|
|
17801
|
+
// consumers in `findCCTPXDeployment` treat a missing value as `true`.
|
|
17802
|
+
enabled: z.boolean().optional()
|
|
17650
17803
|
});
|
|
17651
17804
|
/**
|
|
17652
17805
|
* Schema for one token entry in the token registry response.
|
|
@@ -18130,17 +18283,23 @@ function createNoopLogger() {
|
|
|
18130
18283
|
/**
|
|
18131
18284
|
* Find a token's deployment on the given CCTP domain.
|
|
18132
18285
|
*
|
|
18286
|
+
* A deployment row with `enabled === false` is treated as missing: an IRIS
|
|
18287
|
+
* operator has flipped the per-chain kill switch (CCTPE-3979), so the SDK must
|
|
18288
|
+
* not route through it even though the row still exists in the registry. A
|
|
18289
|
+
* missing or `true` `enabled` value remains routable — the field is optional on
|
|
18290
|
+
* older IRIS versions and absence is read as "allowed".
|
|
18291
|
+
*
|
|
18133
18292
|
* @param token - The registry token whose deployments are searched.
|
|
18134
18293
|
* @param domain - The CCTP domain id to match.
|
|
18135
18294
|
* @returns The matching {@link CCTPXTokenDeployment}, or `undefined` when the
|
|
18136
|
-
* token has no deployment on that domain.
|
|
18295
|
+
* token has no deployment on that domain or the deployment is disabled.
|
|
18137
18296
|
*
|
|
18138
18297
|
* @example
|
|
18139
18298
|
* ```typescript
|
|
18140
18299
|
* const deployment = findCCTPXDeployment(token, 0)
|
|
18141
18300
|
* ```
|
|
18142
18301
|
*/ function findCCTPXDeployment(token, domain) {
|
|
18143
|
-
return token.deployments.find((deployment)=>deployment.domainId === domain);
|
|
18302
|
+
return token.deployments.find((deployment)=>deployment.domainId === domain && deployment.enabled !== false);
|
|
18144
18303
|
}
|
|
18145
18304
|
|
|
18146
18305
|
/**
|
|
@@ -18242,6 +18401,24 @@ function createNoopLogger() {
|
|
|
18242
18401
|
}
|
|
18243
18402
|
}
|
|
18244
18403
|
})
|
|
18404
|
+
],
|
|
18405
|
+
// The token is not deployed on one of the route chains, or an operator has
|
|
18406
|
+
// flipped the per-chain `enabled` kill switch on the IRIS deployment row.
|
|
18407
|
+
// Either way the route is permanently unavailable until the operator intervenes;
|
|
18408
|
+
// a retry at the same source/destination will keep returning this error.
|
|
18409
|
+
[
|
|
18410
|
+
'TOKEN_NOT_SUPPORTED_ON_ROUTE',
|
|
18411
|
+
({ sourceDomain, destinationDomain })=>new KitError({
|
|
18412
|
+
...InputError.UNSUPPORTED_ROUTE,
|
|
18413
|
+
recoverability: 'FATAL',
|
|
18414
|
+
message: 'CCTPx fee quote rejected the route: the token is not supported on ' + 'the source or destination chain (missing deployment or operator-disabled).',
|
|
18415
|
+
cause: {
|
|
18416
|
+
trace: {
|
|
18417
|
+
sourceDomain,
|
|
18418
|
+
destinationDomain
|
|
18419
|
+
}
|
|
18420
|
+
}
|
|
18421
|
+
})
|
|
18245
18422
|
]
|
|
18246
18423
|
]);
|
|
18247
18424
|
/**
|
|
@@ -23498,6 +23675,24 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
23498
23675
|
}
|
|
23499
23676
|
};
|
|
23500
23677
|
|
|
23678
|
+
/**
|
|
23679
|
+
* Check whether a forwarded burn to the given destination asks Circle's
|
|
23680
|
+
* Forwarding Service to set up the recipient's token account before minting.
|
|
23681
|
+
*
|
|
23682
|
+
* Must return `true` exactly for the destinations where
|
|
23683
|
+
* {@link getForwarderHookData} emits the `createAta` frame, so the forwarding
|
|
23684
|
+
* fee quote includes the setup rent whenever the burn requests it.
|
|
23685
|
+
*
|
|
23686
|
+
* @internal
|
|
23687
|
+
* @param chainType - The destination blockchain type.
|
|
23688
|
+
* @returns `true` for Solana destinations, `false` otherwise.
|
|
23689
|
+
*
|
|
23690
|
+
* @example
|
|
23691
|
+
* ```typescript
|
|
23692
|
+
* requiresRecipientSetup('solana') // true
|
|
23693
|
+
* requiresRecipientSetup('evm') // false
|
|
23694
|
+
* ```
|
|
23695
|
+
*/ const requiresRecipientSetup = (chainType)=>chainType === 'solana';
|
|
23501
23696
|
/**
|
|
23502
23697
|
* Build the forwarding `hookData` for a forwarded (Orbit-relayed) CCTP v2 burn,
|
|
23503
23698
|
* tailored to the destination chain.
|
|
@@ -23848,10 +24043,12 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
23848
24043
|
* @param sourceDomain - The source domain
|
|
23849
24044
|
* @param destinationDomain - The destination domain
|
|
23850
24045
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
24046
|
+
* @param includeRecipientSetup - Whether the quote includes recipient account setup rent
|
|
23851
24047
|
* @returns The complete API URL with forward=true query parameter
|
|
23852
|
-
*/ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet) {
|
|
24048
|
+
*/ function buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet, includeRecipientSetup) {
|
|
23853
24049
|
const baseUrl = isTestnet ? IRIS_API_SANDBOX_BASE_URL$1 : IRIS_API_BASE_URL$1;
|
|
23854
|
-
|
|
24050
|
+
const recipientSetupQuery = includeRecipientSetup ? '&includeRecipientSetup=true' : '';
|
|
24051
|
+
return `${baseUrl}/v2/burn/USDC/fees/${sourceDomain.toString()}/${destinationDomain.toString()}?forward=true${recipientSetupQuery}`;
|
|
23855
24052
|
}
|
|
23856
24053
|
/**
|
|
23857
24054
|
* Fetches the forwarding fee for a USDC transfer operation with retry and timeout handling.
|
|
@@ -23868,6 +24065,10 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
23868
24065
|
*
|
|
23869
24066
|
* The high fee tier is always used to provide buffer for destination gas fluctuations.
|
|
23870
24067
|
*
|
|
24068
|
+
* Pass `includeRecipientSetup: true` when the burn asks the Forwarding Service to create
|
|
24069
|
+
* the recipient's token account (Solana destinations). The quote then includes the
|
|
24070
|
+
* account rent, which the quote without this flag leaves out.
|
|
24071
|
+
*
|
|
23871
24072
|
* Implements timeout and retry logic to handle network issues and temporary API failures.
|
|
23872
24073
|
* Each HTTP request is given a maximum of 2 seconds before it is aborted.
|
|
23873
24074
|
* We retry up to 10 times, waiting 200ms between attempts.
|
|
@@ -23876,6 +24077,8 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
23876
24077
|
* @param destinationDomain - The destination domain
|
|
23877
24078
|
* @param isTestnet - Whether the request is for a testnet chain
|
|
23878
24079
|
* @param finalityThreshold - The finality threshold to select the fee tier (1000 for FAST, 2000 for SLOW)
|
|
24080
|
+
* @param includeRecipientSetup - Whether to include recipient account setup rent in the
|
|
24081
|
+
* quote. Defaults to `false`.
|
|
23879
24082
|
* @returns The high-tier forwarding fee in USDC minor units (6 decimals)
|
|
23880
24083
|
* @throws Error if the input domains are invalid, the API request fails, returns invalid data, or network errors occur
|
|
23881
24084
|
*
|
|
@@ -23887,8 +24090,11 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
23887
24090
|
*
|
|
23888
24091
|
* // SLOW transfer - use 2000ms finality threshold
|
|
23889
24092
|
* const slowForwardingFee = await fetchForwardingFee(0, 6, false, 2000)
|
|
24093
|
+
*
|
|
24094
|
+
* // Ethereum -> Solana: include the recipient token account rent
|
|
24095
|
+
* const solanaForwardingFee = await fetchForwardingFee(0, 5, false, 1000, true)
|
|
23890
24096
|
* ```
|
|
23891
|
-
*/ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, finalityThreshold) {
|
|
24097
|
+
*/ async function fetchForwardingFee(sourceDomain, destinationDomain, isTestnet, finalityThreshold, includeRecipientSetup = false) {
|
|
23892
24098
|
// Validate source domain
|
|
23893
24099
|
if (!Number.isInteger(sourceDomain) || sourceDomain < 0) {
|
|
23894
24100
|
throw new KitError({
|
|
@@ -23916,7 +24122,7 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
23916
24122
|
});
|
|
23917
24123
|
}
|
|
23918
24124
|
// Fetch the fee tiers from the API with forward=true
|
|
23919
|
-
const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet);
|
|
24125
|
+
const url = buildForwardingFeeUrl(sourceDomain, destinationDomain, isTestnet, includeRecipientSetup);
|
|
23920
24126
|
const response = await pollApiGet(url, isForwardingFeeResponse, DEFAULT_CONFIG$4);
|
|
23921
24127
|
// Validate that we have at least one fee tier
|
|
23922
24128
|
if (response.length === 0) {
|
|
@@ -26252,7 +26458,7 @@ const mockAttestationMessage = {
|
|
|
26252
26458
|
return step;
|
|
26253
26459
|
}
|
|
26254
26460
|
|
|
26255
|
-
var version$5 = "1.14.
|
|
26461
|
+
var version$5 = "1.14.3";
|
|
26256
26462
|
var pkg$5 = {
|
|
26257
26463
|
version: version$5};
|
|
26258
26464
|
|
|
@@ -27171,9 +27377,34 @@ function assertCCTPV2Config(config) {
|
|
|
27171
27377
|
tokenAddress: source.chain.usdcAddress,
|
|
27172
27378
|
operationContext: sourceOperationContext
|
|
27173
27379
|
});
|
|
27380
|
+
await this.assertAmountCoversRecipientSetupFee(params);
|
|
27174
27381
|
return bridge$1(params, this);
|
|
27175
27382
|
}
|
|
27176
27383
|
/**
|
|
27384
|
+
* Reject a forwarded transfer to Solana whose amount does not exceed the fee.
|
|
27385
|
+
*
|
|
27386
|
+
* CCTP v2 reverts a burn whose `maxFee` is not less than its amount, and the Solana
|
|
27387
|
+
* forwarding fee includes the recipient token account rent. Checking before the
|
|
27388
|
+
* approval avoids sending a transaction that can never be followed by a burn.
|
|
27389
|
+
* Skipped when the caller provides `config.maxFee`.
|
|
27390
|
+
*
|
|
27391
|
+
* @param params - The validated CCTP v2 bridge parameters.
|
|
27392
|
+
* @returns A promise that resolves when the amount covers the fee.
|
|
27393
|
+
* @throws {KitError} INPUT_INVALID_AMOUNT (FATAL) when the fee is greater than or equal
|
|
27394
|
+
* to the amount.
|
|
27395
|
+
*/ async assertAmountCoversRecipientSetupFee(params) {
|
|
27396
|
+
const { destination, amount, config } = params;
|
|
27397
|
+
if (destination.useForwarder !== true || config.maxFee !== undefined || !requiresRecipientSetup(destination.chain.type)) {
|
|
27398
|
+
return;
|
|
27399
|
+
}
|
|
27400
|
+
const { providerFee, forwarderFee } = await this.getMaxFee(params);
|
|
27401
|
+
const maxFee = providerFee + forwarderFee;
|
|
27402
|
+
if (maxFee >= BigInt(amount)) {
|
|
27403
|
+
// Amounts and fees are in USDC minor units (6 decimals)
|
|
27404
|
+
throw createInvalidAmountError(formatUnits(amount, 6), `Amount must be greater than the ${formatUnits(String(maxFee), 6)} USDC fee for forwarded transfers to ${destination.chain.name}, which includes creating the recipient's token account`);
|
|
27405
|
+
}
|
|
27406
|
+
}
|
|
27407
|
+
/**
|
|
27177
27408
|
* Retry a failed or incomplete CCTP v2 bridge operation.
|
|
27178
27409
|
*
|
|
27179
27410
|
* Analyzes the bridge result to determine where to continue and executes
|
|
@@ -27890,6 +28121,9 @@ function assertCCTPV2Config(config) {
|
|
|
27890
28121
|
* | FAST | true | No | maxFee = calculatedBurnFee + forwardingFee |
|
|
27891
28122
|
* | FAST | true | Yes | maxFee = config.maxFee |
|
|
27892
28123
|
*
|
|
28124
|
+
* For forwarded burns to Solana, the forwarding fee includes the rent for creating the
|
|
28125
|
+
* recipient's token account. A caller-provided `maxFee` must cover it too.
|
|
28126
|
+
*
|
|
27893
28127
|
* @param params - The bridge parameters object containing:
|
|
27894
28128
|
* - `source`: The source wallet context with chain definition and adapter
|
|
27895
28129
|
* - `destination`: The destination wallet context with chain definition and adapter
|
|
@@ -27928,6 +28162,7 @@ function assertCCTPV2Config(config) {
|
|
|
27928
28162
|
}
|
|
27929
28163
|
// Get the min finality threshold based on the transfer speed
|
|
27930
28164
|
const minFinalityThreshold = CCTPv2MinFinalityThreshold[transferSpeed];
|
|
28165
|
+
const includeRecipientSetup = requiresRecipientSetup(destination.chain.type);
|
|
27931
28166
|
// SLOW transfer without forwarder: no fee
|
|
27932
28167
|
if (transferSpeed === TransferSpeed.SLOW && !useForwarder) {
|
|
27933
28168
|
return {
|
|
@@ -27937,7 +28172,7 @@ function assertCCTPV2Config(config) {
|
|
|
27937
28172
|
}
|
|
27938
28173
|
// SLOW transfer with forwarder: only forwarding fee
|
|
27939
28174
|
if (transferSpeed === TransferSpeed.SLOW && useForwarder) {
|
|
27940
|
-
const forwarderFee = await fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold);
|
|
28175
|
+
const forwarderFee = await fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold, includeRecipientSetup);
|
|
27941
28176
|
return {
|
|
27942
28177
|
providerFee: 0n,
|
|
27943
28178
|
forwarderFee
|
|
@@ -27947,7 +28182,7 @@ function assertCCTPV2Config(config) {
|
|
|
27947
28182
|
// Fetch fees in parallel when forwarder is enabled
|
|
27948
28183
|
const [scaledBps, forwarderFee] = await Promise.all([
|
|
27949
28184
|
fetchUsdcFastBurnFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet),
|
|
27950
|
-
useForwarder ? fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold) : Promise.resolve(0n)
|
|
28185
|
+
useForwarder ? fetchForwardingFee(source.chain.cctp.domain, destination.chain.cctp.domain, source.chain.isTestnet, minFinalityThreshold, includeRecipientSetup) : Promise.resolve(0n)
|
|
27951
28186
|
]);
|
|
27952
28187
|
// Calculate fee proportional to the transfer amount
|
|
27953
28188
|
const amountInMinorUnits = BigInt(amount);
|
|
@@ -28497,6 +28732,21 @@ function assertCCTPV2Config(config) {
|
|
|
28497
28732
|
'bridge_forward'
|
|
28498
28733
|
]
|
|
28499
28734
|
];
|
|
28735
|
+
/**
|
|
28736
|
+
* Provider step names whose `txHash` is the source-chain transaction that
|
|
28737
|
+
* moves funds into the bridge protocol.
|
|
28738
|
+
*
|
|
28739
|
+
* @remarks
|
|
28740
|
+
* The success event carries this hash so the backend can join it to the
|
|
28741
|
+
* on-chain CCTP / CCTPx event for the same transfer. `burn` is the CCTP v2
|
|
28742
|
+
* step (including batched and source-fee flows); `transfer` is the CCTPx
|
|
28743
|
+
* `CrossChainTokenService` step.
|
|
28744
|
+
*
|
|
28745
|
+
* @internal
|
|
28746
|
+
*/ const BRIDGE_SOURCE_TX_STEP_NAMES = [
|
|
28747
|
+
'burn',
|
|
28748
|
+
'transfer'
|
|
28749
|
+
];
|
|
28500
28750
|
|
|
28501
28751
|
/**
|
|
28502
28752
|
* Base URL for Circle's Quote API (hosted in Iris) on mainnet/production.
|
|
@@ -29592,6 +29842,85 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29592
29842
|
}
|
|
29593
29843
|
return undefined;
|
|
29594
29844
|
}
|
|
29845
|
+
/**
|
|
29846
|
+
* Pick the source-chain transaction hash from a bridge result.
|
|
29847
|
+
*
|
|
29848
|
+
* Walk the steps from the end and return the hash of the most recent
|
|
29849
|
+
* successful source step (`burn` for CCTP v2, `transfer` for CCTPx). Walking
|
|
29850
|
+
* backwards selects the attempt that landed when a retry appended a new
|
|
29851
|
+
* source step after a failed one.
|
|
29852
|
+
*
|
|
29853
|
+
* @param steps - All steps from the bridge result.
|
|
29854
|
+
* @returns The source transaction hash, or `undefined` if no source step recorded one.
|
|
29855
|
+
*
|
|
29856
|
+
* @internal
|
|
29857
|
+
*/ function pickSourceTxHash(steps) {
|
|
29858
|
+
for(let i = steps.length - 1; i >= 0; i--){
|
|
29859
|
+
const step = steps[i];
|
|
29860
|
+
if (step?.txHash != null && step.state === 'success' && BRIDGE_SOURCE_TX_STEP_NAMES.includes(step.name)) {
|
|
29861
|
+
return step.txHash;
|
|
29862
|
+
}
|
|
29863
|
+
}
|
|
29864
|
+
return undefined;
|
|
29865
|
+
}
|
|
29866
|
+
/**
|
|
29867
|
+
* Resolve the token value reported in telemetry.
|
|
29868
|
+
*
|
|
29869
|
+
* CCTPx reports `result.token` as a bytes32 token id, while the backend's
|
|
29870
|
+
* canonical tables key tokens by symbol. Map the canonical id of a known CCTPx
|
|
29871
|
+
* token back to its symbol and pass any other value (USDC, or a registry token
|
|
29872
|
+
* without a known symbol) through unchanged. Registry symbols are not used
|
|
29873
|
+
* because they are not unique across CCTPx tokens. Never throws.
|
|
29874
|
+
*
|
|
29875
|
+
* @param token - The token from a bridge result.
|
|
29876
|
+
* @param sourceChain - The result's source chain; selects testnet or mainnet ids.
|
|
29877
|
+
* @returns The known symbol, or `token` unchanged.
|
|
29878
|
+
*
|
|
29879
|
+
* @internal
|
|
29880
|
+
*/ function toTelemetryToken(token, sourceChain) {
|
|
29881
|
+
try {
|
|
29882
|
+
const network = sourceChain.isTestnet ? 'testnet' : 'mainnet';
|
|
29883
|
+
return resolveToKnownTokenSymbol(token, network) ?? token;
|
|
29884
|
+
} catch {
|
|
29885
|
+
return token;
|
|
29886
|
+
}
|
|
29887
|
+
}
|
|
29888
|
+
/**
|
|
29889
|
+
* Emit the `bridge_bridge` success event for a completed transfer.
|
|
29890
|
+
*
|
|
29891
|
+
* No-ops unless `result.state` is `'success'`. Failed results are already
|
|
29892
|
+
* reported by error telemetry. Never throws: a malformed result (for example
|
|
29893
|
+
* from a custom provider) is reported as a telemetry drop warning and the
|
|
29894
|
+
* caller's result is returned unchanged.
|
|
29895
|
+
*
|
|
29896
|
+
* @param result - The human-readable bridge result returned to the caller.
|
|
29897
|
+
* @param config - Analytics telemetry identity, gated by `disableAnalytics`.
|
|
29898
|
+
*
|
|
29899
|
+
* @internal
|
|
29900
|
+
*/ function emitBridgeSuccessTelemetry(result, config) {
|
|
29901
|
+
if (config.disabled || result.state !== 'success') {
|
|
29902
|
+
return;
|
|
29903
|
+
}
|
|
29904
|
+
try {
|
|
29905
|
+
const txHash = pickSourceTxHash(result.steps);
|
|
29906
|
+
// `JSON.stringify` turns a non-finite number into `null`, which the logs
|
|
29907
|
+
// endpoint's strict schema rejects along with the whole event.
|
|
29908
|
+
const amountIn = Number.parseFloat(result.amount);
|
|
29909
|
+
emitSuccessTelemetry(BRIDGE_EVENT_TYPES.BRIDGE, config, {
|
|
29910
|
+
sourceChain: result.source.chain.chain,
|
|
29911
|
+
destinationChain: result.destination.chain.chain,
|
|
29912
|
+
tokenIn: toTelemetryToken(result.token, result.source.chain),
|
|
29913
|
+
...Number.isFinite(amountIn) && {
|
|
29914
|
+
amountIn
|
|
29915
|
+
},
|
|
29916
|
+
...txHash != null && {
|
|
29917
|
+
txHash
|
|
29918
|
+
}
|
|
29919
|
+
});
|
|
29920
|
+
} catch (telemetryError) {
|
|
29921
|
+
warnTelemetryDrop(BRIDGE_EVENT_TYPES.BRIDGE, telemetryError);
|
|
29922
|
+
}
|
|
29923
|
+
}
|
|
29595
29924
|
/**
|
|
29596
29925
|
* BridgeKit caller component for retry and estimate operations.
|
|
29597
29926
|
*/ const BRIDGE_KIT_CALLER = {
|
|
@@ -29722,6 +30051,10 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29722
30051
|
/** Whether error telemetry is disabled. */ disableErrorReporting;
|
|
29723
30052
|
/** Per-kit telemetry identity for shared helpers. */ telemetryConfig;
|
|
29724
30053
|
/**
|
|
30054
|
+
* Per-kit telemetry identity for success events. Gated by `disableAnalytics`
|
|
30055
|
+
* independently of `disableErrorReporting`.
|
|
30056
|
+
*/ analyticsTelemetryConfig;
|
|
30057
|
+
/**
|
|
29725
30058
|
* Create a new BridgeKit instance.
|
|
29726
30059
|
*
|
|
29727
30060
|
* @param config - The configuration containing the CCTPv2 provider
|
|
@@ -29748,6 +30081,11 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29748
30081
|
sdkVersion: pkg$6.version,
|
|
29749
30082
|
disabled: this.disableErrorReporting
|
|
29750
30083
|
};
|
|
30084
|
+
this.analyticsTelemetryConfig = {
|
|
30085
|
+
sdkName: SDK_NAME$4,
|
|
30086
|
+
sdkVersion: pkg$6.version,
|
|
30087
|
+
disabled: config.disableAnalytics === true
|
|
30088
|
+
};
|
|
29751
30089
|
for (const provider of this.providers){
|
|
29752
30090
|
provider.registerDispatcher(this.actionDispatcher);
|
|
29753
30091
|
}
|
|
@@ -29761,7 +30099,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29761
30099
|
this.actionDispatcher.off(actionOrWildCard, handler);
|
|
29762
30100
|
}
|
|
29763
30101
|
async bridge(params) {
|
|
29764
|
-
|
|
30102
|
+
const bridgeResult = await withErrorTelemetry(async ()=>{
|
|
29765
30103
|
// First validate the parameters
|
|
29766
30104
|
assertBridgeParams(params, bridgeParamsWithChainIdentifierSchema);
|
|
29767
30105
|
// Then resolve chain definitions (includes adapter chain support validation)
|
|
@@ -29798,7 +30136,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29798
30136
|
emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.BRIDGE, this.telemetryConfig, {
|
|
29799
30137
|
sourceChain: resolveChainName(params.from.chain),
|
|
29800
30138
|
destinationChain: resolveChainName(params.to.chain),
|
|
29801
|
-
tokenIn:
|
|
30139
|
+
tokenIn: toTelemetryToken(result.token, result.source.chain),
|
|
29802
30140
|
...txHash != null && {
|
|
29803
30141
|
txHash
|
|
29804
30142
|
}
|
|
@@ -29810,6 +30148,8 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29810
30148
|
destinationChain: resolveChainName(params.to.chain),
|
|
29811
30149
|
tokenIn: params.token
|
|
29812
30150
|
});
|
|
30151
|
+
emitBridgeSuccessTelemetry(bridgeResult, this.analyticsTelemetryConfig);
|
|
30152
|
+
return bridgeResult;
|
|
29813
30153
|
}
|
|
29814
30154
|
/**
|
|
29815
30155
|
* Retry a failed or incomplete cross-chain USDC bridge operation.
|
|
@@ -29880,7 +30220,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29880
30220
|
* )
|
|
29881
30221
|
* ```
|
|
29882
30222
|
*/ async retry(result, context, invocationMeta) {
|
|
29883
|
-
|
|
30223
|
+
const retriedResult = await withErrorTelemetry(async ()=>{
|
|
29884
30224
|
// Cast the providers list to the wide view here. The tuple
|
|
29885
30225
|
// type on `this.providers` retains concrete provider types
|
|
29886
30226
|
// (CCTPv2 narrows `TToken` to `'USDC'`); calling `.retry`
|
|
@@ -29908,7 +30248,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29908
30248
|
emitResultStepErrorTelemetry(failedStep, BRIDGE_STEP_EVENT_MAP, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
|
|
29909
30249
|
sourceChain: result.source.chain.chain,
|
|
29910
30250
|
destinationChain: result.destination.chain.chain,
|
|
29911
|
-
tokenIn: result.token,
|
|
30251
|
+
tokenIn: toTelemetryToken(result.token, result.source.chain),
|
|
29912
30252
|
...txHash != null && {
|
|
29913
30253
|
txHash
|
|
29914
30254
|
}
|
|
@@ -29922,8 +30262,16 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
29922
30262
|
}, BRIDGE_EVENT_TYPES.RETRY, this.telemetryConfig, {
|
|
29923
30263
|
sourceChain: result.source.chain.chain,
|
|
29924
30264
|
destinationChain: result.destination.chain.chain,
|
|
29925
|
-
tokenIn: result.token
|
|
30265
|
+
tokenIn: toTelemetryToken(result.token, result.source.chain)
|
|
29926
30266
|
});
|
|
30267
|
+
// A retry that completes the transfer is the transfer's only success
|
|
30268
|
+
// event, so it reports the same `bridge_bridge` type as `bridge()`. An
|
|
30269
|
+
// input that already succeeded was reported by the call that completed it
|
|
30270
|
+
// (CCTP v2 returns such a result unchanged), so it is not reported again.
|
|
30271
|
+
if (result.state !== 'success') {
|
|
30272
|
+
emitBridgeSuccessTelemetry(retriedResult, this.analyticsTelemetryConfig);
|
|
30273
|
+
}
|
|
30274
|
+
return retriedResult;
|
|
29927
30275
|
}
|
|
29928
30276
|
async estimate(params) {
|
|
29929
30277
|
return withErrorTelemetry(async ()=>{
|
|
@@ -30472,7 +30820,7 @@ async function resolveExecutionQuote(binding, suppliedQuote, refundAddress) {
|
|
|
30472
30820
|
registerKit(`${pkg$6.name}/${pkg$6.version}`);
|
|
30473
30821
|
|
|
30474
30822
|
var name$4 = "@circle-fin/swap-kit";
|
|
30475
|
-
var version$4 = "1.
|
|
30823
|
+
var version$4 = "1.8.1";
|
|
30476
30824
|
var pkg$4 = {
|
|
30477
30825
|
name: name$4,
|
|
30478
30826
|
version: version$4};
|
|
@@ -30932,6 +31280,64 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
30932
31280
|
}, {
|
|
30933
31281
|
message: 'fee amount must be non-negative (>= 0)'
|
|
30934
31282
|
});
|
|
31283
|
+
/**
|
|
31284
|
+
* Parses a display-only field leniently: a value `schema` rejects is replaced
|
|
31285
|
+
* with `fallback` instead of failing the response that carries it.
|
|
31286
|
+
* Implemented with `preprocess` rather than Zod's `.catch()` because static
|
|
31287
|
+
* analysis misreads `.catch` on the schema chain as an unhandled Promise
|
|
31288
|
+
* (S7785).
|
|
31289
|
+
*/ const withFallback = (schema, fallback)=>z.preprocess((value)=>schema.safeParse(value).success ? value : fallback, schema);
|
|
31290
|
+
const swapRouteTokenSchema = z.object({
|
|
31291
|
+
symbol: z.string(),
|
|
31292
|
+
address: z.string(),
|
|
31293
|
+
decimals: z.number()
|
|
31294
|
+
});
|
|
31295
|
+
const swapRouteMetadataSchema = z.record(z.unknown()).optional();
|
|
31296
|
+
const swapRouteStepSchema = z.object({
|
|
31297
|
+
type: z.string(),
|
|
31298
|
+
tool: z.string(),
|
|
31299
|
+
toolName: z.string(),
|
|
31300
|
+
fromToken: swapRouteTokenSchema,
|
|
31301
|
+
toToken: swapRouteTokenSchema,
|
|
31302
|
+
estimate: z.object({
|
|
31303
|
+
fromAmount: z.string(),
|
|
31304
|
+
toAmount: z.string(),
|
|
31305
|
+
gasCostUSD: z.string().optional()
|
|
31306
|
+
}),
|
|
31307
|
+
metadata: swapRouteMetadataSchema
|
|
31308
|
+
});
|
|
31309
|
+
/**
|
|
31310
|
+
* Strict schema for a `SwapRoute`. Module-private: service responses
|
|
31311
|
+
* parse routes leniently through {@link responseSwapRouteSchema}.
|
|
31312
|
+
*/ const swapRouteSchema = z.object({
|
|
31313
|
+
provider: z.string().min(1, 'provider must be a non-empty string'),
|
|
31314
|
+
steps: z.array(swapRouteStepSchema),
|
|
31315
|
+
metadata: swapRouteMetadataSchema
|
|
31316
|
+
});
|
|
31317
|
+
/**
|
|
31318
|
+
* Route details are display-only, so service responses parse them leniently:
|
|
31319
|
+
* steps or metadata in a shape the SDK does not know are dropped rather than
|
|
31320
|
+
* failing the quote or swap they describe. Steps are all-or-nothing: if any
|
|
31321
|
+
* step is malformed, `steps` is empty, so a route never silently loses a hop.
|
|
31322
|
+
*/ const responseSwapRouteSchema = swapRouteSchema.extend({
|
|
31323
|
+
steps: withFallback(swapRouteSchema.shape.steps, []),
|
|
31324
|
+
metadata: withFallback(swapRouteMetadataSchema, undefined)
|
|
31325
|
+
});
|
|
31326
|
+
const createSwapFeeItemSchema = z.object({
|
|
31327
|
+
token: z.string({
|
|
31328
|
+
required_error: 'fee token is required',
|
|
31329
|
+
invalid_type_error: 'fee token must be a string'
|
|
31330
|
+
}).min(1, 'fee token must be a non-empty string'),
|
|
31331
|
+
amount: feeAmountSchema,
|
|
31332
|
+
decimals: z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
|
|
31333
|
+
symbol: z.string({
|
|
31334
|
+
invalid_type_error: 'fee token symbol must be a string'
|
|
31335
|
+
}).min(1, 'fee token symbol must be a non-empty string').optional(),
|
|
31336
|
+
// Descriptive metadata: a value the SDK cannot use is dropped rather than
|
|
31337
|
+
// failing the response that carries the fee.
|
|
31338
|
+
type: withFallback(z.string().min(1).optional(), undefined),
|
|
31339
|
+
chain: withFallback(z.string().min(1).optional(), undefined)
|
|
31340
|
+
});
|
|
30935
31341
|
/**
|
|
30936
31342
|
* Zod schema for validating API keys.
|
|
30937
31343
|
*
|
|
@@ -31017,10 +31423,7 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
31017
31423
|
/** Quote information with estimated amounts and route */ quote: z.object({
|
|
31018
31424
|
/** Estimated output amount (base units) */ estimatedAmount: z.string().min(1, 'estimatedAmount must be a non-empty string').regex(/^\d+$/, 'estimatedAmount must be a numeric string'),
|
|
31019
31425
|
/** Guaranteed minimum output amount (base units) */ minAmount: z.string().min(1, 'minAmount must be a non-empty string').regex(/^\d+$/, 'minAmount must be a numeric string'),
|
|
31020
|
-
/** Route information from DEX aggregator */ route:
|
|
31021
|
-
provider: z.string().min(1, 'provider must be a non-empty string'),
|
|
31022
|
-
steps: z.array(z.unknown())
|
|
31023
|
-
})
|
|
31426
|
+
/** Route information from DEX aggregator */ route: responseSwapRouteSchema
|
|
31024
31427
|
}),
|
|
31025
31428
|
/** Fee context indicating where fees should be collected */ feeContext: quoteFeeContextSchema
|
|
31026
31429
|
});
|
|
@@ -31236,19 +31639,6 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
31236
31639
|
}).min(1, 'toChain must be a non-empty string if provided').optional(),
|
|
31237
31640
|
apiKey: apiKeySchema.optional()
|
|
31238
31641
|
});
|
|
31239
|
-
/**
|
|
31240
|
-
* Zod schema for validating CreateSwapResponse payloads.
|
|
31241
|
-
*/ const createSwapFeeItemSchema = z.object({
|
|
31242
|
-
token: z.string({
|
|
31243
|
-
required_error: 'fee token is required',
|
|
31244
|
-
invalid_type_error: 'fee token must be a string'
|
|
31245
|
-
}).min(1, 'fee token must be a non-empty string'),
|
|
31246
|
-
amount: feeAmountSchema,
|
|
31247
|
-
decimals: z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
|
|
31248
|
-
symbol: z.string({
|
|
31249
|
-
invalid_type_error: 'fee token symbol must be a string'
|
|
31250
|
-
}).min(1, 'fee token symbol must be a non-empty string').optional()
|
|
31251
|
-
});
|
|
31252
31642
|
/**
|
|
31253
31643
|
* Developer fee item schema with basis field.
|
|
31254
31644
|
*/ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
|
|
@@ -31386,6 +31776,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
31386
31776
|
correlationId: z.preprocess((value)=>z.string().uuid().safeParse(value).success ? value : undefined, z.string().optional()),
|
|
31387
31777
|
config: createSwapRequestBaseSchema.shape.config.optional(),
|
|
31388
31778
|
fees: createSwapFeesSchema.optional(),
|
|
31779
|
+
// A route the SDK cannot parse is dropped instead of failing the swap.
|
|
31780
|
+
route: withFallback(responseSwapRouteSchema.optional(), undefined),
|
|
31389
31781
|
transaction: createSwapTransactionSchema
|
|
31390
31782
|
});
|
|
31391
31783
|
|
|
@@ -31413,6 +31805,25 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
31413
31805
|
* }
|
|
31414
31806
|
* ```
|
|
31415
31807
|
*/ const isGetQuoteResponse = (obj)=>getQuoteResponseSchema.safeParse(obj).success;
|
|
31808
|
+
/**
|
|
31809
|
+
* Parse and transform a GetQuoteResponse object.
|
|
31810
|
+
*
|
|
31811
|
+
* Applies the schema's lenient route and fee parsing: entries the SDK does
|
|
31812
|
+
* not recognise are dropped and undeclared fields are stripped, rather than
|
|
31813
|
+
* passed through to callers as the type guard alone would.
|
|
31814
|
+
*
|
|
31815
|
+
* @param obj - The raw API response object to parse.
|
|
31816
|
+
* @returns The validated and transformed GetQuoteResponse.
|
|
31817
|
+
* @throws If validation fails.
|
|
31818
|
+
*
|
|
31819
|
+
* @example
|
|
31820
|
+
* ```typescript
|
|
31821
|
+
* import { parseGetQuoteResponse } from '@core/service-client'
|
|
31822
|
+
*
|
|
31823
|
+
* const quote = parseGetQuoteResponse(await response.json())
|
|
31824
|
+
* console.log(quote.quote.route.steps.length)
|
|
31825
|
+
* ```
|
|
31826
|
+
*/ const parseGetQuoteResponse = (obj)=>getQuoteResponseSchema.parse(obj);
|
|
31416
31827
|
/**
|
|
31417
31828
|
* Type guard to validate CreateSwapResponse objects.
|
|
31418
31829
|
*
|
|
@@ -31818,7 +32229,9 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
31818
32229
|
}
|
|
31819
32230
|
}
|
|
31820
32231
|
};
|
|
31821
|
-
|
|
32232
|
+
// Use type guard for validation in polling, then parse for transformation
|
|
32233
|
+
const rawResponse = await pollApiGet(url, isGetQuoteResponse, effectiveConfig);
|
|
32234
|
+
return parseGetQuoteResponse(rawResponse);
|
|
31822
32235
|
};
|
|
31823
32236
|
|
|
31824
32237
|
/**
|
|
@@ -40742,6 +41155,16 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
40742
41155
|
name: 'USDC',
|
|
40743
41156
|
version: '2'
|
|
40744
41157
|
},
|
|
41158
|
+
Cronos: {
|
|
41159
|
+
chainId: Cronos.chainId,
|
|
41160
|
+
name: 'USDC',
|
|
41161
|
+
version: '2'
|
|
41162
|
+
},
|
|
41163
|
+
Morph: {
|
|
41164
|
+
chainId: Morph.chainId,
|
|
41165
|
+
name: 'USDC',
|
|
41166
|
+
version: '2'
|
|
41167
|
+
},
|
|
40745
41168
|
Arc_Testnet: {
|
|
40746
41169
|
chainId: ArcTestnet.chainId,
|
|
40747
41170
|
name: 'USDC',
|
|
@@ -42310,6 +42733,12 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
|
|
|
42310
42733
|
* i.e. 1 + 0.2 + 0.1 = 1.3. Use the greater of (local estimate × this multiplier)
|
|
42311
42734
|
* or the proxy's gasLimit.
|
|
42312
42735
|
*/ const GAS_SAFETY_MULTIPLIER = 1.3;
|
|
42736
|
+
/**
|
|
42737
|
+
* Gas units used to estimate the displayed gas fee when the service returns an
|
|
42738
|
+
* EVM route without a gasLimit, as direct Circle CCTP plans do. Sized from
|
|
42739
|
+
* observed Adapter executions of those plans. Display-only: it never becomes an
|
|
42740
|
+
* execution gas limit.
|
|
42741
|
+
*/ const DEFAULT_EVM_SWAP_GAS_LIMIT = 500_000n;
|
|
42313
42742
|
const TOKEN_REGISTRY$1 = createTokenRegistry();
|
|
42314
42743
|
/**
|
|
42315
42744
|
* Enhances a KitError with transaction details (txHash and explorerUrl).
|
|
@@ -42826,9 +43255,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
42826
43255
|
// Calculate estimated gas/transaction fee based on chain type
|
|
42827
43256
|
let estimatedGasFee = '0';
|
|
42828
43257
|
const evmGasLimit = 'gasLimit' in serviceResponse.transaction ? serviceResponse.transaction.gasLimit : undefined;
|
|
42829
|
-
if (chain.type === 'evm'
|
|
43258
|
+
if (chain.type === 'evm') {
|
|
42830
43259
|
// EVM: gasLimit (units) × gasPrice (wei/unit) = estimated gas fee (wei)
|
|
42831
|
-
const gasLimitUnits = BigInt(evmGasLimit);
|
|
43260
|
+
const gasLimitUnits = evmGasLimit === undefined ? DEFAULT_EVM_SWAP_GAS_LIMIT : BigInt(evmGasLimit);
|
|
42832
43261
|
// Use calculateTransactionFee which fetches current gas price and applies buffer
|
|
42833
43262
|
const gasEstimate = await params.from.adapter.calculateTransactionFee(gasLimitUnits, undefined, chain);
|
|
42834
43263
|
// Format the fee from wei to human-readable native currency amount
|
|
@@ -42872,7 +43301,10 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
42872
43301
|
const estimateResult = {
|
|
42873
43302
|
stopLimit: formattedStopLimit,
|
|
42874
43303
|
estimatedOutput: formattedEstimatedOutput,
|
|
42875
|
-
fees
|
|
43304
|
+
fees,
|
|
43305
|
+
...serviceResponse.route !== undefined && {
|
|
43306
|
+
route: serviceResponse.route
|
|
43307
|
+
}
|
|
42876
43308
|
};
|
|
42877
43309
|
return estimateResult;
|
|
42878
43310
|
}
|
|
@@ -43257,7 +43689,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
43257
43689
|
* }
|
|
43258
43690
|
* ```
|
|
43259
43691
|
*/ async swap(params) {
|
|
43260
|
-
|
|
43692
|
+
validateServiceSwapParams(params);
|
|
43261
43693
|
const { from, tokenIn, tokenOut, config } = params;
|
|
43262
43694
|
const { adapter, chain } = from;
|
|
43263
43695
|
const destinationChain = params.toChain ?? chain;
|
|
@@ -43394,6 +43826,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
43394
43826
|
...swapResultFees !== undefined && {
|
|
43395
43827
|
fees: swapResultFees
|
|
43396
43828
|
},
|
|
43829
|
+
...serviceResponse.route !== undefined && {
|
|
43830
|
+
route: serviceResponse.route
|
|
43831
|
+
},
|
|
43397
43832
|
progress: statusSnapshot.progress,
|
|
43398
43833
|
...statusSnapshot.amountOut !== undefined && {
|
|
43399
43834
|
amountOut: statusSnapshot.amountOut
|
|
@@ -43594,7 +44029,13 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
43594
44029
|
return {
|
|
43595
44030
|
token: formatted.token,
|
|
43596
44031
|
amount: formatted.amount,
|
|
43597
|
-
type
|
|
44032
|
+
type,
|
|
44033
|
+
...fee.type !== undefined && {
|
|
44034
|
+
component: fee.type
|
|
44035
|
+
},
|
|
44036
|
+
...fee.chain !== undefined && {
|
|
44037
|
+
chain: fee.chain
|
|
44038
|
+
}
|
|
43598
44039
|
};
|
|
43599
44040
|
}));
|
|
43600
44041
|
}
|
|
@@ -43615,7 +44056,13 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
43615
44056
|
token: formatted.token,
|
|
43616
44057
|
amount: formatted.amount,
|
|
43617
44058
|
type: 'developer',
|
|
43618
|
-
recipientAddress
|
|
44059
|
+
recipientAddress,
|
|
44060
|
+
...fee.type !== undefined && {
|
|
44061
|
+
component: fee.type
|
|
44062
|
+
},
|
|
44063
|
+
...fee.chain !== undefined && {
|
|
44064
|
+
chain: fee.chain
|
|
44065
|
+
}
|
|
43619
44066
|
};
|
|
43620
44067
|
}));
|
|
43621
44068
|
}
|
|
@@ -43672,6 +44119,7 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
43672
44119
|
* - customFee: Optional fee configuration
|
|
43673
44120
|
* - apiKey: Optional credential string
|
|
43674
44121
|
* - kitKey: Optional credential string (deprecated alias for apiKey)
|
|
44122
|
+
* - batchTransactions: Optional boolean to opt out of atomic batching
|
|
43675
44123
|
*/ const swapConfigSchema = z.object({
|
|
43676
44124
|
allowanceStrategy: allowanceStrategySchema$1.optional(),
|
|
43677
44125
|
slippageBps: z.number().int().min(0).optional(),
|
|
@@ -43682,7 +44130,10 @@ const optionalSwapChainIdentifierField = swapChainIdentifierField.optional();
|
|
|
43682
44130
|
})(z.string())).optional(),
|
|
43683
44131
|
customFee: swapCustomFeeSchema.optional(),
|
|
43684
44132
|
apiKey: z.string().optional(),
|
|
43685
|
-
kitKey: z.string().optional()
|
|
44133
|
+
kitKey: z.string().optional(),
|
|
44134
|
+
batchTransactions: z.boolean({
|
|
44135
|
+
invalid_type_error: 'batchTransactions must be a boolean'
|
|
44136
|
+
}).optional()
|
|
43686
44137
|
});
|
|
43687
44138
|
const swapDestinationSchema = z.object({
|
|
43688
44139
|
chain: optionalSwapChainIdentifierField,
|
|
@@ -44657,6 +45108,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
44657
45108
|
...formattedFees && {
|
|
44658
45109
|
fees: formattedFees
|
|
44659
45110
|
},
|
|
45111
|
+
...result.route !== undefined && {
|
|
45112
|
+
route: result.route
|
|
45113
|
+
},
|
|
44660
45114
|
...formattedConfig && {
|
|
44661
45115
|
config: formattedConfig
|
|
44662
45116
|
},
|
|
@@ -44918,6 +45372,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
44918
45372
|
estimatedOutput: providerEstimate.estimatedOutput,
|
|
44919
45373
|
...providerEstimate.fees !== undefined && {
|
|
44920
45374
|
fees: providerEstimate.fees
|
|
45375
|
+
},
|
|
45376
|
+
...providerEstimate.route !== undefined && {
|
|
45377
|
+
route: providerEstimate.route
|
|
44921
45378
|
}
|
|
44922
45379
|
};
|
|
44923
45380
|
}
|
|
@@ -46597,6 +47054,9 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
|
|
|
46597
47054
|
...context.disableErrorReporting != null && {
|
|
46598
47055
|
disableErrorReporting: context.disableErrorReporting
|
|
46599
47056
|
},
|
|
47057
|
+
...context.disableAnalytics != null && {
|
|
47058
|
+
disableAnalytics: context.disableAnalytics
|
|
47059
|
+
},
|
|
46600
47060
|
...context.headers != null && {
|
|
46601
47061
|
headers: context.headers
|
|
46602
47062
|
}
|
|
@@ -46717,7 +47177,7 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
|
|
|
46717
47177
|
};
|
|
46718
47178
|
|
|
46719
47179
|
var name$3 = "@circle-fin/earn-kit";
|
|
46720
|
-
var version$3 = "1.
|
|
47180
|
+
var version$3 = "1.10.0";
|
|
46721
47181
|
var pkg$3 = {
|
|
46722
47182
|
name: name$3,
|
|
46723
47183
|
version: version$3};
|
|
@@ -46824,6 +47284,61 @@ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
|
|
|
46824
47284
|
*
|
|
46825
47285
|
* @internal
|
|
46826
47286
|
*/ const EARN_BRIDGE_MAX_VALIDITY_SECONDS = 86_400n;
|
|
47287
|
+
/**
|
|
47288
|
+
* Static fields of the EIP-712 domain for the `DepositIntent` typed-data
|
|
47289
|
+
* intent. `chainId` is excluded here — it must come from the deposit's
|
|
47290
|
+
* actual chain (see {@link buildDepositIntentTypedData}), since a mismatched
|
|
47291
|
+
* `chainId` in the domain separator would make every intent signature
|
|
47292
|
+
* invalid.
|
|
47293
|
+
*
|
|
47294
|
+
* `verifyingContract` is intentionally omitted — the intent is verified
|
|
47295
|
+
* off-chain by the Earn Service, not by a deployed contract.
|
|
47296
|
+
*
|
|
47297
|
+
* @internal
|
|
47298
|
+
*/ const DEPOSIT_INTENT_DOMAIN = {
|
|
47299
|
+
name: 'Circle EarnKit',
|
|
47300
|
+
version: '1'
|
|
47301
|
+
};
|
|
47302
|
+
/**
|
|
47303
|
+
* EIP-712 type definition for the `DepositIntent` struct.
|
|
47304
|
+
*
|
|
47305
|
+
* @internal
|
|
47306
|
+
*/ const DEPOSIT_INTENT_TYPES = {
|
|
47307
|
+
DepositIntent: [
|
|
47308
|
+
{
|
|
47309
|
+
name: 'wallet',
|
|
47310
|
+
type: 'address'
|
|
47311
|
+
},
|
|
47312
|
+
{
|
|
47313
|
+
name: 'vault',
|
|
47314
|
+
type: 'address'
|
|
47315
|
+
},
|
|
47316
|
+
{
|
|
47317
|
+
name: 'amount',
|
|
47318
|
+
type: 'uint256'
|
|
47319
|
+
},
|
|
47320
|
+
{
|
|
47321
|
+
name: 'deadline',
|
|
47322
|
+
type: 'uint256'
|
|
47323
|
+
}
|
|
47324
|
+
]
|
|
47325
|
+
};
|
|
47326
|
+
/**
|
|
47327
|
+
* Lifetime of a `DepositIntent` signature, in seconds.
|
|
47328
|
+
*
|
|
47329
|
+
* The Earn Service rejects a deadline more than 300 seconds ahead of its own
|
|
47330
|
+
* clock. 240 seconds leaves room for clock skew between the client and the
|
|
47331
|
+
* service while still covering the deposit request that follows the
|
|
47332
|
+
* signature.
|
|
47333
|
+
*
|
|
47334
|
+
* @internal
|
|
47335
|
+
*/ const DEPOSIT_INTENT_TTL_SECONDS = 240;
|
|
47336
|
+
/**
|
|
47337
|
+
* Protocol value the Earn Service reports for Veda vaults. A deposit into a
|
|
47338
|
+
* Veda vault needs a signed `DepositIntent`.
|
|
47339
|
+
*
|
|
47340
|
+
* @internal
|
|
47341
|
+
*/ const VEDA_PROTOCOL = 'veda';
|
|
46827
47342
|
/**
|
|
46828
47343
|
* Display symbol for vault share tokens.
|
|
46829
47344
|
*
|
|
@@ -48505,6 +49020,187 @@ function isCrossChainDepositStatusStep(step) {
|
|
|
48505
49020
|
return step.name === 'status';
|
|
48506
49021
|
}
|
|
48507
49022
|
|
|
49023
|
+
/**
|
|
49024
|
+
* Selector of `TellerWithMultiAssetSupport__SharesAreLocked()`.
|
|
49025
|
+
*/ const SHARES_LOCKED_SELECTOR = '0xf64059db';
|
|
49026
|
+
/**
|
|
49027
|
+
* Readable reasons for Veda BoringVault Teller and Accountant reverts.
|
|
49028
|
+
*/ const VEDA_REVERT_REASONS = {
|
|
49029
|
+
'0xe0f9e71d': 'The vault is paused',
|
|
49030
|
+
'0x3b3132f6': 'The vault exchange rate is paused',
|
|
49031
|
+
'0xc8bfa33e': 'The vault does not support this asset for this operation',
|
|
49032
|
+
'0x4b37cd2a': 'The asset amount must be greater than zero',
|
|
49033
|
+
'0x3a8c54c4': 'The share amount must be greater than zero',
|
|
49034
|
+
'0x4bd956a8': 'The deposit would mint fewer vault shares than the signed minimum',
|
|
49035
|
+
'0x9883d840': 'The withdrawal would pay out fewer assets than the signed minimum',
|
|
49036
|
+
'0xed32f3bb': 'The deposit would exceed the vault deposit cap',
|
|
49037
|
+
'0x1b8851f0': 'The permit failed and the token allowance is too low',
|
|
49038
|
+
'0x2821264f': 'The vault denied the share transfer'
|
|
49039
|
+
};
|
|
49040
|
+
/**
|
|
49041
|
+
* Matches a 4-byte selector in a message. The lookarounds stop a longer hex
|
|
49042
|
+
* value, such as an address or a hash, from matching.
|
|
49043
|
+
*/ const SELECTOR_IN_TEXT = /(?<![0-9a-fx])0x[0-9a-f]{8}(?![0-9a-f])/gi;
|
|
49044
|
+
function toRecord(value) {
|
|
49045
|
+
return typeof value === 'object' && value !== null ? value : {};
|
|
49046
|
+
}
|
|
49047
|
+
function isVedaSelector(selector) {
|
|
49048
|
+
return selector === SHARES_LOCKED_SELECTOR || selector in VEDA_REVERT_REASONS;
|
|
49049
|
+
}
|
|
49050
|
+
/**
|
|
49051
|
+
* Reads the selector from the revert data on a trace. The `/next` adapters
|
|
49052
|
+
* keep it on `revertData`. The default adapters keep the whole raw error on
|
|
49053
|
+
* `rawError`.
|
|
49054
|
+
*/ function selectorFromData(trace) {
|
|
49055
|
+
const data = typeof trace['revertData'] === 'string' ? trace['revertData'] : extractRevertData(trace['rawError']);
|
|
49056
|
+
const selector = data?.slice(0, 10).toLowerCase();
|
|
49057
|
+
return selector !== undefined && isVedaSelector(selector) ? selector : undefined;
|
|
49058
|
+
}
|
|
49059
|
+
/**
|
|
49060
|
+
* Reads the selector from error text, for wallets that return no revert
|
|
49061
|
+
* data and put the selector or the decoded error name in the message.
|
|
49062
|
+
*/ function selectorFromText(texts) {
|
|
49063
|
+
for (const text of texts){
|
|
49064
|
+
if (typeof text !== 'string') {
|
|
49065
|
+
continue;
|
|
49066
|
+
}
|
|
49067
|
+
if (/SharesAreLocked/i.test(text)) {
|
|
49068
|
+
return SHARES_LOCKED_SELECTOR;
|
|
49069
|
+
}
|
|
49070
|
+
const selector = text.match(SELECTOR_IN_TEXT)?.map((match)=>match.toLowerCase()).find(isVedaSelector);
|
|
49071
|
+
if (selector !== undefined) {
|
|
49072
|
+
return selector;
|
|
49073
|
+
}
|
|
49074
|
+
}
|
|
49075
|
+
return undefined;
|
|
49076
|
+
}
|
|
49077
|
+
/**
|
|
49078
|
+
* Reads a Veda revert from one error, without looking at nested errors.
|
|
49079
|
+
*/ function readVedaRevert(error) {
|
|
49080
|
+
const trace = toRecord(error.cause?.trace);
|
|
49081
|
+
const selector = selectorFromData(trace) ?? selectorFromText([
|
|
49082
|
+
error.message,
|
|
49083
|
+
trace['reason']
|
|
49084
|
+
]);
|
|
49085
|
+
if (selector === undefined) {
|
|
49086
|
+
return undefined;
|
|
49087
|
+
}
|
|
49088
|
+
const chain = trace['chain'];
|
|
49089
|
+
return {
|
|
49090
|
+
selector,
|
|
49091
|
+
chain: typeof chain === 'string' ? chain : undefined
|
|
49092
|
+
};
|
|
49093
|
+
}
|
|
49094
|
+
/**
|
|
49095
|
+
* Returns the errors that a failed batch nests under `trace.cause` or
|
|
49096
|
+
* `trace.error`.
|
|
49097
|
+
*/ function nestedErrors(error) {
|
|
49098
|
+
const trace = toRecord(error.cause?.trace);
|
|
49099
|
+
return [
|
|
49100
|
+
trace['cause'],
|
|
49101
|
+
trace['error']
|
|
49102
|
+
].filter(isKitError);
|
|
49103
|
+
}
|
|
49104
|
+
/**
|
|
49105
|
+
* Finds a Veda revert on the error or on an error nested in its trace.
|
|
49106
|
+
*/ function findVedaRevert(error) {
|
|
49107
|
+
const seen = new Set();
|
|
49108
|
+
const pending = [
|
|
49109
|
+
error
|
|
49110
|
+
];
|
|
49111
|
+
let current = pending.pop();
|
|
49112
|
+
while(current !== undefined){
|
|
49113
|
+
if (!seen.has(current)) {
|
|
49114
|
+
seen.add(current);
|
|
49115
|
+
const revert = readVedaRevert(current);
|
|
49116
|
+
if (revert !== undefined) {
|
|
49117
|
+
return {
|
|
49118
|
+
...revert,
|
|
49119
|
+
nested: current !== error
|
|
49120
|
+
};
|
|
49121
|
+
}
|
|
49122
|
+
pending.push(...nestedErrors(current));
|
|
49123
|
+
}
|
|
49124
|
+
current = pending.pop();
|
|
49125
|
+
}
|
|
49126
|
+
return undefined;
|
|
49127
|
+
}
|
|
49128
|
+
/**
|
|
49129
|
+
* Map a Veda vault revert to an Earn error.
|
|
49130
|
+
*
|
|
49131
|
+
* A Teller `SharesAreLocked()` revert becomes `EARN_SHARES_LOCKED`. Other
|
|
49132
|
+
* known Veda reverts keep their simulation failed or transaction reverted
|
|
49133
|
+
* code and get a readable reason. A failed batch that wraps a Veda revert
|
|
49134
|
+
* keeps its code, and the reason is added to its message. A timed out batch
|
|
49135
|
+
* is returned unchanged, because its outcome is unknown. Any other error is
|
|
49136
|
+
* returned unchanged.
|
|
49137
|
+
*
|
|
49138
|
+
* @param error - The error caught from an earn operation.
|
|
49139
|
+
* @returns The mapped error, or `error` when it is not a Veda revert.
|
|
49140
|
+
*
|
|
49141
|
+
* @example
|
|
49142
|
+
* ```typescript
|
|
49143
|
+
* import { mapVedaRevert } from './vedaRevert'
|
|
49144
|
+
*
|
|
49145
|
+
* const mapped = mapVedaRevert(error)
|
|
49146
|
+
* console.log(mapped.name) // 'EARN_SHARES_LOCKED' for a share lock revert
|
|
49147
|
+
* ```
|
|
49148
|
+
*
|
|
49149
|
+
* @internal
|
|
49150
|
+
*/ function mapVedaRevert(error) {
|
|
49151
|
+
if (error.name === NetworkError.TIMEOUT.name) {
|
|
49152
|
+
return error;
|
|
49153
|
+
}
|
|
49154
|
+
const revert = findVedaRevert(error);
|
|
49155
|
+
if (revert === undefined) {
|
|
49156
|
+
return error;
|
|
49157
|
+
}
|
|
49158
|
+
const trace = toRecord(error.cause?.trace);
|
|
49159
|
+
const chain = typeof trace['chain'] === 'string' ? trace['chain'] : revert.chain ?? 'EVM';
|
|
49160
|
+
if (revert.selector === SHARES_LOCKED_SELECTOR) {
|
|
49161
|
+
return new KitError({
|
|
49162
|
+
...EarnError.SHARES_LOCKED,
|
|
49163
|
+
// The lock can last hours, so an immediate retry fails the same way.
|
|
49164
|
+
recoverability: 'FATAL',
|
|
49165
|
+
message: `Vault shares on ${chain} are still locked after a recent deposit. Wait for the vault's share lock period to end, then try again.`,
|
|
49166
|
+
cause: {
|
|
49167
|
+
trace: {
|
|
49168
|
+
...trace,
|
|
49169
|
+
selector: revert.selector,
|
|
49170
|
+
chain
|
|
49171
|
+
}
|
|
49172
|
+
}
|
|
49173
|
+
});
|
|
49174
|
+
}
|
|
49175
|
+
const readable = VEDA_REVERT_REASONS[revert.selector];
|
|
49176
|
+
if (readable === undefined) {
|
|
49177
|
+
return error;
|
|
49178
|
+
}
|
|
49179
|
+
const reason = `${readable} (${revert.selector})`;
|
|
49180
|
+
if (error.name === OnchainError.SIMULATION_FAILED.name) {
|
|
49181
|
+
return createSimulationFailedError(chain, reason, trace);
|
|
49182
|
+
}
|
|
49183
|
+
if (error.name === OnchainError.TRANSACTION_REVERTED.name) {
|
|
49184
|
+
return createTransactionRevertedError(chain, reason, trace);
|
|
49185
|
+
}
|
|
49186
|
+
if (!revert.nested) {
|
|
49187
|
+
return error;
|
|
49188
|
+
}
|
|
49189
|
+
return new KitError({
|
|
49190
|
+
code: error.code,
|
|
49191
|
+
name: error.name,
|
|
49192
|
+
type: error.type,
|
|
49193
|
+
recoverability: error.recoverability,
|
|
49194
|
+
message: `${error.message} Reason: ${reason}.`,
|
|
49195
|
+
cause: {
|
|
49196
|
+
trace: {
|
|
49197
|
+
...trace,
|
|
49198
|
+
reason
|
|
49199
|
+
}
|
|
49200
|
+
}
|
|
49201
|
+
});
|
|
49202
|
+
}
|
|
49203
|
+
|
|
48508
49204
|
const EARN_OPERATIONS = new Set([
|
|
48509
49205
|
'deposit',
|
|
48510
49206
|
'withdraw',
|
|
@@ -48550,6 +49246,10 @@ const EARN_OPERATIONS = new Set([
|
|
|
48550
49246
|
* `RESUMABLE` so callers know `retry()` can resume from the failed phase. A
|
|
48551
49247
|
* fatal underlying error stays fatal — it cannot be resumed.
|
|
48552
49248
|
*
|
|
49249
|
+
* A Veda vault revert is mapped first (see {@link mapVedaRevert}): a share
|
|
49250
|
+
* lock becomes `EARN_SHARES_LOCKED`, and other known Veda reverts get a
|
|
49251
|
+
* readable reason.
|
|
49252
|
+
*
|
|
48553
49253
|
* Non-{@link KitError} inputs (which should not occur on provider code paths)
|
|
48554
49254
|
* are normalized to a retryable {@link ServiceError.INTERNAL_ERROR}.
|
|
48555
49255
|
*
|
|
@@ -48571,7 +49271,7 @@ const EARN_OPERATIONS = new Set([
|
|
|
48571
49271
|
* }
|
|
48572
49272
|
* ```
|
|
48573
49273
|
*/ function augmentEarnError(error, context) {
|
|
48574
|
-
const base = isKitError(error) ? error : new KitError({
|
|
49274
|
+
const base = isKitError(error) ? mapVedaRevert(error) : new KitError({
|
|
48575
49275
|
...ServiceError.INTERNAL_ERROR,
|
|
48576
49276
|
recoverability: 'RETRYABLE',
|
|
48577
49277
|
message: getErrorMessage(error)
|
|
@@ -49514,7 +50214,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
49514
50214
|
}
|
|
49515
50215
|
|
|
49516
50216
|
var name$2 = "@circle-fin/provider-earn-service";
|
|
49517
|
-
var version$2 = "1.
|
|
50217
|
+
var version$2 = "1.8.0";
|
|
49518
50218
|
var pkg$2 = {
|
|
49519
50219
|
name: name$2,
|
|
49520
50220
|
version: version$2};
|
|
@@ -49866,8 +50566,9 @@ function toPositionInfo(data) {
|
|
|
49866
50566
|
* Build signed deposit instructions via the Earn Service API.
|
|
49867
50567
|
*
|
|
49868
50568
|
* Call `POST /v1/earnKit/deposit` with the vault address, amount,
|
|
49869
|
-
* wallet address, and chain
|
|
49870
|
-
* parameters for on-chain
|
|
50569
|
+
* wallet address, and chain, plus the signed `DepositIntent` when one is
|
|
50570
|
+
* given. Return EIP-712 signed execution parameters for on-chain
|
|
50571
|
+
* submission.
|
|
49871
50572
|
*
|
|
49872
50573
|
* @param params - Deposit parameters
|
|
49873
50574
|
* @returns Signed execution parameters for the deposit
|
|
@@ -49881,7 +50582,8 @@ function toPositionInfo(data) {
|
|
|
49881
50582
|
vaultAddress: params.vaultAddress,
|
|
49882
50583
|
amount: params.amount,
|
|
49883
50584
|
address: params.address,
|
|
49884
|
-
chain: params.chain
|
|
50585
|
+
chain: params.chain,
|
|
50586
|
+
...params.intent
|
|
49885
50587
|
};
|
|
49886
50588
|
try {
|
|
49887
50589
|
const response = await pollApiPost(url.toString(), requestBody, isDepositResponse, pollingConfig);
|
|
@@ -49893,6 +50595,232 @@ function toPositionInfo(data) {
|
|
|
49893
50595
|
}
|
|
49894
50596
|
}
|
|
49895
50597
|
|
|
50598
|
+
/**
|
|
50599
|
+
* Build the version-1 EarnKit `DepositIntent` EIP-712 typed data.
|
|
50600
|
+
*
|
|
50601
|
+
* The signed struct binds the depositing `wallet`, the target `vault`, the
|
|
50602
|
+
* deposit `amount` in the asset's smallest unit, and a signature `deadline`.
|
|
50603
|
+
* The Earn Service rebuilds the same struct from the deposit request and
|
|
50604
|
+
* rejects the deposit unless `wallet` signed it. The domain intentionally
|
|
50605
|
+
* omits `verifyingContract` because the service verifies the signature
|
|
50606
|
+
* off-chain.
|
|
50607
|
+
*
|
|
50608
|
+
* `domain.chainId` comes from the deposit's chain rather than a constant: the
|
|
50609
|
+
* domain separator must match what the service rebuilds, and a wrong
|
|
50610
|
+
* `chainId` would make every intent signature invalid.
|
|
50611
|
+
*
|
|
50612
|
+
* This function returns the fully-typed {@link DepositIntentTypedData} with
|
|
50613
|
+
* no assertion. {@link resolveDepositIntent} casts it to the adapter's
|
|
50614
|
+
* generic `EIP712Domain` shape only where it calls `adapter.signTypedData`,
|
|
50615
|
+
* since that shared interface always requires `verifyingContract`. The
|
|
50616
|
+
* adapter's runtime `eip712DomainSchema` treats `verifyingContract` as
|
|
50617
|
+
* optional, so the cast is safe at runtime. BorrowKit's
|
|
50618
|
+
* `buildWebhookRegistrationTypedData` works around the same gap.
|
|
50619
|
+
*
|
|
50620
|
+
* @param params - Wallet, vault, base-unit amount, deadline, and chain id.
|
|
50621
|
+
* @returns EIP-712 typed data ready to be cast for `adapter.signTypedData`.
|
|
50622
|
+
*
|
|
50623
|
+
* @example
|
|
50624
|
+
* ```typescript
|
|
50625
|
+
* const typedData = buildDepositIntentTypedData({
|
|
50626
|
+
* wallet: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
50627
|
+
* vault: '0x00000000000000000000000000000000000000a1',
|
|
50628
|
+
* amount: 100_000_000n,
|
|
50629
|
+
* deadline: 1_700_000_000n,
|
|
50630
|
+
* chainId: 5_042_002,
|
|
50631
|
+
* })
|
|
50632
|
+
* ```
|
|
50633
|
+
*
|
|
50634
|
+
* @internal
|
|
50635
|
+
*/ function buildDepositIntentTypedData(params) {
|
|
50636
|
+
return {
|
|
50637
|
+
domain: {
|
|
50638
|
+
...DEPOSIT_INTENT_DOMAIN,
|
|
50639
|
+
chainId: params.chainId
|
|
50640
|
+
},
|
|
50641
|
+
types: DEPOSIT_INTENT_TYPES,
|
|
50642
|
+
primaryType: 'DepositIntent',
|
|
50643
|
+
message: {
|
|
50644
|
+
wallet: params.wallet,
|
|
50645
|
+
vault: params.vault,
|
|
50646
|
+
amount: params.amount,
|
|
50647
|
+
deadline: params.deadline
|
|
50648
|
+
}
|
|
50649
|
+
};
|
|
50650
|
+
}
|
|
50651
|
+
/**
|
|
50652
|
+
* Earn Service error code for a missing, expired, or wrongly signed
|
|
50653
|
+
* `DepositIntent` (`DEPOSIT_INTENT_INVALID`).
|
|
50654
|
+
*/ const DEPOSIT_INTENT_INVALID_CODE = 380418;
|
|
50655
|
+
/**
|
|
50656
|
+
* Sign a `DepositIntent` when the target vault needs one.
|
|
50657
|
+
*
|
|
50658
|
+
* A Veda vault mints shares straight to the depositing wallet, so the Earn
|
|
50659
|
+
* Service only builds a Veda deposit for a wallet that signed a
|
|
50660
|
+
* `DepositIntent` for the same vault and amount. The vault lookup exists only
|
|
50661
|
+
* to find out whether the vault is a Veda vault (and its asset decimals, to
|
|
50662
|
+
* sign the amount). Other vaults ignore the intent, so this returns no intent
|
|
50663
|
+
* for them and the wallet sees no extra signature prompt. A vault the service
|
|
50664
|
+
* does not return is also treated as not needing an intent; the deposit call
|
|
50665
|
+
* then reports why.
|
|
50666
|
+
*
|
|
50667
|
+
* A failed vault lookup does not stop the deposit: this returns the lookup
|
|
50668
|
+
* error with no intent, so a non-Veda deposit works as before. A Veda deposit
|
|
50669
|
+
* then gets `DEPOSIT_INTENT_INVALID` from the service, and
|
|
50670
|
+
* {@link resolveDepositError} reports the lookup error instead. The lookup
|
|
50671
|
+
* fails either by throwing or by returning the vault in its `errors` list (for
|
|
50672
|
+
* example `VAULT_REFRESH_BUSY`); both are kept the same way.
|
|
50673
|
+
*
|
|
50674
|
+
* @param params - Adapter, resolved chain, wallet, vault, human-readable
|
|
50675
|
+
* amount, and service config.
|
|
50676
|
+
* @returns The signed intent when the vault needs one, and the lookup error
|
|
50677
|
+
* when the vault lookup failed.
|
|
50678
|
+
* @throws {@link KitError} `InputError.VALIDATION_FAILED` when the vault needs
|
|
50679
|
+
* an intent but the chain is not EVM, the adapter cannot sign typed data, or
|
|
50680
|
+
* the amount has more decimal places than the vault asset.
|
|
50681
|
+
*
|
|
50682
|
+
* @example
|
|
50683
|
+
* ```typescript
|
|
50684
|
+
* const { intent } = await resolveDepositIntent({
|
|
50685
|
+
* adapter,
|
|
50686
|
+
* chain: ArcTestnet,
|
|
50687
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
50688
|
+
* vaultAddress: '0x00000000000000000000000000000000000000a1',
|
|
50689
|
+
* amount: '100',
|
|
50690
|
+
* config,
|
|
50691
|
+
* })
|
|
50692
|
+
* // intent -> { intentSignature: '0x…', intentDeadline: 1700000240 }
|
|
50693
|
+
* ```
|
|
50694
|
+
*
|
|
50695
|
+
* @internal
|
|
50696
|
+
*/ async function resolveDepositIntent(params) {
|
|
50697
|
+
let lookup;
|
|
50698
|
+
try {
|
|
50699
|
+
lookup = await fetchVaults({
|
|
50700
|
+
// `resolveAdapterContext` already rejected chains the Earn Service does
|
|
50701
|
+
// not support, so the chain is an Earn chain here.
|
|
50702
|
+
vaults: [
|
|
50703
|
+
{
|
|
50704
|
+
chain: params.chain,
|
|
50705
|
+
vaultAddress: params.vaultAddress
|
|
50706
|
+
}
|
|
50707
|
+
],
|
|
50708
|
+
config: params.config
|
|
50709
|
+
});
|
|
50710
|
+
} catch (lookupError) {
|
|
50711
|
+
return {
|
|
50712
|
+
lookupError
|
|
50713
|
+
};
|
|
50714
|
+
}
|
|
50715
|
+
const vaultError = lookup.errors.find((candidate)=>isSameAddress(candidate.vaultAddress, params.vaultAddress));
|
|
50716
|
+
if (vaultError !== undefined) {
|
|
50717
|
+
// Same handling as a thrown lookup error, mapped with the Earn Service
|
|
50718
|
+
// error table so it keeps its recoverability (VAULT_REFRESH_BUSY retries).
|
|
50719
|
+
return {
|
|
50720
|
+
lookupError: parseEarnApiError({
|
|
50721
|
+
responseBody: {
|
|
50722
|
+
code: vaultError.code,
|
|
50723
|
+
message: vaultError.message
|
|
50724
|
+
}
|
|
50725
|
+
}, {
|
|
50726
|
+
operation: 'getVaults'
|
|
50727
|
+
})
|
|
50728
|
+
};
|
|
50729
|
+
}
|
|
50730
|
+
const vault = lookup.vaults.find((candidate)=>isSameAddress(candidate.address, params.vaultAddress));
|
|
50731
|
+
if (vault?.protocol.toLowerCase() !== VEDA_PROTOCOL) {
|
|
50732
|
+
return {};
|
|
50733
|
+
}
|
|
50734
|
+
if (params.chain.type !== 'evm') {
|
|
50735
|
+
throw createValidationFailedError$1('from.chain', params.chain.chain, 'Veda vault deposits need an EVM chain to sign the deposit intent');
|
|
50736
|
+
}
|
|
50737
|
+
if (!canSignTypedData(params.adapter)) {
|
|
50738
|
+
throw createValidationFailedError$1('from.adapter', params.adapter, 'adapter must support signTypedData for Veda vault deposits');
|
|
50739
|
+
}
|
|
50740
|
+
const wallet = assertHexAddress('from.address', params.address, 'Depositor address must be a 0x-prefixed 20-byte hex address.');
|
|
50741
|
+
const assetDecimals = vault.liquidityProfile.totalDeposits.decimals;
|
|
50742
|
+
// Amount.parse truncates extra digits, but the service rejects them. Fail
|
|
50743
|
+
// before the wallet prompt instead of asking to sign a deposit that cannot
|
|
50744
|
+
// go through.
|
|
50745
|
+
assertDecimalPlaces('amount', params.amount, assetDecimals, `amount must have at most ${String(assetDecimals)} decimal places for the vault asset`);
|
|
50746
|
+
const deadline = Math.floor(Date.now() / 1000) + DEPOSIT_INTENT_TTL_SECONDS;
|
|
50747
|
+
const typedData = buildDepositIntentTypedData({
|
|
50748
|
+
wallet,
|
|
50749
|
+
vault: params.vaultAddress,
|
|
50750
|
+
amount: Amount.parse(params.amount, {
|
|
50751
|
+
decimals: assetDecimals
|
|
50752
|
+
}).raw,
|
|
50753
|
+
deadline: BigInt(deadline),
|
|
50754
|
+
chainId: params.chain.chainId
|
|
50755
|
+
});
|
|
50756
|
+
// `typedData.domain` intentionally omits `verifyingContract` (the Earn
|
|
50757
|
+
// Service verifies off-chain), but the shared `signTypedData` signature
|
|
50758
|
+
// requires the standard `EIP712Domain` shape. The adapter's runtime
|
|
50759
|
+
// `eip712DomainSchema` treats `verifyingContract` as optional, so this cast
|
|
50760
|
+
// is safe at runtime.
|
|
50761
|
+
const intentSignature = await params.adapter.signTypedData(typedData, {
|
|
50762
|
+
chain: params.chain,
|
|
50763
|
+
address: wallet
|
|
50764
|
+
});
|
|
50765
|
+
return {
|
|
50766
|
+
intent: {
|
|
50767
|
+
intentSignature,
|
|
50768
|
+
intentDeadline: deadline
|
|
50769
|
+
}
|
|
50770
|
+
};
|
|
50771
|
+
}
|
|
50772
|
+
/**
|
|
50773
|
+
* Pick the error to report for a failed deposit request.
|
|
50774
|
+
*
|
|
50775
|
+
* When the vault lookup failed, the SDK sent the deposit without an intent.
|
|
50776
|
+
* If the service then answers `DEPOSIT_INTENT_INVALID`, the vault is a Veda
|
|
50777
|
+
* vault and the real cause is the failed lookup, not a bad signature. Report
|
|
50778
|
+
* the lookup error, which keeps its own recoverability (often retryable),
|
|
50779
|
+
* instead of a fatal signature rejection.
|
|
50780
|
+
*
|
|
50781
|
+
* When the SDK did send an intent and the service still answers
|
|
50782
|
+
* `DEPOSIT_INTENT_INVALID`, the error stays a fatal signature rejection, but
|
|
50783
|
+
* its message adds that the intent may have expired: the deadline starts
|
|
50784
|
+
* before the wallet prompt, so a slow signer can submit an expired intent.
|
|
50785
|
+
* Calling `deposit()` again signs a new one. Every other error is reported as
|
|
50786
|
+
* is.
|
|
50787
|
+
*
|
|
50788
|
+
* @param resolution - The result of {@link resolveDepositIntent}.
|
|
50789
|
+
* @param error - The error from the deposit request.
|
|
50790
|
+
* @returns The error to throw.
|
|
50791
|
+
*
|
|
50792
|
+
* @example
|
|
50793
|
+
* ```typescript
|
|
50794
|
+
* try {
|
|
50795
|
+
* return await fetchDeposit({ ...params, intent: resolution.intent })
|
|
50796
|
+
* } catch (error) {
|
|
50797
|
+
* throw resolveDepositError(resolution, error)
|
|
50798
|
+
* }
|
|
50799
|
+
* ```
|
|
50800
|
+
*
|
|
50801
|
+
* @internal
|
|
50802
|
+
*/ function resolveDepositError(resolution, error) {
|
|
50803
|
+
if (resolution.lookupError !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
|
|
50804
|
+
return resolution.lookupError;
|
|
50805
|
+
}
|
|
50806
|
+
if (resolution.intent !== undefined && isKitError(error) && isEarnServiceCode(error.cause?.trace, DEPOSIT_INTENT_INVALID_CODE)) {
|
|
50807
|
+
return new KitError({
|
|
50808
|
+
code: error.code,
|
|
50809
|
+
name: error.name,
|
|
50810
|
+
type: error.type,
|
|
50811
|
+
recoverability: error.recoverability,
|
|
50812
|
+
message: `${error.message} The deposit intent may have expired before the deposit was submitted (it is valid for ${String(DEPOSIT_INTENT_TTL_SECONDS)} seconds from when the SDK asks the wallet to sign). Call deposit() again to sign a new intent.`,
|
|
50813
|
+
...error.cause !== undefined && {
|
|
50814
|
+
cause: error.cause
|
|
50815
|
+
}
|
|
50816
|
+
});
|
|
50817
|
+
}
|
|
50818
|
+
return error;
|
|
50819
|
+
}
|
|
50820
|
+
function isEarnServiceCode(trace, code) {
|
|
50821
|
+
return typeof trace === 'object' && trace !== null && 'earnServiceCode' in trace && trace.earnServiceCode === code;
|
|
50822
|
+
}
|
|
50823
|
+
|
|
49896
50824
|
/**
|
|
49897
50825
|
* Build cross-chain deposit instructions via the bridge API.
|
|
49898
50826
|
*
|
|
@@ -50775,7 +51703,7 @@ function reportTransactionFailure(reportContext, action, error) {
|
|
|
50775
51703
|
}
|
|
50776
51704
|
function transactionReportErrorCode(error) {
|
|
50777
51705
|
if (isKitError(error)) {
|
|
50778
|
-
return error.name;
|
|
51706
|
+
return mapVedaRevert(error).name;
|
|
50779
51707
|
}
|
|
50780
51708
|
const message = getErrorMessage(error);
|
|
50781
51709
|
if (/user (rejected|denied)|rejected by user/i.test(message)) {
|
|
@@ -51131,13 +52059,31 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
51131
52059
|
const adapterContractAddress = requireAdapterContract(chain);
|
|
51132
52060
|
const { adapter } = params.from;
|
|
51133
52061
|
const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
51134
|
-
const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>
|
|
52062
|
+
const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>{
|
|
52063
|
+
// Veda vaults mint shares straight to `address`, so the service
|
|
52064
|
+
// needs proof that `address` asked for this deposit. Sign it here,
|
|
52065
|
+
// inside fetchParams, so a rejected prompt fails this phase.
|
|
52066
|
+
const resolution = await resolveDepositIntent({
|
|
52067
|
+
adapter,
|
|
52068
|
+
chain,
|
|
52069
|
+
address,
|
|
51135
52070
|
vaultAddress,
|
|
51136
52071
|
amount: params.amount,
|
|
51137
|
-
address,
|
|
51138
|
-
chain: apiChain,
|
|
51139
52072
|
config
|
|
51140
|
-
})
|
|
52073
|
+
});
|
|
52074
|
+
try {
|
|
52075
|
+
return await fetchDeposit({
|
|
52076
|
+
vaultAddress,
|
|
52077
|
+
amount: params.amount,
|
|
52078
|
+
address,
|
|
52079
|
+
chain: apiChain,
|
|
52080
|
+
intent: resolution.intent,
|
|
52081
|
+
config
|
|
52082
|
+
});
|
|
52083
|
+
} catch (error) {
|
|
52084
|
+
throw resolveDepositError(resolution, error);
|
|
52085
|
+
}
|
|
52086
|
+
}, ()=>undefined);
|
|
51141
52087
|
validateExecutionDeadline$1(executionParams);
|
|
51142
52088
|
const transactionReportContext = {
|
|
51143
52089
|
execId,
|
|
@@ -54144,7 +55090,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
54144
55090
|
};
|
|
54145
55091
|
|
|
54146
55092
|
var name$1 = "@circle-fin/borrow-kit";
|
|
54147
|
-
var version$1 = "1.
|
|
55093
|
+
var version$1 = "1.1.1";
|
|
54148
55094
|
var pkg$1 = {
|
|
54149
55095
|
name: name$1,
|
|
54150
55096
|
version: version$1};
|
|
@@ -64868,7 +65814,7 @@ function normaliseLoadTimeout(value) {
|
|
|
64868
65814
|
currency: z.string().min(1).optional(),
|
|
64869
65815
|
email: z.string().email().optional(),
|
|
64870
65816
|
assets: onrampAssetSelectionSchema.optional(),
|
|
64871
|
-
metadata: z.record(z.string(), z.
|
|
65817
|
+
metadata: z.record(z.string(), z.string()).optional()
|
|
64872
65818
|
});
|
|
64873
65819
|
|
|
64874
65820
|
/**
|
|
@@ -67053,7 +67999,7 @@ async function deposit$2(context, params) {
|
|
|
67053
67999
|
}
|
|
67054
68000
|
|
|
67055
68001
|
var name = "@circle-fin/unified-balance-kit";
|
|
67056
|
-
var version = "1.8.
|
|
68002
|
+
var version = "1.8.2";
|
|
67057
68003
|
var pkg = {
|
|
67058
68004
|
name: name,
|
|
67059
68005
|
version: version};
|