@chainlink/ccip-sdk 0.92.0 → 0.93.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 +20 -18
- package/dist/api/index.d.ts +103 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +141 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/types.d.ts +38 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/aptos/index.d.ts +20 -33
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +34 -26
- package/dist/aptos/index.js.map +1 -1
- package/dist/aptos/logs.js +1 -1
- package/dist/aptos/logs.js.map +1 -1
- package/dist/aptos/token.js.map +1 -1
- package/dist/chain.d.ts +206 -71
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +89 -20
- package/dist/chain.js.map +1 -1
- package/dist/commits.d.ts +2 -2
- package/dist/commits.d.ts.map +1 -1
- package/dist/commits.js +4 -4
- package/dist/commits.js.map +1 -1
- package/dist/errors/CCIPError.d.ts.map +1 -1
- package/dist/errors/CCIPError.js +3 -2
- package/dist/errors/CCIPError.js.map +1 -1
- package/dist/errors/codes.d.ts +4 -1
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +6 -1
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +5 -2
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +8 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.d.ts.map +1 -1
- package/dist/errors/recovery.js +4 -1
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +29 -4
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +48 -6
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/errors.js.map +1 -1
- package/dist/evm/index.d.ts +24 -48
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +71 -59
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/logs.js.map +1 -1
- package/dist/evm/offchain.js +3 -2
- package/dist/evm/offchain.js.map +1 -1
- package/dist/evm/viem/client-adapter.d.ts +68 -0
- package/dist/evm/viem/client-adapter.d.ts.map +1 -0
- package/dist/evm/viem/client-adapter.js +104 -0
- package/dist/evm/viem/client-adapter.js.map +1 -0
- package/dist/evm/viem/index.d.ts +29 -0
- package/dist/evm/viem/index.d.ts.map +1 -0
- package/dist/evm/viem/index.js +28 -0
- package/dist/evm/viem/index.js.map +1 -0
- package/dist/evm/viem/types.d.ts +13 -0
- package/dist/evm/viem/types.d.ts.map +1 -0
- package/dist/evm/viem/types.js +2 -0
- package/dist/evm/viem/types.js.map +1 -0
- package/dist/evm/viem/wallet-adapter.d.ts +58 -0
- package/dist/evm/viem/wallet-adapter.d.ts.map +1 -0
- package/dist/evm/viem/wallet-adapter.js +197 -0
- package/dist/evm/viem/wallet-adapter.js.map +1 -0
- package/dist/execution.d.ts +1 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +2 -2
- package/dist/execution.js.map +1 -1
- package/dist/explorer.d.ts +74 -0
- package/dist/explorer.d.ts.map +1 -0
- package/dist/explorer.js +67 -0
- package/dist/explorer.js.map +1 -0
- package/dist/gas.js.map +1 -1
- package/dist/hasher/merklemulti.js.map +1 -1
- package/dist/http-status.d.ts +20 -0
- package/dist/http-status.d.ts.map +1 -0
- package/dist/http-status.js +25 -0
- package/dist/http-status.js.map +1 -0
- package/dist/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/offchain.js.map +1 -1
- package/dist/requests.d.ts +4 -4
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +6 -6
- package/dist/requests.js.map +1 -1
- package/dist/solana/cleanup.js +2 -2
- package/dist/solana/cleanup.js.map +1 -1
- package/dist/solana/exec.js +1 -5
- package/dist/solana/exec.js.map +1 -1
- package/dist/solana/index.d.ts +28 -57
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +66 -70
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/logs.js +2 -2
- package/dist/solana/logs.js.map +1 -1
- package/dist/solana/offchain.js +3 -3
- package/dist/solana/offchain.js.map +1 -1
- package/dist/solana/send.js +1 -1
- package/dist/solana/send.js.map +1 -1
- package/dist/solana/utils.js +1 -1
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/discovery.d.ts.map +1 -1
- package/dist/sui/discovery.js +2 -5
- package/dist/sui/discovery.js.map +1 -1
- package/dist/sui/events.d.ts.map +1 -1
- package/dist/sui/events.js +2 -8
- package/dist/sui/events.js.map +1 -1
- package/dist/sui/index.d.ts +18 -29
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +24 -26
- package/dist/sui/index.js.map +1 -1
- package/dist/sui/manuallyExec/encoder.d.ts.map +1 -1
- package/dist/sui/manuallyExec/encoder.js +0 -3
- package/dist/sui/manuallyExec/encoder.js.map +1 -1
- package/dist/sui/manuallyExec/index.d.ts.map +1 -1
- package/dist/sui/manuallyExec/index.js +1 -2
- package/dist/sui/manuallyExec/index.js.map +1 -1
- package/dist/sui/objects.js +4 -4
- package/dist/sui/objects.js.map +1 -1
- package/dist/ton/exec.d.ts +2 -2
- package/dist/ton/exec.d.ts.map +1 -1
- package/dist/ton/exec.js.map +1 -1
- package/dist/ton/hasher.js +5 -5
- package/dist/ton/hasher.js.map +1 -1
- package/dist/ton/index.d.ts +54 -49
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +331 -236
- package/dist/ton/index.js.map +1 -1
- package/dist/ton/logs.d.ts +11 -22
- package/dist/ton/logs.d.ts.map +1 -1
- package/dist/ton/logs.js +95 -118
- package/dist/ton/logs.js.map +1 -1
- package/dist/ton/types.d.ts +9 -9
- package/dist/ton/types.d.ts.map +1 -1
- package/dist/ton/types.js +5 -9
- package/dist/ton/types.js.map +1 -1
- package/dist/ton/utils.d.ts +8 -27
- package/dist/ton/utils.d.ts.map +1 -1
- package/dist/ton/utils.js +31 -111
- package/dist/ton/utils.js.map +1 -1
- package/dist/types.d.ts +34 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +30 -0
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +6 -5
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +76 -52
- package/dist/utils.js.map +1 -1
- package/dist/viem.d.ts +6 -0
- package/dist/viem.d.ts.map +1 -0
- package/dist/viem.js +6 -0
- package/dist/viem.js.map +1 -0
- package/package.json +18 -3
- package/src/api/index.ts +167 -0
- package/src/api/types.ts +39 -0
- package/src/aptos/index.ts +57 -64
- package/src/aptos/logs.ts +10 -10
- package/src/aptos/token.ts +1 -1
- package/src/chain.ts +274 -97
- package/src/commits.ts +5 -5
- package/src/errors/CCIPError.ts +5 -2
- package/src/errors/codes.ts +8 -1
- package/src/errors/index.ts +15 -2
- package/src/errors/recovery.ts +9 -1
- package/src/errors/specialized.ts +61 -6
- package/src/evm/errors.ts +2 -2
- package/src/evm/index.ts +107 -120
- package/src/evm/logs.ts +4 -4
- package/src/evm/offchain.ts +5 -4
- package/src/evm/viem/client-adapter.ts +124 -0
- package/src/evm/viem/index.ts +29 -0
- package/src/evm/viem/types.ts +14 -0
- package/src/evm/viem/wallet-adapter.ts +233 -0
- package/src/execution.ts +9 -9
- package/src/explorer.ts +90 -0
- package/src/gas.ts +2 -2
- package/src/hasher/merklemulti.ts +7 -7
- package/src/http-status.ts +31 -0
- package/src/index.ts +19 -1
- package/src/offchain.ts +1 -1
- package/src/requests.ts +9 -12
- package/src/solana/cleanup.ts +4 -4
- package/src/solana/exec.ts +13 -18
- package/src/solana/index.ts +92 -117
- package/src/solana/logs.ts +8 -8
- package/src/solana/offchain.ts +3 -3
- package/src/solana/send.ts +20 -20
- package/src/solana/utils.ts +4 -4
- package/src/sui/discovery.ts +4 -10
- package/src/sui/events.ts +5 -12
- package/src/sui/index.ts +36 -48
- package/src/sui/manuallyExec/encoder.ts +0 -4
- package/src/sui/manuallyExec/index.ts +1 -3
- package/src/sui/objects.ts +14 -14
- package/src/ton/exec.ts +2 -5
- package/src/ton/hasher.ts +5 -5
- package/src/ton/index.ts +392 -316
- package/src/ton/logs.ts +122 -143
- package/src/ton/types.ts +17 -21
- package/src/ton/utils.ts +39 -145
- package/src/types.ts +36 -0
- package/src/utils.ts +96 -66
- package/src/viem.ts +5 -0
- package/tsconfig.json +3 -2
- package/dist/ton/bindings/offramp.d.ts +0 -48
- package/dist/ton/bindings/offramp.d.ts.map +0 -1
- package/dist/ton/bindings/offramp.js +0 -63
- package/dist/ton/bindings/offramp.js.map +0 -1
- package/dist/ton/bindings/onramp.d.ts +0 -40
- package/dist/ton/bindings/onramp.d.ts.map +0 -1
- package/dist/ton/bindings/onramp.js +0 -51
- package/dist/ton/bindings/onramp.js.map +0 -1
- package/dist/ton/bindings/router.d.ts +0 -47
- package/dist/ton/bindings/router.d.ts.map +0 -1
- package/dist/ton/bindings/router.js +0 -51
- package/dist/ton/bindings/router.js.map +0 -1
- package/src/ton/bindings/offramp.ts +0 -96
- package/src/ton/bindings/onramp.ts +0 -72
- package/src/ton/bindings/router.ts +0 -65
package/src/ton/index.ts
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import { Address, Cell, beginCell, toNano } from '@ton/core'
|
|
2
|
-
import {
|
|
3
|
-
import { type
|
|
4
|
-
import {
|
|
1
|
+
import { type Transaction, Address, Cell, beginCell, toNano } from '@ton/core'
|
|
2
|
+
import { TonClient } from '@ton/ton'
|
|
3
|
+
import { type AxiosAdapter, getAdapter } from 'axios'
|
|
4
|
+
import { type BytesLike, hexlify, isBytesLike, isHexString, toBeArray, toBeHex } from 'ethers'
|
|
5
|
+
import { type Memoized, memoize } from 'micro-memoize'
|
|
5
6
|
import type { PickDeep } from 'type-fest'
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import { type LogFilter, Chain } from '../chain.ts'
|
|
8
|
+
import { streamTransactionsForAddress } from './logs.ts'
|
|
9
|
+
import { type ChainContext, type LogFilter, Chain } from '../chain.ts'
|
|
9
10
|
import {
|
|
10
11
|
CCIPArgumentInvalidError,
|
|
11
12
|
CCIPExtraArgsInvalidError,
|
|
12
13
|
CCIPHttpError,
|
|
13
14
|
CCIPNotImplementedError,
|
|
15
|
+
CCIPReceiptNotFoundError,
|
|
14
16
|
CCIPSourceChainUnsupportedError,
|
|
17
|
+
CCIPTopicsInvalidError,
|
|
15
18
|
CCIPTransactionNotFoundError,
|
|
16
19
|
CCIPWalletInvalidError,
|
|
17
20
|
} from '../errors/specialized.ts'
|
|
18
21
|
import { type EVMExtraArgsV2, type ExtraArgs, EVMExtraArgsV2Tag } from '../extra-args.ts'
|
|
19
|
-
import {
|
|
22
|
+
import { getMessagesInTx } from '../requests.ts'
|
|
20
23
|
import { supportedChains } from '../supported-chains.ts'
|
|
21
24
|
import {
|
|
22
|
-
type
|
|
25
|
+
type CCIPExecution,
|
|
23
26
|
type CCIPRequest,
|
|
24
27
|
type ChainTransaction,
|
|
25
28
|
type CommitReport,
|
|
@@ -31,23 +34,22 @@ import {
|
|
|
31
34
|
type OffchainTokenData,
|
|
32
35
|
type WithLogger,
|
|
33
36
|
ChainFamily,
|
|
37
|
+
ExecutionState,
|
|
34
38
|
} from '../types.ts'
|
|
35
39
|
import {
|
|
36
40
|
bytesToBuffer,
|
|
37
41
|
createRateLimitedFetch,
|
|
38
42
|
decodeAddress,
|
|
39
|
-
getDataBytes,
|
|
40
43
|
networkInfo,
|
|
41
44
|
parseTypeAndVersion,
|
|
45
|
+
sleep,
|
|
42
46
|
} from '../utils.ts'
|
|
43
|
-
import { OffRamp } from './bindings/offramp.ts'
|
|
44
|
-
import { OnRamp } from './bindings/onramp.ts'
|
|
45
|
-
import { Router } from './bindings/router.ts'
|
|
46
47
|
import { generateUnsignedExecuteReport as generateUnsignedExecuteReportImpl } from './exec.ts'
|
|
47
48
|
import { getTONLeafHasher } from './hasher.ts'
|
|
48
49
|
import { type CCIPMessage_V1_6_TON, type UnsignedTONTx, isTONWallet } from './types.ts'
|
|
49
|
-
import { lookupTxByRawHash, parseJettonContent
|
|
50
|
+
import { crc32, lookupTxByRawHash, parseJettonContent } from './utils.ts'
|
|
50
51
|
import type { LeafHasher } from '../hasher/common.ts'
|
|
52
|
+
export type { TONWallet, UnsignedTONTx } from './types.ts'
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* Type guard to check if an error is a TVM error with an exit code.
|
|
@@ -74,13 +76,8 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
74
76
|
}
|
|
75
77
|
static readonly family = ChainFamily.TON
|
|
76
78
|
static readonly decimals = 9 // TON uses 9 decimals (nanotons)
|
|
77
|
-
|
|
78
|
-
readonly provider:
|
|
79
|
-
/**
|
|
80
|
-
* Cache mapping logical time (lt) to Unix timestamp.
|
|
81
|
-
* Populated during getLogs iteration for later getBlockTimestamp lookups.
|
|
82
|
-
*/
|
|
83
|
-
private readonly ltTimestampCache: Map<number, number> = new Map()
|
|
79
|
+
readonly rateLimitedFetch: typeof fetch
|
|
80
|
+
readonly provider: TonClient
|
|
84
81
|
|
|
85
82
|
/**
|
|
86
83
|
* Creates a new TONChain instance.
|
|
@@ -88,74 +85,128 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
88
85
|
* @param network - Network information for this chain.
|
|
89
86
|
* @param ctx - Context containing logger.
|
|
90
87
|
*/
|
|
91
|
-
constructor(
|
|
88
|
+
constructor(
|
|
89
|
+
client: TonClient,
|
|
90
|
+
network: NetworkInfo,
|
|
91
|
+
ctx?: ChainContext & { fetchFn?: typeof fetch },
|
|
92
|
+
) {
|
|
92
93
|
super(network, ctx)
|
|
93
94
|
this.provider = client
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
|
|
96
|
+
const txCache = new Map<string, Transaction[]>()
|
|
97
|
+
const txDepleted: Record<string, boolean> = {}
|
|
98
|
+
const origGetTransactions = this.provider.getTransactions.bind(this.provider)
|
|
99
|
+
// cached getTransactions, used for getLogs
|
|
100
|
+
this.provider.getTransactions = async (
|
|
101
|
+
address: Address,
|
|
102
|
+
opts: Parameters<typeof this.provider.getTransactions>[1],
|
|
103
|
+
): Promise<Transaction[]> => {
|
|
104
|
+
const key = address.toString()
|
|
105
|
+
let allTxs
|
|
106
|
+
if (txCache.has(key)) {
|
|
107
|
+
allTxs = txCache.get(key)!
|
|
108
|
+
} else {
|
|
109
|
+
allTxs = [] as Transaction[]
|
|
110
|
+
txCache.set(key, allTxs)
|
|
111
|
+
}
|
|
112
|
+
let txs
|
|
113
|
+
if (!opts.hash) {
|
|
114
|
+
// if no cursor, always fetch most recent transactions
|
|
115
|
+
txs = await origGetTransactions(address, opts)
|
|
116
|
+
} else {
|
|
117
|
+
const hash = opts.hash
|
|
118
|
+
// otherwise, look to see if we have it already cached
|
|
119
|
+
let idx = allTxs.findIndex((tx) => tx.hash().toString('base64') === hash)
|
|
120
|
+
if (idx >= 0 && !opts.inclusive) idx++ // skip first if not inclusive
|
|
121
|
+
// if found, and we have more than requested limit in cache, or we'd previously reached bottom of address
|
|
122
|
+
if (idx >= 0 && (allTxs.length - idx >= opts.limit || txDepleted[key])) {
|
|
123
|
+
return allTxs.slice(idx, idx + opts.limit) // return cached
|
|
124
|
+
}
|
|
125
|
+
// otherwise, fetch after end
|
|
126
|
+
txs = await origGetTransactions(address, opts)
|
|
127
|
+
}
|
|
128
|
+
// add/merge unique/new/unseen txs to allTxs
|
|
129
|
+
const allTxsHashes = new Set(allTxs.map((tx) => tx.hash().toString('base64')))
|
|
130
|
+
allTxs.push(...txs.filter((tx) => !allTxsHashes.has(tx.hash().toString('base64'))))
|
|
131
|
+
allTxs.sort((a, b) => Number(b.lt - a.lt)) // merge sorted inverse order
|
|
132
|
+
if (txs.length < opts.limit) txDepleted[key] = true // bottom reached
|
|
133
|
+
return txs
|
|
105
134
|
}
|
|
106
135
|
|
|
136
|
+
// Rate-limited fetch for TonCenter API (public tier: ~1 req/sec)
|
|
137
|
+
this.rateLimitedFetch =
|
|
138
|
+
ctx?.fetchFn ?? createRateLimitedFetch({ maxRequests: 1, windowMs: 1500, maxRetries: 5 }, ctx)
|
|
139
|
+
|
|
107
140
|
this.getTransaction = memoize(this.getTransaction.bind(this), {
|
|
108
141
|
maxSize: 100,
|
|
109
142
|
})
|
|
143
|
+
|
|
144
|
+
this.getBlockTimestamp = memoize(this.getBlockTimestamp.bind(this), {
|
|
145
|
+
async: true,
|
|
146
|
+
maxArgs: 1,
|
|
147
|
+
maxSize: 100,
|
|
148
|
+
forceUpdate: ([k]) => typeof k !== 'number' || k <= 0,
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
this.typeAndVersion = memoize(this.typeAndVersion.bind(this), {
|
|
152
|
+
maxArgs: 1,
|
|
153
|
+
async: true,
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Detect client network and instantiate a TONChain instance.
|
|
159
|
+
*/
|
|
160
|
+
static async fromClient(
|
|
161
|
+
client: TonClient,
|
|
162
|
+
ctx?: ChainContext & { fetchFn?: typeof fetch },
|
|
163
|
+
): Promise<TONChain> {
|
|
164
|
+
// Verify connection by getting the latest block
|
|
165
|
+
const isTestnet =
|
|
166
|
+
(
|
|
167
|
+
await client.getContractState(
|
|
168
|
+
Address.parse('EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'), // mainnet USDT
|
|
169
|
+
)
|
|
170
|
+
).state !== 'active'
|
|
171
|
+
return new TONChain(client, networkInfo(isTestnet ? 'ton-testnet' : 'ton-mainnet'), ctx)
|
|
110
172
|
}
|
|
111
173
|
|
|
112
174
|
/**
|
|
113
175
|
* Creates a TONChain instance from an RPC URL.
|
|
114
176
|
* Verifies the connection and detects the network.
|
|
115
177
|
*
|
|
116
|
-
* @param url - RPC endpoint URL for
|
|
178
|
+
* @param url - RPC endpoint URL for TonClient (v2).
|
|
117
179
|
* @param ctx - Context containing logger.
|
|
118
180
|
* @returns A new TONChain instance.
|
|
119
181
|
*/
|
|
120
|
-
static async fromUrl(url: string, ctx?:
|
|
182
|
+
static async fromUrl(url: string, ctx?: ChainContext): Promise<TONChain> {
|
|
121
183
|
const { logger = console } = ctx ?? {}
|
|
184
|
+
if (!url.endsWith('/jsonRPC')) url += '/jsonRPC'
|
|
122
185
|
|
|
123
|
-
|
|
124
|
-
let
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
186
|
+
let fetchFn
|
|
187
|
+
let httpAdapter
|
|
188
|
+
if (['toncenter.com', 'tonapi.io'].some((d) => url.includes(d))) {
|
|
189
|
+
logger.warn(
|
|
190
|
+
'Public TONCenter API calls are rate-limited to ~1 req/sec, some commands may be slow',
|
|
191
|
+
)
|
|
192
|
+
fetchFn = createRateLimitedFetch({ maxRequests: 1, windowMs: 1500, maxRetries: 5 }, ctx)
|
|
193
|
+
httpAdapter = (getAdapter as (name: string, config: object) => AxiosAdapter)('fetch', {
|
|
194
|
+
env: { fetch: fetchFn },
|
|
195
|
+
})
|
|
129
196
|
}
|
|
130
197
|
|
|
131
|
-
const
|
|
132
|
-
const client = new TonClient4({ endpoint: url })
|
|
133
|
-
|
|
134
|
-
// Verify connection by getting the latest block
|
|
198
|
+
const client = new TonClient({ endpoint: url, httpAdapter })
|
|
135
199
|
try {
|
|
136
|
-
await
|
|
137
|
-
|
|
200
|
+
const chain = await this.fromClient(client, {
|
|
201
|
+
...ctx,
|
|
202
|
+
fetchFn,
|
|
203
|
+
})
|
|
204
|
+
logger.debug(`Connected to TON V2 endpoint: ${url}`)
|
|
205
|
+
return chain
|
|
138
206
|
} catch (error) {
|
|
139
207
|
const message = error instanceof Error ? error.message : String(error)
|
|
140
|
-
throw new CCIPHttpError(0, `Failed to connect to
|
|
208
|
+
throw new CCIPHttpError(0, `Failed to connect to TONv2 endpoint ${url}: ${message}`)
|
|
141
209
|
}
|
|
142
|
-
|
|
143
|
-
// Detect network from hostname
|
|
144
|
-
let networkId: string
|
|
145
|
-
if (hostname.includes('testnet')) {
|
|
146
|
-
networkId = 'ton-testnet'
|
|
147
|
-
} else if (
|
|
148
|
-
hostname === 'localhost' ||
|
|
149
|
-
hostname === '127.0.0.1' ||
|
|
150
|
-
hostname.includes('sandbox')
|
|
151
|
-
) {
|
|
152
|
-
networkId = 'ton-localnet'
|
|
153
|
-
} else {
|
|
154
|
-
// Default to mainnet for production endpoints
|
|
155
|
-
networkId = 'ton-mainnet'
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return new TONChain(client, networkInfo(networkId), ctx)
|
|
159
210
|
}
|
|
160
211
|
|
|
161
212
|
/**
|
|
@@ -169,16 +220,8 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
169
220
|
* @returns Unix timestamp in seconds
|
|
170
221
|
*/
|
|
171
222
|
async getBlockTimestamp(block: number | 'finalized'): Promise<number> {
|
|
172
|
-
if (block
|
|
173
|
-
|
|
174
|
-
const lastBlock = await this.provider.getLastBlock()
|
|
175
|
-
return lastBlock.now
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Check lt → timestamp cache
|
|
179
|
-
const cached = this.ltTimestampCache.get(block)
|
|
180
|
-
if (cached !== undefined) {
|
|
181
|
-
return cached
|
|
223
|
+
if (typeof block != 'number') {
|
|
224
|
+
return Promise.resolve(Math.floor(Date.now() / 1000))
|
|
182
225
|
}
|
|
183
226
|
|
|
184
227
|
// For TON, we cannot look up timestamp by lt alone without the account address.
|
|
@@ -196,97 +239,97 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
196
239
|
* 1. Composite format: "workchain:address:lt:hash" (e.g., "0:abc123...def:12345:abc123...def")
|
|
197
240
|
* 2. Raw hash format: 64-character hex string resolved via TonCenter V3 API
|
|
198
241
|
*
|
|
199
|
-
* Note:
|
|
242
|
+
* Note: TonClient requires (address, lt, hash) for lookups. Raw hash lookups
|
|
200
243
|
* use TonCenter's V3 index API to resolve the hash to a full identifier first.
|
|
201
244
|
*
|
|
202
|
-
* @param
|
|
245
|
+
* @param tx - Transaction identifier in either format
|
|
203
246
|
* @returns ChainTransaction with transaction details
|
|
204
247
|
* Note: `blockNumber` contains logical time (lt), not block seqno
|
|
205
248
|
*/
|
|
206
|
-
async getTransaction(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
249
|
+
async getTransaction(tx: string | Transaction): Promise<ChainTransaction> {
|
|
250
|
+
let address
|
|
251
|
+
if (typeof tx === 'string') {
|
|
252
|
+
let parts = tx.split(':')
|
|
253
|
+
|
|
254
|
+
// If not composite format (4 parts), check if it's a raw 64-char hex hash
|
|
255
|
+
if (parts.length !== 4) {
|
|
256
|
+
const cleanHash = tx.startsWith('0x') || tx.startsWith('0X') ? tx.slice(2) : tx
|
|
257
|
+
|
|
258
|
+
if (!/^[a-fA-F0-9]{64}$/.test(cleanHash))
|
|
259
|
+
throw new CCIPArgumentInvalidError(
|
|
260
|
+
'hash',
|
|
261
|
+
`Invalid TON transaction hash format: "${tx}". Expected "workchain:address:lt:hash" or 64-char hex hash`,
|
|
262
|
+
)
|
|
215
263
|
const txInfo = await lookupTxByRawHash(
|
|
216
264
|
cleanHash,
|
|
217
|
-
isTestnet,
|
|
265
|
+
this.network.isTestnet,
|
|
218
266
|
this.rateLimitedFetch,
|
|
219
|
-
this
|
|
267
|
+
this,
|
|
220
268
|
)
|
|
221
269
|
|
|
222
|
-
|
|
223
|
-
this.logger.debug
|
|
224
|
-
|
|
225
|
-
return this.getTransaction(compositeHash)
|
|
270
|
+
tx = `${txInfo.account}:${txInfo.lt}:${cleanHash}`
|
|
271
|
+
this.logger.debug(`Resolved raw hash to composite: ${tx}`)
|
|
272
|
+
parts = tx.split(':')
|
|
226
273
|
}
|
|
227
274
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Parse composite format: workchain:address:lt:hash
|
|
235
|
-
const address = Address.parseRaw(`${parts[0]}:${parts[1]}`)
|
|
236
|
-
const lt = parts[2]
|
|
237
|
-
const txHash = parts[3]
|
|
275
|
+
// Parse composite format: workchain:address:lt:hash
|
|
276
|
+
address = Address.parseRaw(`${parts[0]}:${parts[1]}`)
|
|
277
|
+
const [, , lt, txHash] = parts as [string, string, string, string]
|
|
238
278
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
throw new CCIPTransactionNotFoundError(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const txs = await this.provider.getAccountTransactions(
|
|
250
|
-
address,
|
|
251
|
-
BigInt(lt),
|
|
252
|
-
Buffer.from(txHash, 'hex'),
|
|
253
|
-
)
|
|
254
|
-
|
|
255
|
-
if (!txs || txs.length === 0) {
|
|
256
|
-
throw new CCIPTransactionNotFoundError(hash)
|
|
279
|
+
// Fetch transactions and find the one we're looking for
|
|
280
|
+
const tx_ = await this.provider.getTransaction(
|
|
281
|
+
address,
|
|
282
|
+
lt,
|
|
283
|
+
Buffer.from(txHash, 'hex').toString('base64'),
|
|
284
|
+
)
|
|
285
|
+
if (!tx_) throw new CCIPTransactionNotFoundError(tx)
|
|
286
|
+
tx = tx_
|
|
287
|
+
} else {
|
|
288
|
+
address = new Address(0, Buffer.from(toBeArray(tx.address, 32)))
|
|
257
289
|
}
|
|
258
290
|
|
|
259
|
-
const tx = txs[0].tx
|
|
260
|
-
const txLt = Number(tx.lt)
|
|
261
|
-
|
|
262
291
|
// Cache lt → timestamp for later getBlockTimestamp lookups
|
|
263
|
-
this.
|
|
292
|
+
;(this.getBlockTimestamp as Memoized<typeof this.getBlockTimestamp, { async: true }>).cache.set(
|
|
293
|
+
[Number(tx.lt)],
|
|
294
|
+
Promise.resolve(tx.now),
|
|
295
|
+
)
|
|
264
296
|
|
|
265
297
|
// Extract logs from outgoing external messages
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
address: address.toRawString(),
|
|
273
|
-
topics: [],
|
|
274
|
-
data: msg.body.toBoc().toString('base64'),
|
|
275
|
-
blockNumber: txLt, // Note: This is lt (logical time), not block seqno
|
|
276
|
-
transactionHash: hash,
|
|
277
|
-
index: index,
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
index++
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return {
|
|
284
|
-
hash,
|
|
285
|
-
logs,
|
|
286
|
-
blockNumber: txLt, // Note: This is lt (logical time), not block seqno
|
|
298
|
+
// Build composite hash format: workchain:address:lt:hash
|
|
299
|
+
const compositeHash = `${address.toRawString()}:${tx.lt}:${tx.hash().toString('hex')}`
|
|
300
|
+
const res = {
|
|
301
|
+
hash: compositeHash,
|
|
302
|
+
logs: [] as Log_[],
|
|
303
|
+
blockNumber: Number(tx.lt), // Note: This is lt (logical time), not block seqno
|
|
287
304
|
timestamp: tx.now,
|
|
288
305
|
from: address.toRawString(),
|
|
306
|
+
tx,
|
|
289
307
|
}
|
|
308
|
+
const logs: Log_[] = []
|
|
309
|
+
for (const [index, msg] of tx.outMessages) {
|
|
310
|
+
if (msg.info.type !== 'external-out') continue
|
|
311
|
+
const topics = []
|
|
312
|
+
// logs are external messages where dest "address" is the uint32 topic (e.g. crc32("ExecutionStateChanged"))
|
|
313
|
+
if (msg.info.dest && msg.info.dest.value > 0n && msg.info.dest.value < 2n ** 32n)
|
|
314
|
+
topics.push(toBeHex(msg.info.dest.value, 4))
|
|
315
|
+
let data = ''
|
|
316
|
+
try {
|
|
317
|
+
data = msg.body.toBoc().toString('base64')
|
|
318
|
+
} catch (_) {
|
|
319
|
+
// ignore
|
|
320
|
+
}
|
|
321
|
+
logs.push({
|
|
322
|
+
address: msg.info.src.toRawString(),
|
|
323
|
+
topics,
|
|
324
|
+
data,
|
|
325
|
+
blockNumber: res.blockNumber, // Note: This is lt (logical time), not block seqno
|
|
326
|
+
transactionHash: res.hash,
|
|
327
|
+
index,
|
|
328
|
+
tx: res,
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
res.logs = logs
|
|
332
|
+
return res
|
|
290
333
|
}
|
|
291
334
|
|
|
292
335
|
/**
|
|
@@ -297,21 +340,34 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
297
340
|
*
|
|
298
341
|
* @param opts - Log filter options (startBlock/endBlock are interpreted as lt values)
|
|
299
342
|
*/
|
|
300
|
-
async *getLogs(opts: LogFilter
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
343
|
+
async *getLogs(opts: LogFilter): AsyncIterableIterator<Log_> {
|
|
344
|
+
let topics
|
|
345
|
+
if (opts.topics?.length) {
|
|
346
|
+
if (!opts.topics.every((topic) => typeof topic === 'string'))
|
|
347
|
+
throw new CCIPTopicsInvalidError(opts.topics)
|
|
348
|
+
// append events discriminants (if not 0x-8B already), but keep OG topics
|
|
349
|
+
topics = new Set([
|
|
350
|
+
...opts.topics,
|
|
351
|
+
...opts.topics.filter((t) => !isHexString(t, 8)).map((t) => crc32(t)),
|
|
352
|
+
])
|
|
353
|
+
}
|
|
354
|
+
for await (const tx of streamTransactionsForAddress(opts, this)) {
|
|
355
|
+
const logs =
|
|
356
|
+
opts.startBlock == null && opts.startTime == null ? tx.logs.toReversed() : tx.logs
|
|
357
|
+
for (const log of logs) {
|
|
358
|
+
if (topics && !topics.has(log.topics[0]!)) continue
|
|
359
|
+
yield log
|
|
360
|
+
}
|
|
304
361
|
}
|
|
305
|
-
yield* fetchLogs(this.provider, opts, this.ltTimestampCache, decoders)
|
|
306
362
|
}
|
|
307
363
|
|
|
308
|
-
/** {@inheritDoc Chain.
|
|
309
|
-
override async
|
|
310
|
-
return
|
|
364
|
+
/** {@inheritDoc Chain.getMessagesInTx} */
|
|
365
|
+
override async getMessagesInTx(tx: string | ChainTransaction): Promise<CCIPRequest[]> {
|
|
366
|
+
return getMessagesInTx(this, typeof tx === 'string' ? await this.getTransaction(tx) : tx)
|
|
311
367
|
}
|
|
312
368
|
|
|
313
|
-
/** {@inheritDoc Chain.
|
|
314
|
-
override async
|
|
369
|
+
/** {@inheritDoc Chain.getMessagesInBatch} */
|
|
370
|
+
override async getMessagesInBatch<
|
|
315
371
|
R extends PickDeep<
|
|
316
372
|
CCIPRequest,
|
|
317
373
|
'lane' | `log.${'topics' | 'address' | 'blockNumber'}` | 'message.sequenceNumber'
|
|
@@ -321,28 +377,20 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
321
377
|
_commit: Pick<CommitReport, 'minSeqNr' | 'maxSeqNr'>,
|
|
322
378
|
_opts?: { page?: number },
|
|
323
379
|
): Promise<R['message'][]> {
|
|
324
|
-
return Promise.reject(new CCIPNotImplementedError('
|
|
380
|
+
return Promise.reject(new CCIPNotImplementedError('getMessagesInBatch'))
|
|
325
381
|
}
|
|
326
382
|
|
|
327
383
|
/** {@inheritDoc Chain.typeAndVersion} */
|
|
328
|
-
async typeAndVersion(
|
|
329
|
-
address: string,
|
|
330
|
-
): Promise<
|
|
331
|
-
| [type_: string, version: string, typeAndVersion: string]
|
|
332
|
-
| [type_: string, version: string, typeAndVersion: string, suffix: string]
|
|
333
|
-
> {
|
|
384
|
+
async typeAndVersion(address: string) {
|
|
334
385
|
const tonAddress = Address.parse(address)
|
|
335
386
|
|
|
336
|
-
// Get current block for state lookup
|
|
337
|
-
const lastBlock = await this.provider.getLastBlock()
|
|
338
|
-
|
|
339
387
|
// Call the typeAndVersion getter method on the contract
|
|
340
|
-
const result = await this.provider.runMethod(
|
|
388
|
+
const result = await this.provider.runMethod(tonAddress, 'typeAndVersion')
|
|
341
389
|
|
|
342
390
|
// Parse the two string slices returned by the contract
|
|
343
391
|
// TON contracts return strings as cells with snake format encoding
|
|
344
|
-
const typeCell = result.
|
|
345
|
-
const versionCell = result.
|
|
392
|
+
const typeCell = result.stack.readCell()
|
|
393
|
+
const versionCell = result.stack.readCell()
|
|
346
394
|
|
|
347
395
|
// Load strings from cells using snake format
|
|
348
396
|
const contractType = typeCell.beginParse().loadStringTail()
|
|
@@ -355,31 +403,25 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
355
403
|
// Format as "Type Version" and use the common parser
|
|
356
404
|
const typeAndVersionStr = `${shortType} ${version}`
|
|
357
405
|
|
|
358
|
-
return parseTypeAndVersion(typeAndVersionStr)
|
|
359
|
-
| [type_: string, version: string, typeAndVersion: string]
|
|
360
|
-
| [type_: string, version: string, typeAndVersion: string, suffix: string]
|
|
406
|
+
return parseTypeAndVersion(typeAndVersionStr)
|
|
361
407
|
}
|
|
362
408
|
|
|
363
409
|
/** {@inheritDoc Chain.getRouterForOnRamp} */
|
|
364
410
|
async getRouterForOnRamp(onRamp: string, destChainSelector: bigint): Promise<string> {
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return destConfig.router.toString()
|
|
411
|
+
const { stack: destConfig } = await this.provider.runMethod(
|
|
412
|
+
Address.parse(onRamp),
|
|
413
|
+
'destChainConfig',
|
|
414
|
+
[{ type: 'int', value: destChainSelector }],
|
|
415
|
+
)
|
|
416
|
+
return destConfig.readAddress().toRawString()
|
|
373
417
|
}
|
|
374
418
|
|
|
375
419
|
/** {@inheritDoc Chain.getRouterForOffRamp} */
|
|
376
420
|
async getRouterForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const sourceConfig = await openedContract.getSourceChainConfig(sourceChainSelector)
|
|
382
|
-
return sourceConfig.router.toString()
|
|
421
|
+
const { stack } = await this.provider.runMethod(Address.parse(offRamp), 'sourceChainConfig', [
|
|
422
|
+
{ type: 'int', value: sourceChainSelector },
|
|
423
|
+
])
|
|
424
|
+
return stack.readAddress().toRawString()
|
|
383
425
|
}
|
|
384
426
|
|
|
385
427
|
/** {@inheritDoc Chain.getNativeTokenForRouter} */
|
|
@@ -389,42 +431,54 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
389
431
|
|
|
390
432
|
/** {@inheritDoc Chain.getOffRampsForRouter} */
|
|
391
433
|
async getOffRampsForRouter(router: string, sourceChainSelector: bigint): Promise<string[]> {
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const offRamp = await openedContract.getOffRamp(sourceChainSelector)
|
|
399
|
-
return [offRamp.toString()]
|
|
400
|
-
} catch (error) {
|
|
401
|
-
if (isTvmError(error) && error.exitCode === 261) {
|
|
402
|
-
return [] // Return empty array if no OffRamp configured for this source chain
|
|
403
|
-
}
|
|
404
|
-
throw error
|
|
405
|
-
}
|
|
434
|
+
const routerContract = this.provider.provider(Address.parse(router))
|
|
435
|
+
// Get the specific OffRamp for the source chain selector
|
|
436
|
+
const { stack } = await routerContract.get('offRamp', [
|
|
437
|
+
{ type: 'int', value: sourceChainSelector },
|
|
438
|
+
])
|
|
439
|
+
return [stack.readAddress().toRawString()]
|
|
406
440
|
}
|
|
407
441
|
|
|
408
442
|
/** {@inheritDoc Chain.getOnRampForRouter} */
|
|
409
443
|
async getOnRampForRouter(router: string, destChainSelector: bigint): Promise<string> {
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
return
|
|
444
|
+
const routerContract = this.provider.provider(Address.parse(router))
|
|
445
|
+
// Get the specific OnRamp for the source chain selector
|
|
446
|
+
const { stack } = await routerContract.get('onRamp', [
|
|
447
|
+
{ type: 'int', value: destChainSelector },
|
|
448
|
+
])
|
|
449
|
+
return stack.readAddress().toRawString()
|
|
416
450
|
}
|
|
417
451
|
|
|
418
452
|
/** {@inheritDoc Chain.getOnRampForOffRamp} */
|
|
419
453
|
async getOnRampForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
|
|
420
|
-
const offRampAddress = Address.parse(offRamp)
|
|
421
|
-
const offRampContract = OffRamp.createFromAddress(offRampAddress)
|
|
422
|
-
const openedContract = this.provider.open(offRampContract)
|
|
423
|
-
|
|
424
454
|
try {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
455
|
+
const offRampContract = this.provider.provider(Address.parse(offRamp))
|
|
456
|
+
|
|
457
|
+
const { stack } = await offRampContract.get('sourceChainConfig', [
|
|
458
|
+
{ type: 'int', value: sourceChainSelector },
|
|
459
|
+
])
|
|
460
|
+
stack.readAddress() // router
|
|
461
|
+
stack.readBoolean() // isEnabled
|
|
462
|
+
stack.readBigNumber() // minSeqNr
|
|
463
|
+
stack.readBoolean() // isRMNVerificationDisabled
|
|
464
|
+
|
|
465
|
+
// onRamp is stored as CrossChainAddress cell
|
|
466
|
+
const onRampCell = stack.readCell()
|
|
467
|
+
const onRampSlice = onRampCell.beginParse()
|
|
468
|
+
|
|
469
|
+
// Check if length-prefixed or raw format based on cell bit length
|
|
470
|
+
const cellBits = onRampCell.bits.length
|
|
471
|
+
let onRamp: Buffer
|
|
472
|
+
|
|
473
|
+
if (cellBits === 160) {
|
|
474
|
+
// Raw 20-byte EVM address (no length prefix)
|
|
475
|
+
onRamp = onRampSlice.loadBuffer(20)
|
|
476
|
+
} else {
|
|
477
|
+
// Length-prefixed format: 8-bit length + data
|
|
478
|
+
const onRampLength = onRampSlice.loadUint(8)
|
|
479
|
+
onRamp = onRampSlice.loadBuffer(onRampLength)
|
|
480
|
+
}
|
|
481
|
+
return decodeAddress(onRamp, networkInfo(sourceChainSelector).family)
|
|
428
482
|
} catch (error) {
|
|
429
483
|
if (isTvmError(error) && error.exitCode === 266) {
|
|
430
484
|
throw new CCIPSourceChainUnsupportedError(sourceChainSelector, {
|
|
@@ -449,25 +503,23 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
449
503
|
/** {@inheritDoc Chain.getTokenInfo} */
|
|
450
504
|
async getTokenInfo(token: string): Promise<{ symbol: string; decimals: number }> {
|
|
451
505
|
const tokenAddress = Address.parse(token)
|
|
452
|
-
|
|
506
|
+
if (tokenAddress.toRawString().match(/^[0:]+1$/)) {
|
|
507
|
+
return { symbol: 'TON', decimals: (this.constructor as typeof TONChain).decimals }
|
|
508
|
+
}
|
|
453
509
|
|
|
454
510
|
try {
|
|
455
|
-
const
|
|
456
|
-
lastBlock.last.seqno,
|
|
457
|
-
tokenAddress,
|
|
458
|
-
'get_jetton_data',
|
|
459
|
-
)
|
|
511
|
+
const { stack } = await this.provider.runMethod(tokenAddress, 'get_jetton_data')
|
|
460
512
|
|
|
461
513
|
// skips
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
514
|
+
stack.readBigNumber() // total_supply
|
|
515
|
+
stack.readBigNumber() // mintable
|
|
516
|
+
stack.readAddress() // admin_address
|
|
465
517
|
|
|
466
|
-
const contentCell =
|
|
518
|
+
const contentCell = stack.readCell()
|
|
467
519
|
return parseJettonContent(contentCell, this.rateLimitedFetch, this.logger)
|
|
468
520
|
} catch (error) {
|
|
469
|
-
this.logger.debug
|
|
470
|
-
return { symbol: '', decimals:
|
|
521
|
+
this.logger.debug(`Failed to get jetton data for ${token}:`, error)
|
|
522
|
+
return { symbol: '', decimals: (this.constructor as typeof TONChain).decimals }
|
|
471
523
|
}
|
|
472
524
|
}
|
|
473
525
|
|
|
@@ -481,20 +533,27 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
481
533
|
* @param log - Log with data field.
|
|
482
534
|
* @returns Decoded CCIPMessage or undefined if not valid.
|
|
483
535
|
*/
|
|
484
|
-
static decodeMessage(
|
|
485
|
-
|
|
536
|
+
static decodeMessage({
|
|
537
|
+
data,
|
|
538
|
+
topics,
|
|
539
|
+
}: {
|
|
540
|
+
data: unknown
|
|
541
|
+
topics?: readonly string[]
|
|
542
|
+
}): CCIPMessage_V1_6_TON | undefined {
|
|
543
|
+
if (!data || typeof data !== 'string') return
|
|
544
|
+
if (topics?.length && topics[0] !== crc32('CCIPMessageSent')) return
|
|
486
545
|
|
|
487
546
|
try {
|
|
488
547
|
// Parse BOC from base64
|
|
489
|
-
const boc =
|
|
490
|
-
const cell = Cell.fromBoc(boc)[0]
|
|
548
|
+
const boc = bytesToBuffer(data)
|
|
549
|
+
const cell = Cell.fromBoc(boc)[0]!
|
|
491
550
|
const slice = cell.beginParse()
|
|
492
551
|
|
|
493
552
|
// Load header fields directly (no topic prefix)
|
|
494
553
|
// Structure from TVM2AnyRampMessage:
|
|
495
554
|
// header: RampMessageHeader + sender: address + body: Cell + feeValueJuels: uint96
|
|
496
555
|
const header = {
|
|
497
|
-
messageId:
|
|
556
|
+
messageId: toBeHex(slice.loadUintBig(256), 32),
|
|
498
557
|
sourceChainSelector: slice.loadUintBig(64),
|
|
499
558
|
destChainSelector: slice.loadUintBig(64),
|
|
500
559
|
sequenceNumber: slice.loadUintBig(64),
|
|
@@ -502,7 +561,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
502
561
|
}
|
|
503
562
|
|
|
504
563
|
// Load sender address
|
|
505
|
-
const sender = slice.loadAddress()
|
|
564
|
+
const sender = slice.loadAddress().toString()
|
|
506
565
|
|
|
507
566
|
// Load body cell ref
|
|
508
567
|
const bodyCell = slice.loadRef()
|
|
@@ -532,7 +591,6 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
532
591
|
// Load data from ref 1
|
|
533
592
|
const dataSlice = bodySlice.loadRef().beginParse()
|
|
534
593
|
const dataBytes = dataSlice.loadBuffer(dataSlice.remainingBits / 8)
|
|
535
|
-
const data = '0x' + dataBytes.toString('hex')
|
|
536
594
|
|
|
537
595
|
// Load extraArgs from ref 2
|
|
538
596
|
const extraArgsCell = bodySlice.loadRef()
|
|
@@ -569,7 +627,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
569
627
|
...header,
|
|
570
628
|
sender,
|
|
571
629
|
receiver,
|
|
572
|
-
data,
|
|
630
|
+
data: hexlify(dataBytes),
|
|
573
631
|
tokenAmounts,
|
|
574
632
|
feeToken,
|
|
575
633
|
feeTokenAmount,
|
|
@@ -594,7 +652,6 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
594
652
|
* @returns Hex string of BOC-encoded extra args (0x-prefixed)
|
|
595
653
|
*/
|
|
596
654
|
static encodeExtraArgs(args: ExtraArgs): string {
|
|
597
|
-
if (!args) return '0x'
|
|
598
655
|
if ('gasLimit' in args && 'allowOutOfOrderExecution' in args) {
|
|
599
656
|
const cell = beginCell()
|
|
600
657
|
.storeUint(Number(EVMExtraArgsV2Tag), 32) // magic tag
|
|
@@ -623,11 +680,11 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
623
680
|
static decodeExtraArgs(
|
|
624
681
|
extraArgs: BytesLike,
|
|
625
682
|
): (EVMExtraArgsV2 & { _tag: 'EVMExtraArgsV2' }) | undefined {
|
|
626
|
-
const data =
|
|
683
|
+
const data = bytesToBuffer(extraArgs)
|
|
627
684
|
|
|
628
685
|
try {
|
|
629
686
|
// Parse BOC format to extract cell data
|
|
630
|
-
const cell = Cell.fromBoc(data)[0]
|
|
687
|
+
const cell = Cell.fromBoc(data)[0]!
|
|
631
688
|
const slice = cell.beginParse()
|
|
632
689
|
|
|
633
690
|
// Load and verify magic tag to ensure correct extra args type
|
|
@@ -652,35 +709,37 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
652
709
|
* @param lane - Optional lane info for filtering.
|
|
653
710
|
* @returns Array of CommitReport or undefined if not a valid commit event.
|
|
654
711
|
*/
|
|
655
|
-
static decodeCommits(
|
|
656
|
-
|
|
657
|
-
|
|
712
|
+
static decodeCommits(
|
|
713
|
+
{ data, topics }: { data: unknown; topics?: readonly string[] },
|
|
714
|
+
lane?: Lane,
|
|
715
|
+
): CommitReport[] | undefined {
|
|
716
|
+
if (!data || typeof data !== 'string') return
|
|
717
|
+
if (topics?.length && topics[0] !== crc32('CommitReportAccepted')) return
|
|
658
718
|
try {
|
|
659
|
-
const boc =
|
|
660
|
-
const cell = Cell.fromBoc(boc)[0]
|
|
719
|
+
const boc = bytesToBuffer(data)
|
|
720
|
+
const cell = Cell.fromBoc(boc)[0]!
|
|
661
721
|
const slice = cell.beginParse()
|
|
662
722
|
|
|
663
723
|
// Cell body starts directly with hasMerkleRoot (topic is in message header)
|
|
664
724
|
const hasMerkleRoot = slice.loadBit()
|
|
665
725
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
return undefined
|
|
669
|
-
}
|
|
726
|
+
// No merkle root: could be price-only update, skip for now
|
|
727
|
+
if (!hasMerkleRoot) return
|
|
670
728
|
|
|
671
729
|
// Read MerkleRoot fields inline
|
|
672
730
|
const sourceChainSelector = slice.loadUintBig(64)
|
|
673
731
|
const onRampLen = slice.loadUint(8)
|
|
674
732
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
return undefined
|
|
678
|
-
}
|
|
733
|
+
// Invalid onRamp length
|
|
734
|
+
if (onRampLen === 0 || onRampLen > 32) return
|
|
679
735
|
|
|
680
|
-
const
|
|
736
|
+
const onRampAddress = decodeAddress(
|
|
737
|
+
slice.loadBuffer(onRampLen),
|
|
738
|
+
networkInfo(sourceChainSelector).family,
|
|
739
|
+
)
|
|
681
740
|
const minSeqNr = slice.loadUintBig(64)
|
|
682
741
|
const maxSeqNr = slice.loadUintBig(64)
|
|
683
|
-
const merkleRoot =
|
|
742
|
+
const merkleRoot = hexlify(slice.loadBuffer(32))
|
|
684
743
|
|
|
685
744
|
// Read hasPriceUpdates (1 bit): we don't need the data but should consume it
|
|
686
745
|
if (slice.remainingBits >= 1) {
|
|
@@ -692,7 +751,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
692
751
|
|
|
693
752
|
const report: CommitReport = {
|
|
694
753
|
sourceChainSelector,
|
|
695
|
-
onRampAddress
|
|
754
|
+
onRampAddress,
|
|
696
755
|
minSeqNr,
|
|
697
756
|
maxSeqNr,
|
|
698
757
|
merkleRoot,
|
|
@@ -700,23 +759,66 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
700
759
|
|
|
701
760
|
// Filter by lane if provided
|
|
702
761
|
if (lane) {
|
|
703
|
-
if (report.sourceChainSelector !== lane.sourceChainSelector) return
|
|
704
|
-
if (report.onRampAddress
|
|
762
|
+
if (report.sourceChainSelector !== lane.sourceChainSelector) return
|
|
763
|
+
if (report.onRampAddress !== lane.onRamp) return
|
|
705
764
|
}
|
|
706
765
|
|
|
707
766
|
return [report]
|
|
708
767
|
} catch {
|
|
709
|
-
return
|
|
768
|
+
return
|
|
710
769
|
}
|
|
711
770
|
}
|
|
712
771
|
|
|
713
772
|
/**
|
|
714
773
|
* Decodes an execution receipt from a TON log event.
|
|
715
|
-
*
|
|
774
|
+
*
|
|
775
|
+
* The ExecutionStateChanged event structure (topic is in message header, not body):
|
|
776
|
+
* - sourceChainSelector: uint64 (8 bytes)
|
|
777
|
+
* - sequenceNumber: uint64 (8 bytes)
|
|
778
|
+
* - messageId: uint256 (32 bytes)
|
|
779
|
+
* - state: uint8 (1 byte) - Untouched=0, InProgress=1, Success=2, Failure=3
|
|
780
|
+
*
|
|
781
|
+
* @param log - Log with data field (base64-encoded BOC).
|
|
716
782
|
* @returns ExecutionReceipt or undefined if not valid.
|
|
717
783
|
*/
|
|
718
|
-
static decodeReceipt(
|
|
719
|
-
|
|
784
|
+
static decodeReceipt({
|
|
785
|
+
data,
|
|
786
|
+
topics,
|
|
787
|
+
}: {
|
|
788
|
+
data: unknown
|
|
789
|
+
topics?: readonly string[]
|
|
790
|
+
}): ExecutionReceipt | undefined {
|
|
791
|
+
if (!data || typeof data !== 'string') return
|
|
792
|
+
if (topics?.length && topics[0] !== crc32('ExecutionStateChanged')) return
|
|
793
|
+
|
|
794
|
+
try {
|
|
795
|
+
const boc = bytesToBuffer(data)
|
|
796
|
+
const cell = Cell.fromBoc(boc)[0]!
|
|
797
|
+
const slice = cell.beginParse()
|
|
798
|
+
|
|
799
|
+
// ExecutionStateChanged has no refs
|
|
800
|
+
if (cell.refs.length > 0) return
|
|
801
|
+
|
|
802
|
+
// Cell body contains only the struct fields
|
|
803
|
+
// ExecutionStateChanged: sourceChainSelector(64) + sequenceNumber(64) + messageId(256) + state(8)
|
|
804
|
+
const sourceChainSelector = slice.loadUintBig(64)
|
|
805
|
+
const sequenceNumber = slice.loadUintBig(64)
|
|
806
|
+
const messageId = toBeHex(slice.loadUintBig(256), 32)
|
|
807
|
+
const state = slice.loadUint(8)
|
|
808
|
+
|
|
809
|
+
// Validate state is a valid ExecutionState (2-3)
|
|
810
|
+
// TON has intermediary txs with state 1 (InProgress), but we filter them here
|
|
811
|
+
if (state !== ExecutionState.Success && state !== ExecutionState.Failed) return
|
|
812
|
+
|
|
813
|
+
return {
|
|
814
|
+
messageId,
|
|
815
|
+
sequenceNumber,
|
|
816
|
+
sourceChainSelector,
|
|
817
|
+
state: state as ExecutionState,
|
|
818
|
+
}
|
|
819
|
+
} catch {
|
|
820
|
+
// ignore
|
|
821
|
+
}
|
|
720
822
|
}
|
|
721
823
|
|
|
722
824
|
/**
|
|
@@ -799,7 +901,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
799
901
|
const parts = hash.split(':')
|
|
800
902
|
if (parts.length === 4) {
|
|
801
903
|
// Composite format: workchain:address:lt:hash - return just the hash part
|
|
802
|
-
return parts[3]
|
|
904
|
+
return parts[3]!
|
|
803
905
|
}
|
|
804
906
|
// Already raw format or unknown - return as-is
|
|
805
907
|
return hash
|
|
@@ -823,7 +925,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
823
925
|
// Check for composite format: workchain:address:lt:hash
|
|
824
926
|
const parts = v.split(':')
|
|
825
927
|
if (parts.length === 4) {
|
|
826
|
-
const [workchain, address, lt, hash] = parts
|
|
928
|
+
const [workchain, address, lt, hash] = parts as [string, string, string, string]
|
|
827
929
|
// workchain should be a number (typically 0 or -1)
|
|
828
930
|
if (!/^-?\d+$/.test(workchain)) return false
|
|
829
931
|
// address should be 64-char hex
|
|
@@ -848,43 +950,33 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
848
950
|
}
|
|
849
951
|
|
|
850
952
|
/** {@inheritDoc Chain.getFee} */
|
|
851
|
-
async getFee(
|
|
953
|
+
async getFee(_opts: Parameters<Chain['getFee']>[0]): Promise<bigint> {
|
|
852
954
|
return Promise.reject(new CCIPNotImplementedError('getFee'))
|
|
853
955
|
}
|
|
854
956
|
|
|
855
957
|
/** {@inheritDoc Chain.generateUnsignedSendMessage} */
|
|
856
958
|
generateUnsignedSendMessage(
|
|
857
|
-
|
|
858
|
-
_router: string,
|
|
859
|
-
_destChainSelector: bigint,
|
|
860
|
-
_message: AnyMessage & { fee?: bigint },
|
|
861
|
-
_opts?: { approveMax?: boolean },
|
|
959
|
+
_opts: Parameters<Chain['generateUnsignedSendMessage']>[0],
|
|
862
960
|
): Promise<never> {
|
|
863
961
|
return Promise.reject(new CCIPNotImplementedError('generateUnsignedSendMessage'))
|
|
864
962
|
}
|
|
865
963
|
|
|
866
964
|
/** {@inheritDoc Chain.sendMessage} */
|
|
867
|
-
async sendMessage(
|
|
868
|
-
_router: string,
|
|
869
|
-
_destChainSelector: bigint,
|
|
870
|
-
_message: AnyMessage & { fee: bigint },
|
|
871
|
-
_opts?: { wallet?: unknown; approveMax?: boolean },
|
|
872
|
-
): Promise<CCIPRequest> {
|
|
965
|
+
async sendMessage(_opts: Parameters<Chain['sendMessage']>[0]): Promise<CCIPRequest> {
|
|
873
966
|
return Promise.reject(new CCIPNotImplementedError('sendMessage'))
|
|
874
967
|
}
|
|
875
968
|
|
|
876
|
-
/** {@inheritDoc Chain.
|
|
877
|
-
|
|
969
|
+
/** {@inheritDoc Chain.getOffchainTokenData} */
|
|
970
|
+
getOffchainTokenData(request: CCIPRequest): Promise<OffchainTokenData[]> {
|
|
878
971
|
return Promise.resolve(request.message.tokenAmounts.map(() => undefined))
|
|
879
972
|
}
|
|
880
973
|
|
|
881
974
|
/** {@inheritDoc Chain.generateUnsignedExecuteReport} */
|
|
882
|
-
generateUnsignedExecuteReport(
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
): Promise<UnsignedTONTx> {
|
|
975
|
+
generateUnsignedExecuteReport({
|
|
976
|
+
offRamp,
|
|
977
|
+
execReport,
|
|
978
|
+
...opts
|
|
979
|
+
}: Parameters<Chain['generateUnsignedExecuteReport']>[0]): Promise<UnsignedTONTx> {
|
|
888
980
|
if (!('allowOutOfOrderExecution' in execReport.message && 'gasLimit' in execReport.message)) {
|
|
889
981
|
throw new CCIPExtraArgsInvalidError('TON')
|
|
890
982
|
}
|
|
@@ -897,57 +989,41 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
897
989
|
|
|
898
990
|
return Promise.resolve({
|
|
899
991
|
family: ChainFamily.TON,
|
|
900
|
-
|
|
901
|
-
body: unsigned.body,
|
|
992
|
+
...unsigned,
|
|
902
993
|
})
|
|
903
994
|
}
|
|
904
995
|
|
|
905
996
|
/** {@inheritDoc Chain.executeReport} */
|
|
906
|
-
async executeReport(
|
|
907
|
-
offRamp
|
|
908
|
-
execReport: ExecutionReport,
|
|
909
|
-
opts: { wallet: unknown; gasLimit?: number },
|
|
910
|
-
): Promise<ChainTransaction> {
|
|
911
|
-
const wallet = opts.wallet
|
|
997
|
+
async executeReport(opts: Parameters<Chain['executeReport']>[0]): Promise<CCIPExecution> {
|
|
998
|
+
const { offRamp, wallet } = opts
|
|
912
999
|
if (!isTONWallet(wallet)) {
|
|
913
1000
|
throw new CCIPWalletInvalidError(wallet)
|
|
914
1001
|
}
|
|
1002
|
+
const payer = await wallet.getAddress()
|
|
915
1003
|
|
|
916
|
-
const unsigned = await this.generateUnsignedExecuteReport(
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
opts,
|
|
921
|
-
)
|
|
1004
|
+
const { family: _, ...unsigned } = await this.generateUnsignedExecuteReport({
|
|
1005
|
+
...opts,
|
|
1006
|
+
payer,
|
|
1007
|
+
})
|
|
922
1008
|
|
|
1009
|
+
const startTime = Math.floor(Date.now() / 1000)
|
|
923
1010
|
// Open wallet and send transaction using the unsigned data
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
await openedWallet.sendTransfer({
|
|
928
|
-
seqno,
|
|
929
|
-
secretKey: wallet.keyPair.secretKey,
|
|
930
|
-
messages: [
|
|
931
|
-
internal({
|
|
932
|
-
to: unsigned.to,
|
|
933
|
-
value: toNano('0.2'), // TODO: FIXME: estimate proper value for execution costs instead of hardcoding.
|
|
934
|
-
body: unsigned.body,
|
|
935
|
-
}),
|
|
936
|
-
],
|
|
1011
|
+
const seqno = await wallet.sendTransaction({
|
|
1012
|
+
value: toNano('0.3'),
|
|
1013
|
+
...unsigned,
|
|
937
1014
|
})
|
|
938
1015
|
|
|
939
|
-
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
)
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
return this.getTransaction(hash)
|
|
1016
|
+
const message = opts.execReport.message as CCIPMessage_V1_6_TON
|
|
1017
|
+
for await (const exec of this.getExecutionReceipts({
|
|
1018
|
+
offRamp,
|
|
1019
|
+
messageId: message.messageId,
|
|
1020
|
+
sourceChainSelector: message.sourceChainSelector,
|
|
1021
|
+
startTime,
|
|
1022
|
+
watch: sleep(10 * 60e3 /* 10m */),
|
|
1023
|
+
})) {
|
|
1024
|
+
return exec // break and return on first yield
|
|
1025
|
+
}
|
|
1026
|
+
throw new CCIPReceiptNotFoundError(seqno.toString())
|
|
951
1027
|
}
|
|
952
1028
|
|
|
953
1029
|
/**
|