@chainlink/ccip-sdk 1.0.0 → 1.1.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 +4 -4
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +110 -11
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +34 -0
- package/dist/api/types.d.ts.map +1 -1
- package/dist/chain.d.ts +93 -4
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +78 -8
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +1 -1
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +2 -1
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +2 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.js +1 -1
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +22 -19
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +30 -25
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/abi/OffRamp_2_0.d.ts +24 -12
- package/dist/evm/abi/OffRamp_2_0.d.ts.map +1 -1
- package/dist/evm/abi/OffRamp_2_0.js +16 -8
- package/dist/evm/abi/OffRamp_2_0.js.map +1 -1
- package/dist/evm/abi/TokenPool_2_0.d.ts +1552 -0
- package/dist/evm/abi/TokenPool_2_0.d.ts.map +1 -0
- package/dist/evm/abi/TokenPool_2_0.js +1637 -0
- package/dist/evm/abi/TokenPool_2_0.js.map +1 -0
- package/dist/evm/const.d.ts +1 -0
- package/dist/evm/const.d.ts.map +1 -1
- package/dist/evm/const.js +2 -0
- package/dist/evm/const.js.map +1 -1
- package/dist/evm/index.d.ts +10 -4
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +138 -41
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/messages.d.ts +2 -33
- package/dist/evm/messages.d.ts.map +1 -1
- package/dist/evm/messages.js +0 -210
- package/dist/evm/messages.js.map +1 -1
- package/dist/gas.d.ts +4 -0
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +27 -21
- package/dist/gas.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/messages.d.ts +34 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +211 -0
- package/dist/messages.js.map +1 -0
- package/dist/solana/cleanup.js +2 -2
- package/dist/solana/cleanup.js.map +1 -1
- package/dist/solana/exec.js +1 -1
- package/dist/solana/exec.js.map +1 -1
- package/dist/solana/index.d.ts +19 -19
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +14 -0
- package/dist/solana/index.js.map +1 -1
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +14 -1
- package/dist/sui/index.js.map +1 -1
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +3 -1
- package/dist/ton/index.js.map +1 -1
- package/package.json +5 -5
- package/src/api/index.ts +126 -11
- package/src/api/types.ts +43 -0
- package/src/chain.ts +131 -11
- package/src/errors/codes.ts +2 -1
- package/src/errors/index.ts +1 -1
- package/src/errors/recovery.ts +1 -1
- package/src/errors/specialized.ts +35 -30
- package/src/evm/abi/OffRamp_2_0.ts +16 -8
- package/src/evm/abi/TokenPool_2_0.ts +1636 -0
- package/src/evm/const.ts +2 -0
- package/src/evm/index.ts +230 -75
- package/src/evm/messages.ts +3 -285
- package/src/gas.ts +27 -19
- package/src/index.ts +2 -1
- package/src/messages.ts +278 -0
- package/src/solana/cleanup.ts +2 -2
- package/src/solana/exec.ts +1 -1
- package/src/solana/index.ts +17 -0
- package/src/sui/index.ts +17 -0
- package/src/ton/index.ts +5 -1
package/src/api/index.ts
CHANGED
|
@@ -2,23 +2,26 @@ import { memoize } from 'micro-memoize'
|
|
|
2
2
|
import type { SetRequired } from 'type-fest'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
CCIPApiClientNotAvailableError,
|
|
5
6
|
CCIPHttpError,
|
|
6
7
|
CCIPLaneNotFoundError,
|
|
7
8
|
CCIPMessageIdNotFoundError,
|
|
8
9
|
CCIPMessageNotFoundInTxError,
|
|
9
|
-
CCIPNotImplementedError,
|
|
10
10
|
CCIPTimeoutError,
|
|
11
11
|
CCIPUnexpectedPaginationError,
|
|
12
12
|
} from '../errors/index.ts'
|
|
13
13
|
import { HttpStatus } from '../http-status.ts'
|
|
14
|
+
import { decodeMessageV1 } from '../messages.ts'
|
|
14
15
|
import { decodeMessage } from '../requests.ts'
|
|
15
16
|
import {
|
|
16
17
|
type CCIPMessage,
|
|
17
18
|
type CCIPRequest,
|
|
18
19
|
type ChainLog,
|
|
19
20
|
type ExecutionInput,
|
|
21
|
+
type Lane,
|
|
20
22
|
type Logger,
|
|
21
23
|
type NetworkInfo,
|
|
24
|
+
type OffchainTokenData,
|
|
22
25
|
type WithLogger,
|
|
23
26
|
CCIPVersion,
|
|
24
27
|
ChainFamily,
|
|
@@ -29,11 +32,13 @@ import { bigIntReviver, parseJson } from '../utils.ts'
|
|
|
29
32
|
import type {
|
|
30
33
|
APIErrorResponse,
|
|
31
34
|
LaneLatencyResponse,
|
|
35
|
+
RawExecutionInputsResult,
|
|
32
36
|
RawLaneLatencyResponse,
|
|
33
37
|
RawMessageResponse,
|
|
34
38
|
RawMessagesResponse,
|
|
35
39
|
RawNetworkInfo,
|
|
36
40
|
} from './types.ts'
|
|
41
|
+
import { calculateManualExecProof } from '../execution.ts'
|
|
37
42
|
|
|
38
43
|
export type { APICCIPRequestMetadata, APIErrorResponse, LaneLatencyResponse } from './types.ts'
|
|
39
44
|
|
|
@@ -46,7 +51,7 @@ export const DEFAULT_TIMEOUT_MS = 30000
|
|
|
46
51
|
/** SDK version string for telemetry header */
|
|
47
52
|
// generate:nofail
|
|
48
53
|
// `export const SDK_VERSION = '${require('./package.json').version}-${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}'`
|
|
49
|
-
export const SDK_VERSION = '1.
|
|
54
|
+
export const SDK_VERSION = '1.1.0-30c18a9'
|
|
50
55
|
// generate:end
|
|
51
56
|
|
|
52
57
|
/** SDK telemetry header name */
|
|
@@ -134,7 +139,7 @@ export class CCIPAPIClient {
|
|
|
134
139
|
static {
|
|
135
140
|
CCIPAPIClient.fromUrl = memoize(
|
|
136
141
|
(baseUrl?: string, ctx?: CCIPAPIClientContext) => new CCIPAPIClient(baseUrl, ctx),
|
|
137
|
-
{ maxArgs: 1 },
|
|
142
|
+
{ maxArgs: 1, transformKey: ([baseUrl]) => [baseUrl ?? DEFAULT_API_BASE_URL] },
|
|
138
143
|
)
|
|
139
144
|
}
|
|
140
145
|
|
|
@@ -144,10 +149,26 @@ export class CCIPAPIClient {
|
|
|
144
149
|
* @param ctx - Optional context with logger and custom fetch
|
|
145
150
|
*/
|
|
146
151
|
constructor(baseUrl?: string, ctx?: CCIPAPIClientContext) {
|
|
152
|
+
if (typeof baseUrl === 'boolean' || (baseUrl as unknown) === null)
|
|
153
|
+
throw new CCIPApiClientNotAvailableError({ context: { baseUrl } }) // shouldn't happen
|
|
147
154
|
this.baseUrl = baseUrl ?? DEFAULT_API_BASE_URL
|
|
148
155
|
this.logger = ctx?.logger ?? console
|
|
149
156
|
this.timeoutMs = ctx?.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
150
157
|
this._fetch = ctx?.fetch ?? globalThis.fetch.bind(globalThis)
|
|
158
|
+
|
|
159
|
+
this.getMessageById = memoize(this.getMessageById.bind(this), {
|
|
160
|
+
async: true,
|
|
161
|
+
expires: 4_000,
|
|
162
|
+
maxArgs: 1,
|
|
163
|
+
maxSize: 100,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
this.getExecutionInput = memoize(this.getExecutionInput.bind(this), {
|
|
167
|
+
async: true,
|
|
168
|
+
expires: 4_000,
|
|
169
|
+
maxArgs: 1,
|
|
170
|
+
maxSize: 100,
|
|
171
|
+
})
|
|
151
172
|
}
|
|
152
173
|
|
|
153
174
|
/**
|
|
@@ -437,15 +458,109 @@ export class CCIPAPIClient {
|
|
|
437
458
|
/**
|
|
438
459
|
* Fetches the execution input for a given message by id.
|
|
439
460
|
* @param messageId - The ID of the message to fetch the execution input for.
|
|
440
|
-
* @returns Either `{ encodedMessage, verifications }` or `{ message, offchainTokenData, ...proof }`, and
|
|
461
|
+
* @returns Either `{ encodedMessage, verifications }` or `{ message, offchainTokenData, ...proof }`, offRamp and lane
|
|
441
462
|
*/
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
463
|
+
async getExecutionInput(messageId: string): Promise<ExecutionInput & Lane & { offRamp: string }> {
|
|
464
|
+
const url = `${this.baseUrl}/v2/messages/${encodeURIComponent(messageId)}/execution-inputs`
|
|
465
|
+
|
|
466
|
+
this.logger.debug(`CCIPAPIClient: GET ${url}`)
|
|
467
|
+
|
|
468
|
+
const response = await this._fetchWithTimeout(url, 'getExecutionInput')
|
|
469
|
+
if (!response.ok) {
|
|
470
|
+
// Try to parse structured error response from API
|
|
471
|
+
let apiError: APIErrorResponse | undefined
|
|
472
|
+
try {
|
|
473
|
+
apiError = parseJson<APIErrorResponse>(await response.text())
|
|
474
|
+
} catch {
|
|
475
|
+
// Response body not JSON, use HTTP status only
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// 404 - Message not found
|
|
479
|
+
if (response.status === HttpStatus.NOT_FOUND) {
|
|
480
|
+
throw new CCIPMessageIdNotFoundError(messageId, {
|
|
481
|
+
context: apiError
|
|
482
|
+
? {
|
|
483
|
+
apiErrorCode: apiError.error,
|
|
484
|
+
apiErrorMessage: apiError.message,
|
|
485
|
+
}
|
|
486
|
+
: undefined,
|
|
487
|
+
})
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Generic HTTP error for other cases
|
|
491
|
+
throw new CCIPHttpError(response.status, response.statusText, {
|
|
492
|
+
context: apiError
|
|
493
|
+
? {
|
|
494
|
+
apiErrorCode: apiError.error,
|
|
495
|
+
apiErrorMessage: apiError.message,
|
|
496
|
+
}
|
|
497
|
+
: undefined,
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const raw = JSON.parse(await response.text(), bigIntReviver) as RawExecutionInputsResult
|
|
502
|
+
this.logger.debug('getExecutionInput raw response:', raw)
|
|
503
|
+
|
|
504
|
+
const offRamp = raw.offramp
|
|
505
|
+
let lane: Lane
|
|
506
|
+
if ('encodedMessage' in raw) {
|
|
507
|
+
// CCIP 2.0 messages use MessageV1Codec, which is chain-independent serialization
|
|
508
|
+
const {
|
|
509
|
+
sourceChainSelector,
|
|
510
|
+
destChainSelector,
|
|
511
|
+
onRampAddress: onRamp,
|
|
512
|
+
} = decodeMessageV1(raw.encodedMessage)
|
|
513
|
+
return {
|
|
514
|
+
sourceChainSelector,
|
|
515
|
+
destChainSelector,
|
|
516
|
+
onRamp,
|
|
517
|
+
offRamp,
|
|
518
|
+
version: CCIPVersion.V2_0,
|
|
519
|
+
encodedMessage: raw.encodedMessage,
|
|
520
|
+
verifications: (raw.ccvData ?? []).map((ccvData, i) => ({
|
|
521
|
+
ccvData,
|
|
522
|
+
destAddress: raw.verifierAddresses[i]!,
|
|
523
|
+
})),
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const messagesInBatch = raw.messageBatch.map(decodeMessage)
|
|
528
|
+
const message = messagesInBatch.find((message) => message.messageId === messageId)!
|
|
529
|
+
if ('onramp' in raw && raw.onramp && raw.version) {
|
|
530
|
+
lane = {
|
|
531
|
+
sourceChainSelector: raw.sourceChainSelector,
|
|
532
|
+
destChainSelector: raw.destChainSelector,
|
|
533
|
+
onRamp: raw.onramp,
|
|
534
|
+
version: raw.version as CCIPVersion,
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
;({ lane } = await this.getMessageById(messageId))
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const proof = calculateManualExecProof(messagesInBatch, lane, messageId, raw.merkleRoot, this)
|
|
541
|
+
|
|
542
|
+
const rawMessage = raw.messageBatch.find((message) => message.messageId === messageId)!
|
|
543
|
+
const offchainTokenData: OffchainTokenData[] = rawMessage.tokenAmounts.map(() => undefined)
|
|
544
|
+
if (rawMessage.usdcData?.status === 'complete')
|
|
545
|
+
offchainTokenData[0] = {
|
|
546
|
+
_tag: 'usdc',
|
|
547
|
+
message: rawMessage.usdcData.message_bytes_hex!,
|
|
548
|
+
attestation: rawMessage.usdcData.attestation!,
|
|
549
|
+
}
|
|
550
|
+
else if (rawMessage.lbtcData?.status === 'NOTARIZATION_STATUS_SESSION_APPROVED')
|
|
551
|
+
offchainTokenData[0] = {
|
|
552
|
+
_tag: 'lbtc',
|
|
553
|
+
message_hash: rawMessage.lbtcData.message_hash!,
|
|
554
|
+
attestation: rawMessage.lbtcData.attestation!,
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return {
|
|
558
|
+
offRamp,
|
|
559
|
+
...lane,
|
|
560
|
+
message,
|
|
561
|
+
offchainTokenData,
|
|
562
|
+
...proof,
|
|
563
|
+
} as ExecutionInput & Lane & { offRamp: string }
|
|
449
564
|
}
|
|
450
565
|
|
|
451
566
|
/**
|
package/src/api/types.ts
CHANGED
|
@@ -182,3 +182,46 @@ export type APICCIPRequestMetadata = {
|
|
|
182
182
|
/** Destination network metadata. */
|
|
183
183
|
destNetworkInfo: NetworkInfo
|
|
184
184
|
}
|
|
185
|
+
|
|
186
|
+
// ============================================================================
|
|
187
|
+
// GET /v2/messages/${messageId}/execution-inputs search endpoint types
|
|
188
|
+
// ============================================================================
|
|
189
|
+
|
|
190
|
+
/** Raw API response from GET /v2/messages/:messageId/execution-inputs */
|
|
191
|
+
export type RawExecutionInputsResult = {
|
|
192
|
+
offramp: string
|
|
193
|
+
} & (
|
|
194
|
+
| {
|
|
195
|
+
onramp: string
|
|
196
|
+
sourceChainSelector: bigint
|
|
197
|
+
destChainSelector: bigint
|
|
198
|
+
version: string
|
|
199
|
+
}
|
|
200
|
+
| object
|
|
201
|
+
) &
|
|
202
|
+
(
|
|
203
|
+
| {
|
|
204
|
+
merkleRoot?: string
|
|
205
|
+
messageBatch: {
|
|
206
|
+
[key: string]: unknown
|
|
207
|
+
messageId: string
|
|
208
|
+
tokenAmounts: { token: string; amount: string }[]
|
|
209
|
+
usdcData?: {
|
|
210
|
+
status: 'pending_confirmations' | 'complete'
|
|
211
|
+
attestation?: string
|
|
212
|
+
message_bytes_hex?: string
|
|
213
|
+
}
|
|
214
|
+
lbtcData?: {
|
|
215
|
+
status: 'NOTARIZATION_STATUS_SESSION_APPROVED' | 'NOTARIZATION_STATUS_SESSION_PENDING'
|
|
216
|
+
attestation?: string
|
|
217
|
+
message_hash?: string
|
|
218
|
+
}
|
|
219
|
+
}[]
|
|
220
|
+
}
|
|
221
|
+
| {
|
|
222
|
+
encodedMessage: string
|
|
223
|
+
verificationComplete?: boolean
|
|
224
|
+
ccvData?: string[]
|
|
225
|
+
verifierAddresses: string[]
|
|
226
|
+
}
|
|
227
|
+
)
|
package/src/chain.ts
CHANGED
|
@@ -6,8 +6,10 @@ import type { UnsignedAptosTx } from './aptos/types.ts'
|
|
|
6
6
|
import { getOnchainCommitReport } from './commits.ts'
|
|
7
7
|
import {
|
|
8
8
|
CCIPApiClientNotAvailableError,
|
|
9
|
+
CCIPArgumentInvalidError,
|
|
9
10
|
CCIPChainFamilyMismatchError,
|
|
10
11
|
CCIPExecTxRevertedError,
|
|
12
|
+
CCIPNotImplementedError,
|
|
11
13
|
CCIPTokenPoolChainConfigNotFoundError,
|
|
12
14
|
CCIPTransactionNotFinalizedError,
|
|
13
15
|
} from './errors/index.ts'
|
|
@@ -50,8 +52,12 @@ import {
|
|
|
50
52
|
} from './types.ts'
|
|
51
53
|
import { networkInfo, util, withRetry } from './utils.ts'
|
|
52
54
|
|
|
53
|
-
/**
|
|
54
|
-
const
|
|
55
|
+
/** All valid field names for GenericExtraArgsV2. */
|
|
56
|
+
const V2_FIELDS = new Set(['gasLimit', 'allowOutOfOrderExecution'])
|
|
57
|
+
|
|
58
|
+
/** All valid field names for GenericExtraArgsV3. */
|
|
59
|
+
const V3_FIELDS = new Set([
|
|
60
|
+
'gasLimit',
|
|
55
61
|
'blockConfirmations',
|
|
56
62
|
'ccvs',
|
|
57
63
|
'ccvArgs',
|
|
@@ -59,12 +65,26 @@ const V3_ONLY_FIELDS = [
|
|
|
59
65
|
'executorArgs',
|
|
60
66
|
'tokenReceiver',
|
|
61
67
|
'tokenArgs',
|
|
62
|
-
]
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
/** Throw if any key in extraArgs is not in the allowed set. */
|
|
71
|
+
function assertNoUnknownFields(
|
|
72
|
+
extraArgs: Partial<ExtraArgs>,
|
|
73
|
+
allowed: Set<string>,
|
|
74
|
+
variant: string,
|
|
75
|
+
): void {
|
|
76
|
+
const unknown = Object.keys(extraArgs).filter((k) => !allowed.has(k))
|
|
77
|
+
if (unknown.length)
|
|
78
|
+
throw new CCIPArgumentInvalidError(
|
|
79
|
+
'extraArgs',
|
|
80
|
+
`unknown field(s) for ${variant}: ${unknown.map((k) => JSON.stringify(k)).join(', ')}`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
63
83
|
|
|
64
|
-
/** Check if extraArgs contains any V3-only fields. */
|
|
84
|
+
/** Check if extraArgs contains any V3-only fields (i.e. fields in V3 but not in V2). */
|
|
65
85
|
function hasV3ExtraArgs(extraArgs: Partial<ExtraArgs> | undefined): boolean {
|
|
66
86
|
if (!extraArgs) return false
|
|
67
|
-
return
|
|
87
|
+
return Object.keys(extraArgs).some((k) => V3_FIELDS.has(k) && !V2_FIELDS.has(k))
|
|
68
88
|
}
|
|
69
89
|
|
|
70
90
|
/**
|
|
@@ -94,12 +114,13 @@ export type ChainContext = WithLogger & {
|
|
|
94
114
|
* CCIP API client instance for lane information queries.
|
|
95
115
|
*
|
|
96
116
|
* - `undefined` (default): Creates CCIPAPIClient with {@link DEFAULT_API_BASE_URL}
|
|
117
|
+
* - `string`: Creates CCIPAPIClient with provided URL
|
|
97
118
|
* - `CCIPAPIClient`: Uses provided instance (allows custom URL, fetch, etc.)
|
|
98
119
|
* - `null`: Disables API client entirely (getLaneLatency() will throw)
|
|
99
120
|
*
|
|
100
121
|
* Default: `undefined` (auto-create with production endpoint)
|
|
101
122
|
*/
|
|
102
|
-
apiClient?: CCIPAPIClient | null
|
|
123
|
+
apiClient?: CCIPAPIClient | string | null
|
|
103
124
|
|
|
104
125
|
/**
|
|
105
126
|
* Retry configuration for API fallback operations.
|
|
@@ -173,6 +194,52 @@ export type TokenInfo = {
|
|
|
173
194
|
readonly name?: string
|
|
174
195
|
}
|
|
175
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Available lane feature keys.
|
|
199
|
+
* These represent features or thresholds that can be configured per-lane.
|
|
200
|
+
*/
|
|
201
|
+
export const LaneFeature = {
|
|
202
|
+
/**
|
|
203
|
+
* Minimum block confirmations for Faster Time to Finality (FTF).
|
|
204
|
+
* - **absent**: the lane does not support FTF (pre-v2.0 lane).
|
|
205
|
+
* - **0**: the lane supports FTF, but it is not enabled for this
|
|
206
|
+
* token (e.g. the token pool predates FTF, or FTF is configured
|
|
207
|
+
* to use default finality only).
|
|
208
|
+
* - **\> 0**: FTF is enabled; this is the minimum number of block
|
|
209
|
+
* confirmations required to use it.
|
|
210
|
+
*/
|
|
211
|
+
MIN_BLOCK_CONFIRMATIONS: 'MIN_BLOCK_CONFIRMATIONS',
|
|
212
|
+
/**
|
|
213
|
+
* Rate limiter bucket state for the lane/token with default finality.
|
|
214
|
+
*/
|
|
215
|
+
RATE_LIMITS: 'RATE_LIMITS',
|
|
216
|
+
/**
|
|
217
|
+
* Rate limiter bucket state when using non-default finality (FTF).
|
|
218
|
+
* Only meaningful when FTF is supported on this lane, i.e.
|
|
219
|
+
* {@link LaneFeature.MIN_BLOCK_CONFIRMATIONS} is present and \> 0.
|
|
220
|
+
* If absent, the default rate limits ({@link LaneFeature.RATE_LIMITS}) apply even when using custom finality.
|
|
221
|
+
*/
|
|
222
|
+
CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITS: 'CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITS',
|
|
223
|
+
} as const
|
|
224
|
+
/** Type representing one of the lane feature keys. */
|
|
225
|
+
export type LaneFeature = (typeof LaneFeature)[keyof typeof LaneFeature]
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Lane features record.
|
|
229
|
+
* Maps feature keys to their values.
|
|
230
|
+
*/
|
|
231
|
+
export interface LaneFeatures extends Record<LaneFeature, unknown> {
|
|
232
|
+
/** Minimum block confirmations for FTF. */
|
|
233
|
+
MIN_BLOCK_CONFIRMATIONS: number
|
|
234
|
+
/** Rate limiter bucket state for the lane/token with default finality. */
|
|
235
|
+
RATE_LIMITS: RateLimiterState
|
|
236
|
+
/**
|
|
237
|
+
* Rate limiter bucket state when using non-default finality (FTF).
|
|
238
|
+
* If absent, the default rate limits ({@link LaneFeatures.RATE_LIMITS}) apply even when using custom finality.
|
|
239
|
+
*/
|
|
240
|
+
CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITS: RateLimiterState
|
|
241
|
+
}
|
|
242
|
+
|
|
176
243
|
/**
|
|
177
244
|
* Options for getBalance query.
|
|
178
245
|
*/
|
|
@@ -231,11 +298,19 @@ export type TokenPoolRemote = {
|
|
|
231
298
|
* - Version management (different pool implementations)
|
|
232
299
|
*/
|
|
233
300
|
remotePools: string[]
|
|
234
|
-
/** Inbound rate limiter state for tokens coming into this chain. */
|
|
235
|
-
inboundRateLimiterState: RateLimiterState
|
|
236
301
|
/** Outbound rate limiter state for tokens leaving this chain. */
|
|
237
302
|
outboundRateLimiterState: RateLimiterState
|
|
238
|
-
|
|
303
|
+
/** Inbound rate limiter state for tokens coming into this chain. */
|
|
304
|
+
inboundRateLimiterState: RateLimiterState
|
|
305
|
+
} & (
|
|
306
|
+
| {
|
|
307
|
+
/** Outbound rate limiter state for tokens leaving this chain (FTF/v2). */
|
|
308
|
+
customBlockConfirmationsOutboundRateLimiterState: RateLimiterState
|
|
309
|
+
/** Inbound rate limiter state for tokens coming into this chain (FTF/v2). */
|
|
310
|
+
customBlockConfirmationsInboundRateLimiterState: RateLimiterState
|
|
311
|
+
}
|
|
312
|
+
| object
|
|
313
|
+
)
|
|
239
314
|
|
|
240
315
|
/**
|
|
241
316
|
* Token pool configuration returned by {@link Chain.getTokenPoolConfig}.
|
|
@@ -256,6 +331,13 @@ export type TokenPoolConfig = {
|
|
|
256
331
|
* May be undefined for older pool implementations that don't expose this method.
|
|
257
332
|
*/
|
|
258
333
|
typeAndVersion?: string
|
|
334
|
+
/**
|
|
335
|
+
* Min custom block confirmations for Faster Time to Finality (FTF),
|
|
336
|
+
* if TokenPool version \>= v2.0.0 and FTF is supported on this lane.
|
|
337
|
+
* `0` indicates FTF is supported but not enabled for this token; `>0` indicates FTF is enabled
|
|
338
|
+
* with this many minimum confirmations.
|
|
339
|
+
*/
|
|
340
|
+
minBlockConfirmations?: number
|
|
259
341
|
}
|
|
260
342
|
|
|
261
343
|
/**
|
|
@@ -362,11 +444,11 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
362
444
|
if (apiClient === null) {
|
|
363
445
|
this.apiClient = null // Explicit opt-out
|
|
364
446
|
this.apiRetryConfig = null // No retry config needed without API client
|
|
365
|
-
} else if (apiClient !==
|
|
447
|
+
} else if (apiClient && typeof apiClient !== 'string') {
|
|
366
448
|
this.apiClient = apiClient // Use provided instance
|
|
367
449
|
this.apiRetryConfig = { ...DEFAULT_API_RETRY_CONFIG, ...apiRetryConfig }
|
|
368
450
|
} else {
|
|
369
|
-
this.apiClient = CCIPAPIClient.fromUrl(
|
|
451
|
+
this.apiClient = CCIPAPIClient.fromUrl(apiClient, ctx) // default=undefined or provided string as URL
|
|
370
452
|
this.apiRetryConfig = { ...DEFAULT_API_RETRY_CONFIG, ...apiRetryConfig }
|
|
371
453
|
}
|
|
372
454
|
}
|
|
@@ -1050,6 +1132,41 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
1050
1132
|
return this.apiClient.getLaneLatency(this.network.chainSelector, destChainSelector)
|
|
1051
1133
|
}
|
|
1052
1134
|
|
|
1135
|
+
/**
|
|
1136
|
+
* Retrieve features for a lane (router/destChainSelector/token triplet).
|
|
1137
|
+
*
|
|
1138
|
+
* @param _opts - Options containing router address, destChainSelector, and optional token
|
|
1139
|
+
* address (the token to be transferred in a hypothetical message on this lane)
|
|
1140
|
+
* @returns Promise resolving to partial lane features record
|
|
1141
|
+
*
|
|
1142
|
+
* @throws {@link CCIPNotImplementedError} if not implemented for this chain family
|
|
1143
|
+
*
|
|
1144
|
+
* @example Get lane features
|
|
1145
|
+
* ```typescript
|
|
1146
|
+
* const features = await chain.getLaneFeatures({
|
|
1147
|
+
* router: '0x...',
|
|
1148
|
+
* destChainSelector: 4949039107694359620n,
|
|
1149
|
+
* })
|
|
1150
|
+
* // MIN_BLOCK_CONFIRMATIONS has three states:
|
|
1151
|
+
* // - undefined: FTF is not supported on this lane (pre-v2.0)
|
|
1152
|
+
* // - 0: the lane supports FTF, but it is not enabled for this token
|
|
1153
|
+
* // - > 0: FTF is enabled with this many block confirmations
|
|
1154
|
+
* const ftf = features.MIN_BLOCK_CONFIRMATIONS
|
|
1155
|
+
* if (ftf != null && ftf > 0) {
|
|
1156
|
+
* console.log(`FTF enabled with ${ftf} confirmations`)
|
|
1157
|
+
* } else if (ftf === 0) {
|
|
1158
|
+
* console.log('FTF supported on this lane but not enabled for this token')
|
|
1159
|
+
* }
|
|
1160
|
+
* ```
|
|
1161
|
+
*/
|
|
1162
|
+
getLaneFeatures(_opts: {
|
|
1163
|
+
router: string
|
|
1164
|
+
destChainSelector: bigint
|
|
1165
|
+
token?: string
|
|
1166
|
+
}): Promise<Partial<LaneFeatures>> {
|
|
1167
|
+
return Promise.reject(new CCIPNotImplementedError('getLaneFeatures'))
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1053
1170
|
/**
|
|
1054
1171
|
* Default/generic implementation of getExecutionReceipts.
|
|
1055
1172
|
* Yields execution receipts for a given offRamp.
|
|
@@ -1311,6 +1428,8 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
1311
1428
|
|
|
1312
1429
|
// Detect if user wants V3 by checking for any V3-only field
|
|
1313
1430
|
if (hasV3ExtraArgs(message.extraArgs)) {
|
|
1431
|
+
if (message.extraArgs)
|
|
1432
|
+
assertNoUnknownFields(message.extraArgs, V3_FIELDS, 'GenericExtraArgsV3')
|
|
1314
1433
|
// V3 defaults (GenericExtraArgsV3)
|
|
1315
1434
|
return {
|
|
1316
1435
|
...message,
|
|
@@ -1328,6 +1447,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
1328
1447
|
}
|
|
1329
1448
|
}
|
|
1330
1449
|
|
|
1450
|
+
if (message.extraArgs) assertNoUnknownFields(message.extraArgs, V2_FIELDS, 'EVMExtraArgsV2')
|
|
1331
1451
|
// Default to V2 (GenericExtraArgsV2, aka EVMExtraArgsV2)
|
|
1332
1452
|
return {
|
|
1333
1453
|
...message,
|
package/src/errors/codes.ts
CHANGED
|
@@ -27,6 +27,7 @@ export const CCIPErrorCode = {
|
|
|
27
27
|
MESSAGE_NOT_IN_BATCH: 'MESSAGE_NOT_IN_BATCH',
|
|
28
28
|
MESSAGE_CHAIN_MISMATCH: 'MESSAGE_CHAIN_MISMATCH',
|
|
29
29
|
MESSAGE_RETRIEVAL_FAILED: 'MESSAGE_RETRIEVAL_FAILED',
|
|
30
|
+
MESSAGE_NOT_VERIFIED_YET: 'MESSAGE_NOT_VERIFIED_YET',
|
|
30
31
|
|
|
31
32
|
// Lane & Routing
|
|
32
33
|
OFFRAMP_NOT_FOUND: 'OFFRAMP_NOT_FOUND',
|
|
@@ -109,7 +110,6 @@ export const CCIPErrorCode = {
|
|
|
109
110
|
LBTC_ATTESTATION_NOT_FOUND: 'LBTC_ATTESTATION_NOT_FOUND',
|
|
110
111
|
LBTC_ATTESTATION_NOT_APPROVED: 'LBTC_ATTESTATION_NOT_APPROVED',
|
|
111
112
|
CCTP_DECODE_FAILED: 'CCTP_DECODE_FAILED',
|
|
112
|
-
CCTP_MULTIPLE_EVENTS: 'CCTP_MULTIPLE_EVENTS',
|
|
113
113
|
|
|
114
114
|
// Log & Event
|
|
115
115
|
LOG_DATA_INVALID: 'LOG_DATA_INVALID',
|
|
@@ -178,6 +178,7 @@ export const TRANSIENT_ERROR_CODES = new Set<CCIPErrorCode>([
|
|
|
178
178
|
CCIPErrorCode.TRANSACTION_NOT_FINALIZED,
|
|
179
179
|
CCIPErrorCode.MESSAGE_ID_NOT_FOUND,
|
|
180
180
|
CCIPErrorCode.MESSAGE_BATCH_INCOMPLETE,
|
|
181
|
+
CCIPErrorCode.MESSAGE_NOT_VERIFIED_YET,
|
|
181
182
|
CCIPErrorCode.COMMIT_NOT_FOUND,
|
|
182
183
|
CCIPErrorCode.RECEIPT_NOT_FOUND,
|
|
183
184
|
CCIPErrorCode.USDC_ATTESTATION_FAILED,
|
package/src/errors/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ export {
|
|
|
31
31
|
CCIPMessageInvalidError,
|
|
32
32
|
CCIPMessageNotFoundInTxError,
|
|
33
33
|
CCIPMessageNotInBatchError,
|
|
34
|
+
CCIPMessageNotVerifiedYetError,
|
|
34
35
|
CCIPMessageRetrievalError,
|
|
35
36
|
} from './specialized.ts'
|
|
36
37
|
|
|
@@ -100,7 +101,6 @@ export {
|
|
|
100
101
|
export {
|
|
101
102
|
CCIPBlockTimeNotFoundError,
|
|
102
103
|
CCIPCctpDecodeError,
|
|
103
|
-
CCIPCctpMultipleEventsError,
|
|
104
104
|
CCIPExecutionReportChainMismatchError,
|
|
105
105
|
CCIPExecutionStateInvalidError,
|
|
106
106
|
CCIPExtraArgsLengthInvalidError,
|
package/src/errors/recovery.ts
CHANGED
|
@@ -37,6 +37,7 @@ export const DEFAULT_RECOVERY_HINTS: Partial<Record<CCIPErrorCode, string>> = {
|
|
|
37
37
|
'Verify you are using the correct destination chain. Check that sourceChainSelector and destChainSelector match your lane.',
|
|
38
38
|
MESSAGE_RETRIEVAL_FAILED:
|
|
39
39
|
'Both API and RPC failed to retrieve the message. Verify the transaction hash is correct and the transaction is confirmed. Check RPC and network connectivity.',
|
|
40
|
+
MESSAGE_NOT_VERIFIED_YET: 'Message not yet committed or verified; wait and retry.',
|
|
40
41
|
MESSAGE_VERSION_INVALID:
|
|
41
42
|
'Ensure the source chain onRamp uses CCIP v1.6. Older message versions are not compatible with this destination.',
|
|
42
43
|
|
|
@@ -129,7 +130,6 @@ export const DEFAULT_RECOVERY_HINTS: Partial<Record<CCIPErrorCode, string>> = {
|
|
|
129
130
|
LBTC_ATTESTATION_NOT_APPROVED: 'LBTC attestation not yet approved. Wait for notarization.',
|
|
130
131
|
CCTP_DECODE_FAILED:
|
|
131
132
|
'Ensure the transaction contains a valid CCTP MessageSent event. Verify this is a USDC transfer.',
|
|
132
|
-
CCTP_MULTIPLE_EVENTS: 'Multiple CCTP events found. Expected only one per transaction.',
|
|
133
133
|
|
|
134
134
|
LOG_DATA_INVALID: 'Ensure the log data is a valid hex string from a transaction receipt.',
|
|
135
135
|
LOG_DATA_MISSING: 'Log data is missing or not a string.',
|
|
@@ -380,6 +380,41 @@ export class CCIPMessageRetrievalError extends CCIPError {
|
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
/**
|
|
384
|
+
* Thrown when a CCIP message has not been verified yet by the offchain system.
|
|
385
|
+
* This is a transient error - the message needs time to be verified before execution input can be retrieved.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* try {
|
|
390
|
+
* const execInput = await api.getExecutionInput(messageId)
|
|
391
|
+
* } catch (error) {
|
|
392
|
+
* if (error instanceof CCIPMessageNotVerifiedYetError) {
|
|
393
|
+
* console.log(`Message not verified yet, retry after ${error.retryAfterMs}ms`)
|
|
394
|
+
* await sleep(error.retryAfterMs ?? 15000)
|
|
395
|
+
* // Retry the request
|
|
396
|
+
* }
|
|
397
|
+
* }
|
|
398
|
+
* ```
|
|
399
|
+
*/
|
|
400
|
+
export class CCIPMessageNotVerifiedYetError extends CCIPError {
|
|
401
|
+
override readonly name = 'CCIPMessageNotVerifiedYetError'
|
|
402
|
+
/** Creates a message not verified yet error. */
|
|
403
|
+
constructor(messageId: string, options?: CCIPErrorOptions) {
|
|
404
|
+
super(
|
|
405
|
+
CCIPErrorCode.MESSAGE_NOT_VERIFIED_YET,
|
|
406
|
+
`Message ${messageId} has not been verified yet. The offchain verification system needs time to process this message.`,
|
|
407
|
+
{
|
|
408
|
+
...options,
|
|
409
|
+
isTransient: true,
|
|
410
|
+
retryAfterMs: 15000,
|
|
411
|
+
recovery: 'Wait for the message to be verified by the offchain system, then retry.',
|
|
412
|
+
context: { ...options?.context, messageId },
|
|
413
|
+
},
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
383
418
|
// Lane & Routing
|
|
384
419
|
|
|
385
420
|
/**
|
|
@@ -3238,36 +3273,6 @@ export class CCIPSolanaLaneVersionUnsupportedError extends CCIPError {
|
|
|
3238
3273
|
}
|
|
3239
3274
|
}
|
|
3240
3275
|
|
|
3241
|
-
/**
|
|
3242
|
-
* Thrown when multiple CCTP events found in transaction.
|
|
3243
|
-
*
|
|
3244
|
-
* @example
|
|
3245
|
-
* ```typescript
|
|
3246
|
-
* try {
|
|
3247
|
-
* const cctpData = await chain.getOffchainTokenData(request)
|
|
3248
|
-
* } catch (error) {
|
|
3249
|
-
* if (error instanceof CCIPCctpMultipleEventsError) {
|
|
3250
|
-
* console.log(`Found ${error.context.count} events, expected 1`)
|
|
3251
|
-
* }
|
|
3252
|
-
* }
|
|
3253
|
-
* ```
|
|
3254
|
-
*/
|
|
3255
|
-
export class CCIPCctpMultipleEventsError extends CCIPError {
|
|
3256
|
-
override readonly name = 'CCIPCctpMultipleEventsError'
|
|
3257
|
-
/** Creates a CCTP multiple events error. */
|
|
3258
|
-
constructor(count: number, txSignature: string, options?: CCIPErrorOptions) {
|
|
3259
|
-
super(
|
|
3260
|
-
CCIPErrorCode.CCTP_MULTIPLE_EVENTS,
|
|
3261
|
-
`Expected only 1 CcipCctpMessageSentEvent, found ${count} in transaction ${txSignature}`,
|
|
3262
|
-
{
|
|
3263
|
-
...options,
|
|
3264
|
-
isTransient: false,
|
|
3265
|
-
context: { ...options?.context, count, txSignature },
|
|
3266
|
-
},
|
|
3267
|
-
)
|
|
3268
|
-
}
|
|
3269
|
-
}
|
|
3270
|
-
|
|
3271
3276
|
/**
|
|
3272
3277
|
* Thrown when compute units exceed limit.
|
|
3273
3278
|
*
|
|
@@ -576,14 +576,6 @@ export default [
|
|
|
576
576
|
name: 'DuplicateCCVNotAllowed',
|
|
577
577
|
inputs: [{ name: 'ccvAddress', type: 'address', internalType: 'address' }],
|
|
578
578
|
},
|
|
579
|
-
{
|
|
580
|
-
type: 'error',
|
|
581
|
-
name: 'ExecutionError',
|
|
582
|
-
inputs: [
|
|
583
|
-
{ name: 'messageId', type: 'bytes32', internalType: 'bytes32' },
|
|
584
|
-
{ name: 'err', type: 'bytes', internalType: 'bytes' },
|
|
585
|
-
],
|
|
586
|
-
},
|
|
587
579
|
{ type: 'error', name: 'GasCannotBeZero', inputs: [] },
|
|
588
580
|
{
|
|
589
581
|
type: 'error',
|
|
@@ -664,6 +656,14 @@ export default [
|
|
|
664
656
|
name: 'InvalidOnRamp',
|
|
665
657
|
inputs: [{ name: 'got', type: 'bytes', internalType: 'bytes' }],
|
|
666
658
|
},
|
|
659
|
+
{
|
|
660
|
+
type: 'error',
|
|
661
|
+
name: 'InvalidOptionalThreshold',
|
|
662
|
+
inputs: [
|
|
663
|
+
{ name: 'wanted', type: 'uint8', internalType: 'uint8' },
|
|
664
|
+
{ name: 'got', type: 'uint256', internalType: 'uint256' },
|
|
665
|
+
],
|
|
666
|
+
},
|
|
667
667
|
{
|
|
668
668
|
type: 'error',
|
|
669
669
|
name: 'InvalidVerifierResultsLength',
|
|
@@ -678,6 +678,14 @@ export default [
|
|
|
678
678
|
name: 'MustSpecifyDefaultOrRequiredCCVs',
|
|
679
679
|
inputs: [],
|
|
680
680
|
},
|
|
681
|
+
{
|
|
682
|
+
type: 'error',
|
|
683
|
+
name: 'NoStateProgressMade',
|
|
684
|
+
inputs: [
|
|
685
|
+
{ name: 'messageId', type: 'bytes32', internalType: 'bytes32' },
|
|
686
|
+
{ name: 'err', type: 'bytes', internalType: 'bytes' },
|
|
687
|
+
],
|
|
688
|
+
},
|
|
681
689
|
{
|
|
682
690
|
type: 'error',
|
|
683
691
|
name: 'NotACompatiblePool',
|