@circle-fin/provider-cctp-v2 1.0.1 → 1.0.2
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 +100 -0
- package/{index.cjs.js → index.cjs} +159 -94
- package/index.cjs.map +1 -0
- package/index.d.ts +67 -10
- package/index.mjs +157 -92
- package/package.json +5 -3
- package/index.cjs.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @circle-fin/provider-cctp-v2
|
|
2
|
+
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fix CommonJS compatibility by correcting file extensions and package exports. This resolves a "ReferenceError: require is not defined" error that occurred when using packages in CommonJS projects with ts-node.
|
|
8
|
+
|
|
9
|
+
- Fixes a bug where custom recipient addresses were not respected in cross-chain USDC transfers. Previously, when using `recipientAddress` to specify a different destination address than the signer (common in custody solutions or when bridging to third-party addresses), the provider would incorrectly use the signer's address as the mint recipient, causing transfers to fail or mint to the wrong address.
|
|
10
|
+
|
|
11
|
+
With this fix, the provider now correctly uses `recipientAddress` when provided, while still using the signer's address for transaction authorization. This enables:
|
|
12
|
+
- **Third-party transfers**: Bridge USDC to any address without requiring them to sign
|
|
13
|
+
- **Custody integrations**: Separate signing authority from fund destination
|
|
14
|
+
- **Developer-controlled flows**: API-based signers can bridge to user-specified recipients
|
|
15
|
+
|
|
16
|
+
**No changes required** for existing code - the fix is backward compatible. If you don't provide `recipientAddress`, transfers work exactly as before. If you were previously experiencing failures when specifying a custom recipient, this update resolves those issues.
|
|
17
|
+
|
|
18
|
+
- Improves static gas estimate values for contract calls. Updates adapters' `prepare()` method so that transaction simulation is now performed only during `execute()`, not during `estimate()`. Adds an optional fallback parameter to `estimate()` for cases where gas estimation fails.
|
|
19
|
+
|
|
20
|
+
## 1.0.1
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- Standardize `maxFee` parameter to accept human-readable values. The `maxFee` parameter in `BridgeConfig` now correctly accepts human-readable token amounts (e.g., `"1"` for 1 USDC, `"0.5"` for 0.5 USDC), matching the behavior of `customFee.value`. This resolves an undocumented inconsistency in the API. If you were previously passing values in smallest units, update to human-readable format: use `"1"` instead of `"1000000"` for 1 USDC.
|
|
25
|
+
- Add support for Arc Testnet chain definition. Arc is Circle's EVM-compatible Layer-1 blockchain designed for stablecoin finance and asset
|
|
26
|
+
tokenization, featuring USDC as the native gas token and sub-second finality via the Malachite BFT consensus engine.
|
|
27
|
+
- Fix bridge event dispatching in retry flow to include complete step metadata. The retry flow was incorrectly dispatching only step.data instead of the full step object, causing retry events to miss critical metadata like txHash, explorerUrl, name, and state. This change also introduces a shared dispatchStepEvent utility to eliminate code duplication between bridge and retry flows.
|
|
28
|
+
- These changes standardize and clarifiy error handling across bridge steps, make all bridge steps only need 1 blockchain confirmation when waiting for the step transaction, refactor burn transfer speed logic for simplicity, and improve polling configuration for slow attestation fetches.
|
|
29
|
+
- Update Sonic Testnet chain definition to canonical network. The `SonicTestnet` chain definition now points to the official Sonic Testnet (chainId: 14601) instead of the deprecated Sonic Blaze Testnet (chainId: 57054). The RPC endpoint has been updated to `https://rpc.testnet.soniclabs.com`, the display name simplified to "Sonic Testnet", and the USDC contract address updated to the new deployment.
|
|
30
|
+
|
|
31
|
+
**Breaking Changes:**
|
|
32
|
+
- **Chain ID:** 57054 → 14601
|
|
33
|
+
- **RPC Endpoint:** `https://rpc.blaze.soniclabs.com` → `https://rpc.testnet.soniclabs.com`
|
|
34
|
+
- **USDC Address:** `0xA4879Fed32Ecbef99399e5cbC247E533421C4eC6` → `0x0BA304580ee7c9a980CF72e55f5Ed2E9fd30Bc51`
|
|
35
|
+
|
|
36
|
+
**Migration:** If you were using `SonicTestnet`, your application will automatically connect to the new network upon upgrading. Any accounts, contracts, or transactions on the old Blaze testnet (chainId: 57054) will need to be recreated on the new testnet.
|
|
37
|
+
|
|
38
|
+
## 1.0.0
|
|
39
|
+
|
|
40
|
+
### Major Changes
|
|
41
|
+
|
|
42
|
+
- # CCTP v2 Provider 1.0.0 Release 🎉
|
|
43
|
+
|
|
44
|
+
Circle's Cross-Chain Transfer Protocol (CCTP) v2 integration - the primary transport layer for secure, native USDC transfers across supported blockchain networks.
|
|
45
|
+
|
|
46
|
+
## 🚀 Core CCTP Features
|
|
47
|
+
- **Native USDC Bridging**: Direct integration with Circle's official CCTP v2 protocol
|
|
48
|
+
- **Multi-chain Support**: Seamless transfers between EVM chains and Solana
|
|
49
|
+
- **Attestation Management**: Automatic handling of Circle's attestation service
|
|
50
|
+
|
|
51
|
+
## ⚡ Transfer Speed Options
|
|
52
|
+
|
|
53
|
+
**FAST Transfers**
|
|
54
|
+
- Optimized for speed with Circle's fast liquidity network
|
|
55
|
+
- Dynamic fee calculation based on transfer amount and network conditions
|
|
56
|
+
- Automatic fee estimation with 10% buffer for fluctuations
|
|
57
|
+
- Typical completion time: ~1 minute
|
|
58
|
+
|
|
59
|
+
**SLOW Transfers**
|
|
60
|
+
- Zero protocol fees for cost-optimized transfers
|
|
61
|
+
- Standard CCTP attestation flow
|
|
62
|
+
- Typical completion time: 10-20 minutes
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Fast transfer with automatic fee calculation
|
|
66
|
+
const result = await provider.bridge({
|
|
67
|
+
source: { adapter: sourceAdapter, chain: 'Ethereum' },
|
|
68
|
+
destination: { adapter: destAdapter, chain: 'Base' },
|
|
69
|
+
amount: '10.0',
|
|
70
|
+
config: { transferSpeed: 'FAST' },
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Slow transfer with zero fees
|
|
74
|
+
const result = await provider.bridge({
|
|
75
|
+
source: { adapter: sourceAdapter, chain: 'Ethereum' },
|
|
76
|
+
destination: { adapter: destAdapter, chain: 'Base' },
|
|
77
|
+
amount: '10.0',
|
|
78
|
+
config: { transferSpeed: 'SLOW' },
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 🔄 Advanced Retry Support
|
|
83
|
+
- **Step-by-step Recovery**: Resume from burn, attestation, or mint phases
|
|
84
|
+
- **Automatic State Analysis**: Intelligent detection of completion status
|
|
85
|
+
- **Network Resilience**: Handle temporary API and RPC failures
|
|
86
|
+
- **Transaction Resubmission**: Retry failed blockchain transactions
|
|
87
|
+
|
|
88
|
+
## 💰 Fee Management
|
|
89
|
+
- **Dynamic Fee Calculation**: Real-time fee estimation from Circle's API
|
|
90
|
+
- **Custom Fee Limits**: Set maximum fees to control costs
|
|
91
|
+
- **Protocol Fee Transparency**: Clear breakdown of all fees involved
|
|
92
|
+
- **Multi-chain Fee Support**: Different fee structures per chain pair
|
|
93
|
+
|
|
94
|
+
## 🛡️ Security & Reliability
|
|
95
|
+
- **No Custom Cryptography**: Uses only Circle's audited smart contracts
|
|
96
|
+
- **Deterministic Operations**: Predictable outcomes and gas estimation
|
|
97
|
+
- **Comprehensive Validation**: Runtime parameter validation and type safety
|
|
98
|
+
- **Production Ready**: Battle-tested protocol with institutional adoption
|
|
99
|
+
|
|
100
|
+
This provider implements Circle's official CCTP v2 specification, ensuring maximum security and compatibility with the broader USDC ecosystem.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
// Buffer polyfill setup - executes before any other code
|
|
22
22
|
// Ensures globalThis.Buffer is available for @solana/spl-token and other Solana libraries
|
|
23
|
-
|
|
23
|
+
const { Buffer } = require('buffer');
|
|
24
24
|
if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
|
|
25
25
|
globalThis.Buffer = Buffer;
|
|
26
26
|
}
|
|
@@ -30,10 +30,10 @@ if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
var zod = require('zod');
|
|
33
|
+
var units = require('@ethersproject/units');
|
|
33
34
|
var bytes = require('@ethersproject/bytes');
|
|
34
35
|
var address = require('@ethersproject/address');
|
|
35
36
|
var bs58 = require('bs58');
|
|
36
|
-
var units = require('@ethersproject/units');
|
|
37
37
|
|
|
38
38
|
function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
|
|
39
39
|
|
|
@@ -2997,6 +2997,84 @@ const pollApiGet = async (url, isValidType, config) => {
|
|
|
2997
2997
|
return pollApiWithValidation(url, 'GET', isValidType, config);
|
|
2998
2998
|
};
|
|
2999
2999
|
|
|
3000
|
+
/**
|
|
3001
|
+
* Convert a value from its smallest unit representation to a human-readable decimal string.
|
|
3002
|
+
*
|
|
3003
|
+
* This function normalizes token values from their blockchain representation (where
|
|
3004
|
+
* everything is stored as integers in the smallest denomination) to human-readable
|
|
3005
|
+
* decimal format. Uses the battle-tested implementation from @ethersproject/units.
|
|
3006
|
+
*
|
|
3007
|
+
* @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
|
|
3008
|
+
* @param decimals - The number of decimal places for the unit conversion
|
|
3009
|
+
* @returns A human-readable decimal string (e.g., "1.0")
|
|
3010
|
+
* @throws Error if the value is not a valid numeric string
|
|
3011
|
+
*
|
|
3012
|
+
* @example
|
|
3013
|
+
* ```typescript
|
|
3014
|
+
* import { formatUnits } from '@core/utils'
|
|
3015
|
+
*
|
|
3016
|
+
* // Format USDC (6 decimals)
|
|
3017
|
+
* const usdcFormatted = formatUnits('1000000', 6)
|
|
3018
|
+
* console.log(usdcFormatted) // "1.0"
|
|
3019
|
+
*
|
|
3020
|
+
* // Format ETH (18 decimals)
|
|
3021
|
+
* const ethFormatted = formatUnits('1000000000000000000', 18)
|
|
3022
|
+
* console.log(ethFormatted) // "1.0"
|
|
3023
|
+
*
|
|
3024
|
+
* // Format with fractional part
|
|
3025
|
+
* const fractionalFormatted = formatUnits('1500000', 6)
|
|
3026
|
+
* console.log(fractionalFormatted) // "1.5"
|
|
3027
|
+
* ```
|
|
3028
|
+
*/
|
|
3029
|
+
const formatUnits = (value, decimals) => {
|
|
3030
|
+
return units.formatUnits(value, decimals);
|
|
3031
|
+
};
|
|
3032
|
+
|
|
3033
|
+
/**
|
|
3034
|
+
* Format a token amount into a human-readable decimal string.
|
|
3035
|
+
*
|
|
3036
|
+
* Accepts a smallest-unit string and either assumes USDC's 6 decimals or derives the
|
|
3037
|
+
* native decimals from the provided chain definition. Delegates to {@link formatUnits}
|
|
3038
|
+
* to preserve consistent rounding and formatting behaviour across the SDK.
|
|
3039
|
+
*
|
|
3040
|
+
* @remarks
|
|
3041
|
+
* When `token` is `'native'`, supply a chain identifier that {@link resolveChainIdentifier}
|
|
3042
|
+
* can resolve so the native currency decimals can be determined.
|
|
3043
|
+
*
|
|
3044
|
+
* @param params - The formatting input including the raw value and token selector.
|
|
3045
|
+
* @returns The decimal string representation of the amount.
|
|
3046
|
+
* @throws Error if the value cannot be parsed or if the chain identifier is unknown.
|
|
3047
|
+
*
|
|
3048
|
+
* @example
|
|
3049
|
+
* ```typescript
|
|
3050
|
+
* import { formatAmount } from '@core/utils'
|
|
3051
|
+
* import { Ethereum } from '@core/chains'
|
|
3052
|
+
*
|
|
3053
|
+
* const usdcAmount = formatAmount({ value: '1000000', token: 'USDC' })
|
|
3054
|
+
* console.log(usdcAmount) // "1"
|
|
3055
|
+
*
|
|
3056
|
+
* const ethAmount = formatAmount({
|
|
3057
|
+
* value: '3141592000000000000',
|
|
3058
|
+
* token: 'native',
|
|
3059
|
+
* chain: Ethereum,
|
|
3060
|
+
* })
|
|
3061
|
+
* console.log(ethAmount) // "3.141592"
|
|
3062
|
+
* ```
|
|
3063
|
+
*/
|
|
3064
|
+
const formatAmount = (params) => {
|
|
3065
|
+
const { value, token } = params;
|
|
3066
|
+
switch (token) {
|
|
3067
|
+
case 'USDC':
|
|
3068
|
+
return formatUnits(value, 6);
|
|
3069
|
+
case 'native':
|
|
3070
|
+
return formatUnits(value, resolveChainIdentifier(params.chain).nativeCurrency.decimals);
|
|
3071
|
+
default:
|
|
3072
|
+
// This will cause a compile-time error if a new token type is added to
|
|
3073
|
+
// `FormatAmountParams` but not handled in this switch statement, ensuring exhaustiveness.
|
|
3074
|
+
throw new Error(`formatAmount: Unhandled token type: ${token}`);
|
|
3075
|
+
}
|
|
3076
|
+
};
|
|
3077
|
+
|
|
3000
3078
|
/**
|
|
3001
3079
|
* Custom error class for insufficient funds errors.
|
|
3002
3080
|
* Provides structured error information while hiding implementation details.
|
|
@@ -3004,7 +3082,7 @@ const pollApiGet = async (url, isValidType, config) => {
|
|
|
3004
3082
|
class InsufficientFundsError extends Error {
|
|
3005
3083
|
balance;
|
|
3006
3084
|
amount;
|
|
3007
|
-
|
|
3085
|
+
token;
|
|
3008
3086
|
tokenAddress;
|
|
3009
3087
|
chain;
|
|
3010
3088
|
/**
|
|
@@ -3012,16 +3090,27 @@ class InsufficientFundsError extends Error {
|
|
|
3012
3090
|
*
|
|
3013
3091
|
* @param balance - The current token balance in the wallet (smallest unit).
|
|
3014
3092
|
* @param amount - The required transaction amount (smallest unit).
|
|
3015
|
-
* @param
|
|
3093
|
+
* @param token - The token ('USDC' or 'native').
|
|
3016
3094
|
* @param tokenAddress - The contract address of the token.
|
|
3017
3095
|
* @param chain - The blockchain network name where the transaction failed.
|
|
3018
3096
|
*/
|
|
3019
|
-
constructor(balance, amount,
|
|
3020
|
-
const
|
|
3097
|
+
constructor(balance, amount, token, tokenAddress, chain) {
|
|
3098
|
+
const formattedBalance = formatAmount({
|
|
3099
|
+
value: balance,
|
|
3100
|
+
token,
|
|
3101
|
+
chain,
|
|
3102
|
+
});
|
|
3103
|
+
const formattedAmount = formatAmount({
|
|
3104
|
+
value: amount,
|
|
3105
|
+
token,
|
|
3106
|
+
chain,
|
|
3107
|
+
});
|
|
3108
|
+
const chainName = typeof chain === 'object' && 'name' in chain ? chain.name : chain;
|
|
3109
|
+
const message = `Insufficient funds for ${token} (${tokenAddress}) transaction on ${chainName}. Balance: ${formattedBalance} ${token}, is less than transaction amount: ${formattedAmount} ${token}.`;
|
|
3021
3110
|
super(message);
|
|
3022
3111
|
this.balance = balance;
|
|
3023
3112
|
this.amount = amount;
|
|
3024
|
-
this.
|
|
3113
|
+
this.token = token;
|
|
3025
3114
|
this.tokenAddress = tokenAddress;
|
|
3026
3115
|
this.chain = chain;
|
|
3027
3116
|
this.name = 'InsufficientFundsError';
|
|
@@ -3400,39 +3489,6 @@ const convertAddress = (address, targetFormat) => {
|
|
|
3400
3489
|
throw new Error(`Unsupported address format: ${address}`);
|
|
3401
3490
|
};
|
|
3402
3491
|
|
|
3403
|
-
/**
|
|
3404
|
-
* Convert a value from its smallest unit representation to a human-readable decimal string.
|
|
3405
|
-
*
|
|
3406
|
-
* This function normalizes token values from their blockchain representation (where
|
|
3407
|
-
* everything is stored as integers in the smallest denomination) to human-readable
|
|
3408
|
-
* decimal format. Uses the battle-tested implementation from @ethersproject/units.
|
|
3409
|
-
*
|
|
3410
|
-
* @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
|
|
3411
|
-
* @param decimals - The number of decimal places for the unit conversion
|
|
3412
|
-
* @returns A human-readable decimal string (e.g., "1.0")
|
|
3413
|
-
* @throws Error if the value is not a valid numeric string
|
|
3414
|
-
*
|
|
3415
|
-
* @example
|
|
3416
|
-
* ```typescript
|
|
3417
|
-
* import { formatUnits } from '@core/utils'
|
|
3418
|
-
*
|
|
3419
|
-
* // Format USDC (6 decimals)
|
|
3420
|
-
* const usdcFormatted = formatUnits('1000000', 6)
|
|
3421
|
-
* console.log(usdcFormatted) // "1.0"
|
|
3422
|
-
*
|
|
3423
|
-
* // Format ETH (18 decimals)
|
|
3424
|
-
* const ethFormatted = formatUnits('1000000000000000000', 18)
|
|
3425
|
-
* console.log(ethFormatted) // "1.0"
|
|
3426
|
-
*
|
|
3427
|
-
* // Format with fractional part
|
|
3428
|
-
* const fractionalFormatted = formatUnits('1500000', 6)
|
|
3429
|
-
* console.log(fractionalFormatted) // "1.5"
|
|
3430
|
-
* ```
|
|
3431
|
-
*/
|
|
3432
|
-
const formatUnits = (value, decimals) => {
|
|
3433
|
-
return units.formatUnits(value, decimals);
|
|
3434
|
-
};
|
|
3435
|
-
|
|
3436
3492
|
/**
|
|
3437
3493
|
* Build a complete explorer URL from a chain definition and transaction hash.
|
|
3438
3494
|
*
|
|
@@ -3858,9 +3914,11 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
|
|
|
3858
3914
|
* amount and invalid atttestation messages.
|
|
3859
3915
|
*
|
|
3860
3916
|
* These values were obtained by averaging gas for the function call from the last 3 months.
|
|
3917
|
+
* see: https://dune.com/queries/6022210/9697710/
|
|
3861
3918
|
*/
|
|
3862
|
-
const DEPOSIT_FOR_BURN_GAS_ESTIMATE_EVM =
|
|
3863
|
-
const
|
|
3919
|
+
const DEPOSIT_FOR_BURN_GAS_ESTIMATE_EVM = 169914n; // (99p: 111_521n + max: 226_506n) / 2 = 169_914n
|
|
3920
|
+
const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201525n; // p99 and max are same here: 201_525n
|
|
3921
|
+
const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237401n; // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
|
|
3864
3922
|
/**
|
|
3865
3923
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
3866
3924
|
*
|
|
@@ -4675,7 +4733,9 @@ async function assertCCTPv2AttestationParams(attestation, params) {
|
|
|
4675
4733
|
const errors = [];
|
|
4676
4734
|
const message = attestation.decodedMessage;
|
|
4677
4735
|
const messageBody = message.decodedMessageBody;
|
|
4678
|
-
|
|
4736
|
+
// Use recipientAddress if provided, otherwise use destination.address
|
|
4737
|
+
const destinationAddressForMint = params.destination.recipientAddress ?? params.destination.address;
|
|
4738
|
+
const mintRecipient = await getMintRecipientAccount(params.destination.chain.type, destinationAddressForMint, params.destination.chain.usdcAddress);
|
|
4679
4739
|
let sender;
|
|
4680
4740
|
if (hasCustomContractSupport(params.source.chain, 'bridge')) {
|
|
4681
4741
|
if (params.source.chain.type === 'solana') {
|
|
@@ -4933,6 +4993,35 @@ async function bridgeMint({ params, provider, }, attestation) {
|
|
|
4933
4993
|
request: await provider.mint(params.source, params.destination, attestation),
|
|
4934
4994
|
});
|
|
4935
4995
|
}
|
|
4996
|
+
const mockAttestationMessage = {
|
|
4997
|
+
attestation: '0x8bd9b9e63eb05128eb2896b63e3e1df39bfd6bbfb893b69dc53c39252aeb85df1ded70263b07da17abf88e6e1b77f16ebcdb4eb1c6b4ea4c625215e5cb9dddb81bffe0b9d75e2094f05e48f18f70d0b254999bde90bab26f5c0da29b2d7e00feca1cfed119cba0d2a0e887648a40e803e0ca34aa8fd94ce068515eeaef72b520aa1b',
|
|
4998
|
+
message: '0x000000010000000000000006f446cb82eb486fef485c14c301eaab73aa35f87e3feda3547acf0f33cd4b40f30000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000000000000000000000000000000000000000000000000003e8000003e8000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000023f9a5bea7b92a0638520607407bc7f0310aeed400000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000c5567a5e3370d4dbfb0540025078e283e36a363d000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000001f6b158',
|
|
4999
|
+
eventNonce: '0xf446cb82eb486fef485c14c301eaab73aa35f87e3feda3547acf0f33cd4b40f3',
|
|
5000
|
+
cctpVersion: 2,
|
|
5001
|
+
status: 'complete',
|
|
5002
|
+
decodedMessage: {
|
|
5003
|
+
sourceDomain: '0',
|
|
5004
|
+
destinationDomain: '6',
|
|
5005
|
+
nonce: '0xf446cb82eb486fef485c14c301eaab73aa35f87e3feda3547acf0f33cd4b40f3',
|
|
5006
|
+
sender: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5007
|
+
recipient: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5008
|
+
destinationCaller: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
5009
|
+
minFinalityThreshold: '1000',
|
|
5010
|
+
finalityThresholdExecuted: '1000',
|
|
5011
|
+
messageBody: '0x000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000023f9a5bea7b92a0638520607407bc7f0310aeed400000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000c5567a5e3370d4dbfb0540025078e283e36a363d000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000001f6b158',
|
|
5012
|
+
decodedMessageBody: {
|
|
5013
|
+
burnToken: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238',
|
|
5014
|
+
mintRecipient: '0x23f9a5bea7b92a0638520607407bc7f0310aeed4',
|
|
5015
|
+
amount: '100000',
|
|
5016
|
+
messageSender: '0xc5567a5e3370d4dbfb0540025078e283e36a363d',
|
|
5017
|
+
maxFee: '11',
|
|
5018
|
+
feeExecuted: '10',
|
|
5019
|
+
expirationBlock: '32944472',
|
|
5020
|
+
hookData: null,
|
|
5021
|
+
},
|
|
5022
|
+
},
|
|
5023
|
+
delayReason: null,
|
|
5024
|
+
};
|
|
4936
5025
|
|
|
4937
5026
|
/**
|
|
4938
5027
|
* Ordered sequence of CCTP v2 bridge step executors.
|
|
@@ -5430,10 +5519,12 @@ zod.z.object({
|
|
|
5430
5519
|
*/
|
|
5431
5520
|
const validateBalanceForTransaction = async (params) => {
|
|
5432
5521
|
const { amount, adapter, token, tokenAddress, operationContext } = params;
|
|
5433
|
-
const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
|
|
5522
|
+
const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
|
|
5523
|
+
walletAddress: operationContext.address,
|
|
5524
|
+
}, operationContext);
|
|
5434
5525
|
const balance = await balancePrepared.execute();
|
|
5435
5526
|
if (BigInt(balance) < BigInt(amount)) {
|
|
5436
|
-
throw new InsufficientFundsError(balance.toString(), amount, token, tokenAddress,
|
|
5527
|
+
throw new InsufficientFundsError(balance.toString(), amount, token, tokenAddress, operationContext.chain);
|
|
5437
5528
|
}
|
|
5438
5529
|
};
|
|
5439
5530
|
|
|
@@ -5997,22 +6088,22 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
5997
6088
|
async estimate(params) {
|
|
5998
6089
|
// CCTP-specific transfer params validation (includes base validation)
|
|
5999
6090
|
assertCCTPv2BridgeParams(params);
|
|
6000
|
-
const { source, destination, amount
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
}
|
|
6091
|
+
const { source, destination, amount } = params;
|
|
6092
|
+
const estimateBurn = async () => {
|
|
6093
|
+
const burn = await this.burn(params);
|
|
6094
|
+
return await burn.estimate(undefined, await source.adapter.calculateTransactionFee(hasCustomContractSupport(source.chain, 'bridge')
|
|
6095
|
+
? CUSTOM_BURN_GAS_ESTIMATE_EVM
|
|
6096
|
+
: DEPOSIT_FOR_BURN_GAS_ESTIMATE_EVM, undefined, source.chain));
|
|
6097
|
+
};
|
|
6098
|
+
const estimateMint = async () => {
|
|
6099
|
+
const mint = await this.mint(source, destination, mockAttestationMessage);
|
|
6100
|
+
return await mint.estimate(undefined, await destination.adapter.calculateTransactionFee(RECEIVE_MESSAGE_GAS_ESTIMATE_EVM, undefined, destination.chain));
|
|
6101
|
+
};
|
|
6011
6102
|
// Parallelize all independent async operations
|
|
6012
6103
|
const [approveEstimate, depositForBurnFee, receiveMessageFee, maxFee] = await Promise.allSettled([
|
|
6013
6104
|
this.approve(source, amount).then(async (approve) => approve.estimate()),
|
|
6014
|
-
|
|
6015
|
-
|
|
6105
|
+
estimateBurn(),
|
|
6106
|
+
estimateMint(),
|
|
6016
6107
|
params.config?.transferSpeed === undefined ||
|
|
6017
6108
|
params.config?.transferSpeed === TransferSpeed.FAST
|
|
6018
6109
|
? this.getMaxFee(params)
|
|
@@ -6179,14 +6270,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6179
6270
|
delegate: spenderAddress,
|
|
6180
6271
|
chain: resolvedContext?.chain ?? chain,
|
|
6181
6272
|
};
|
|
6182
|
-
|
|
6183
|
-
if (resolvedContext) {
|
|
6184
|
-
return await adapter.prepareAction('usdc.increaseAllowance', actionParams, resolvedContext);
|
|
6185
|
-
}
|
|
6186
|
-
else {
|
|
6187
|
-
// Legacy fallback for adapters without capabilities
|
|
6188
|
-
return await adapter.prepareAction('usdc.increaseAllowance', actionParams);
|
|
6189
|
-
}
|
|
6273
|
+
return await adapter.prepareAction('usdc.increaseAllowance', actionParams, resolvedContext);
|
|
6190
6274
|
}
|
|
6191
6275
|
/**
|
|
6192
6276
|
* Prepares a CCTP v2 token minting transaction on the destination chain.
|
|
@@ -6230,14 +6314,8 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6230
6314
|
assertCCTPv2WalletContext(destination);
|
|
6231
6315
|
// Extract operation context from destination wallet context
|
|
6232
6316
|
const operationContext = this.extractOperationContext(destination);
|
|
6233
|
-
//
|
|
6234
|
-
|
|
6235
|
-
try {
|
|
6236
|
-
resolvedContext = await resolveOperationContext(destination.adapter, operationContext);
|
|
6237
|
-
}
|
|
6238
|
-
catch (error) {
|
|
6239
|
-
throw new Error(`Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
|
|
6240
|
-
}
|
|
6317
|
+
// Use recipientAddress if provided, otherwise use destination.address
|
|
6318
|
+
const destinationAddressForMint = destination.recipientAddress ?? destination.address;
|
|
6241
6319
|
// Prepare action parameters
|
|
6242
6320
|
const actionParams = {
|
|
6243
6321
|
fromChain: source.chain,
|
|
@@ -6245,16 +6323,9 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6245
6323
|
message: attestation.message,
|
|
6246
6324
|
attestation: attestation.attestation,
|
|
6247
6325
|
eventNonce: attestation.eventNonce,
|
|
6248
|
-
destinationAddress:
|
|
6326
|
+
destinationAddress: destinationAddressForMint,
|
|
6249
6327
|
};
|
|
6250
|
-
|
|
6251
|
-
if (resolvedContext) {
|
|
6252
|
-
return await destination.adapter.prepareAction('cctp.v2.receiveMessage', actionParams, resolvedContext);
|
|
6253
|
-
}
|
|
6254
|
-
else {
|
|
6255
|
-
// Legacy fallback for adapters without capabilities
|
|
6256
|
-
return await destination.adapter.prepareAction('cctp.v2.receiveMessage', actionParams);
|
|
6257
|
-
}
|
|
6328
|
+
return await destination.adapter.prepareAction('cctp.v2.receiveMessage', actionParams, operationContext);
|
|
6258
6329
|
}
|
|
6259
6330
|
/**
|
|
6260
6331
|
* Fetches attestation data for a burn transaction from the IRIS API.
|
|
@@ -6453,10 +6524,11 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6453
6524
|
// Get the min finality threshold based on the transfer speed
|
|
6454
6525
|
const minFinalityThreshold = CCTPv2MinFinalityThreshold[transferSpeed];
|
|
6455
6526
|
// Calculate the max fee
|
|
6527
|
+
// Use recipientAddress if provided, otherwise use destination.address
|
|
6528
|
+
const destinationAddressForMint = destination.recipientAddress ?? destination.address;
|
|
6456
6529
|
const [maxFee, mintRecipient] = await Promise.all([
|
|
6457
6530
|
this.getMaxFee(params),
|
|
6458
|
-
getMintRecipientAccount(destination.chain.type, destination.
|
|
6459
|
-
(await destination.adapter.getAddress(destination.chain)), destination.chain.usdcAddress),
|
|
6531
|
+
getMintRecipientAccount(destination.chain.type, destinationAddressForMint, destination.chain.usdcAddress),
|
|
6460
6532
|
]);
|
|
6461
6533
|
const actionParams = {
|
|
6462
6534
|
fromChain: source.chain,
|
|
@@ -6486,14 +6558,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6486
6558
|
const finalParams = useCustomBurn
|
|
6487
6559
|
? { ...actionParams, feeRecipient: recipientAddress, protocolFee }
|
|
6488
6560
|
: actionParams;
|
|
6489
|
-
|
|
6490
|
-
if (resolvedContext) {
|
|
6491
|
-
return await source.adapter.prepareAction(actionType, finalParams, resolvedContext);
|
|
6492
|
-
}
|
|
6493
|
-
else {
|
|
6494
|
-
// Legacy fallback for adapters without capabilities
|
|
6495
|
-
return await source.adapter.prepareAction(actionType, finalParams);
|
|
6496
|
-
}
|
|
6561
|
+
return await source.adapter.prepareAction(actionType, finalParams, resolvedContext);
|
|
6497
6562
|
}
|
|
6498
6563
|
/**
|
|
6499
6564
|
* Waits for a transaction to be mined and confirmed on the blockchain.
|
|
@@ -6523,4 +6588,4 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6523
6588
|
|
|
6524
6589
|
exports.CCTPV2BridgingProvider = CCTPV2BridgingProvider;
|
|
6525
6590
|
exports.getMintRecipientAccount = getMintRecipientAccount;
|
|
6526
|
-
//# sourceMappingURL=index.cjs.
|
|
6591
|
+
//# sourceMappingURL=index.cjs.map
|
package/index.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/index.d.ts
CHANGED
|
@@ -594,10 +594,11 @@ interface EvmPreparedChainRequest {
|
|
|
594
594
|
* Estimate the gas cost for the contract execution.
|
|
595
595
|
*
|
|
596
596
|
* @param overrides - Optional parameters to override the default estimation behavior
|
|
597
|
+
* @param fallback - Optional fallback gas information to use if the estimation fails
|
|
597
598
|
* @returns A promise that resolves to the estimated gas information
|
|
598
599
|
* @throws If the estimation fails
|
|
599
600
|
*/
|
|
600
|
-
estimate(overrides?: EvmEstimateOverrides): Promise<EstimatedGas>;
|
|
601
|
+
estimate(overrides?: EvmEstimateOverrides, fallback?: EstimatedGas): Promise<EstimatedGas>;
|
|
601
602
|
/**
|
|
602
603
|
* Execute the prepared contract call.
|
|
603
604
|
*
|
|
@@ -675,7 +676,7 @@ interface SolanaPreparedChainRequest {
|
|
|
675
676
|
/** The type of the chain request. */
|
|
676
677
|
type: 'solana';
|
|
677
678
|
/** Estimate the compute units and fee for the transaction. */
|
|
678
|
-
estimate(overrides?: SolanaEstimateOverrides): Promise<EstimatedGas>;
|
|
679
|
+
estimate(overrides?: SolanaEstimateOverrides, fallback?: EstimatedGas): Promise<EstimatedGas>;
|
|
679
680
|
/** Execute the prepared transaction. */
|
|
680
681
|
execute(overrides?: SolanaExecuteOverrides): Promise<string>;
|
|
681
682
|
}
|
|
@@ -707,7 +708,7 @@ interface NoopPreparedChainRequest {
|
|
|
707
708
|
* Placeholder for the estimate method.
|
|
708
709
|
* @returns The estimated gas cost.
|
|
709
710
|
*/
|
|
710
|
-
estimate: () => Promise<EstimatedGas>;
|
|
711
|
+
estimate: (overrides?: EvmEstimateOverrides | SolanaEstimateOverrides, fallback?: EstimatedGas) => Promise<EstimatedGas>;
|
|
711
712
|
/**
|
|
712
713
|
* Placeholder for the execute method.
|
|
713
714
|
* @returns The transaction hash.
|
|
@@ -1965,10 +1966,23 @@ interface AdapterCapabilities {
|
|
|
1965
1966
|
*/
|
|
1966
1967
|
declare abstract class Adapter<TAdapterCapabilities extends AdapterCapabilities = AdapterCapabilities> {
|
|
1967
1968
|
/**
|
|
1968
|
-
* The type of the chain.
|
|
1969
|
-
*
|
|
1969
|
+
* The type of the chain for this adapter.
|
|
1970
|
+
*
|
|
1971
|
+
* - For concrete adapters, this should be a real chain type (e.g., `'evm'`, `'solana'`, etc.) from the ChainType union.
|
|
1972
|
+
* - For hybrid adapters (adapters that route to concrete adapters supporting multiple ecosystems),
|
|
1973
|
+
* set this property to the string literal `'hybrid'`.
|
|
1974
|
+
*
|
|
1975
|
+
* Note: `'hybrid'` is not a legal ChainType and should only be used as a marker for multi-ecosystem adapters.
|
|
1976
|
+
* Hybrid adapters do not interact directly with any chain, but instead route requests to a concrete underlying adapter.
|
|
1977
|
+
*
|
|
1978
|
+
* @example
|
|
1979
|
+
* // For an EVM-only adapter:
|
|
1980
|
+
* chainType = 'evm'
|
|
1981
|
+
*
|
|
1982
|
+
* // For a hybrid adapter:
|
|
1983
|
+
* chainType = 'hybrid'
|
|
1970
1984
|
*/
|
|
1971
|
-
abstract chainType: ChainType;
|
|
1985
|
+
abstract chainType: ChainType | 'hybrid';
|
|
1972
1986
|
/**
|
|
1973
1987
|
* Capabilities of this adapter, defining address control model and supported chains.
|
|
1974
1988
|
*
|
|
@@ -2050,7 +2064,7 @@ declare abstract class Adapter<TAdapterCapabilities extends AdapterCapabilities
|
|
|
2050
2064
|
* })
|
|
2051
2065
|
* ```
|
|
2052
2066
|
*/
|
|
2053
|
-
prepareAction<TActionKey extends ActionKeys>(action: TActionKey, params: ActionPayload<TActionKey>, ctx
|
|
2067
|
+
prepareAction<TActionKey extends ActionKeys>(action: TActionKey, params: ActionPayload<TActionKey>, ctx: OperationContext<TAdapterCapabilities>): Promise<PreparedChainRequest>;
|
|
2054
2068
|
/**
|
|
2055
2069
|
* Prepares a transaction for future gas estimation and execution.
|
|
2056
2070
|
*
|
|
@@ -2435,6 +2449,49 @@ TChainDefinition extends ChainDefinition = ChainDefinition> {
|
|
|
2435
2449
|
*/
|
|
2436
2450
|
chain: TChainDefinition;
|
|
2437
2451
|
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Wallet context for bridge destinations with optional custom recipient.
|
|
2454
|
+
*
|
|
2455
|
+
* Extends WalletContext to support scenarios where the recipient address
|
|
2456
|
+
* differs from the signer address (e.g., bridging to a third-party wallet).
|
|
2457
|
+
* The signer address is used for transaction authorization, while the
|
|
2458
|
+
* recipient address specifies where the minted funds should be sent.
|
|
2459
|
+
*
|
|
2460
|
+
* @typeParam TAdapterCapabilities - The adapter capabilities type to use for the wallet context.
|
|
2461
|
+
* @typeParam TChainDefinition - The chain definition type to use for the wallet context.
|
|
2462
|
+
*
|
|
2463
|
+
* @example
|
|
2464
|
+
* ```typescript
|
|
2465
|
+
* import type { DestinationWalletContext } from '@core/provider'
|
|
2466
|
+
* import { adapter, blockchain } from './setup'
|
|
2467
|
+
*
|
|
2468
|
+
* // Bridge to a custom recipient address
|
|
2469
|
+
* const destination: DestinationWalletContext = {
|
|
2470
|
+
* adapter,
|
|
2471
|
+
* address: '0x1234...abcd', // Signer address
|
|
2472
|
+
* chain: blockchain,
|
|
2473
|
+
* recipientAddress: '0x9876...fedc' // Custom recipient
|
|
2474
|
+
* }
|
|
2475
|
+
* ```
|
|
2476
|
+
*/
|
|
2477
|
+
interface DestinationWalletContext<TAdapterCapabilities extends AdapterCapabilities = AdapterCapabilities,
|
|
2478
|
+
/**
|
|
2479
|
+
* The chain definition type to use for the wallet context.
|
|
2480
|
+
*
|
|
2481
|
+
* @defaultValue ChainDefinition
|
|
2482
|
+
*/
|
|
2483
|
+
TChainDefinition extends ChainDefinition = ChainDefinition> extends WalletContext<TAdapterCapabilities, TChainDefinition> {
|
|
2484
|
+
/**
|
|
2485
|
+
* Optional custom recipient address for minted funds.
|
|
2486
|
+
*
|
|
2487
|
+
* When provided, minted tokens will be sent to this address instead of
|
|
2488
|
+
* the address specified in the wallet context. The wallet context address
|
|
2489
|
+
* is still used for transaction signing and authorization.
|
|
2490
|
+
*
|
|
2491
|
+
* Must be a valid address format for the specified blockchain.
|
|
2492
|
+
*/
|
|
2493
|
+
recipientAddress?: string;
|
|
2494
|
+
}
|
|
2438
2495
|
/**
|
|
2439
2496
|
* Parameters for executing a cross-chain bridge operation.
|
|
2440
2497
|
*/
|
|
@@ -2448,7 +2505,7 @@ TChainDefinition extends ChainDefinition = ChainDefinition> {
|
|
|
2448
2505
|
/** The source adapter containing wallet and chain information */
|
|
2449
2506
|
source: WalletContext<TFromCapabilities, TChainDefinition>;
|
|
2450
2507
|
/** The destination adapter containing wallet and chain information */
|
|
2451
|
-
destination:
|
|
2508
|
+
destination: DestinationWalletContext<TToCapabilities, TChainDefinition>;
|
|
2452
2509
|
/** The amount to transfer (as a string to avoid precision issues) */
|
|
2453
2510
|
amount: string;
|
|
2454
2511
|
/** The token to transfer (currently only USDC is supported) */
|
|
@@ -3150,7 +3207,7 @@ type CCTPV2BridgeParams<TFromAdapterCapabilities extends AdapterCapabilities = A
|
|
|
3150
3207
|
/**
|
|
3151
3208
|
* The destination wallet context containing the chain definition and wallet address.
|
|
3152
3209
|
*/
|
|
3153
|
-
destination:
|
|
3210
|
+
destination: DestinationWalletContext<TToAdapterCapabilities, ChainDefinitionWithCCTPv2>;
|
|
3154
3211
|
/**
|
|
3155
3212
|
* The bridge configuration for the CCTPv2 transfer.
|
|
3156
3213
|
*/
|
|
@@ -3523,7 +3580,7 @@ declare class CCTPV2BridgingProvider extends BridgingProvider<CCTPV2Actions> imp
|
|
|
3523
3580
|
* console.log('Mint transaction:', txHash)
|
|
3524
3581
|
* ```
|
|
3525
3582
|
*/
|
|
3526
|
-
mint<TFromAdapterCapabilities extends AdapterCapabilities, TToAdapterCapabilities extends AdapterCapabilities>(source: WalletContext<TFromAdapterCapabilities, ChainDefinitionWithCCTPv2>, destination:
|
|
3583
|
+
mint<TFromAdapterCapabilities extends AdapterCapabilities, TToAdapterCapabilities extends AdapterCapabilities>(source: WalletContext<TFromAdapterCapabilities, ChainDefinitionWithCCTPv2>, destination: BridgeParams<TFromAdapterCapabilities, TToAdapterCapabilities>['destination'], attestation: AttestationMessage): Promise<PreparedChainRequest>;
|
|
3527
3584
|
/**
|
|
3528
3585
|
* Fetches attestation data for a burn transaction from the IRIS API.
|
|
3529
3586
|
*
|
package/index.mjs
CHANGED
|
@@ -28,10 +28,10 @@ if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
import { z } from 'zod';
|
|
31
|
+
import { formatUnits as formatUnits$1 } from '@ethersproject/units';
|
|
31
32
|
import { hexlify, hexZeroPad } from '@ethersproject/bytes';
|
|
32
33
|
import { getAddress } from '@ethersproject/address';
|
|
33
34
|
import bs58 from 'bs58';
|
|
34
|
-
import { formatUnits as formatUnits$1 } from '@ethersproject/units';
|
|
35
35
|
|
|
36
36
|
// -----------------------------------------------------------------------------
|
|
37
37
|
// Blockchain Enum
|
|
@@ -2991,6 +2991,84 @@ const pollApiGet = async (url, isValidType, config) => {
|
|
|
2991
2991
|
return pollApiWithValidation(url, 'GET', isValidType, config);
|
|
2992
2992
|
};
|
|
2993
2993
|
|
|
2994
|
+
/**
|
|
2995
|
+
* Convert a value from its smallest unit representation to a human-readable decimal string.
|
|
2996
|
+
*
|
|
2997
|
+
* This function normalizes token values from their blockchain representation (where
|
|
2998
|
+
* everything is stored as integers in the smallest denomination) to human-readable
|
|
2999
|
+
* decimal format. Uses the battle-tested implementation from @ethersproject/units.
|
|
3000
|
+
*
|
|
3001
|
+
* @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
|
|
3002
|
+
* @param decimals - The number of decimal places for the unit conversion
|
|
3003
|
+
* @returns A human-readable decimal string (e.g., "1.0")
|
|
3004
|
+
* @throws Error if the value is not a valid numeric string
|
|
3005
|
+
*
|
|
3006
|
+
* @example
|
|
3007
|
+
* ```typescript
|
|
3008
|
+
* import { formatUnits } from '@core/utils'
|
|
3009
|
+
*
|
|
3010
|
+
* // Format USDC (6 decimals)
|
|
3011
|
+
* const usdcFormatted = formatUnits('1000000', 6)
|
|
3012
|
+
* console.log(usdcFormatted) // "1.0"
|
|
3013
|
+
*
|
|
3014
|
+
* // Format ETH (18 decimals)
|
|
3015
|
+
* const ethFormatted = formatUnits('1000000000000000000', 18)
|
|
3016
|
+
* console.log(ethFormatted) // "1.0"
|
|
3017
|
+
*
|
|
3018
|
+
* // Format with fractional part
|
|
3019
|
+
* const fractionalFormatted = formatUnits('1500000', 6)
|
|
3020
|
+
* console.log(fractionalFormatted) // "1.5"
|
|
3021
|
+
* ```
|
|
3022
|
+
*/
|
|
3023
|
+
const formatUnits = (value, decimals) => {
|
|
3024
|
+
return formatUnits$1(value, decimals);
|
|
3025
|
+
};
|
|
3026
|
+
|
|
3027
|
+
/**
|
|
3028
|
+
* Format a token amount into a human-readable decimal string.
|
|
3029
|
+
*
|
|
3030
|
+
* Accepts a smallest-unit string and either assumes USDC's 6 decimals or derives the
|
|
3031
|
+
* native decimals from the provided chain definition. Delegates to {@link formatUnits}
|
|
3032
|
+
* to preserve consistent rounding and formatting behaviour across the SDK.
|
|
3033
|
+
*
|
|
3034
|
+
* @remarks
|
|
3035
|
+
* When `token` is `'native'`, supply a chain identifier that {@link resolveChainIdentifier}
|
|
3036
|
+
* can resolve so the native currency decimals can be determined.
|
|
3037
|
+
*
|
|
3038
|
+
* @param params - The formatting input including the raw value and token selector.
|
|
3039
|
+
* @returns The decimal string representation of the amount.
|
|
3040
|
+
* @throws Error if the value cannot be parsed or if the chain identifier is unknown.
|
|
3041
|
+
*
|
|
3042
|
+
* @example
|
|
3043
|
+
* ```typescript
|
|
3044
|
+
* import { formatAmount } from '@core/utils'
|
|
3045
|
+
* import { Ethereum } from '@core/chains'
|
|
3046
|
+
*
|
|
3047
|
+
* const usdcAmount = formatAmount({ value: '1000000', token: 'USDC' })
|
|
3048
|
+
* console.log(usdcAmount) // "1"
|
|
3049
|
+
*
|
|
3050
|
+
* const ethAmount = formatAmount({
|
|
3051
|
+
* value: '3141592000000000000',
|
|
3052
|
+
* token: 'native',
|
|
3053
|
+
* chain: Ethereum,
|
|
3054
|
+
* })
|
|
3055
|
+
* console.log(ethAmount) // "3.141592"
|
|
3056
|
+
* ```
|
|
3057
|
+
*/
|
|
3058
|
+
const formatAmount = (params) => {
|
|
3059
|
+
const { value, token } = params;
|
|
3060
|
+
switch (token) {
|
|
3061
|
+
case 'USDC':
|
|
3062
|
+
return formatUnits(value, 6);
|
|
3063
|
+
case 'native':
|
|
3064
|
+
return formatUnits(value, resolveChainIdentifier(params.chain).nativeCurrency.decimals);
|
|
3065
|
+
default:
|
|
3066
|
+
// This will cause a compile-time error if a new token type is added to
|
|
3067
|
+
// `FormatAmountParams` but not handled in this switch statement, ensuring exhaustiveness.
|
|
3068
|
+
throw new Error(`formatAmount: Unhandled token type: ${token}`);
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
|
|
2994
3072
|
/**
|
|
2995
3073
|
* Custom error class for insufficient funds errors.
|
|
2996
3074
|
* Provides structured error information while hiding implementation details.
|
|
@@ -2998,7 +3076,7 @@ const pollApiGet = async (url, isValidType, config) => {
|
|
|
2998
3076
|
class InsufficientFundsError extends Error {
|
|
2999
3077
|
balance;
|
|
3000
3078
|
amount;
|
|
3001
|
-
|
|
3079
|
+
token;
|
|
3002
3080
|
tokenAddress;
|
|
3003
3081
|
chain;
|
|
3004
3082
|
/**
|
|
@@ -3006,16 +3084,27 @@ class InsufficientFundsError extends Error {
|
|
|
3006
3084
|
*
|
|
3007
3085
|
* @param balance - The current token balance in the wallet (smallest unit).
|
|
3008
3086
|
* @param amount - The required transaction amount (smallest unit).
|
|
3009
|
-
* @param
|
|
3087
|
+
* @param token - The token ('USDC' or 'native').
|
|
3010
3088
|
* @param tokenAddress - The contract address of the token.
|
|
3011
3089
|
* @param chain - The blockchain network name where the transaction failed.
|
|
3012
3090
|
*/
|
|
3013
|
-
constructor(balance, amount,
|
|
3014
|
-
const
|
|
3091
|
+
constructor(balance, amount, token, tokenAddress, chain) {
|
|
3092
|
+
const formattedBalance = formatAmount({
|
|
3093
|
+
value: balance,
|
|
3094
|
+
token,
|
|
3095
|
+
chain,
|
|
3096
|
+
});
|
|
3097
|
+
const formattedAmount = formatAmount({
|
|
3098
|
+
value: amount,
|
|
3099
|
+
token,
|
|
3100
|
+
chain,
|
|
3101
|
+
});
|
|
3102
|
+
const chainName = typeof chain === 'object' && 'name' in chain ? chain.name : chain;
|
|
3103
|
+
const message = `Insufficient funds for ${token} (${tokenAddress}) transaction on ${chainName}. Balance: ${formattedBalance} ${token}, is less than transaction amount: ${formattedAmount} ${token}.`;
|
|
3015
3104
|
super(message);
|
|
3016
3105
|
this.balance = balance;
|
|
3017
3106
|
this.amount = amount;
|
|
3018
|
-
this.
|
|
3107
|
+
this.token = token;
|
|
3019
3108
|
this.tokenAddress = tokenAddress;
|
|
3020
3109
|
this.chain = chain;
|
|
3021
3110
|
this.name = 'InsufficientFundsError';
|
|
@@ -3394,39 +3483,6 @@ const convertAddress = (address, targetFormat) => {
|
|
|
3394
3483
|
throw new Error(`Unsupported address format: ${address}`);
|
|
3395
3484
|
};
|
|
3396
3485
|
|
|
3397
|
-
/**
|
|
3398
|
-
* Convert a value from its smallest unit representation to a human-readable decimal string.
|
|
3399
|
-
*
|
|
3400
|
-
* This function normalizes token values from their blockchain representation (where
|
|
3401
|
-
* everything is stored as integers in the smallest denomination) to human-readable
|
|
3402
|
-
* decimal format. Uses the battle-tested implementation from @ethersproject/units.
|
|
3403
|
-
*
|
|
3404
|
-
* @param value - The value in smallest units (e.g., "1000000" for 1 USDC with 6 decimals)
|
|
3405
|
-
* @param decimals - The number of decimal places for the unit conversion
|
|
3406
|
-
* @returns A human-readable decimal string (e.g., "1.0")
|
|
3407
|
-
* @throws Error if the value is not a valid numeric string
|
|
3408
|
-
*
|
|
3409
|
-
* @example
|
|
3410
|
-
* ```typescript
|
|
3411
|
-
* import { formatUnits } from '@core/utils'
|
|
3412
|
-
*
|
|
3413
|
-
* // Format USDC (6 decimals)
|
|
3414
|
-
* const usdcFormatted = formatUnits('1000000', 6)
|
|
3415
|
-
* console.log(usdcFormatted) // "1.0"
|
|
3416
|
-
*
|
|
3417
|
-
* // Format ETH (18 decimals)
|
|
3418
|
-
* const ethFormatted = formatUnits('1000000000000000000', 18)
|
|
3419
|
-
* console.log(ethFormatted) // "1.0"
|
|
3420
|
-
*
|
|
3421
|
-
* // Format with fractional part
|
|
3422
|
-
* const fractionalFormatted = formatUnits('1500000', 6)
|
|
3423
|
-
* console.log(fractionalFormatted) // "1.5"
|
|
3424
|
-
* ```
|
|
3425
|
-
*/
|
|
3426
|
-
const formatUnits = (value, decimals) => {
|
|
3427
|
-
return formatUnits$1(value, decimals);
|
|
3428
|
-
};
|
|
3429
|
-
|
|
3430
3486
|
/**
|
|
3431
3487
|
* Build a complete explorer URL from a chain definition and transaction hash.
|
|
3432
3488
|
*
|
|
@@ -3852,9 +3908,11 @@ async function fetchUsdcFastBurnFee(sourceDomain, destinationDomain, isTestnet)
|
|
|
3852
3908
|
* amount and invalid atttestation messages.
|
|
3853
3909
|
*
|
|
3854
3910
|
* These values were obtained by averaging gas for the function call from the last 3 months.
|
|
3911
|
+
* see: https://dune.com/queries/6022210/9697710/
|
|
3855
3912
|
*/
|
|
3856
|
-
const DEPOSIT_FOR_BURN_GAS_ESTIMATE_EVM =
|
|
3857
|
-
const
|
|
3913
|
+
const DEPOSIT_FOR_BURN_GAS_ESTIMATE_EVM = 169914n; // (99p: 111_521n + max: 226_506n) / 2 = 169_914n
|
|
3914
|
+
const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201525n; // p99 and max are same here: 201_525n
|
|
3915
|
+
const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237401n; // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
|
|
3858
3916
|
/**
|
|
3859
3917
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
3860
3918
|
*
|
|
@@ -4669,7 +4727,9 @@ async function assertCCTPv2AttestationParams(attestation, params) {
|
|
|
4669
4727
|
const errors = [];
|
|
4670
4728
|
const message = attestation.decodedMessage;
|
|
4671
4729
|
const messageBody = message.decodedMessageBody;
|
|
4672
|
-
|
|
4730
|
+
// Use recipientAddress if provided, otherwise use destination.address
|
|
4731
|
+
const destinationAddressForMint = params.destination.recipientAddress ?? params.destination.address;
|
|
4732
|
+
const mintRecipient = await getMintRecipientAccount(params.destination.chain.type, destinationAddressForMint, params.destination.chain.usdcAddress);
|
|
4673
4733
|
let sender;
|
|
4674
4734
|
if (hasCustomContractSupport(params.source.chain, 'bridge')) {
|
|
4675
4735
|
if (params.source.chain.type === 'solana') {
|
|
@@ -4927,6 +4987,35 @@ async function bridgeMint({ params, provider, }, attestation) {
|
|
|
4927
4987
|
request: await provider.mint(params.source, params.destination, attestation),
|
|
4928
4988
|
});
|
|
4929
4989
|
}
|
|
4990
|
+
const mockAttestationMessage = {
|
|
4991
|
+
attestation: '0x8bd9b9e63eb05128eb2896b63e3e1df39bfd6bbfb893b69dc53c39252aeb85df1ded70263b07da17abf88e6e1b77f16ebcdb4eb1c6b4ea4c625215e5cb9dddb81bffe0b9d75e2094f05e48f18f70d0b254999bde90bab26f5c0da29b2d7e00feca1cfed119cba0d2a0e887648a40e803e0ca34aa8fd94ce068515eeaef72b520aa1b',
|
|
4992
|
+
message: '0x000000010000000000000006f446cb82eb486fef485c14c301eaab73aa35f87e3feda3547acf0f33cd4b40f30000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000008fe6b999dc680ccfdd5bf7eb0974218be2542daa0000000000000000000000000000000000000000000000000000000000000000000003e8000003e8000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000023f9a5bea7b92a0638520607407bc7f0310aeed400000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000c5567a5e3370d4dbfb0540025078e283e36a363d000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000001f6b158',
|
|
4993
|
+
eventNonce: '0xf446cb82eb486fef485c14c301eaab73aa35f87e3feda3547acf0f33cd4b40f3',
|
|
4994
|
+
cctpVersion: 2,
|
|
4995
|
+
status: 'complete',
|
|
4996
|
+
decodedMessage: {
|
|
4997
|
+
sourceDomain: '0',
|
|
4998
|
+
destinationDomain: '6',
|
|
4999
|
+
nonce: '0xf446cb82eb486fef485c14c301eaab73aa35f87e3feda3547acf0f33cd4b40f3',
|
|
5000
|
+
sender: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5001
|
+
recipient: '0x8fe6b999dc680ccfdd5bf7eb0974218be2542daa',
|
|
5002
|
+
destinationCaller: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
5003
|
+
minFinalityThreshold: '1000',
|
|
5004
|
+
finalityThresholdExecuted: '1000',
|
|
5005
|
+
messageBody: '0x000000010000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000023f9a5bea7b92a0638520607407bc7f0310aeed400000000000000000000000000000000000000000000000000000000000186a0000000000000000000000000c5567a5e3370d4dbfb0540025078e283e36a363d000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000001f6b158',
|
|
5006
|
+
decodedMessageBody: {
|
|
5007
|
+
burnToken: '0x1c7d4b196cb0c7b01d743fbc6116a902379c7238',
|
|
5008
|
+
mintRecipient: '0x23f9a5bea7b92a0638520607407bc7f0310aeed4',
|
|
5009
|
+
amount: '100000',
|
|
5010
|
+
messageSender: '0xc5567a5e3370d4dbfb0540025078e283e36a363d',
|
|
5011
|
+
maxFee: '11',
|
|
5012
|
+
feeExecuted: '10',
|
|
5013
|
+
expirationBlock: '32944472',
|
|
5014
|
+
hookData: null,
|
|
5015
|
+
},
|
|
5016
|
+
},
|
|
5017
|
+
delayReason: null,
|
|
5018
|
+
};
|
|
4930
5019
|
|
|
4931
5020
|
/**
|
|
4932
5021
|
* Ordered sequence of CCTP v2 bridge step executors.
|
|
@@ -5424,10 +5513,12 @@ z.object({
|
|
|
5424
5513
|
*/
|
|
5425
5514
|
const validateBalanceForTransaction = async (params) => {
|
|
5426
5515
|
const { amount, adapter, token, tokenAddress, operationContext } = params;
|
|
5427
|
-
const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
|
|
5516
|
+
const balancePrepared = await adapter.prepareAction('usdc.balanceOf', {
|
|
5517
|
+
walletAddress: operationContext.address,
|
|
5518
|
+
}, operationContext);
|
|
5428
5519
|
const balance = await balancePrepared.execute();
|
|
5429
5520
|
if (BigInt(balance) < BigInt(amount)) {
|
|
5430
|
-
throw new InsufficientFundsError(balance.toString(), amount, token, tokenAddress,
|
|
5521
|
+
throw new InsufficientFundsError(balance.toString(), amount, token, tokenAddress, operationContext.chain);
|
|
5431
5522
|
}
|
|
5432
5523
|
};
|
|
5433
5524
|
|
|
@@ -5991,22 +6082,22 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
5991
6082
|
async estimate(params) {
|
|
5992
6083
|
// CCTP-specific transfer params validation (includes base validation)
|
|
5993
6084
|
assertCCTPv2BridgeParams(params);
|
|
5994
|
-
const { source, destination, amount
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
}
|
|
6085
|
+
const { source, destination, amount } = params;
|
|
6086
|
+
const estimateBurn = async () => {
|
|
6087
|
+
const burn = await this.burn(params);
|
|
6088
|
+
return await burn.estimate(undefined, await source.adapter.calculateTransactionFee(hasCustomContractSupport(source.chain, 'bridge')
|
|
6089
|
+
? CUSTOM_BURN_GAS_ESTIMATE_EVM
|
|
6090
|
+
: DEPOSIT_FOR_BURN_GAS_ESTIMATE_EVM, undefined, source.chain));
|
|
6091
|
+
};
|
|
6092
|
+
const estimateMint = async () => {
|
|
6093
|
+
const mint = await this.mint(source, destination, mockAttestationMessage);
|
|
6094
|
+
return await mint.estimate(undefined, await destination.adapter.calculateTransactionFee(RECEIVE_MESSAGE_GAS_ESTIMATE_EVM, undefined, destination.chain));
|
|
6095
|
+
};
|
|
6005
6096
|
// Parallelize all independent async operations
|
|
6006
6097
|
const [approveEstimate, depositForBurnFee, receiveMessageFee, maxFee] = await Promise.allSettled([
|
|
6007
6098
|
this.approve(source, amount).then(async (approve) => approve.estimate()),
|
|
6008
|
-
|
|
6009
|
-
|
|
6099
|
+
estimateBurn(),
|
|
6100
|
+
estimateMint(),
|
|
6010
6101
|
params.config?.transferSpeed === undefined ||
|
|
6011
6102
|
params.config?.transferSpeed === TransferSpeed.FAST
|
|
6012
6103
|
? this.getMaxFee(params)
|
|
@@ -6173,14 +6264,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6173
6264
|
delegate: spenderAddress,
|
|
6174
6265
|
chain: resolvedContext?.chain ?? chain,
|
|
6175
6266
|
};
|
|
6176
|
-
|
|
6177
|
-
if (resolvedContext) {
|
|
6178
|
-
return await adapter.prepareAction('usdc.increaseAllowance', actionParams, resolvedContext);
|
|
6179
|
-
}
|
|
6180
|
-
else {
|
|
6181
|
-
// Legacy fallback for adapters without capabilities
|
|
6182
|
-
return await adapter.prepareAction('usdc.increaseAllowance', actionParams);
|
|
6183
|
-
}
|
|
6267
|
+
return await adapter.prepareAction('usdc.increaseAllowance', actionParams, resolvedContext);
|
|
6184
6268
|
}
|
|
6185
6269
|
/**
|
|
6186
6270
|
* Prepares a CCTP v2 token minting transaction on the destination chain.
|
|
@@ -6224,14 +6308,8 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6224
6308
|
assertCCTPv2WalletContext(destination);
|
|
6225
6309
|
// Extract operation context from destination wallet context
|
|
6226
6310
|
const operationContext = this.extractOperationContext(destination);
|
|
6227
|
-
//
|
|
6228
|
-
|
|
6229
|
-
try {
|
|
6230
|
-
resolvedContext = await resolveOperationContext(destination.adapter, operationContext);
|
|
6231
|
-
}
|
|
6232
|
-
catch (error) {
|
|
6233
|
-
throw new Error(`Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
|
|
6234
|
-
}
|
|
6311
|
+
// Use recipientAddress if provided, otherwise use destination.address
|
|
6312
|
+
const destinationAddressForMint = destination.recipientAddress ?? destination.address;
|
|
6235
6313
|
// Prepare action parameters
|
|
6236
6314
|
const actionParams = {
|
|
6237
6315
|
fromChain: source.chain,
|
|
@@ -6239,16 +6317,9 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6239
6317
|
message: attestation.message,
|
|
6240
6318
|
attestation: attestation.attestation,
|
|
6241
6319
|
eventNonce: attestation.eventNonce,
|
|
6242
|
-
destinationAddress:
|
|
6320
|
+
destinationAddress: destinationAddressForMint,
|
|
6243
6321
|
};
|
|
6244
|
-
|
|
6245
|
-
if (resolvedContext) {
|
|
6246
|
-
return await destination.adapter.prepareAction('cctp.v2.receiveMessage', actionParams, resolvedContext);
|
|
6247
|
-
}
|
|
6248
|
-
else {
|
|
6249
|
-
// Legacy fallback for adapters without capabilities
|
|
6250
|
-
return await destination.adapter.prepareAction('cctp.v2.receiveMessage', actionParams);
|
|
6251
|
-
}
|
|
6322
|
+
return await destination.adapter.prepareAction('cctp.v2.receiveMessage', actionParams, operationContext);
|
|
6252
6323
|
}
|
|
6253
6324
|
/**
|
|
6254
6325
|
* Fetches attestation data for a burn transaction from the IRIS API.
|
|
@@ -6447,10 +6518,11 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6447
6518
|
// Get the min finality threshold based on the transfer speed
|
|
6448
6519
|
const minFinalityThreshold = CCTPv2MinFinalityThreshold[transferSpeed];
|
|
6449
6520
|
// Calculate the max fee
|
|
6521
|
+
// Use recipientAddress if provided, otherwise use destination.address
|
|
6522
|
+
const destinationAddressForMint = destination.recipientAddress ?? destination.address;
|
|
6450
6523
|
const [maxFee, mintRecipient] = await Promise.all([
|
|
6451
6524
|
this.getMaxFee(params),
|
|
6452
|
-
getMintRecipientAccount(destination.chain.type, destination.
|
|
6453
|
-
(await destination.adapter.getAddress(destination.chain)), destination.chain.usdcAddress),
|
|
6525
|
+
getMintRecipientAccount(destination.chain.type, destinationAddressForMint, destination.chain.usdcAddress),
|
|
6454
6526
|
]);
|
|
6455
6527
|
const actionParams = {
|
|
6456
6528
|
fromChain: source.chain,
|
|
@@ -6480,14 +6552,7 @@ class CCTPV2BridgingProvider extends BridgingProvider {
|
|
|
6480
6552
|
const finalParams = useCustomBurn
|
|
6481
6553
|
? { ...actionParams, feeRecipient: recipientAddress, protocolFee }
|
|
6482
6554
|
: actionParams;
|
|
6483
|
-
|
|
6484
|
-
if (resolvedContext) {
|
|
6485
|
-
return await source.adapter.prepareAction(actionType, finalParams, resolvedContext);
|
|
6486
|
-
}
|
|
6487
|
-
else {
|
|
6488
|
-
// Legacy fallback for adapters without capabilities
|
|
6489
|
-
return await source.adapter.prepareAction(actionType, finalParams);
|
|
6490
|
-
}
|
|
6555
|
+
return await source.adapter.prepareAction(actionType, finalParams, resolvedContext);
|
|
6491
6556
|
}
|
|
6492
6557
|
/**
|
|
6493
6558
|
* Waits for a transaction to be mined and confirmed on the blockchain.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@circle-fin/provider-cctp-v2",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Circle's official Cross-Chain Transfer Protocol v2 provider for native USDC bridging",
|
|
5
|
-
"main": "./index.cjs
|
|
5
|
+
"main": "./index.cjs",
|
|
6
6
|
"module": "./index.mjs",
|
|
7
7
|
"types": "./index.d.ts",
|
|
8
8
|
"dependencies": {
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
".": {
|
|
35
35
|
"types": "./index.d.ts",
|
|
36
36
|
"import": "./index.mjs",
|
|
37
|
-
"
|
|
37
|
+
"require": "./index.cjs",
|
|
38
|
+
"default": "./index.cjs"
|
|
38
39
|
}
|
|
39
40
|
},
|
|
40
41
|
"files": [
|
|
41
42
|
"index.*",
|
|
42
43
|
"README.md",
|
|
44
|
+
"CHANGELOG.md",
|
|
43
45
|
"LICENSE",
|
|
44
46
|
"package.json"
|
|
45
47
|
],
|
package/index.cjs.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|