@chainlink/ccip-sdk 1.2.0 → 1.3.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/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +10 -20
- package/dist/api/index.js.map +1 -1
- package/dist/aptos/index.d.ts +2 -2
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +1 -1
- package/dist/aptos/index.js.map +1 -1
- package/dist/chain.d.ts +75 -2
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +20 -1
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +1 -0
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +1 -0
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.d.ts.map +1 -1
- package/dist/errors/recovery.js +1 -0
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +8 -0
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +10 -0
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/abi/CCTPVerifier_2_0.d.ts +1118 -0
- package/dist/evm/abi/CCTPVerifier_2_0.d.ts.map +1 -0
- package/dist/evm/abi/CCTPVerifier_2_0.js +1147 -0
- package/dist/evm/abi/CCTPVerifier_2_0.js.map +1 -0
- package/dist/evm/abi/USDCTokenPoolProxy_2_0.d.ts +825 -0
- package/dist/evm/abi/USDCTokenPoolProxy_2_0.d.ts.map +1 -0
- package/dist/evm/abi/USDCTokenPoolProxy_2_0.js +873 -0
- package/dist/evm/abi/USDCTokenPoolProxy_2_0.js.map +1 -0
- package/dist/evm/abi/VersionedVerifierResolver_2_0.d.ts +350 -0
- package/dist/evm/abi/VersionedVerifierResolver_2_0.d.ts.map +1 -0
- package/dist/evm/abi/VersionedVerifierResolver_2_0.js +370 -0
- package/dist/evm/abi/VersionedVerifierResolver_2_0.js.map +1 -0
- package/dist/evm/const.d.ts +3 -0
- package/dist/evm/const.d.ts.map +1 -1
- package/dist/evm/const.js +6 -0
- package/dist/evm/const.js.map +1 -1
- package/dist/evm/index.d.ts +24 -3
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +201 -8
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/types.d.ts +1 -1
- package/dist/evm/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/offchain.d.ts +27 -0
- package/dist/offchain.d.ts.map +1 -1
- package/dist/offchain.js +49 -7
- package/dist/offchain.js.map +1 -1
- package/dist/requests.d.ts +1 -25
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +2 -57
- package/dist/requests.js.map +1 -1
- package/dist/solana/index.d.ts +2 -2
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +2 -2
- package/dist/solana/index.js.map +1 -1
- package/dist/sui/index.d.ts +2 -2
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +2 -2
- package/dist/sui/index.js.map +1 -1
- package/dist/ton/index.d.ts +2 -2
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +28 -49
- package/dist/ton/index.js.map +1 -1
- package/dist/ton/send.d.ts +13 -1
- package/dist/ton/send.d.ts.map +1 -1
- package/dist/ton/send.js +16 -16
- package/dist/ton/send.js.map +1 -1
- package/dist/utils.d.ts +16 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +31 -1
- package/dist/utils.js.map +1 -1
- package/package.json +7 -7
- package/src/api/index.ts +9 -23
- package/src/aptos/index.ts +5 -1
- package/src/chain.ts +86 -3
- package/src/errors/codes.ts +1 -0
- package/src/errors/index.ts +1 -0
- package/src/errors/recovery.ts +2 -0
- package/src/errors/specialized.ts +15 -0
- package/src/evm/abi/CCTPVerifier_2_0.ts +1146 -0
- package/src/evm/abi/USDCTokenPoolProxy_2_0.ts +872 -0
- package/src/evm/abi/VersionedVerifierResolver_2_0.ts +369 -0
- package/src/evm/const.ts +6 -0
- package/src/evm/index.ts +277 -10
- package/src/evm/types.ts +1 -1
- package/src/index.ts +6 -2
- package/src/offchain.ts +58 -8
- package/src/requests.ts +2 -59
- package/src/solana/index.ts +8 -2
- package/src/sui/index.ts +5 -2
- package/src/ton/index.ts +41 -56
- package/src/ton/send.ts +20 -21
- package/src/utils.ts +42 -0
package/src/evm/index.ts
CHANGED
|
@@ -34,6 +34,9 @@ import {
|
|
|
34
34
|
type LogFilter,
|
|
35
35
|
type RateLimiterState,
|
|
36
36
|
type TokenPoolRemote,
|
|
37
|
+
type TokenTransferFeeConfig,
|
|
38
|
+
type TokenTransferFeeOpts,
|
|
39
|
+
type TotalFeesEstimate,
|
|
37
40
|
Chain,
|
|
38
41
|
LaneFeature,
|
|
39
42
|
} from '../chain.ts'
|
|
@@ -60,6 +63,7 @@ import {
|
|
|
60
63
|
} from '../errors/index.ts'
|
|
61
64
|
import type { ExtraArgs } from '../extra-args.ts'
|
|
62
65
|
import type { LeafHasher } from '../hasher/common.ts'
|
|
66
|
+
import { CCTP_FINALITY_FAST, getUsdcBurnFees } from '../offchain.ts'
|
|
63
67
|
import { supportedChains } from '../supported-chains.ts'
|
|
64
68
|
import {
|
|
65
69
|
type CCIPExecution,
|
|
@@ -87,6 +91,7 @@ import {
|
|
|
87
91
|
parseTypeAndVersion,
|
|
88
92
|
} from '../utils.ts'
|
|
89
93
|
import type Token_ABI from './abi/BurnMintERC677Token.ts'
|
|
94
|
+
import type CCTPVerifier_2_0_ABI from './abi/CCTPVerifier_2_0.ts'
|
|
90
95
|
import type FeeQuoter_ABI from './abi/FeeQuoter_1_6.ts'
|
|
91
96
|
import type TokenPool_1_5_ABI from './abi/LockReleaseTokenPool_1_5.ts'
|
|
92
97
|
import type TokenPool_ABI from './abi/LockReleaseTokenPool_1_6_1.ts'
|
|
@@ -101,6 +106,8 @@ import type OnRamp_2_0_ABI from './abi/OnRamp_2_0.ts'
|
|
|
101
106
|
import type Router_ABI from './abi/Router.ts'
|
|
102
107
|
import type TokenAdminRegistry_1_5_ABI from './abi/TokenAdminRegistry_1_5.ts'
|
|
103
108
|
import type TokenPool_2_0_ABI from './abi/TokenPool_2_0.ts'
|
|
109
|
+
import type USDCTokenPoolProxy_2_0_ABI from './abi/USDCTokenPoolProxy_2_0.ts'
|
|
110
|
+
import type VersionedVerifierResolver_2_0_ABI from './abi/VersionedVerifierResolver_2_0.ts'
|
|
104
111
|
import {
|
|
105
112
|
CCV_INDEXER_URL,
|
|
106
113
|
VersionedContractABI,
|
|
@@ -241,6 +248,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
241
248
|
maxArgs: 1,
|
|
242
249
|
})
|
|
243
250
|
this.getFeeTokens = memoize(this.getFeeTokens.bind(this), { async: true, maxArgs: 1 })
|
|
251
|
+
this.detectUsdcDomains = memoize(this.detectUsdcDomains.bind(this))
|
|
252
|
+
this.resolveVerifier = memoize(this.resolveVerifier.bind(this))
|
|
244
253
|
}
|
|
245
254
|
|
|
246
255
|
/**
|
|
@@ -973,11 +982,24 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
973
982
|
if (version < CCIPVersion.V1_6)
|
|
974
983
|
throw new CCIPVersionFeatureUnavailableError('feeQuoter', version, 'v1.6')
|
|
975
984
|
|
|
985
|
+
const isOnRamp = type.includes('OnRamp')
|
|
976
986
|
const contract = new Contract(
|
|
977
987
|
address,
|
|
978
|
-
|
|
988
|
+
version < CCIPVersion.V2_0
|
|
989
|
+
? isOnRamp
|
|
990
|
+
? interfaces.OnRamp_v1_6
|
|
991
|
+
: interfaces.OffRamp_v1_6
|
|
992
|
+
: isOnRamp
|
|
993
|
+
? interfaces.OnRamp_v2_0
|
|
994
|
+
: interfaces.OffRamp_v2_0,
|
|
979
995
|
this.provider,
|
|
980
|
-
) as unknown as TypedContract<
|
|
996
|
+
) as unknown as TypedContract<
|
|
997
|
+
| typeof OnRamp_1_6_ABI
|
|
998
|
+
| typeof OffRamp_1_6_ABI
|
|
999
|
+
| typeof OnRamp_2_0_ABI
|
|
1000
|
+
| typeof OffRamp_2_0_ABI
|
|
1001
|
+
>
|
|
1002
|
+
|
|
981
1003
|
const { feeQuoter } = await contract.getDynamicConfig()
|
|
982
1004
|
return feeQuoter as string
|
|
983
1005
|
}
|
|
@@ -1005,6 +1027,210 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1005
1027
|
})
|
|
1006
1028
|
}
|
|
1007
1029
|
|
|
1030
|
+
/**
|
|
1031
|
+
* Detect whether a token pool is a USDC/CCTP pool via typeAndVersion, then resolve
|
|
1032
|
+
* the CCTPVerifier address and fetch source/dest CCTP domain IDs.
|
|
1033
|
+
*
|
|
1034
|
+
* @param tokenPool - The token pool address to check.
|
|
1035
|
+
* @param destChainSelector - Destination chain selector for getDomain().
|
|
1036
|
+
* @param ccvs - Cross-chain verifier addresses from extraArgs (fallback for verifier discovery).
|
|
1037
|
+
* @returns Source and dest CCTP domain IDs, or undefined if not a USDC pool.
|
|
1038
|
+
*/
|
|
1039
|
+
private async detectUsdcDomains(
|
|
1040
|
+
tokenPool: string,
|
|
1041
|
+
destChainSelector: bigint,
|
|
1042
|
+
ccvs: string[] = [],
|
|
1043
|
+
): Promise<{ sourceDomain: number; destDomain: number } | undefined> {
|
|
1044
|
+
// 1. Check if pool is USDCTokenPoolProxy
|
|
1045
|
+
let poolType: string
|
|
1046
|
+
try {
|
|
1047
|
+
;[poolType] = await this.typeAndVersion(tokenPool)
|
|
1048
|
+
} catch {
|
|
1049
|
+
return undefined
|
|
1050
|
+
}
|
|
1051
|
+
if (poolType !== 'USDCTokenPoolProxy') return undefined
|
|
1052
|
+
|
|
1053
|
+
// 2. Find CCTPVerifier address
|
|
1054
|
+
let verifierAddress: string | undefined
|
|
1055
|
+
|
|
1056
|
+
// 2a. Try pool's getStaticConfig (returns resolver/verifier address)
|
|
1057
|
+
try {
|
|
1058
|
+
const proxy = new Contract(
|
|
1059
|
+
tokenPool,
|
|
1060
|
+
interfaces.USDCTokenPoolProxy_v2_0,
|
|
1061
|
+
this.provider,
|
|
1062
|
+
) as unknown as TypedContract<typeof USDCTokenPoolProxy_2_0_ABI>
|
|
1063
|
+
const [, , cctpVerifier] = await proxy.getStaticConfig()
|
|
1064
|
+
const candidate = cctpVerifier as string
|
|
1065
|
+
if (candidate && candidate !== ZeroAddress) {
|
|
1066
|
+
verifierAddress = await this.resolveVerifier(candidate, destChainSelector)
|
|
1067
|
+
}
|
|
1068
|
+
} catch {
|
|
1069
|
+
/* proxy may not be initialized */
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// 2b. Fall back to scanning ccvs from extraArgs
|
|
1073
|
+
if (!verifierAddress) {
|
|
1074
|
+
for (const ccv of ccvs) {
|
|
1075
|
+
if (!ccv) continue
|
|
1076
|
+
try {
|
|
1077
|
+
const resolved = await this.resolveVerifier(ccv, destChainSelector)
|
|
1078
|
+
if (resolved) {
|
|
1079
|
+
verifierAddress = resolved
|
|
1080
|
+
break
|
|
1081
|
+
}
|
|
1082
|
+
} catch {
|
|
1083
|
+
/* not a valid contract */
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
if (!verifierAddress) return undefined
|
|
1089
|
+
|
|
1090
|
+
// 3. Fetch source and dest CCTP domain IDs from verifier
|
|
1091
|
+
try {
|
|
1092
|
+
const verifier = new Contract(
|
|
1093
|
+
verifierAddress,
|
|
1094
|
+
interfaces.CCTPVerifier_v2_0,
|
|
1095
|
+
this.provider,
|
|
1096
|
+
) as unknown as TypedContract<typeof CCTPVerifier_2_0_ABI>
|
|
1097
|
+
const [staticConfig, domainResult] = await Promise.all([
|
|
1098
|
+
verifier.getStaticConfig(),
|
|
1099
|
+
verifier.getDomain(destChainSelector),
|
|
1100
|
+
])
|
|
1101
|
+
return {
|
|
1102
|
+
sourceDomain: Number(staticConfig[3]), // localDomainIdentifier
|
|
1103
|
+
destDomain: Number(domainResult.domainIdentifier),
|
|
1104
|
+
}
|
|
1105
|
+
} catch (err) {
|
|
1106
|
+
if (isError(err, 'CALL_EXCEPTION')) return undefined
|
|
1107
|
+
throw CCIPError.from(err)
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Given a candidate address, check if it's a CCTPVerifier or VersionedVerifierResolver
|
|
1113
|
+
* and return the actual verifier address (resolving through the resolver if needed).
|
|
1114
|
+
*/
|
|
1115
|
+
private async resolveVerifier(
|
|
1116
|
+
candidate: string,
|
|
1117
|
+
destChainSelector: bigint,
|
|
1118
|
+
): Promise<string | undefined> {
|
|
1119
|
+
try {
|
|
1120
|
+
const [candidateType] = await this.typeAndVersion(candidate)
|
|
1121
|
+
if (candidateType === 'VersionedVerifierResolver') {
|
|
1122
|
+
const resolver = new Contract(
|
|
1123
|
+
candidate,
|
|
1124
|
+
interfaces.VersionedVerifierResolver_v2_0,
|
|
1125
|
+
this.provider,
|
|
1126
|
+
) as unknown as TypedContract<typeof VersionedVerifierResolver_2_0_ABI>
|
|
1127
|
+
return (await resolver.getOutboundImplementation(destChainSelector, '0x')) as string
|
|
1128
|
+
}
|
|
1129
|
+
if (candidateType === 'CCTPVerifier') return candidate
|
|
1130
|
+
} catch {
|
|
1131
|
+
/* not a valid versioned contract */
|
|
1132
|
+
}
|
|
1133
|
+
return undefined
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** {@inheritDoc Chain.getTotalFeesEstimate} */
|
|
1137
|
+
override async getTotalFeesEstimate(
|
|
1138
|
+
opts: Parameters<Chain['getTotalFeesEstimate']>[0],
|
|
1139
|
+
): Promise<TotalFeesEstimate> {
|
|
1140
|
+
const tokenAmounts = opts.message.tokenAmounts
|
|
1141
|
+
const ccipFee$ = this.getFee(opts)
|
|
1142
|
+
|
|
1143
|
+
if (!tokenAmounts?.length) {
|
|
1144
|
+
return { ccipFee: await ccipFee$ }
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const { token, amount } = tokenAmounts[0]!
|
|
1148
|
+
|
|
1149
|
+
// Determine blockConfirmations and tokenArgs from extraArgs
|
|
1150
|
+
const extraArgs = opts.message.extraArgs
|
|
1151
|
+
let blockConfirmations = 0
|
|
1152
|
+
let tokenArgs: string = '0x'
|
|
1153
|
+
if (extraArgs && 'blockConfirmations' in extraArgs) {
|
|
1154
|
+
blockConfirmations = extraArgs.blockConfirmations as number
|
|
1155
|
+
tokenArgs = hexlify(extraArgs.tokenArgs as BytesLike)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Skip pool-level fee lookup for pre-v2.0 lanes
|
|
1159
|
+
const onRamp = await this.getOnRampForRouter(opts.router, opts.destChainSelector)
|
|
1160
|
+
const [, version] = await this.typeAndVersion(onRamp)
|
|
1161
|
+
if (version < CCIPVersion.V2_0) {
|
|
1162
|
+
return { ccipFee: await ccipFee$ }
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const onRampContract = new Contract(
|
|
1166
|
+
onRamp,
|
|
1167
|
+
interfaces.OnRamp_v2_0,
|
|
1168
|
+
this.provider,
|
|
1169
|
+
) as unknown as TypedContract<typeof OnRamp_2_0_ABI>
|
|
1170
|
+
|
|
1171
|
+
const poolAddress = (await onRampContract.getPoolBySourceToken(
|
|
1172
|
+
opts.destChainSelector,
|
|
1173
|
+
token,
|
|
1174
|
+
)) as string
|
|
1175
|
+
|
|
1176
|
+
const [ccipFee, { tokenTransferFeeConfig }, usdcDomains] = await Promise.all([
|
|
1177
|
+
ccipFee$,
|
|
1178
|
+
this.getTokenPoolConfig(poolAddress, {
|
|
1179
|
+
destChainSelector: opts.destChainSelector,
|
|
1180
|
+
blockConfirmationsRequested: blockConfirmations,
|
|
1181
|
+
tokenArgs,
|
|
1182
|
+
}),
|
|
1183
|
+
this.detectUsdcDomains(
|
|
1184
|
+
poolAddress,
|
|
1185
|
+
opts.destChainSelector,
|
|
1186
|
+
extraArgs && 'ccvs' in extraArgs ? extraArgs.ccvs : [],
|
|
1187
|
+
),
|
|
1188
|
+
])
|
|
1189
|
+
|
|
1190
|
+
// USDC path: use Circle CCTP burn fees
|
|
1191
|
+
if (usdcDomains) {
|
|
1192
|
+
const burnFees = await getUsdcBurnFees(
|
|
1193
|
+
usdcDomains.sourceDomain,
|
|
1194
|
+
usdcDomains.destDomain,
|
|
1195
|
+
this.network.networkType,
|
|
1196
|
+
)
|
|
1197
|
+
const fast = blockConfirmations > 0
|
|
1198
|
+
// Tiers are sorted ascending by finalityThreshold; findLast for fast ensures
|
|
1199
|
+
// we pick the highest tier still within the fast threshold.
|
|
1200
|
+
const tier = fast
|
|
1201
|
+
? burnFees.findLast((t) => t.finalityThreshold <= CCTP_FINALITY_FAST)
|
|
1202
|
+
: burnFees.find((t) => t.finalityThreshold > CCTP_FINALITY_FAST)
|
|
1203
|
+
if (tier && tier.minimumFee > 0) {
|
|
1204
|
+
return {
|
|
1205
|
+
ccipFee,
|
|
1206
|
+
tokenTransferFee: {
|
|
1207
|
+
feeDeducted: (BigInt(amount) * BigInt(tier.minimumFee)) / 10_000n,
|
|
1208
|
+
bps: tier.minimumFee,
|
|
1209
|
+
},
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
return { ccipFee }
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Non-USDC path: use on-chain tokenTransferFeeConfig
|
|
1216
|
+
if (!tokenTransferFeeConfig || !tokenTransferFeeConfig.isEnabled) {
|
|
1217
|
+
return { ccipFee }
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const useCustom = blockConfirmations > 0
|
|
1221
|
+
const bps = useCustom
|
|
1222
|
+
? tokenTransferFeeConfig.customBlockConfirmationsTransferFeeBps
|
|
1223
|
+
: tokenTransferFeeConfig.defaultBlockConfirmationsTransferFeeBps
|
|
1224
|
+
|
|
1225
|
+
return {
|
|
1226
|
+
ccipFee,
|
|
1227
|
+
tokenTransferFee: {
|
|
1228
|
+
feeDeducted: (BigInt(amount) * BigInt(bps)) / 10_000n,
|
|
1229
|
+
bps,
|
|
1230
|
+
},
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1008
1234
|
/**
|
|
1009
1235
|
* Generates unsigned EVM transactions for sending a CCIP message.
|
|
1010
1236
|
*
|
|
@@ -1413,44 +1639,84 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1413
1639
|
* Fetches the token pool configuration for an EVM token pool contract.
|
|
1414
1640
|
*
|
|
1415
1641
|
* @param tokenPool - Token pool contract address.
|
|
1416
|
-
* @
|
|
1642
|
+
* @param feeOpts - Optional parameters to also fetch token transfer fee config.
|
|
1643
|
+
* @returns Token pool config containing token, router, typeAndVersion, and optionally
|
|
1644
|
+
* minBlockConfirmations and tokenTransferFeeConfig.
|
|
1417
1645
|
*
|
|
1418
1646
|
* @remarks
|
|
1419
1647
|
* For pools with version \>= 2.0, also returns `minBlockConfirmations` for
|
|
1420
1648
|
* Faster-Than-Finality (FTF) support. Pre-2.0 pools omit this field.
|
|
1649
|
+
* When `feeOpts` is provided and the pool is v2.0+, also fetches token transfer fee config.
|
|
1421
1650
|
*/
|
|
1422
|
-
async getTokenPoolConfig(
|
|
1651
|
+
async getTokenPoolConfig(
|
|
1652
|
+
tokenPool: string,
|
|
1653
|
+
feeOpts?: TokenTransferFeeOpts,
|
|
1654
|
+
): Promise<{
|
|
1423
1655
|
token: string
|
|
1424
1656
|
router: string
|
|
1425
1657
|
typeAndVersion: string
|
|
1426
1658
|
minBlockConfirmations?: number
|
|
1659
|
+
tokenTransferFeeConfig?: TokenTransferFeeConfig
|
|
1427
1660
|
}> {
|
|
1428
1661
|
const [_, version, typeAndVersion] = await this.typeAndVersion(tokenPool)
|
|
1429
1662
|
|
|
1430
|
-
let
|
|
1663
|
+
let token, router, minBlockConfirmations, tokenTransferFeeConfig
|
|
1431
1664
|
if (version < CCIPVersion.V2_0) {
|
|
1432
|
-
contract = new Contract(
|
|
1665
|
+
const contract = new Contract(
|
|
1433
1666
|
tokenPool,
|
|
1434
1667
|
interfaces.TokenPool_v1_6,
|
|
1435
1668
|
this.provider,
|
|
1436
1669
|
) as unknown as TypedContract<typeof TokenPool_ABI>
|
|
1670
|
+
token = contract.getToken()
|
|
1437
1671
|
router = contract.getRouter()
|
|
1438
1672
|
} else {
|
|
1439
|
-
contract = new Contract(
|
|
1673
|
+
const contract = new Contract(
|
|
1440
1674
|
tokenPool,
|
|
1441
1675
|
interfaces.TokenPool_v2_0,
|
|
1442
1676
|
this.provider,
|
|
1443
1677
|
) as unknown as TypedContract<typeof TokenPool_2_0_ABI>
|
|
1678
|
+
token = contract.getToken()
|
|
1444
1679
|
router = contract.getDynamicConfig().then(([router]) => router)
|
|
1445
1680
|
minBlockConfirmations = contract.getMinBlockConfirmations().catch((err) => {
|
|
1446
1681
|
if (isError(err, 'CALL_EXCEPTION')) return 0
|
|
1447
1682
|
throw CCIPError.from(err)
|
|
1448
1683
|
})
|
|
1684
|
+
if (feeOpts) {
|
|
1685
|
+
tokenTransferFeeConfig = token.then((tokenAddr) =>
|
|
1686
|
+
contract
|
|
1687
|
+
.getTokenTransferFeeConfig(
|
|
1688
|
+
tokenAddr as string,
|
|
1689
|
+
feeOpts.destChainSelector,
|
|
1690
|
+
BigInt(feeOpts.blockConfirmationsRequested),
|
|
1691
|
+
feeOpts.tokenArgs,
|
|
1692
|
+
)
|
|
1693
|
+
.then((result) => ({
|
|
1694
|
+
destGasOverhead: Number(result.destGasOverhead),
|
|
1695
|
+
destBytesOverhead: Number(result.destBytesOverhead),
|
|
1696
|
+
defaultBlockConfirmationsFeeUSDCents: Number(
|
|
1697
|
+
result.defaultBlockConfirmationsFeeUSDCents,
|
|
1698
|
+
),
|
|
1699
|
+
customBlockConfirmationsFeeUSDCents: Number(
|
|
1700
|
+
result.customBlockConfirmationsFeeUSDCents,
|
|
1701
|
+
),
|
|
1702
|
+
defaultBlockConfirmationsTransferFeeBps: Number(
|
|
1703
|
+
result.defaultBlockConfirmationsTransferFeeBps,
|
|
1704
|
+
),
|
|
1705
|
+
customBlockConfirmationsTransferFeeBps: Number(
|
|
1706
|
+
result.customBlockConfirmationsTransferFeeBps,
|
|
1707
|
+
),
|
|
1708
|
+
isEnabled: result.isEnabled,
|
|
1709
|
+
}))
|
|
1710
|
+
.catch((err) => {
|
|
1711
|
+
if (isError(err, 'CALL_EXCEPTION')) return undefined
|
|
1712
|
+
throw CCIPError.from(err, 'UNKNOWN')
|
|
1713
|
+
}),
|
|
1714
|
+
)
|
|
1715
|
+
}
|
|
1449
1716
|
}
|
|
1450
|
-
const token = contract.getToken()
|
|
1451
1717
|
|
|
1452
|
-
return Promise.all([token, router, minBlockConfirmations]).then(
|
|
1453
|
-
([token, router, minBlockConfirmations]) => {
|
|
1718
|
+
return Promise.all([token, router, minBlockConfirmations, tokenTransferFeeConfig]).then(
|
|
1719
|
+
([token, router, minBlockConfirmations, tokenTransferFeeConfig]) => {
|
|
1454
1720
|
return {
|
|
1455
1721
|
token: token as CleanAddressable<typeof token>,
|
|
1456
1722
|
router: router as CleanAddressable<typeof router>,
|
|
@@ -1458,6 +1724,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
|
|
|
1458
1724
|
...(minBlockConfirmations != null && {
|
|
1459
1725
|
minBlockConfirmations: Number(minBlockConfirmations),
|
|
1460
1726
|
}),
|
|
1727
|
+
...(tokenTransferFeeConfig != null && { tokenTransferFeeConfig }),
|
|
1461
1728
|
}
|
|
1462
1729
|
},
|
|
1463
1730
|
)
|
package/src/evm/types.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { ChainFamily } from '../types.ts'
|
|
|
7
7
|
*/
|
|
8
8
|
export type UnsignedEVMTx = {
|
|
9
9
|
family: typeof ChainFamily.EVM
|
|
10
|
-
transactions: Pick<TransactionRequest, 'from' | 'to' | 'data' | 'gasLimit'>[]
|
|
10
|
+
transactions: Pick<TransactionRequest, 'from' | 'to' | 'data' | 'gasLimit' | 'value'>[]
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,10 @@ export type {
|
|
|
37
37
|
TokenInfo,
|
|
38
38
|
TokenPoolConfig,
|
|
39
39
|
TokenPoolRemote,
|
|
40
|
+
TokenTransferFee,
|
|
41
|
+
TokenTransferFeeConfig,
|
|
42
|
+
TokenTransferFeeOpts,
|
|
43
|
+
TotalFeesEstimate,
|
|
40
44
|
} from './chain.ts'
|
|
41
45
|
export { DEFAULT_API_RETRY_CONFIG, LaneFeature } from './chain.ts'
|
|
42
46
|
export { calculateManualExecProof, discoverOffRamp } from './execution.ts'
|
|
@@ -51,8 +55,8 @@ export {
|
|
|
51
55
|
encodeExtraArgs,
|
|
52
56
|
} from './extra-args.ts'
|
|
53
57
|
export { estimateReceiveExecution } from './gas.ts'
|
|
54
|
-
export { getOffchainTokenData } from './offchain.ts'
|
|
55
|
-
export { decodeMessage,
|
|
58
|
+
export { CCTP_FINALITY_FAST, CCTP_FINALITY_STANDARD, getOffchainTokenData } from './offchain.ts'
|
|
59
|
+
export { decodeMessage, sourceToDestTokenAddresses } from './requests.ts'
|
|
56
60
|
export {
|
|
57
61
|
type CCIPExecution,
|
|
58
62
|
type CCIPMessage,
|
package/src/offchain.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { type BytesLike, dataLength, dataSlice,
|
|
1
|
+
import { type BytesLike, dataLength, dataSlice, toNumber } from 'ethers'
|
|
2
2
|
import type { PickDeep } from 'type-fest'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
CCIPLbtcAttestationNotApprovedError,
|
|
6
6
|
CCIPLbtcAttestationNotFoundError,
|
|
7
7
|
CCIPUsdcAttestationError,
|
|
8
|
+
CCIPUsdcBurnFeesError,
|
|
8
9
|
} from './errors/index.ts'
|
|
9
10
|
import { parseSourceTokenData } from './evm/messages.ts'
|
|
10
11
|
import { type CCIPRequest, type OffchainTokenData, type WithLogger, NetworkType } from './types.ts'
|
|
11
|
-
import { networkInfo } from './utils.ts'
|
|
12
|
+
import { fetchWithTimeout, getDataBytes, networkInfo } from './utils.ts'
|
|
12
13
|
|
|
13
14
|
const CIRCLE_API_URL = {
|
|
14
15
|
mainnet: 'https://iris-api.circle.com',
|
|
@@ -60,6 +61,57 @@ export async function getUsdcAttestation(
|
|
|
60
61
|
return att
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
/**
|
|
65
|
+
* CCTP V2 finality tier identifiers returned by Circle's burn-fees API.
|
|
66
|
+
*
|
|
67
|
+
* These are **opaque tier IDs**, not block counts or durations.
|
|
68
|
+
* The CCTP V2 whitepaper (Section 8, Table 2) defines exactly two tiers today;
|
|
69
|
+
* additional tiers may be added in the future (the wide spacing between values
|
|
70
|
+
* is intentional to leave room).
|
|
71
|
+
*
|
|
72
|
+
* @see https://developers.circle.com/cctp/concepts/fees
|
|
73
|
+
* @see CCTP V2 Whitepaper, Section 8 — "Finality Levels"
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/** Fast / pre-finality tier: attested seconds after soft confirmation. */
|
|
77
|
+
export const CCTP_FINALITY_FAST = 1000
|
|
78
|
+
|
|
79
|
+
/** Standard / finalized tier: attested after full on-chain finality. */
|
|
80
|
+
export const CCTP_FINALITY_STANDARD = 2000
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Fetches USDC burn fee tiers from Circle's CCTP API.
|
|
84
|
+
*
|
|
85
|
+
* @param sourceDomain - CCTP source domain identifier
|
|
86
|
+
* @param destDomain - CCTP destination domain identifier
|
|
87
|
+
* @param networkType - network type (mainnet or testnet)
|
|
88
|
+
* @returns Array of fee tiers with finality thresholds and BPS fees
|
|
89
|
+
*/
|
|
90
|
+
export async function getUsdcBurnFees(
|
|
91
|
+
sourceDomain: number,
|
|
92
|
+
destDomain: number,
|
|
93
|
+
networkType: NetworkType,
|
|
94
|
+
): Promise<{ finalityThreshold: number; minimumFee: number }[]> {
|
|
95
|
+
const baseUrl =
|
|
96
|
+
networkType === NetworkType.Mainnet ? CIRCLE_API_URL.mainnet : CIRCLE_API_URL.testnet
|
|
97
|
+
const url = `${baseUrl}/v2/burn/USDC/fees/${sourceDomain}/${destDomain}`
|
|
98
|
+
const res = await fetchWithTimeout(url, 'getUsdcBurnFees')
|
|
99
|
+
if (!res.ok) {
|
|
100
|
+
throw new CCIPUsdcBurnFeesError(sourceDomain, destDomain, res.status)
|
|
101
|
+
}
|
|
102
|
+
const json: unknown = await res.json()
|
|
103
|
+
if (!Array.isArray(json)) {
|
|
104
|
+
throw new CCIPUsdcBurnFeesError(sourceDomain, destDomain, res.status)
|
|
105
|
+
}
|
|
106
|
+
for (const tier of json) {
|
|
107
|
+
const t = tier as Record<string, unknown>
|
|
108
|
+
if (typeof t.finalityThreshold !== 'number' || typeof t.minimumFee !== 'number') {
|
|
109
|
+
throw new CCIPUsdcBurnFeesError(sourceDomain, destDomain, res.status)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return json as { finalityThreshold: number; minimumFee: number }[]
|
|
113
|
+
}
|
|
114
|
+
|
|
63
115
|
const LOMBARD_API_URL = {
|
|
64
116
|
mainnet: 'https://mainnet.prod.lombard.finance',
|
|
65
117
|
testnet: 'https://gastald-testnet.prod.lombard.finance',
|
|
@@ -122,7 +174,7 @@ export async function getOffchainTokenData(
|
|
|
122
174
|
const { networkType } = networkInfo(request.message.sourceChainSelector)
|
|
123
175
|
|
|
124
176
|
function looksUsdcData(extraData: BytesLike) {
|
|
125
|
-
if (
|
|
177
|
+
if (getDataBytes(extraData).length !== 64) return
|
|
126
178
|
// USDCTokenPool's extraData is a packed `SourceTokenDataPayloadV1{uint64 nonce, uint32 sourceDomain}`,
|
|
127
179
|
// which we need to query CCTPv2 (by sourceDomain and txHash) and to filter by nonce among messages,
|
|
128
180
|
// if more than one in tx
|
|
@@ -139,11 +191,9 @@ export async function getOffchainTokenData(
|
|
|
139
191
|
|
|
140
192
|
function looksLbtcData(extraData: BytesLike) {
|
|
141
193
|
// LBTC returns `message_hash`/`payloadHash` directly as `bytes32 extraData`
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
)
|
|
146
|
-
return true
|
|
194
|
+
const bytes = getDataBytes(extraData)
|
|
195
|
+
// looks like a hash
|
|
196
|
+
if (bytes.length === 32 && bytes.filter(Boolean).length > 20) return true
|
|
147
197
|
}
|
|
148
198
|
|
|
149
199
|
return Promise.all(
|
package/src/requests.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type BytesLike, hexlify, isBytesLike, toBigInt } from 'ethers'
|
|
2
2
|
import type { PickDeep } from 'type-fest'
|
|
3
3
|
|
|
4
|
-
import { type ChainStatic,
|
|
4
|
+
import { type ChainStatic, Chain } from './chain.ts'
|
|
5
5
|
import {
|
|
6
6
|
CCIPChainFamilyUnsupportedError,
|
|
7
7
|
CCIPMessageBatchIncompleteError,
|
|
@@ -197,6 +197,7 @@ export function decodeMessage(data: string | Uint8Array | Record<string, unknown
|
|
|
197
197
|
*/
|
|
198
198
|
export function buildMessageForDest(message: MessageInput, dest: ChainFamily): AnyMessage {
|
|
199
199
|
const chain = supportedChains[dest] ?? Chain
|
|
200
|
+
if (message.extraArgs && '_tag' in message.extraArgs) delete message.extraArgs._tag
|
|
200
201
|
return chain.buildMessageForDest(message)
|
|
201
202
|
}
|
|
202
203
|
|
|
@@ -392,64 +393,6 @@ export async function getMessagesInBatch<
|
|
|
392
393
|
return messages
|
|
393
394
|
}
|
|
394
395
|
|
|
395
|
-
/**
|
|
396
|
-
* Fetches CCIP requests originated by a specific sender.
|
|
397
|
-
* @param source - Source chain instance.
|
|
398
|
-
* @param sender - Sender address.
|
|
399
|
-
* @param filter - Log filter options.
|
|
400
|
-
* @returns Async generator of CCIP requests.
|
|
401
|
-
* @throws {@link CCIPChainFamilyUnsupportedError} if chain family not supported for legacy messages
|
|
402
|
-
*
|
|
403
|
-
* @example
|
|
404
|
-
* ```typescript
|
|
405
|
-
* import { getMessagesForSender, EVMChain } from '@chainlink/ccip-sdk'
|
|
406
|
-
*
|
|
407
|
-
* const chain = await EVMChain.fromUrl('https://rpc.sepolia.org')
|
|
408
|
-
*
|
|
409
|
-
* for await (const request of getMessagesForSender(chain, '0xSenderAddress', {})) {
|
|
410
|
-
* console.log('Message ID:', request.message.messageId)
|
|
411
|
-
* console.log('Destination:', request.lane.destChainSelector)
|
|
412
|
-
* }
|
|
413
|
-
* ```
|
|
414
|
-
*
|
|
415
|
-
* @see {@link getMessagesInTx} - Fetch from specific transaction
|
|
416
|
-
* @see {@link getMessageById} - Search by messageId
|
|
417
|
-
*/
|
|
418
|
-
export async function* getMessagesForSender(
|
|
419
|
-
source: Chain,
|
|
420
|
-
sender: string,
|
|
421
|
-
filter: Pick<LogFilter, 'address' | 'startBlock' | 'startTime' | 'endBlock'>,
|
|
422
|
-
): AsyncGenerator<Omit<CCIPRequest, 'tx' | 'timestamp'>, void, unknown> {
|
|
423
|
-
const filterWithSender = {
|
|
424
|
-
...filter,
|
|
425
|
-
sender, // some chain families may use this to look for account lookup/narrow down the search
|
|
426
|
-
topics: ['CCIPSendRequested', 'CCIPMessageSent'],
|
|
427
|
-
}
|
|
428
|
-
for await (const log of source.getLogs(filterWithSender)) {
|
|
429
|
-
const message = (source.constructor as ChainStatic).decodeMessage(log)
|
|
430
|
-
if (message?.sender !== sender) continue
|
|
431
|
-
let destChainSelector, version
|
|
432
|
-
if ('destChainSelector' in message) {
|
|
433
|
-
destChainSelector = message.destChainSelector
|
|
434
|
-
;[, version] = await source.typeAndVersion(log.address)
|
|
435
|
-
} else if (source.network.family === ChainFamily.EVM) {
|
|
436
|
-
;({ destChainSelector, version } = await (source as EVMChain).getLaneForOnRamp(log.address))
|
|
437
|
-
} else {
|
|
438
|
-
throw new CCIPChainFamilyUnsupportedError(source.network.family)
|
|
439
|
-
}
|
|
440
|
-
yield {
|
|
441
|
-
lane: {
|
|
442
|
-
sourceChainSelector: source.network.chainSelector,
|
|
443
|
-
destChainSelector,
|
|
444
|
-
onRamp: log.address,
|
|
445
|
-
version: version as CCIPVersion,
|
|
446
|
-
},
|
|
447
|
-
message,
|
|
448
|
-
log,
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
396
|
/**
|
|
454
397
|
* Map source token to its pool address and destination token address.
|
|
455
398
|
*
|
package/src/solana/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
type LogFilter,
|
|
36
36
|
type TokenInfo,
|
|
37
37
|
type TokenPoolRemote,
|
|
38
|
+
type TokenTransferFeeOpts,
|
|
38
39
|
Chain,
|
|
39
40
|
} from '../chain.ts'
|
|
40
41
|
import {
|
|
@@ -1366,7 +1367,10 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
1366
1367
|
* {@inheritDoc Chain.getTokenPoolConfig}
|
|
1367
1368
|
* @throws {@link CCIPTokenPoolStateNotFoundError} if token pool state not found
|
|
1368
1369
|
*/
|
|
1369
|
-
async getTokenPoolConfig(
|
|
1370
|
+
async getTokenPoolConfig(
|
|
1371
|
+
tokenPool: string,
|
|
1372
|
+
_feeOpts?: TokenTransferFeeOpts,
|
|
1373
|
+
): Promise<{
|
|
1370
1374
|
token: string
|
|
1371
1375
|
router: string
|
|
1372
1376
|
tokenPoolProgram: string
|
|
@@ -1601,7 +1605,9 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
|
|
|
1601
1605
|
'accountIsWritableBitmap',
|
|
1602
1606
|
])
|
|
1603
1607
|
if (message.extraArgs) {
|
|
1604
|
-
const unknown = Object.keys(message.extraArgs).filter(
|
|
1608
|
+
const unknown = Object.keys(message.extraArgs).filter(
|
|
1609
|
+
(k) => k !== '_tag' && !SVM_EXTRA_ARGS_FIELDS.has(k),
|
|
1610
|
+
)
|
|
1605
1611
|
if (unknown.length)
|
|
1606
1612
|
throw new CCIPArgumentInvalidError(
|
|
1607
1613
|
'extraArgs',
|
package/src/sui/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type ChainStatic,
|
|
13
13
|
type GetBalanceOpts,
|
|
14
14
|
type LogFilter,
|
|
15
|
+
type TokenTransferFeeOpts,
|
|
15
16
|
Chain,
|
|
16
17
|
} from '../chain.ts'
|
|
17
18
|
import { getCcipStateAddress, getOffRampForCcip } from './discovery.ts'
|
|
@@ -793,7 +794,7 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
793
794
|
}
|
|
794
795
|
|
|
795
796
|
/** {@inheritDoc Chain.getTokenPoolConfig} */
|
|
796
|
-
async getTokenPoolConfig(_tokenPool: string): Promise<never> {
|
|
797
|
+
async getTokenPoolConfig(_tokenPool: string, _feeOpts?: TokenTransferFeeOpts): Promise<never> {
|
|
797
798
|
return Promise.reject(new CCIPNotImplementedError('SuiChain.getTokenPoolConfig'))
|
|
798
799
|
}
|
|
799
800
|
|
|
@@ -826,7 +827,9 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
826
827
|
'accounts', // alias for receiverObjectIds
|
|
827
828
|
])
|
|
828
829
|
if (message.extraArgs) {
|
|
829
|
-
const unknown = Object.keys(message.extraArgs).filter(
|
|
830
|
+
const unknown = Object.keys(message.extraArgs).filter(
|
|
831
|
+
(k) => k !== '_tag' && !SUI_EXTRA_ARGS_FIELDS.has(k),
|
|
832
|
+
)
|
|
830
833
|
if (unknown.length)
|
|
831
834
|
throw new CCIPArgumentInvalidError(
|
|
832
835
|
'extraArgs',
|