@chainlink/ccip-sdk 0.95.0 → 0.96.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/README.md +2 -2
- package/dist/all-chains.d.ts +23 -0
- package/dist/all-chains.d.ts.map +1 -0
- package/dist/all-chains.js +24 -0
- package/dist/all-chains.js.map +1 -0
- package/dist/api/index.d.ts +15 -12
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +20 -16
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +25 -29
- package/dist/api/types.d.ts.map +1 -1
- package/dist/aptos/index.d.ts +33 -8
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +74 -41
- package/dist/aptos/index.js.map +1 -1
- package/dist/chain.d.ts +220 -41
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +105 -15
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +2 -0
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +2 -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 +2 -0
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +12 -6
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +19 -7
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/extra-args.d.ts +25 -0
- package/dist/evm/extra-args.d.ts.map +1 -0
- package/dist/evm/extra-args.js +328 -0
- package/dist/evm/extra-args.js.map +1 -0
- package/dist/evm/gas.d.ts.map +1 -1
- package/dist/evm/gas.js +7 -12
- package/dist/evm/gas.js.map +1 -1
- package/dist/evm/index.d.ts +70 -24
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +72 -91
- package/dist/evm/index.js.map +1 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +16 -2
- package/dist/execution.js.map +1 -1
- package/dist/extra-args.d.ts +103 -4
- package/dist/extra-args.d.ts.map +1 -1
- package/dist/extra-args.js +28 -3
- package/dist/extra-args.js.map +1 -1
- package/dist/gas.d.ts +6 -3
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +14 -6
- package/dist/gas.js.map +1 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/requests.d.ts +17 -9
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +17 -9
- package/dist/requests.js.map +1 -1
- package/dist/selectors.d.ts.map +1 -1
- package/dist/selectors.js +12 -0
- package/dist/selectors.js.map +1 -1
- package/dist/solana/index.d.ts +70 -15
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +72 -16
- package/dist/solana/index.js.map +1 -1
- package/dist/sui/index.d.ts +37 -9
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +40 -11
- package/dist/sui/index.js.map +1 -1
- package/dist/ton/index.d.ts +65 -19
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +155 -25
- package/dist/ton/index.js.map +1 -1
- package/dist/ton/send.d.ts +52 -0
- package/dist/ton/send.d.ts.map +1 -0
- package/dist/ton/send.js +166 -0
- package/dist/ton/send.js.map +1 -0
- package/dist/types.d.ts +102 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +15 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +19 -6
- package/dist/utils.js.map +1 -1
- package/package.json +12 -7
- package/src/all-chains.ts +26 -0
- package/src/api/index.ts +26 -25
- package/src/api/types.ts +25 -30
- package/src/aptos/index.ts +79 -43
- package/src/chain.ts +274 -46
- package/src/errors/codes.ts +2 -0
- package/src/errors/index.ts +1 -1
- package/src/errors/recovery.ts +2 -0
- package/src/errors/specialized.ts +24 -7
- package/src/evm/extra-args.ts +377 -0
- package/src/evm/gas.ts +14 -13
- package/src/evm/index.ts +76 -125
- package/src/execution.ts +18 -2
- package/src/extra-args.ts +108 -4
- package/src/gas.ts +16 -9
- package/src/index.ts +12 -9
- package/src/requests.ts +17 -9
- package/src/selectors.ts +12 -0
- package/src/solana/index.ts +72 -16
- package/src/sui/index.ts +40 -11
- package/src/ton/index.ts +192 -27
- package/src/ton/send.ts +222 -0
- package/src/types.ts +103 -1
- package/src/utils.ts +19 -6
package/src/ton/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { type Memoized, memoize } from 'micro-memoize'
|
|
|
8
8
|
import type { PickDeep } from 'type-fest'
|
|
9
9
|
|
|
10
10
|
import { streamTransactionsForAddress } from './logs.ts'
|
|
11
|
+
import { generateUnsignedCcipSend, getFee as getFeeImpl } from './send.ts'
|
|
11
12
|
import { type ChainContext, type GetBalanceOpts, type LogFilter, Chain } from '../chain.ts'
|
|
12
13
|
import {
|
|
13
14
|
CCIPArgumentInvalidError,
|
|
@@ -21,6 +22,8 @@ import {
|
|
|
21
22
|
CCIPWalletInvalidError,
|
|
22
23
|
} from '../errors/specialized.ts'
|
|
23
24
|
import { type EVMExtraArgsV2, type ExtraArgs, EVMExtraArgsV2Tag } from '../extra-args.ts'
|
|
25
|
+
import type { LeafHasher } from '../hasher/common.ts'
|
|
26
|
+
import { buildMessageForDest } from '../requests.ts'
|
|
24
27
|
import { supportedChains } from '../supported-chains.ts'
|
|
25
28
|
import {
|
|
26
29
|
type CCIPExecution,
|
|
@@ -34,6 +37,7 @@ import {
|
|
|
34
37
|
type NetworkInfo,
|
|
35
38
|
type OffchainTokenData,
|
|
36
39
|
type WithLogger,
|
|
40
|
+
CCIPVersion,
|
|
37
41
|
ChainFamily,
|
|
38
42
|
ExecutionState,
|
|
39
43
|
} from '../types.ts'
|
|
@@ -49,7 +53,6 @@ import { generateUnsignedExecuteReport as generateUnsignedExecuteReportImpl } fr
|
|
|
49
53
|
import { getTONLeafHasher } from './hasher.ts'
|
|
50
54
|
import { type CCIPMessage_V1_6_TON, type UnsignedTONTx, isTONWallet } from './types.ts'
|
|
51
55
|
import { crc32, lookupTxByRawHash, parseJettonContent } from './utils.ts'
|
|
52
|
-
import type { LeafHasher } from '../hasher/common.ts'
|
|
53
56
|
export type { TONWallet, UnsignedTONTx } from './types.ts'
|
|
54
57
|
|
|
55
58
|
/**
|
|
@@ -179,6 +182,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
179
182
|
* @param url - RPC endpoint URL for TonClient (v2).
|
|
180
183
|
* @param ctx - Context containing logger.
|
|
181
184
|
* @returns A new TONChain instance.
|
|
185
|
+
* @throws {@link CCIPHttpError} if connection to the RPC endpoint fails
|
|
182
186
|
*/
|
|
183
187
|
static async fromUrl(url: string, ctx?: ChainContext): Promise<TONChain> {
|
|
184
188
|
const { logger = console } = ctx ?? {}
|
|
@@ -219,6 +223,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
219
223
|
*
|
|
220
224
|
* @param block - Logical time (lt) as number, or 'finalized' for latest block timestamp
|
|
221
225
|
* @returns Unix timestamp in seconds
|
|
226
|
+
* @throws {@link CCIPNotImplementedError} if lt is not in cache
|
|
222
227
|
*/
|
|
223
228
|
async getBlockTimestamp(block: number | 'finalized'): Promise<number> {
|
|
224
229
|
if (typeof block != 'number') {
|
|
@@ -246,6 +251,8 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
246
251
|
* @param tx - Transaction identifier in either format
|
|
247
252
|
* @returns ChainTransaction with transaction details
|
|
248
253
|
* Note: `blockNumber` contains logical time (lt), not block seqno
|
|
254
|
+
* @throws {@link CCIPArgumentInvalidError} if hash format is invalid
|
|
255
|
+
* @throws {@link CCIPTransactionNotFoundError} if transaction not found
|
|
249
256
|
*/
|
|
250
257
|
async getTransaction(tx: string | Transaction): Promise<ChainTransaction> {
|
|
251
258
|
let address
|
|
@@ -340,6 +347,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
340
347
|
* not block sequence numbers. This is because TON transaction APIs are indexed by lt.
|
|
341
348
|
*
|
|
342
349
|
* @param opts - Log filter options (startBlock/endBlock are interpreted as lt values)
|
|
350
|
+
* @throws {@link CCIPTopicsInvalidError} if topics format is invalid
|
|
343
351
|
*/
|
|
344
352
|
async *getLogs(opts: LogFilter): AsyncIterableIterator<Log_> {
|
|
345
353
|
let topics
|
|
@@ -362,7 +370,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
362
370
|
}
|
|
363
371
|
}
|
|
364
372
|
|
|
365
|
-
/**
|
|
373
|
+
/**
|
|
374
|
+
* {@inheritDoc Chain.getMessagesInBatch}
|
|
375
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
376
|
+
*/
|
|
366
377
|
override async getMessagesInBatch<
|
|
367
378
|
R extends PickDeep<
|
|
368
379
|
CCIPRequest,
|
|
@@ -420,9 +431,14 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
420
431
|
return stack.readAddress().toRawString()
|
|
421
432
|
}
|
|
422
433
|
|
|
423
|
-
/**
|
|
434
|
+
/**
|
|
435
|
+
* {@inheritDoc Chain.getNativeTokenForRouter}
|
|
436
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
437
|
+
*/
|
|
424
438
|
getNativeTokenForRouter(_router: string): Promise<string> {
|
|
425
|
-
|
|
439
|
+
// TON native token is represented as address 0:0...01 (workchain 0, hash = 1)
|
|
440
|
+
// This is a convention for representing native TON in CCIP
|
|
441
|
+
return Promise.resolve('0:0000000000000000000000000000000000000000000000000000000000000001')
|
|
426
442
|
}
|
|
427
443
|
|
|
428
444
|
/** {@inheritDoc Chain.getOffRampsForRouter} */
|
|
@@ -445,7 +461,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
445
461
|
return stack.readAddress().toRawString()
|
|
446
462
|
}
|
|
447
463
|
|
|
448
|
-
/**
|
|
464
|
+
/**
|
|
465
|
+
* {@inheritDoc Chain.getOnRampForOffRamp}
|
|
466
|
+
* @throws {@link CCIPSourceChainUnsupportedError} if source chain is not configured
|
|
467
|
+
*/
|
|
449
468
|
async getOnRampForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
|
|
450
469
|
try {
|
|
451
470
|
const offRampContract = this.provider.provider(Address.parse(offRamp))
|
|
@@ -491,7 +510,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
491
510
|
return Promise.resolve(offRamp)
|
|
492
511
|
}
|
|
493
512
|
|
|
494
|
-
/**
|
|
513
|
+
/**
|
|
514
|
+
* {@inheritDoc Chain.getTokenForTokenPool}
|
|
515
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
516
|
+
*/
|
|
495
517
|
async getTokenForTokenPool(_tokenPool: string): Promise<string> {
|
|
496
518
|
return Promise.reject(new CCIPNotImplementedError('getTokenForTokenPool'))
|
|
497
519
|
}
|
|
@@ -519,12 +541,45 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
519
541
|
}
|
|
520
542
|
}
|
|
521
543
|
|
|
522
|
-
/**
|
|
523
|
-
|
|
524
|
-
|
|
544
|
+
/**
|
|
545
|
+
* {@inheritDoc Chain.getBalance}
|
|
546
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
547
|
+
*/
|
|
548
|
+
async getBalance(opts: GetBalanceOpts): Promise<bigint> {
|
|
549
|
+
const { holder, token } = opts
|
|
550
|
+
const holderAddress = Address.parse(holder)
|
|
551
|
+
|
|
552
|
+
if (!token) {
|
|
553
|
+
// Get native TON balance
|
|
554
|
+
const state = await this.provider.getContractState(holderAddress)
|
|
555
|
+
return state.balance
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// For jetton balance, we need to:
|
|
559
|
+
// 1. Derive the jetton wallet address for this holder
|
|
560
|
+
// 2. Query the balance from that wallet contract
|
|
561
|
+
const jettonMaster = Address.parse(token)
|
|
562
|
+
const { stack } = await this.provider.runMethod(jettonMaster, 'get_wallet_address', [
|
|
563
|
+
{ type: 'slice', cell: beginCell().storeAddress(holderAddress).endCell() },
|
|
564
|
+
])
|
|
565
|
+
const jettonWalletAddress = stack.readAddress()
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
const { stack: balanceStack } = await this.provider.runMethod(
|
|
569
|
+
jettonWalletAddress,
|
|
570
|
+
'get_wallet_data',
|
|
571
|
+
)
|
|
572
|
+
return balanceStack.readBigNumber() // First value is balance
|
|
573
|
+
} catch {
|
|
574
|
+
// Wallet doesn't exist yet = 0 balance
|
|
575
|
+
return 0n
|
|
576
|
+
}
|
|
525
577
|
}
|
|
526
578
|
|
|
527
|
-
/**
|
|
579
|
+
/**
|
|
580
|
+
* {@inheritDoc Chain.getTokenAdminRegistryFor}
|
|
581
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
582
|
+
*/
|
|
528
583
|
getTokenAdminRegistryFor(_address: string): Promise<string> {
|
|
529
584
|
return Promise.reject(new CCIPNotImplementedError('getTokenAdminRegistryFor'))
|
|
530
585
|
}
|
|
@@ -832,6 +887,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
832
887
|
* and raw format strings ("workchain:hash").
|
|
833
888
|
* @param bytes - Bytes or string to convert.
|
|
834
889
|
* @returns TON raw address string in format "workchain:hash".
|
|
890
|
+
* @throws {@link CCIPArgumentInvalidError} if bytes length is invalid
|
|
835
891
|
*/
|
|
836
892
|
static getAddress(bytes: BytesLike): string {
|
|
837
893
|
// If it's already a string address, try to parse and return raw format
|
|
@@ -951,20 +1007,107 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
951
1007
|
}
|
|
952
1008
|
|
|
953
1009
|
/** {@inheritDoc Chain.getFee} */
|
|
954
|
-
async getFee(
|
|
955
|
-
|
|
1010
|
+
async getFee({
|
|
1011
|
+
router,
|
|
1012
|
+
destChainSelector,
|
|
1013
|
+
message,
|
|
1014
|
+
}: Parameters<Chain['getFee']>[0]): Promise<bigint> {
|
|
1015
|
+
return getFeeImpl(
|
|
1016
|
+
this,
|
|
1017
|
+
router,
|
|
1018
|
+
destChainSelector,
|
|
1019
|
+
buildMessageForDest(message, networkInfo(destChainSelector).family),
|
|
1020
|
+
)
|
|
956
1021
|
}
|
|
957
1022
|
|
|
958
1023
|
/** {@inheritDoc Chain.generateUnsignedSendMessage} */
|
|
959
|
-
generateUnsignedSendMessage(
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1024
|
+
async generateUnsignedSendMessage({
|
|
1025
|
+
router,
|
|
1026
|
+
destChainSelector,
|
|
1027
|
+
message,
|
|
1028
|
+
sender,
|
|
1029
|
+
}: Parameters<Chain['generateUnsignedSendMessage']>[0]): Promise<UnsignedTONTx> {
|
|
1030
|
+
// Convert MessageInput to AnyMessage with defaults
|
|
1031
|
+
const populatedMessage = buildMessageForDest(message, networkInfo(destChainSelector).family)
|
|
1032
|
+
|
|
1033
|
+
// Calculate fee if not provided
|
|
1034
|
+
const fee =
|
|
1035
|
+
message.fee ??
|
|
1036
|
+
(await this.getFee({
|
|
1037
|
+
router,
|
|
1038
|
+
destChainSelector,
|
|
1039
|
+
message: populatedMessage,
|
|
1040
|
+
}))
|
|
1041
|
+
|
|
1042
|
+
const unsigned = generateUnsignedCcipSend(this, sender, router, destChainSelector, {
|
|
1043
|
+
...populatedMessage,
|
|
1044
|
+
fee,
|
|
1045
|
+
})
|
|
1046
|
+
|
|
1047
|
+
return {
|
|
1048
|
+
family: ChainFamily.TON,
|
|
1049
|
+
...unsigned,
|
|
1050
|
+
}
|
|
963
1051
|
}
|
|
964
1052
|
|
|
965
1053
|
/** {@inheritDoc Chain.sendMessage} */
|
|
966
|
-
async sendMessage(
|
|
967
|
-
|
|
1054
|
+
async sendMessage({
|
|
1055
|
+
router,
|
|
1056
|
+
destChainSelector,
|
|
1057
|
+
message,
|
|
1058
|
+
wallet,
|
|
1059
|
+
}: Parameters<Chain['sendMessage']>[0]): Promise<CCIPRequest> {
|
|
1060
|
+
if (!isTONWallet(wallet)) {
|
|
1061
|
+
throw new CCIPWalletInvalidError(wallet)
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const sender = await wallet.getAddress()
|
|
1065
|
+
|
|
1066
|
+
// Generate unsigned transaction with fee calculation if needed
|
|
1067
|
+
const { family: _, ...unsigned } = await this.generateUnsignedSendMessage({
|
|
1068
|
+
router,
|
|
1069
|
+
destChainSelector,
|
|
1070
|
+
message,
|
|
1071
|
+
sender,
|
|
1072
|
+
})
|
|
1073
|
+
|
|
1074
|
+
// Send transaction
|
|
1075
|
+
const startTime = Math.floor(Date.now() / 1000)
|
|
1076
|
+
const seqno = await wallet.sendTransaction(unsigned)
|
|
1077
|
+
|
|
1078
|
+
this.logger.info('CCIP send transaction submitted, seqno:', seqno)
|
|
1079
|
+
|
|
1080
|
+
// Wait for CCIPMessageSent event and extract the request
|
|
1081
|
+
// Query the OnRamp for the CCIPMessageSent event
|
|
1082
|
+
const onRamp = await this.getOnRampForRouter(router, destChainSelector)
|
|
1083
|
+
|
|
1084
|
+
// Poll for the message in recent logs
|
|
1085
|
+
for await (const log of this.getLogs({
|
|
1086
|
+
address: onRamp,
|
|
1087
|
+
topics: [crc32('CCIPMessageSent')],
|
|
1088
|
+
startTime,
|
|
1089
|
+
watch: sleep(5 * 60e3 /* 5m timeout */),
|
|
1090
|
+
})) {
|
|
1091
|
+
const msg = TONChain.decodeMessage(log)
|
|
1092
|
+
if (!msg) continue
|
|
1093
|
+
|
|
1094
|
+
// Found our message: construct and return the CCIPRequest
|
|
1095
|
+
const tx = log.tx ?? (await this.getTransaction(log.transactionHash))
|
|
1096
|
+
|
|
1097
|
+
return {
|
|
1098
|
+
lane: {
|
|
1099
|
+
sourceChainSelector: this.network.chainSelector,
|
|
1100
|
+
destChainSelector,
|
|
1101
|
+
onRamp,
|
|
1102
|
+
version: CCIPVersion.V1_6,
|
|
1103
|
+
},
|
|
1104
|
+
message: msg,
|
|
1105
|
+
log,
|
|
1106
|
+
tx,
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
throw new CCIPTransactionNotFoundError(seqno.toString())
|
|
968
1111
|
}
|
|
969
1112
|
|
|
970
1113
|
/** {@inheritDoc Chain.getOffchainTokenData} */
|
|
@@ -972,7 +1115,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
972
1115
|
return Promise.resolve(request.message.tokenAmounts.map(() => undefined))
|
|
973
1116
|
}
|
|
974
1117
|
|
|
975
|
-
/**
|
|
1118
|
+
/**
|
|
1119
|
+
* {@inheritDoc Chain.generateUnsignedExecuteReport}
|
|
1120
|
+
* @throws {@link CCIPExtraArgsInvalidError} if extra args are not EVMExtraArgsV2 format
|
|
1121
|
+
*/
|
|
976
1122
|
generateUnsignedExecuteReport({
|
|
977
1123
|
offRamp,
|
|
978
1124
|
execReport,
|
|
@@ -994,7 +1140,11 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
994
1140
|
})
|
|
995
1141
|
}
|
|
996
1142
|
|
|
997
|
-
/**
|
|
1143
|
+
/**
|
|
1144
|
+
* {@inheritDoc Chain.executeReport}
|
|
1145
|
+
* @throws {@link CCIPWalletInvalidError} if wallet is not a valid TON wallet
|
|
1146
|
+
* @throws {@link CCIPReceiptNotFoundError} if execution receipt not found within timeout
|
|
1147
|
+
*/
|
|
998
1148
|
async executeReport(opts: Parameters<Chain['executeReport']>[0]): Promise<CCIPExecution> {
|
|
999
1149
|
const { offRamp, wallet } = opts
|
|
1000
1150
|
if (!isTONWallet(wallet)) {
|
|
@@ -1039,27 +1189,42 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
1039
1189
|
}
|
|
1040
1190
|
}
|
|
1041
1191
|
|
|
1042
|
-
/**
|
|
1192
|
+
/**
|
|
1193
|
+
* {@inheritDoc Chain.getSupportedTokens}
|
|
1194
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
1195
|
+
*/
|
|
1043
1196
|
async getSupportedTokens(_address: string): Promise<string[]> {
|
|
1044
1197
|
return Promise.reject(new CCIPNotImplementedError('getSupportedTokens'))
|
|
1045
1198
|
}
|
|
1046
1199
|
|
|
1047
|
-
/**
|
|
1200
|
+
/**
|
|
1201
|
+
* {@inheritDoc Chain.getRegistryTokenConfig}
|
|
1202
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
1203
|
+
*/
|
|
1048
1204
|
async getRegistryTokenConfig(_address: string, _tokenName: string): Promise<never> {
|
|
1049
1205
|
return Promise.reject(new CCIPNotImplementedError('getRegistryTokenConfig'))
|
|
1050
1206
|
}
|
|
1051
1207
|
|
|
1052
|
-
/**
|
|
1053
|
-
|
|
1054
|
-
|
|
1208
|
+
/**
|
|
1209
|
+
* {@inheritDoc Chain.getTokenPoolConfig}
|
|
1210
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
1211
|
+
*/
|
|
1212
|
+
async getTokenPoolConfig(_tokenPool: string): Promise<never> {
|
|
1213
|
+
return Promise.reject(new CCIPNotImplementedError('getTokenPoolConfig'))
|
|
1055
1214
|
}
|
|
1056
1215
|
|
|
1057
|
-
/**
|
|
1216
|
+
/**
|
|
1217
|
+
* {@inheritDoc Chain.getTokenPoolRemotes}
|
|
1218
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
1219
|
+
*/
|
|
1058
1220
|
async getTokenPoolRemotes(_tokenPool: string): Promise<never> {
|
|
1059
1221
|
return Promise.reject(new CCIPNotImplementedError('getTokenPoolRemotes'))
|
|
1060
1222
|
}
|
|
1061
1223
|
|
|
1062
|
-
/**
|
|
1224
|
+
/**
|
|
1225
|
+
* {@inheritDoc Chain.getFeeTokens}
|
|
1226
|
+
* @throws {@link CCIPNotImplementedError} always (not implemented for TON)
|
|
1227
|
+
*/
|
|
1063
1228
|
async getFeeTokens(_router: string): Promise<never> {
|
|
1064
1229
|
return Promise.reject(new CCIPNotImplementedError('getFeeTokens'))
|
|
1065
1230
|
}
|
package/src/ton/send.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { type Cell, beginCell, toNano } from '@ton/core'
|
|
2
|
+
import { type TonClient, Address } from '@ton/ton'
|
|
3
|
+
import { zeroPadValue } from 'ethers'
|
|
4
|
+
|
|
5
|
+
import type { UnsignedTONTx } from './types.ts'
|
|
6
|
+
import { CCIPError, CCIPErrorCode } from '../errors/index.ts'
|
|
7
|
+
import { EVMExtraArgsV2Tag } from '../extra-args.ts'
|
|
8
|
+
import type { AnyMessage, WithLogger } from '../types.ts'
|
|
9
|
+
import { bytesToBuffer, getDataBytes } from '../utils.ts'
|
|
10
|
+
|
|
11
|
+
/** Opcode for Router ccipSend operation */
|
|
12
|
+
export const CCIP_SEND_OPCODE = 0x31768d95
|
|
13
|
+
|
|
14
|
+
/** Default gas buffer to add to fee for transaction execution */
|
|
15
|
+
export const DEFAULT_GAS_BUFFER = toNano('0.5')
|
|
16
|
+
|
|
17
|
+
/** Default gas limit for destination chain execution */
|
|
18
|
+
export const DEFAULT_GAS_LIMIT = 200_000n
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* WRAPPED_NATIVE address for TON - sentinel address representing native TON.
|
|
22
|
+
* Used as feeToken for native TON payments in FeeQuoter calls.
|
|
23
|
+
*/
|
|
24
|
+
export const WRAPPED_NATIVE = Address.parse(
|
|
25
|
+
'0:0000000000000000000000000000000000000000000000000000000000000001',
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Encodes token amounts as a snaked cell.
|
|
30
|
+
* Empty cell for no tokens.
|
|
31
|
+
*/
|
|
32
|
+
function encodeTokenAmounts(
|
|
33
|
+
tokenAmounts: readonly { token: string; amount: bigint }[] | undefined,
|
|
34
|
+
): Cell {
|
|
35
|
+
if (!tokenAmounts || tokenAmounts.length === 0) {
|
|
36
|
+
return beginCell().endCell()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const builder = beginCell()
|
|
40
|
+
for (const ta of tokenAmounts) {
|
|
41
|
+
builder.storeRef(
|
|
42
|
+
beginCell().storeAddress(Address.parse(ta.token)).storeUint(ta.amount, 256).endCell(),
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
return builder.endCell()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Encodes extraArgs as a Cell using the GenericExtraArgsV2 (EVMExtraArgsV2) format.
|
|
50
|
+
*
|
|
51
|
+
* Format per chainlink-ton TL-B:
|
|
52
|
+
* - tag: 32-bit opcode (0x181dcf10)
|
|
53
|
+
* - gasLimit: Maybe<uint256> (1 bit flag + 256 bits if present)
|
|
54
|
+
* - allowOutOfOrderExecution: 1 bit (must be true)
|
|
55
|
+
*/
|
|
56
|
+
function encodeExtraArgsCell(extraArgs: AnyMessage['extraArgs']): Cell {
|
|
57
|
+
const allowOutOfOrderExecution = true
|
|
58
|
+
|
|
59
|
+
let gasLimit = 0n
|
|
60
|
+
let hasGasLimit = false
|
|
61
|
+
|
|
62
|
+
if ('gasLimit' in extraArgs && extraArgs.gasLimit > 0n) {
|
|
63
|
+
hasGasLimit = true
|
|
64
|
+
gasLimit = extraArgs.gasLimit
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const builder = beginCell()
|
|
68
|
+
.storeUint(Number(EVMExtraArgsV2Tag), 32) // 0x181dcf10
|
|
69
|
+
.storeBit(hasGasLimit)
|
|
70
|
+
|
|
71
|
+
if (hasGasLimit) {
|
|
72
|
+
builder.storeUint(gasLimit, 256)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return builder.storeBit(allowOutOfOrderExecution).endCell()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Builds the Router ccipSend message cell.
|
|
80
|
+
*
|
|
81
|
+
* Relies on TL-B structure (Router_CCIPSend) from chainlink-ton repo.
|
|
82
|
+
*/
|
|
83
|
+
export function buildCcipSendCell(
|
|
84
|
+
destChainSelector: bigint,
|
|
85
|
+
message: AnyMessage,
|
|
86
|
+
feeTokenAddress: Address | null = null,
|
|
87
|
+
queryId = 0n,
|
|
88
|
+
): Cell {
|
|
89
|
+
// Get receiver bytes and pad to 32 bytes for cross-chain encoding
|
|
90
|
+
const paddedReceiver = bytesToBuffer(zeroPadValue(getDataBytes(message.receiver), 32))
|
|
91
|
+
|
|
92
|
+
// Data cell (ref 0)
|
|
93
|
+
const dataCell = beginCell()
|
|
94
|
+
.storeBuffer(bytesToBuffer(message.data || '0x'))
|
|
95
|
+
.endCell()
|
|
96
|
+
|
|
97
|
+
// Token amounts snaked cell (ref 1)
|
|
98
|
+
const tokenAmountsCell = encodeTokenAmounts(message.tokenAmounts)
|
|
99
|
+
|
|
100
|
+
// ExtraArgs cell (ref 2)
|
|
101
|
+
const extraArgsCell = encodeExtraArgsCell(message.extraArgs)
|
|
102
|
+
|
|
103
|
+
return beginCell()
|
|
104
|
+
.storeUint(CCIP_SEND_OPCODE, 32) // opcode
|
|
105
|
+
.storeUint(Number(queryId), 64) // queryID
|
|
106
|
+
.storeUint(destChainSelector, 64) // destChainSelector
|
|
107
|
+
.storeUint(paddedReceiver.length, 8) // receiver length in bytes
|
|
108
|
+
.storeBuffer(paddedReceiver) // receiver bytes (32 bytes, left-padded)
|
|
109
|
+
.storeRef(dataCell) // ref 0: data
|
|
110
|
+
.storeRef(tokenAmountsCell) // ref 1: tokenAmounts
|
|
111
|
+
.storeAddress(feeTokenAddress) // null = addr_none for native TON
|
|
112
|
+
.storeRef(extraArgsCell) // ref 2: extraArgs
|
|
113
|
+
.endCell()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Gets the fee for sending a CCIP message by calling FeeQuoter.validatedFee.
|
|
118
|
+
*
|
|
119
|
+
* @param ctx - Context with TonClient provider and logger
|
|
120
|
+
* @param router - Router contract address
|
|
121
|
+
* @param destChainSelector - Destination chain selector
|
|
122
|
+
* @param message - CCIP message to quote
|
|
123
|
+
* @returns Fee amount in nanotons
|
|
124
|
+
*/
|
|
125
|
+
export async function getFee(
|
|
126
|
+
ctx: { provider: TonClient } & WithLogger,
|
|
127
|
+
router: string,
|
|
128
|
+
destChainSelector: bigint,
|
|
129
|
+
message: AnyMessage,
|
|
130
|
+
): Promise<bigint> {
|
|
131
|
+
const { provider, logger = console } = ctx
|
|
132
|
+
const routerAddress = Address.parse(router)
|
|
133
|
+
|
|
134
|
+
// FeeQuoter requires WRAPPED_NATIVE for native TON
|
|
135
|
+
const feeTokenAddress = message.feeToken ? Address.parse(message.feeToken) : WRAPPED_NATIVE
|
|
136
|
+
|
|
137
|
+
// Get FeeQuoter address via OnRamp
|
|
138
|
+
let feeQuoterAddress: Address
|
|
139
|
+
try {
|
|
140
|
+
const { stack: onRampStack } = await provider.runMethod(routerAddress, 'onRamp', [
|
|
141
|
+
{ type: 'int', value: destChainSelector },
|
|
142
|
+
])
|
|
143
|
+
const onRampAddress = onRampStack.readAddress()
|
|
144
|
+
logger.debug('OnRamp:', onRampAddress.toString())
|
|
145
|
+
|
|
146
|
+
const { stack: feeQuoterStack } = await provider.runMethod(onRampAddress, 'feeQuoter', [
|
|
147
|
+
{ type: 'int', value: destChainSelector },
|
|
148
|
+
])
|
|
149
|
+
feeQuoterAddress = feeQuoterStack.readAddress()
|
|
150
|
+
logger.debug('FeeQuoter:', feeQuoterAddress.toString())
|
|
151
|
+
} catch (e) {
|
|
152
|
+
throw new CCIPError(
|
|
153
|
+
CCIPErrorCode.CONTRACT_TYPE_INVALID,
|
|
154
|
+
`Could not get FeeQuoter address: ${e instanceof Error ? e.message : String(e)}`,
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Build stack parameters for validatedFee call
|
|
159
|
+
const paddedReceiver = bytesToBuffer(zeroPadValue(getDataBytes(message.receiver), 32))
|
|
160
|
+
const receiverSlice = beginCell().storeBuffer(paddedReceiver).endCell()
|
|
161
|
+
const dataCell = beginCell()
|
|
162
|
+
.storeBuffer(bytesToBuffer(message.data || '0x'))
|
|
163
|
+
.endCell()
|
|
164
|
+
const tokenAmountsCell = encodeTokenAmounts(message.tokenAmounts)
|
|
165
|
+
const extraArgsCell = encodeExtraArgsCell(message.extraArgs)
|
|
166
|
+
const feeTokenSlice = beginCell().storeAddress(feeTokenAddress).endCell()
|
|
167
|
+
|
|
168
|
+
const { stack: feeStack } = await provider.runMethod(feeQuoterAddress, 'validatedFee', [
|
|
169
|
+
{ type: 'int', value: 0n },
|
|
170
|
+
{ type: 'int', value: destChainSelector },
|
|
171
|
+
{ type: 'slice', cell: receiverSlice },
|
|
172
|
+
{ type: 'cell', cell: dataCell },
|
|
173
|
+
{ type: 'cell', cell: tokenAmountsCell },
|
|
174
|
+
{ type: 'slice', cell: feeTokenSlice },
|
|
175
|
+
{ type: 'cell', cell: extraArgsCell },
|
|
176
|
+
])
|
|
177
|
+
|
|
178
|
+
const fee = feeStack.readBigNumber()
|
|
179
|
+
if (fee < 0n) {
|
|
180
|
+
throw new CCIPError(CCIPErrorCode.MESSAGE_INVALID, `Invalid fee: ${fee}`)
|
|
181
|
+
}
|
|
182
|
+
logger.debug('CCIP fee:', fee.toString(), 'nanotons')
|
|
183
|
+
return fee
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Generates an unsigned CCIP send transaction for the Router.
|
|
188
|
+
*
|
|
189
|
+
* @param ctx - Context with TonClient provider and logger
|
|
190
|
+
* @param _sender - Sender address (unused, for interface compatibility)
|
|
191
|
+
* @param router - Router contract address
|
|
192
|
+
* @param destChainSelector - Destination chain selector
|
|
193
|
+
* @param message - CCIP message with fee included
|
|
194
|
+
* @param opts - Optional gas buffer override
|
|
195
|
+
* @returns Unsigned transaction ready for signing
|
|
196
|
+
*/
|
|
197
|
+
export function generateUnsignedCcipSend(
|
|
198
|
+
ctx: { provider: TonClient } & WithLogger,
|
|
199
|
+
_sender: string,
|
|
200
|
+
router: string,
|
|
201
|
+
destChainSelector: bigint,
|
|
202
|
+
message: AnyMessage & { fee: bigint },
|
|
203
|
+
opts?: { gasBuffer?: bigint },
|
|
204
|
+
): Omit<UnsignedTONTx, 'family'> {
|
|
205
|
+
const { logger = console } = ctx
|
|
206
|
+
const gasBuffer = opts?.gasBuffer ?? DEFAULT_GAS_BUFFER
|
|
207
|
+
|
|
208
|
+
// Router accepts addr_none for native TON (unlike FeeQuoter which needs WRAPPED_NATIVE)
|
|
209
|
+
const feeTokenAddress = message.feeToken ? Address.parse(message.feeToken) : null
|
|
210
|
+
|
|
211
|
+
const ccipSendCell = buildCcipSendCell(destChainSelector, message, feeTokenAddress)
|
|
212
|
+
const totalValue = message.fee + gasBuffer
|
|
213
|
+
|
|
214
|
+
logger.debug('Generating ccipSend tx to router:', router)
|
|
215
|
+
logger.debug('Total value:', totalValue.toString(), 'nanotons')
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
to: router,
|
|
219
|
+
body: ccipSendCell,
|
|
220
|
+
value: totalValue,
|
|
221
|
+
}
|
|
222
|
+
}
|