@chainlink/ccip-sdk 1.0.0 → 1.1.1
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/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +5 -5
- package/dist/aptos/index.js.map +1 -1
- package/dist/chain.d.ts +109 -5
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +96 -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 +139 -51
- 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 +3 -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 +17 -2
- package/dist/solana/index.js.map +1 -1
- package/dist/sui/exec.d.ts +30 -0
- package/dist/sui/exec.d.ts.map +1 -0
- package/dist/sui/exec.js +92 -0
- package/dist/sui/exec.js.map +1 -0
- package/dist/sui/index.d.ts +7 -2
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +36 -65
- package/dist/sui/index.js.map +1 -1
- package/dist/sui/manuallyExec/index.d.ts.map +1 -1
- package/dist/sui/manuallyExec/index.js +10 -13
- package/dist/sui/manuallyExec/index.js.map +1 -1
- package/dist/sui/objects.d.ts.map +1 -1
- package/dist/sui/objects.js +4 -2
- package/dist/sui/objects.js.map +1 -1
- package/dist/sui/types.d.ts +9 -1
- package/dist/sui/types.d.ts.map +1 -1
- package/dist/sui/types.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/aptos/index.ts +7 -9
- package/src/chain.ts +152 -12
- 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 +231 -84
- package/src/evm/messages.ts +3 -285
- package/src/gas.ts +27 -19
- package/src/index.ts +3 -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 +20 -2
- package/src/sui/exec.ts +131 -0
- package/src/sui/index.ts +50 -98
- package/src/sui/manuallyExec/index.ts +11 -17
- package/src/sui/objects.ts +4 -2
- package/src/sui/types.ts +10 -1
- 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.1-593a607'
|
|
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/aptos/index.ts
CHANGED
|
@@ -560,13 +560,11 @@ export class AptosChain extends Chain<typeof ChainFamily.Aptos> {
|
|
|
560
560
|
payer,
|
|
561
561
|
...opts
|
|
562
562
|
}: Parameters<Chain['generateUnsignedExecute']>[0]): Promise<UnsignedAptosTx> {
|
|
563
|
+
const resolved = await this.resolveExecuteOpts(opts)
|
|
563
564
|
if (
|
|
564
|
-
!(
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
'allowOutOfOrderExecution' in opts.input.message &&
|
|
568
|
-
'gasLimit' in opts.input.message
|
|
569
|
-
)
|
|
565
|
+
!('message' in resolved.input) ||
|
|
566
|
+
!('allowOutOfOrderExecution' in resolved.input.message) ||
|
|
567
|
+
!('gasLimit' in resolved.input.message)
|
|
570
568
|
) {
|
|
571
569
|
throw new CCIPAptosExtraArgsV2RequiredError()
|
|
572
570
|
}
|
|
@@ -574,9 +572,9 @@ export class AptosChain extends Chain<typeof ChainFamily.Aptos> {
|
|
|
574
572
|
const tx = await generateUnsignedExecuteReport(
|
|
575
573
|
this.provider,
|
|
576
574
|
payer,
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
575
|
+
resolved.offRamp,
|
|
576
|
+
resolved.input as ExecutionInput<CCIPMessage_V1_6_EVM>,
|
|
577
|
+
resolved,
|
|
580
578
|
)
|
|
581
579
|
return {
|
|
582
580
|
family: ChainFamily.Aptos,
|
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'
|
|
@@ -26,6 +28,7 @@ import { getOffchainTokenData } from './offchain.ts'
|
|
|
26
28
|
import { getMessagesInTx } from './requests.ts'
|
|
27
29
|
import { DEFAULT_GAS_LIMIT } from './shared/constants.ts'
|
|
28
30
|
import type { UnsignedSolanaTx } from './solana/types.ts'
|
|
31
|
+
import type { UnsignedSuiTx } from './sui/types.ts'
|
|
29
32
|
import type { UnsignedTONTx } from './ton/types.ts'
|
|
30
33
|
import {
|
|
31
34
|
type AnyMessage,
|
|
@@ -50,8 +53,12 @@ import {
|
|
|
50
53
|
} from './types.ts'
|
|
51
54
|
import { networkInfo, util, withRetry } from './utils.ts'
|
|
52
55
|
|
|
53
|
-
/**
|
|
54
|
-
const
|
|
56
|
+
/** All valid field names for GenericExtraArgsV2. */
|
|
57
|
+
const V2_FIELDS = new Set(['gasLimit', 'allowOutOfOrderExecution'])
|
|
58
|
+
|
|
59
|
+
/** All valid field names for GenericExtraArgsV3. */
|
|
60
|
+
const V3_FIELDS = new Set([
|
|
61
|
+
'gasLimit',
|
|
55
62
|
'blockConfirmations',
|
|
56
63
|
'ccvs',
|
|
57
64
|
'ccvArgs',
|
|
@@ -59,12 +66,26 @@ const V3_ONLY_FIELDS = [
|
|
|
59
66
|
'executorArgs',
|
|
60
67
|
'tokenReceiver',
|
|
61
68
|
'tokenArgs',
|
|
62
|
-
]
|
|
69
|
+
])
|
|
70
|
+
|
|
71
|
+
/** Throw if any key in extraArgs is not in the allowed set. */
|
|
72
|
+
function assertNoUnknownFields(
|
|
73
|
+
extraArgs: Partial<ExtraArgs>,
|
|
74
|
+
allowed: Set<string>,
|
|
75
|
+
variant: string,
|
|
76
|
+
): void {
|
|
77
|
+
const unknown = Object.keys(extraArgs).filter((k) => !allowed.has(k))
|
|
78
|
+
if (unknown.length)
|
|
79
|
+
throw new CCIPArgumentInvalidError(
|
|
80
|
+
'extraArgs',
|
|
81
|
+
`unknown field(s) for ${variant}: ${unknown.map((k) => JSON.stringify(k)).join(', ')}`,
|
|
82
|
+
)
|
|
83
|
+
}
|
|
63
84
|
|
|
64
|
-
/** Check if extraArgs contains any V3-only fields. */
|
|
85
|
+
/** Check if extraArgs contains any V3-only fields (i.e. fields in V3 but not in V2). */
|
|
65
86
|
function hasV3ExtraArgs(extraArgs: Partial<ExtraArgs> | undefined): boolean {
|
|
66
87
|
if (!extraArgs) return false
|
|
67
|
-
return
|
|
88
|
+
return Object.keys(extraArgs).some((k) => V3_FIELDS.has(k) && !V2_FIELDS.has(k))
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
/**
|
|
@@ -94,12 +115,13 @@ export type ChainContext = WithLogger & {
|
|
|
94
115
|
* CCIP API client instance for lane information queries.
|
|
95
116
|
*
|
|
96
117
|
* - `undefined` (default): Creates CCIPAPIClient with {@link DEFAULT_API_BASE_URL}
|
|
118
|
+
* - `string`: Creates CCIPAPIClient with provided URL
|
|
97
119
|
* - `CCIPAPIClient`: Uses provided instance (allows custom URL, fetch, etc.)
|
|
98
120
|
* - `null`: Disables API client entirely (getLaneLatency() will throw)
|
|
99
121
|
*
|
|
100
122
|
* Default: `undefined` (auto-create with production endpoint)
|
|
101
123
|
*/
|
|
102
|
-
apiClient?: CCIPAPIClient | null
|
|
124
|
+
apiClient?: CCIPAPIClient | string | null
|
|
103
125
|
|
|
104
126
|
/**
|
|
105
127
|
* Retry configuration for API fallback operations.
|
|
@@ -173,6 +195,52 @@ export type TokenInfo = {
|
|
|
173
195
|
readonly name?: string
|
|
174
196
|
}
|
|
175
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Available lane feature keys.
|
|
200
|
+
* These represent features or thresholds that can be configured per-lane.
|
|
201
|
+
*/
|
|
202
|
+
export const LaneFeature = {
|
|
203
|
+
/**
|
|
204
|
+
* Minimum block confirmations for Faster Time to Finality (FTF).
|
|
205
|
+
* - **absent**: the lane does not support FTF (pre-v2.0 lane).
|
|
206
|
+
* - **0**: the lane supports FTF, but it is not enabled for this
|
|
207
|
+
* token (e.g. the token pool predates FTF, or FTF is configured
|
|
208
|
+
* to use default finality only).
|
|
209
|
+
* - **\> 0**: FTF is enabled; this is the minimum number of block
|
|
210
|
+
* confirmations required to use it.
|
|
211
|
+
*/
|
|
212
|
+
MIN_BLOCK_CONFIRMATIONS: 'MIN_BLOCK_CONFIRMATIONS',
|
|
213
|
+
/**
|
|
214
|
+
* Rate limiter bucket state for the lane/token with default finality.
|
|
215
|
+
*/
|
|
216
|
+
RATE_LIMITS: 'RATE_LIMITS',
|
|
217
|
+
/**
|
|
218
|
+
* Rate limiter bucket state when using non-default finality (FTF).
|
|
219
|
+
* Only meaningful when FTF is supported on this lane, i.e.
|
|
220
|
+
* {@link LaneFeature.MIN_BLOCK_CONFIRMATIONS} is present and \> 0.
|
|
221
|
+
* If absent, the default rate limits ({@link LaneFeature.RATE_LIMITS}) apply even when using custom finality.
|
|
222
|
+
*/
|
|
223
|
+
CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITS: 'CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITS',
|
|
224
|
+
} as const
|
|
225
|
+
/** Type representing one of the lane feature keys. */
|
|
226
|
+
export type LaneFeature = (typeof LaneFeature)[keyof typeof LaneFeature]
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Lane features record.
|
|
230
|
+
* Maps feature keys to their values.
|
|
231
|
+
*/
|
|
232
|
+
export interface LaneFeatures extends Record<LaneFeature, unknown> {
|
|
233
|
+
/** Minimum block confirmations for FTF. */
|
|
234
|
+
MIN_BLOCK_CONFIRMATIONS: number
|
|
235
|
+
/** Rate limiter bucket state for the lane/token with default finality. */
|
|
236
|
+
RATE_LIMITS: RateLimiterState
|
|
237
|
+
/**
|
|
238
|
+
* Rate limiter bucket state when using non-default finality (FTF).
|
|
239
|
+
* If absent, the default rate limits ({@link LaneFeatures.RATE_LIMITS}) apply even when using custom finality.
|
|
240
|
+
*/
|
|
241
|
+
CUSTOM_BLOCK_CONFIRMATIONS_RATE_LIMITS: RateLimiterState
|
|
242
|
+
}
|
|
243
|
+
|
|
176
244
|
/**
|
|
177
245
|
* Options for getBalance query.
|
|
178
246
|
*/
|
|
@@ -231,11 +299,19 @@ export type TokenPoolRemote = {
|
|
|
231
299
|
* - Version management (different pool implementations)
|
|
232
300
|
*/
|
|
233
301
|
remotePools: string[]
|
|
234
|
-
/** Inbound rate limiter state for tokens coming into this chain. */
|
|
235
|
-
inboundRateLimiterState: RateLimiterState
|
|
236
302
|
/** Outbound rate limiter state for tokens leaving this chain. */
|
|
237
303
|
outboundRateLimiterState: RateLimiterState
|
|
238
|
-
|
|
304
|
+
/** Inbound rate limiter state for tokens coming into this chain. */
|
|
305
|
+
inboundRateLimiterState: RateLimiterState
|
|
306
|
+
} & (
|
|
307
|
+
| {
|
|
308
|
+
/** Outbound rate limiter state for tokens leaving this chain (FTF/v2). */
|
|
309
|
+
customBlockConfirmationsOutboundRateLimiterState: RateLimiterState
|
|
310
|
+
/** Inbound rate limiter state for tokens coming into this chain (FTF/v2). */
|
|
311
|
+
customBlockConfirmationsInboundRateLimiterState: RateLimiterState
|
|
312
|
+
}
|
|
313
|
+
| object
|
|
314
|
+
)
|
|
239
315
|
|
|
240
316
|
/**
|
|
241
317
|
* Token pool configuration returned by {@link Chain.getTokenPoolConfig}.
|
|
@@ -256,6 +332,13 @@ export type TokenPoolConfig = {
|
|
|
256
332
|
* May be undefined for older pool implementations that don't expose this method.
|
|
257
333
|
*/
|
|
258
334
|
typeAndVersion?: string
|
|
335
|
+
/**
|
|
336
|
+
* Min custom block confirmations for Faster Time to Finality (FTF),
|
|
337
|
+
* if TokenPool version \>= v2.0.0 and FTF is supported on this lane.
|
|
338
|
+
* `0` indicates FTF is supported but not enabled for this token; `>0` indicates FTF is enabled
|
|
339
|
+
* with this many minimum confirmations.
|
|
340
|
+
*/
|
|
341
|
+
minBlockConfirmations?: number
|
|
259
342
|
}
|
|
260
343
|
|
|
261
344
|
/**
|
|
@@ -282,7 +365,7 @@ export type UnsignedTx = {
|
|
|
282
365
|
[ChainFamily.Solana]: UnsignedSolanaTx
|
|
283
366
|
[ChainFamily.Aptos]: UnsignedAptosTx
|
|
284
367
|
[ChainFamily.TON]: UnsignedTONTx
|
|
285
|
-
[ChainFamily.Sui]:
|
|
368
|
+
[ChainFamily.Sui]: UnsignedSuiTx
|
|
286
369
|
[ChainFamily.Unknown]: never
|
|
287
370
|
}
|
|
288
371
|
|
|
@@ -362,11 +445,11 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
362
445
|
if (apiClient === null) {
|
|
363
446
|
this.apiClient = null // Explicit opt-out
|
|
364
447
|
this.apiRetryConfig = null // No retry config needed without API client
|
|
365
|
-
} else if (apiClient !==
|
|
448
|
+
} else if (apiClient && typeof apiClient !== 'string') {
|
|
366
449
|
this.apiClient = apiClient // Use provided instance
|
|
367
450
|
this.apiRetryConfig = { ...DEFAULT_API_RETRY_CONFIG, ...apiRetryConfig }
|
|
368
451
|
} else {
|
|
369
|
-
this.apiClient = CCIPAPIClient.fromUrl(
|
|
452
|
+
this.apiClient = CCIPAPIClient.fromUrl(apiClient, ctx) // default=undefined or provided string as URL
|
|
370
453
|
this.apiRetryConfig = { ...DEFAULT_API_RETRY_CONFIG, ...apiRetryConfig }
|
|
371
454
|
}
|
|
372
455
|
}
|
|
@@ -929,6 +1012,25 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
929
1012
|
return getOffchainTokenData(request, this)
|
|
930
1013
|
}
|
|
931
1014
|
|
|
1015
|
+
/**
|
|
1016
|
+
* Resolves {@link ExecuteOpts} that may contain a `messageId` (API shorthand) into the
|
|
1017
|
+
* canonical `{ offRamp, input }` form required by {@link generateUnsignedExecute}.
|
|
1018
|
+
*
|
|
1019
|
+
* When `opts` already contains `input` the method is a no-op and returns it unchanged.
|
|
1020
|
+
* When `opts` contains only a `messageId` it calls `apiClient.getExecutionInput` and merges
|
|
1021
|
+
* the result back with any extra opts fields (e.g. `gasLimit`).
|
|
1022
|
+
*
|
|
1023
|
+
* @throws {@link CCIPApiClientNotAvailableError} if `messageId` is provided but no apiClient
|
|
1024
|
+
*/
|
|
1025
|
+
protected async resolveExecuteOpts(
|
|
1026
|
+
opts: ExecuteOpts,
|
|
1027
|
+
): Promise<Omit<ExecuteOpts, 'messageId'> & { offRamp: string; input: ExecutionInput }> {
|
|
1028
|
+
if ('input' in opts) return opts
|
|
1029
|
+
if (!this.apiClient) throw new CCIPApiClientNotAvailableError()
|
|
1030
|
+
const { offRamp, ...input } = await this.apiClient.getExecutionInput(opts.messageId)
|
|
1031
|
+
return { ...opts, offRamp, input }
|
|
1032
|
+
}
|
|
1033
|
+
|
|
932
1034
|
/**
|
|
933
1035
|
* Generate unsigned tx to manuallyExecute a message.
|
|
934
1036
|
*
|
|
@@ -1050,6 +1152,41 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
1050
1152
|
return this.apiClient.getLaneLatency(this.network.chainSelector, destChainSelector)
|
|
1051
1153
|
}
|
|
1052
1154
|
|
|
1155
|
+
/**
|
|
1156
|
+
* Retrieve features for a lane (router/destChainSelector/token triplet).
|
|
1157
|
+
*
|
|
1158
|
+
* @param _opts - Options containing router address, destChainSelector, and optional token
|
|
1159
|
+
* address (the token to be transferred in a hypothetical message on this lane)
|
|
1160
|
+
* @returns Promise resolving to partial lane features record
|
|
1161
|
+
*
|
|
1162
|
+
* @throws {@link CCIPNotImplementedError} if not implemented for this chain family
|
|
1163
|
+
*
|
|
1164
|
+
* @example Get lane features
|
|
1165
|
+
* ```typescript
|
|
1166
|
+
* const features = await chain.getLaneFeatures({
|
|
1167
|
+
* router: '0x...',
|
|
1168
|
+
* destChainSelector: 4949039107694359620n,
|
|
1169
|
+
* })
|
|
1170
|
+
* // MIN_BLOCK_CONFIRMATIONS has three states:
|
|
1171
|
+
* // - undefined: FTF is not supported on this lane (pre-v2.0)
|
|
1172
|
+
* // - 0: the lane supports FTF, but it is not enabled for this token
|
|
1173
|
+
* // - > 0: FTF is enabled with this many block confirmations
|
|
1174
|
+
* const ftf = features.MIN_BLOCK_CONFIRMATIONS
|
|
1175
|
+
* if (ftf != null && ftf > 0) {
|
|
1176
|
+
* console.log(`FTF enabled with ${ftf} confirmations`)
|
|
1177
|
+
* } else if (ftf === 0) {
|
|
1178
|
+
* console.log('FTF supported on this lane but not enabled for this token')
|
|
1179
|
+
* }
|
|
1180
|
+
* ```
|
|
1181
|
+
*/
|
|
1182
|
+
getLaneFeatures(_opts: {
|
|
1183
|
+
router: string
|
|
1184
|
+
destChainSelector: bigint
|
|
1185
|
+
token?: string
|
|
1186
|
+
}): Promise<Partial<LaneFeatures>> {
|
|
1187
|
+
return Promise.reject(new CCIPNotImplementedError('getLaneFeatures'))
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1053
1190
|
/**
|
|
1054
1191
|
* Default/generic implementation of getExecutionReceipts.
|
|
1055
1192
|
* Yields execution receipts for a given offRamp.
|
|
@@ -1311,6 +1448,8 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
1311
1448
|
|
|
1312
1449
|
// Detect if user wants V3 by checking for any V3-only field
|
|
1313
1450
|
if (hasV3ExtraArgs(message.extraArgs)) {
|
|
1451
|
+
if (message.extraArgs)
|
|
1452
|
+
assertNoUnknownFields(message.extraArgs, V3_FIELDS, 'GenericExtraArgsV3')
|
|
1314
1453
|
// V3 defaults (GenericExtraArgsV3)
|
|
1315
1454
|
return {
|
|
1316
1455
|
...message,
|
|
@@ -1328,6 +1467,7 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
|
|
|
1328
1467
|
}
|
|
1329
1468
|
}
|
|
1330
1469
|
|
|
1470
|
+
if (message.extraArgs) assertNoUnknownFields(message.extraArgs, V2_FIELDS, 'EVMExtraArgsV2')
|
|
1331
1471
|
// Default to V2 (GenericExtraArgsV2, aka EVMExtraArgsV2)
|
|
1332
1472
|
return {
|
|
1333
1473
|
...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
|
*
|