@chainlink/ccip-sdk 0.94.0 → 0.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.d.ts +80 -4
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +262 -6
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +138 -13
- package/dist/api/types.d.ts.map +1 -1
- package/dist/aptos/index.d.ts +5 -9
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +18 -21
- package/dist/aptos/index.js.map +1 -1
- package/dist/aptos/logs.js +3 -3
- package/dist/aptos/logs.js.map +1 -1
- package/dist/chain.d.ts +84 -5
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +63 -2
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +7 -3
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +8 -3
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +7 -7
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +7 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.d.ts.map +1 -1
- package/dist/errors/recovery.js +8 -4
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +53 -18
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +112 -37
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/gas.d.ts +14 -0
- package/dist/evm/gas.d.ts.map +1 -0
- package/dist/evm/gas.js +97 -0
- package/dist/evm/gas.js.map +1 -0
- package/dist/evm/index.d.ts +6 -8
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +23 -14
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/offchain.d.ts.map +1 -1
- package/dist/evm/offchain.js +8 -8
- package/dist/evm/offchain.js.map +1 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +8 -1
- package/dist/execution.js.map +1 -1
- package/dist/gas.d.ts +43 -19
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +48 -68
- package/dist/gas.js.map +1 -1
- package/dist/index.d.ts +15 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/offchain.d.ts +5 -4
- package/dist/offchain.d.ts.map +1 -1
- package/dist/offchain.js +7 -6
- package/dist/offchain.js.map +1 -1
- package/dist/requests.d.ts +13 -11
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +69 -47
- package/dist/requests.js.map +1 -1
- package/dist/selectors.d.ts +2 -1
- package/dist/selectors.d.ts.map +1 -1
- package/dist/selectors.js +613 -278
- package/dist/selectors.js.map +1 -1
- package/dist/solana/exec.d.ts.map +1 -1
- package/dist/solana/exec.js +2 -1
- package/dist/solana/exec.js.map +1 -1
- package/dist/solana/index.d.ts +4 -8
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +20 -13
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/offchain.js +2 -2
- package/dist/solana/offchain.js.map +1 -1
- package/dist/solana/send.d.ts.map +1 -1
- package/dist/solana/send.js +6 -9
- package/dist/solana/send.js.map +1 -1
- package/dist/solana/utils.d.ts +29 -1
- package/dist/solana/utils.d.ts.map +1 -1
- package/dist/solana/utils.js +39 -1
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/discovery.d.ts +7 -4
- package/dist/sui/discovery.d.ts.map +1 -1
- package/dist/sui/discovery.js +66 -19
- package/dist/sui/discovery.js.map +1 -1
- package/dist/sui/events.d.ts +23 -12
- package/dist/sui/events.d.ts.map +1 -1
- package/dist/sui/events.js +267 -128
- package/dist/sui/events.js.map +1 -1
- package/dist/sui/index.d.ts +20 -32
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +246 -148
- package/dist/sui/index.js.map +1 -1
- package/dist/sui/objects.d.ts +14 -4
- package/dist/sui/objects.d.ts.map +1 -1
- package/dist/sui/objects.js +61 -68
- package/dist/sui/objects.js.map +1 -1
- package/dist/sui/types.d.ts +33 -0
- package/dist/sui/types.d.ts.map +1 -1
- package/dist/sui/types.js.map +1 -1
- package/dist/ton/index.d.ts +4 -4
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +7 -8
- package/dist/ton/index.js.map +1 -1
- package/dist/ton/utils.d.ts +3 -3
- package/dist/ton/utils.d.ts.map +1 -1
- package/dist/ton/utils.js +6 -5
- package/dist/ton/utils.js.map +1 -1
- package/dist/types.d.ts +24 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +19 -5
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +52 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +108 -12
- package/dist/utils.js.map +1 -1
- package/package.json +8 -8
- package/src/api/index.ts +343 -9
- package/src/api/types.ts +165 -13
- package/src/aptos/index.ts +19 -33
- package/src/aptos/logs.ts +3 -3
- package/src/chain.ts +139 -10
- package/src/errors/codes.ts +8 -3
- package/src/errors/index.ts +7 -4
- package/src/errors/recovery.ts +16 -5
- package/src/errors/specialized.ts +147 -45
- package/src/evm/gas.ts +149 -0
- package/src/evm/index.ts +47 -30
- package/src/evm/offchain.ts +15 -9
- package/src/execution.ts +8 -1
- package/src/gas.ts +95 -116
- package/src/index.ts +16 -6
- package/src/offchain.ts +12 -6
- package/src/requests.ts +100 -58
- package/src/selectors.ts +620 -280
- package/src/solana/exec.ts +3 -1
- package/src/solana/index.ts +26 -22
- package/src/solana/offchain.ts +2 -2
- package/src/solana/send.ts +5 -23
- package/src/solana/utils.ts +66 -0
- package/src/sui/discovery.ts +92 -31
- package/src/sui/events.ts +346 -239
- package/src/sui/index.ts +325 -201
- package/src/sui/objects.ts +74 -98
- package/src/sui/types.ts +35 -0
- package/src/ton/index.ts +10 -11
- package/src/ton/utils.ts +7 -6
- package/src/types.ts +25 -8
- package/src/utils.ts +151 -16
package/src/sui/index.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { bcs } from '@mysten/sui/bcs'
|
|
3
2
|
import { type SuiTransactionBlockResponse, SuiClient } from '@mysten/sui/client'
|
|
4
3
|
import type { Keypair } from '@mysten/sui/cryptography'
|
|
5
4
|
import { SuiGraphQLClient } from '@mysten/sui/graphql'
|
|
6
5
|
import { Transaction } from '@mysten/sui/transactions'
|
|
7
|
-
import {
|
|
8
|
-
import type
|
|
6
|
+
import { isValidSuiAddress, isValidTransactionDigest, normalizeSuiAddress } from '@mysten/sui/utils'
|
|
7
|
+
import { type BytesLike, dataLength, hexlify, isBytesLike, isHexString } from 'ethers'
|
|
8
|
+
import type { PickDeep, SetOptional } from 'type-fest'
|
|
9
9
|
|
|
10
10
|
import { AptosChain } from '../aptos/index.ts'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type ChainContext,
|
|
13
|
+
type ChainStatic,
|
|
14
|
+
type GetBalanceOpts,
|
|
15
|
+
type LogFilter,
|
|
16
|
+
Chain,
|
|
17
|
+
} from '../chain.ts'
|
|
12
18
|
import {
|
|
13
19
|
CCIPContractNotRouterError,
|
|
14
20
|
CCIPDataFormatUnsupportedError,
|
|
@@ -16,13 +22,17 @@ import {
|
|
|
16
22
|
CCIPErrorCode,
|
|
17
23
|
CCIPExecTxRevertedError,
|
|
18
24
|
CCIPNotImplementedError,
|
|
19
|
-
CCIPSuiMessageVersionInvalidError,
|
|
20
|
-
CCIPVersionFeatureUnavailableError,
|
|
21
25
|
} from '../errors/index.ts'
|
|
26
|
+
import {
|
|
27
|
+
CCIPLogsAddressRequiredError,
|
|
28
|
+
CCIPSuiLogInvalidError,
|
|
29
|
+
CCIPTopicsInvalidError,
|
|
30
|
+
} from '../errors/specialized.ts'
|
|
22
31
|
import type { EVMExtraArgsV2, ExtraArgs, SVMExtraArgsV1, SuiExtraArgsV1 } from '../extra-args.ts'
|
|
23
|
-
import { getSuiLeafHasher } from './hasher.ts'
|
|
24
32
|
import type { LeafHasher } from '../hasher/common.ts'
|
|
33
|
+
import { decodeMessage, getMessagesInBatch } from '../requests.ts'
|
|
25
34
|
import { supportedChains } from '../supported-chains.ts'
|
|
35
|
+
import { getSuiLeafHasher } from './hasher.ts'
|
|
26
36
|
import {
|
|
27
37
|
type AnyMessage,
|
|
28
38
|
type CCIPExecution,
|
|
@@ -41,32 +51,32 @@ import {
|
|
|
41
51
|
type WithLogger,
|
|
42
52
|
ChainFamily,
|
|
43
53
|
} from '../types.ts'
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
import {
|
|
55
|
+
decodeAddress,
|
|
56
|
+
decodeOnRampAddress,
|
|
57
|
+
getDataBytes,
|
|
58
|
+
networkInfo,
|
|
59
|
+
parseTypeAndVersion,
|
|
60
|
+
util,
|
|
61
|
+
} from '../utils.ts'
|
|
62
|
+
import { getCcipStateAddress, getOffRampForCcip } from './discovery.ts'
|
|
63
|
+
import { type CommitEvent, streamSuiLogs } from './events.ts'
|
|
48
64
|
import {
|
|
49
65
|
type SuiManuallyExecuteInput,
|
|
50
66
|
type TokenConfig,
|
|
51
67
|
buildManualExecutionPTB,
|
|
52
68
|
} from './manuallyExec/index.ts'
|
|
53
69
|
import {
|
|
70
|
+
deriveObjectID,
|
|
54
71
|
fetchTokenConfigs,
|
|
55
|
-
|
|
56
|
-
|
|
72
|
+
getLatestPackageId,
|
|
73
|
+
getObjectRef,
|
|
57
74
|
getReceiverModule,
|
|
58
75
|
} from './objects.ts'
|
|
76
|
+
import type { CCIPMessage_V1_6_Sui } from './types.ts'
|
|
59
77
|
|
|
60
|
-
export const SUI_EXTRA_ARGS_V1_TAG = '21ea4ca9' as const
|
|
61
78
|
const DEFAULT_GAS_LIMIT = 1000000n
|
|
62
79
|
|
|
63
|
-
type SuiContractDir = {
|
|
64
|
-
ccip?: string
|
|
65
|
-
onRamp?: string
|
|
66
|
-
offRamp?: string
|
|
67
|
-
router?: string
|
|
68
|
-
}
|
|
69
|
-
|
|
70
80
|
/**
|
|
71
81
|
* Sui chain implementation supporting Sui networks.
|
|
72
82
|
* Note: This implementation is currently a placeholder.
|
|
@@ -82,9 +92,6 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
82
92
|
readonly client: SuiClient
|
|
83
93
|
readonly graphqlClient: SuiGraphQLClient
|
|
84
94
|
|
|
85
|
-
// contracts dir <chainSelectorName, SuiContractDir>
|
|
86
|
-
readonly contractsDir: SuiContractDir
|
|
87
|
-
|
|
88
95
|
/**
|
|
89
96
|
* Creates a new SuiChain instance.
|
|
90
97
|
* @param client - Sui client for interacting with the Sui network.
|
|
@@ -95,7 +102,6 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
95
102
|
|
|
96
103
|
this.client = client
|
|
97
104
|
this.network = network
|
|
98
|
-
this.contractsDir = {}
|
|
99
105
|
|
|
100
106
|
// TODO: Graphql client should come from config
|
|
101
107
|
let graphqlUrl: string
|
|
@@ -140,17 +146,19 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
140
146
|
chainId = 'sui:4' // devnet
|
|
141
147
|
} else {
|
|
142
148
|
throw new CCIPError(
|
|
143
|
-
CCIPErrorCode.
|
|
149
|
+
CCIPErrorCode.CHAIN_FAMILY_UNSUPPORTED,
|
|
144
150
|
`Unsupported Sui chain identifier: ${rawChainId}`,
|
|
145
151
|
)
|
|
146
152
|
}
|
|
147
153
|
|
|
148
154
|
const network = networkInfo(chainId) as NetworkInfo<typeof ChainFamily.Sui>
|
|
149
|
-
|
|
155
|
+
const chain = new SuiChain(client, network, ctx)
|
|
156
|
+
return Object.assign(chain, { url })
|
|
150
157
|
}
|
|
151
158
|
|
|
152
159
|
/** {@inheritDoc Chain.getBlockTimestamp} */
|
|
153
|
-
async getBlockTimestamp(block: number): Promise<number> {
|
|
160
|
+
async getBlockTimestamp(block: number | 'finalized'): Promise<number> {
|
|
161
|
+
if (typeof block !== 'number' || block <= 0) return Math.floor(Date.now() / 1000)
|
|
154
162
|
const checkpoint = await this.client.getCheckpoint({
|
|
155
163
|
id: String(block),
|
|
156
164
|
})
|
|
@@ -176,12 +184,12 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
176
184
|
if (txResponse.events?.length) {
|
|
177
185
|
for (const [i, event] of txResponse.events.entries()) {
|
|
178
186
|
const eventType = event.type
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
const eventName = eventType.
|
|
187
|
+
const splitIdx = eventType.lastIndexOf('::')
|
|
188
|
+
const address = eventType.substring(0, splitIdx)
|
|
189
|
+
const eventName = eventType.substring(splitIdx + 2)
|
|
182
190
|
|
|
183
191
|
events.push({
|
|
184
|
-
address:
|
|
192
|
+
address: address,
|
|
185
193
|
transactionHash: digest,
|
|
186
194
|
index: i,
|
|
187
195
|
blockNumber: Number(txResponse.checkpoint || 0),
|
|
@@ -202,41 +210,21 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
202
210
|
|
|
203
211
|
/** {@inheritDoc Chain.getLogs} */
|
|
204
212
|
async *getLogs(opts: LogFilter & { versionAsHash?: boolean }) {
|
|
205
|
-
if (!
|
|
206
|
-
|
|
207
|
-
}
|
|
213
|
+
if (!opts.address) throw new CCIPLogsAddressRequiredError()
|
|
214
|
+
|
|
208
215
|
// Extract the event type from topics
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
throw new CCIPVersionFeatureUnavailableError(
|
|
212
|
-
'Event type',
|
|
213
|
-
topic || 'unknown',
|
|
214
|
-
'CommitReportAccepted',
|
|
215
|
-
)
|
|
216
|
+
if (opts.topics?.length !== 1 || typeof opts.topics[0] !== 'string') {
|
|
217
|
+
throw new CCIPTopicsInvalidError(opts.topics!)
|
|
216
218
|
}
|
|
219
|
+
const topic = opts.topics[0]
|
|
217
220
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
: new Date(startTime.getTime() + 1 * 24 * 60 * 60 * 1000) // default to +24h
|
|
222
|
-
|
|
223
|
-
this.logger.info(
|
|
224
|
-
`Fetching Sui events of type ${topic} from ${startTime.toISOString()} to ${endTime.toISOString()}`,
|
|
225
|
-
)
|
|
226
|
-
const events = await getSuiEventsInTimeRange<CommitEvent>(
|
|
227
|
-
this.client,
|
|
228
|
-
this.graphqlClient,
|
|
229
|
-
`${this.contractsDir.offRamp}::offramp::CommitReportAccepted`,
|
|
230
|
-
startTime,
|
|
231
|
-
endTime,
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
for (const event of events) {
|
|
235
|
-
const eventData = event.contents.json
|
|
221
|
+
for await (const event of streamSuiLogs<Record<string, unknown>>(this, opts)) {
|
|
222
|
+
const eventData = event.contents?.json
|
|
223
|
+
if (!eventData) continue
|
|
236
224
|
yield {
|
|
237
|
-
address:
|
|
238
|
-
transactionHash: event.transaction
|
|
239
|
-
index:
|
|
225
|
+
address: opts.address,
|
|
226
|
+
transactionHash: event.transaction!.digest,
|
|
227
|
+
index: Number(event.sequenceNumber) || 0,
|
|
240
228
|
blockNumber: Number(event.transaction?.effects.checkpoint.sequenceNumber || 0),
|
|
241
229
|
data: eventData,
|
|
242
230
|
topics: [topic],
|
|
@@ -244,11 +232,6 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
244
232
|
}
|
|
245
233
|
}
|
|
246
234
|
|
|
247
|
-
/** {@inheritDoc Chain.getMessagesInTx} */
|
|
248
|
-
override async getMessagesInTx(_tx: string | ChainTransaction): Promise<CCIPRequest[]> {
|
|
249
|
-
return Promise.reject(new CCIPNotImplementedError('SuiChain.getMessagesInTx'))
|
|
250
|
-
}
|
|
251
|
-
|
|
252
235
|
/** {@inheritDoc Chain.getMessagesInBatch} */
|
|
253
236
|
override async getMessagesInBatch<
|
|
254
237
|
R extends PickDeep<
|
|
@@ -256,25 +239,45 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
256
239
|
'lane' | `log.${'topics' | 'address' | 'blockNumber'}` | 'message.sequenceNumber'
|
|
257
240
|
>,
|
|
258
241
|
>(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
242
|
+
request: R,
|
|
243
|
+
commit: Pick<CommitReport, 'minSeqNr' | 'maxSeqNr'>,
|
|
244
|
+
opts?: { page?: number },
|
|
262
245
|
): Promise<R['message'][]> {
|
|
263
|
-
return
|
|
246
|
+
return getMessagesInBatch(this, request, commit, opts)
|
|
264
247
|
}
|
|
265
248
|
|
|
266
249
|
/** {@inheritDoc Chain.typeAndVersion} */
|
|
267
|
-
async typeAndVersion(
|
|
268
|
-
|
|
250
|
+
async typeAndVersion(address: string) {
|
|
251
|
+
// requires address to have `::<module>` suffix
|
|
252
|
+
address = await getLatestPackageId(address, this.client)
|
|
253
|
+
const target = `${address}::type_and_version`
|
|
254
|
+
|
|
255
|
+
// Use the Transaction builder to create a move call
|
|
256
|
+
const tx = new Transaction()
|
|
257
|
+
// Add move call to the transaction
|
|
258
|
+
tx.moveCall({ target, arguments: [] })
|
|
259
|
+
|
|
260
|
+
// Execute with devInspectTransactionBlock for read-only call
|
|
261
|
+
const result = await this.client.devInspectTransactionBlock({
|
|
262
|
+
sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
263
|
+
transactionBlock: tx,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
if (result.effects.status.status !== 'success' || !result.results?.[0]?.returnValues?.[0]) {
|
|
267
|
+
throw new CCIPDataFormatUnsupportedError(
|
|
268
|
+
`Failed to call ${target}: ${result.effects.status.error || 'No return value'}`,
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const [data] = result.results[0].returnValues[0]
|
|
273
|
+
const res = bcs.String.parse(getDataBytes(data))
|
|
274
|
+
return parseTypeAndVersion(res)
|
|
269
275
|
}
|
|
270
276
|
|
|
271
277
|
/** {@inheritDoc Chain.getRouterForOnRamp} */
|
|
272
278
|
async getRouterForOnRamp(onRamp: string, _destChainSelector: bigint): Promise<string> {
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
this.contractsDir.onRamp = onRamp
|
|
276
|
-
}
|
|
277
|
-
return Promise.resolve(this.contractsDir.onRamp)
|
|
279
|
+
// In Sui, the router is the onRamp package itself
|
|
280
|
+
return Promise.resolve(onRamp)
|
|
278
281
|
}
|
|
279
282
|
|
|
280
283
|
/** {@inheritDoc Chain.getRouterForOffRamp} */
|
|
@@ -283,40 +286,40 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
283
286
|
}
|
|
284
287
|
|
|
285
288
|
/** {@inheritDoc Chain.getNativeTokenForRouter} */
|
|
286
|
-
getNativeTokenForRouter(
|
|
289
|
+
getNativeTokenForRouter(): Promise<string> {
|
|
287
290
|
// SUI native token is always 0x2::sui::SUI
|
|
288
291
|
return Promise.resolve('0x2::sui::SUI')
|
|
289
292
|
}
|
|
290
293
|
|
|
291
294
|
/** {@inheritDoc Chain.getOffRampsForRouter} */
|
|
292
295
|
async getOffRampsForRouter(router: string, _sourceChainSelector: bigint): Promise<string[]> {
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
this.contractsDir.ccip = ccip
|
|
296
|
+
router = await getLatestPackageId(router, this.client)
|
|
297
|
+
const ccip = await getCcipStateAddress(router, this.client)
|
|
298
|
+
const offramp = await getOffRampForCcip(ccip, this.client)
|
|
297
299
|
return [offramp]
|
|
298
300
|
}
|
|
299
301
|
|
|
300
302
|
/** {@inheritDoc Chain.getOnRampForRouter} */
|
|
301
|
-
getOnRampForRouter(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
return Promise.resolve(this.contractsDir.onRamp)
|
|
303
|
+
getOnRampForRouter(router: string, _destChainSelector: bigint): Promise<string> {
|
|
304
|
+
// For Sui, the router is the onramp package address
|
|
305
|
+
return Promise.resolve(router)
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
/** {@inheritDoc Chain.getOnRampForOffRamp} */
|
|
309
309
|
async getOnRampForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
|
|
310
|
-
|
|
311
|
-
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'CCIP address not set in contracts directory')
|
|
312
|
-
}
|
|
313
|
-
const offrampPackageId = offRamp
|
|
310
|
+
offRamp = await getLatestPackageId(offRamp, this.client)
|
|
314
311
|
const functionName = 'get_source_chain_config'
|
|
315
|
-
|
|
312
|
+
// Preserve module suffix if present, otherwise add it
|
|
313
|
+
const target = offRamp.includes('::')
|
|
314
|
+
? `${offRamp}::${functionName}`
|
|
315
|
+
: `${offRamp}::offramp::${functionName}`
|
|
316
|
+
|
|
317
|
+
// Discover the CCIP package from the offramp
|
|
318
|
+
const ccip = await getCcipStateAddress(offRamp, this.client)
|
|
316
319
|
|
|
317
320
|
// Get the OffRampState object
|
|
318
|
-
const offrampStateObject = await
|
|
319
|
-
const ccipObjectRef = await
|
|
321
|
+
const offrampStateObject = await getObjectRef(offRamp, this.client)
|
|
322
|
+
const ccipObjectRef = await getObjectRef(ccip, this.client)
|
|
320
323
|
// Use the Transaction builder to create a move call
|
|
321
324
|
const tx = new Transaction()
|
|
322
325
|
|
|
@@ -381,43 +384,170 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
381
384
|
}
|
|
382
385
|
|
|
383
386
|
/** {@inheritDoc Chain.getTokenForTokenPool} */
|
|
384
|
-
getTokenForTokenPool(
|
|
385
|
-
|
|
387
|
+
async getTokenForTokenPool(tokenPool: string): Promise<string> {
|
|
388
|
+
const normalizedTokenPool = normalizeSuiAddress(tokenPool)
|
|
389
|
+
|
|
390
|
+
// Get objects owned by this package (looking for state pointers)
|
|
391
|
+
const objects = await this.client.getOwnedObjects({
|
|
392
|
+
owner: normalizedTokenPool,
|
|
393
|
+
options: { showType: true, showContent: true },
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
const tpType = objects.data
|
|
397
|
+
.find((obj) => obj.data?.type?.includes('token_pool::'))
|
|
398
|
+
?.data?.type?.split('::')[1]
|
|
399
|
+
|
|
400
|
+
const allowedTps = ['managed_token_pool', 'burn_mint_token_pool', 'lock_release_token_pool']
|
|
401
|
+
if (!tpType || !allowedTps.includes(tpType)) {
|
|
402
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid token pool type: ${tpType}`)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Find the state pointer object
|
|
406
|
+
let stateObjectPointerId: string | undefined
|
|
407
|
+
for (const obj of objects.data) {
|
|
408
|
+
const content = obj.data?.content
|
|
409
|
+
if (content?.dataType !== 'moveObject') continue
|
|
410
|
+
|
|
411
|
+
const fields = content.fields as Record<string, unknown>
|
|
412
|
+
// Look for a pointer field that references the state object
|
|
413
|
+
stateObjectPointerId = fields[`${tpType}_object_id`] as string
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!stateObjectPointerId) {
|
|
417
|
+
throw new CCIPError(
|
|
418
|
+
CCIPErrorCode.UNKNOWN,
|
|
419
|
+
`No token pool state pointer found for ${tokenPool}`,
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const stateNamesPerTP: Record<string, string> = {
|
|
424
|
+
managed_token_pool: 'ManagedTokenPoolState',
|
|
425
|
+
burn_mint_token_pool: 'BurnMintTokenPoolState',
|
|
426
|
+
lock_release_token_pool: 'LockReleaseTokenPoolState',
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const poolStateObject = deriveObjectID(
|
|
430
|
+
stateObjectPointerId,
|
|
431
|
+
new TextEncoder().encode(stateNamesPerTP[tpType]),
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
// Get object info to get the coin type
|
|
435
|
+
const info = await this.client.getObject({
|
|
436
|
+
id: poolStateObject,
|
|
437
|
+
options: { showType: true, showContent: true },
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
const type = info.data?.type
|
|
441
|
+
if (!type) {
|
|
442
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading token pool state object type')
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Extract the type parameter T from ManagedTokenPoolState<T>
|
|
446
|
+
const typeMatch = type.match(/(?:Managed|BurnMint|LockRelease)TokenPoolState<(.+)>$/)
|
|
447
|
+
if (!typeMatch || !typeMatch[1]) {
|
|
448
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid pool state type format: ${type}`)
|
|
449
|
+
}
|
|
450
|
+
const tokenType = typeMatch[1]
|
|
451
|
+
|
|
452
|
+
// Call get_token function from managed_token_pool contract with the type parameter
|
|
453
|
+
const target = type.split('<')[0]?.split('::').slice(0, 2).join('::') + '::get_token'
|
|
454
|
+
if (!target) {
|
|
455
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid pool state type format: ${type}`)
|
|
456
|
+
}
|
|
457
|
+
const tx = new Transaction()
|
|
458
|
+
tx.moveCall({
|
|
459
|
+
target,
|
|
460
|
+
typeArguments: [tokenType],
|
|
461
|
+
arguments: [tx.object(poolStateObject)],
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
const result = await this.client.devInspectTransactionBlock({
|
|
465
|
+
sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
466
|
+
transactionBlock: tx,
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
if (result.effects.status.status !== 'success' || !result.results?.[0]?.returnValues?.[0]) {
|
|
470
|
+
throw new CCIPDataFormatUnsupportedError(
|
|
471
|
+
`Failed to call ${target}: ${result.effects.status.error || 'No return value'}`,
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Parse the return value to get the coin metadata address (32 bytes)
|
|
476
|
+
const returnValue = result.results[0].returnValues[0]
|
|
477
|
+
const [data] = returnValue
|
|
478
|
+
const coinMetadataBytes = new Uint8Array(data)
|
|
479
|
+
const coinMetadataAddress = normalizeSuiAddress(hexlify(coinMetadataBytes))
|
|
480
|
+
|
|
481
|
+
return coinMetadataAddress
|
|
386
482
|
}
|
|
387
483
|
|
|
388
484
|
/** {@inheritDoc Chain.getTokenInfo} */
|
|
389
485
|
async getTokenInfo(token: string): Promise<{ symbol: string; decimals: number }> {
|
|
390
|
-
|
|
391
|
-
if (
|
|
392
|
-
|
|
486
|
+
const normalizedTokenAddress = normalizeSuiAddress(token)
|
|
487
|
+
if (!isValidSuiAddress(normalizedTokenAddress)) {
|
|
488
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
|
|
393
489
|
}
|
|
394
490
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
491
|
+
const objectResponse = await this.client.getObject({
|
|
492
|
+
id: normalizedTokenAddress,
|
|
493
|
+
options: { showType: true },
|
|
494
|
+
})
|
|
399
495
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
496
|
+
const getCoinFromMetadata = (metadata: string) => {
|
|
497
|
+
// Extract the type parameter from CoinMetadata<...>
|
|
498
|
+
const match = metadata.match(/CoinMetadata<(.+)>$/)
|
|
499
|
+
|
|
500
|
+
if (!match || !match[1]) {
|
|
501
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid metadata format: ${metadata}`)
|
|
405
502
|
}
|
|
406
|
-
|
|
407
|
-
|
|
503
|
+
|
|
504
|
+
return match[1]
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
let coinType: string
|
|
508
|
+
const objectType = objectResponse.data?.type
|
|
509
|
+
|
|
510
|
+
// Check if this is a CoinMetadata object or a coin type string
|
|
511
|
+
if (objectType?.includes('CoinMetadata')) {
|
|
512
|
+
coinType = getCoinFromMetadata(objectType)
|
|
513
|
+
} else if (token.includes('::')) {
|
|
514
|
+
// This is a coin type string (e.g., "0xabc::coin::COIN")
|
|
515
|
+
coinType = token
|
|
516
|
+
} else {
|
|
517
|
+
// This is a package address or unknown format
|
|
518
|
+
throw new CCIPError(
|
|
519
|
+
CCIPErrorCode.UNKNOWN,
|
|
520
|
+
`Token address ${token} is not a CoinMetadata object or coin type. Expected format: package::module::Type`,
|
|
521
|
+
)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (coinType.split('::').length < 3) {
|
|
525
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let metadata = null
|
|
529
|
+
try {
|
|
530
|
+
metadata = await this.client.getCoinMetadata({ coinType })
|
|
531
|
+
} catch (e) {
|
|
532
|
+
console.error('Error fetching coin metadata:', e)
|
|
533
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
|
|
408
534
|
}
|
|
409
535
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
536
|
+
if (!metadata) {
|
|
537
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
|
|
538
|
+
}
|
|
413
539
|
|
|
414
540
|
return {
|
|
415
|
-
symbol: symbol
|
|
416
|
-
decimals:
|
|
541
|
+
symbol: metadata.symbol,
|
|
542
|
+
decimals: metadata.decimals,
|
|
417
543
|
}
|
|
418
544
|
}
|
|
419
545
|
|
|
420
|
-
/** {@inheritDoc Chain.
|
|
546
|
+
/** {@inheritDoc Chain.getBalance} */
|
|
547
|
+
async getBalance(_opts: GetBalanceOpts): Promise<bigint> {
|
|
548
|
+
return Promise.reject(new CCIPNotImplementedError('SuiChain.getBalance'))
|
|
549
|
+
}
|
|
550
|
+
|
|
421
551
|
/** {@inheritDoc Chain.getTokenAdminRegistryFor} */
|
|
422
552
|
getTokenAdminRegistryFor(_address: string): Promise<string> {
|
|
423
553
|
return Promise.reject(new CCIPNotImplementedError())
|
|
@@ -426,11 +556,22 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
426
556
|
// Static methods for decoding
|
|
427
557
|
/**
|
|
428
558
|
* Decodes a CCIP message from a Sui log event.
|
|
429
|
-
* @param
|
|
559
|
+
* @param log - Log event data.
|
|
430
560
|
* @returns Decoded CCIPMessage or undefined if not valid.
|
|
431
561
|
*/
|
|
432
|
-
static decodeMessage(
|
|
433
|
-
|
|
562
|
+
static decodeMessage(log: Log_): CCIPMessage | undefined {
|
|
563
|
+
const { data } = log
|
|
564
|
+
if (
|
|
565
|
+
(typeof data !== 'string' || !data.startsWith('{')) &&
|
|
566
|
+
(typeof data !== 'object' || isBytesLike(data))
|
|
567
|
+
)
|
|
568
|
+
throw new CCIPSuiLogInvalidError(util.inspect(log))
|
|
569
|
+
// offload massaging to generic decodeJsonMessage
|
|
570
|
+
try {
|
|
571
|
+
return decodeMessage(data)
|
|
572
|
+
} catch (_) {
|
|
573
|
+
// return undefined
|
|
574
|
+
}
|
|
434
575
|
}
|
|
435
576
|
|
|
436
577
|
/**
|
|
@@ -459,30 +600,36 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
459
600
|
/**
|
|
460
601
|
* Decodes commit reports from a log entry.
|
|
461
602
|
* @param log - The log entry to decode.
|
|
462
|
-
* @param
|
|
603
|
+
* @param lane - Optional lane information.
|
|
463
604
|
* @returns Array of decoded commit reports or undefined.
|
|
464
605
|
*/
|
|
465
|
-
static decodeCommits(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
if (!
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
return
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
606
|
+
static decodeCommits(
|
|
607
|
+
{ data, topics }: SetOptional<Pick<Log_, 'data' | 'topics'>, 'topics'>,
|
|
608
|
+
lane?: Lane,
|
|
609
|
+
): CommitReport[] | undefined {
|
|
610
|
+
// Check if this is an CommitReportAccepted event
|
|
611
|
+
if (topics?.[0] && topics[0] !== 'CommitReportAccepted') return
|
|
612
|
+
|
|
613
|
+
// Basic log data structure validation
|
|
614
|
+
if (!data || typeof data !== 'object' || !('unblessed_merkle_roots' in data)) return
|
|
615
|
+
|
|
616
|
+
const eventData = data as CommitEvent
|
|
617
|
+
const rootsRaw = eventData.blessed_merkle_roots.concat(eventData.unblessed_merkle_roots)
|
|
618
|
+
return rootsRaw
|
|
619
|
+
.map((root) => {
|
|
620
|
+
return {
|
|
621
|
+
sourceChainSelector: BigInt(root.source_chain_selector),
|
|
622
|
+
onRampAddress: decodeOnRampAddress(root.on_ramp_address),
|
|
623
|
+
minSeqNr: BigInt(root.min_seq_nr),
|
|
624
|
+
maxSeqNr: BigInt(root.max_seq_nr),
|
|
625
|
+
merkleRoot: hexlify(getDataBytes(root.merkle_root)),
|
|
626
|
+
}
|
|
627
|
+
})
|
|
628
|
+
.filter((r) =>
|
|
629
|
+
lane
|
|
630
|
+
? r.sourceChainSelector === lane.sourceChainSelector && r.onRampAddress === lane.onRamp
|
|
631
|
+
: true,
|
|
632
|
+
)
|
|
486
633
|
}
|
|
487
634
|
|
|
488
635
|
/**
|
|
@@ -490,52 +637,32 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
490
637
|
* @param log - The log entry to decode.
|
|
491
638
|
* @returns Decoded execution receipt or undefined.
|
|
492
639
|
*/
|
|
493
|
-
static decodeReceipt(
|
|
640
|
+
static decodeReceipt({
|
|
641
|
+
data,
|
|
642
|
+
topics,
|
|
643
|
+
}: SetOptional<Pick<Log_, 'data' | 'topics'>, 'topics'>): ExecutionReceipt | undefined {
|
|
494
644
|
// Check if this is an ExecutionStateChanged event
|
|
495
|
-
|
|
496
|
-
if (topic !== 'ExecutionStateChanged') {
|
|
497
|
-
return undefined
|
|
498
|
-
}
|
|
645
|
+
if (topics?.[0] && topics[0] !== 'ExecutionStateChanged') return
|
|
499
646
|
|
|
500
|
-
//
|
|
501
|
-
if (!
|
|
502
|
-
return
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
const eventData = log.data as {
|
|
506
|
-
message_hash?: number[]
|
|
507
|
-
message_id?: number[]
|
|
508
|
-
sequence_number?: string
|
|
509
|
-
source_chain_selector?: string
|
|
510
|
-
state?: number
|
|
647
|
+
// Basic log data structure validation
|
|
648
|
+
if (!data || typeof data !== 'object' || !('message_id' in data) || !('state' in data)) {
|
|
649
|
+
return
|
|
511
650
|
}
|
|
512
651
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
) {
|
|
520
|
-
return undefined
|
|
652
|
+
const eventData = data as {
|
|
653
|
+
message_hash: BytesLike
|
|
654
|
+
message_id: BytesLike
|
|
655
|
+
sequence_number: string
|
|
656
|
+
source_chain_selector: string
|
|
657
|
+
state: number
|
|
521
658
|
}
|
|
522
659
|
|
|
523
|
-
const toHex = (bytes: BytesLike | number[]) => hexlify(bytesToBuffer(bytes))
|
|
524
|
-
|
|
525
|
-
// Convert message_id bytes array to hex string
|
|
526
|
-
const messageId = toHex(eventData.message_id)
|
|
527
|
-
|
|
528
|
-
// Convert message_hash bytes array to hex string (if present)
|
|
529
|
-
const messageHash = eventData.message_hash ? toHex(eventData.message_hash) : undefined
|
|
530
|
-
|
|
531
660
|
return {
|
|
532
|
-
messageId,
|
|
661
|
+
messageId: hexlify(getDataBytes(eventData.message_id)),
|
|
533
662
|
sequenceNumber: BigInt(eventData.sequence_number),
|
|
534
|
-
state: eventData.state as ExecutionState,
|
|
535
|
-
sourceChainSelector: eventData.source_chain_selector
|
|
536
|
-
|
|
537
|
-
: undefined,
|
|
538
|
-
messageHash,
|
|
663
|
+
state: Number(eventData.state) as ExecutionState,
|
|
664
|
+
sourceChainSelector: BigInt(eventData.source_chain_selector),
|
|
665
|
+
messageHash: hexlify(getDataBytes(eventData.message_hash)),
|
|
539
666
|
}
|
|
540
667
|
}
|
|
541
668
|
|
|
@@ -544,15 +671,17 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
544
671
|
* @param bytes - Bytes to convert.
|
|
545
672
|
* @returns Sui address.
|
|
546
673
|
*/
|
|
547
|
-
static getAddress(bytes: BytesLike): string {
|
|
674
|
+
static getAddress(bytes: BytesLike | readonly number[]): string {
|
|
548
675
|
return AptosChain.getAddress(bytes)
|
|
549
676
|
}
|
|
550
677
|
|
|
551
678
|
/**
|
|
552
679
|
* Validates a transaction hash format for Sui
|
|
553
680
|
*/
|
|
554
|
-
static isTxHash(
|
|
555
|
-
return false
|
|
681
|
+
static isTxHash(v: unknown): v is string {
|
|
682
|
+
if (typeof v !== 'string') return false
|
|
683
|
+
// check in both hex and base58 formats
|
|
684
|
+
return isHexString(v, 32) || isValidTransactionDigest(v)
|
|
556
685
|
}
|
|
557
686
|
|
|
558
687
|
/**
|
|
@@ -583,9 +712,6 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
583
712
|
|
|
584
713
|
/** {@inheritDoc Chain.getOffchainTokenData} */
|
|
585
714
|
getOffchainTokenData(request: CCIPRequest): Promise<OffchainTokenData[]> {
|
|
586
|
-
if (!('receiverObjectIds' in request.message)) {
|
|
587
|
-
throw new CCIPSuiMessageVersionInvalidError()
|
|
588
|
-
}
|
|
589
715
|
// default offchain token data
|
|
590
716
|
return Promise.resolve(request.message.tokenAmounts.map(() => undefined))
|
|
591
717
|
}
|
|
@@ -603,19 +729,17 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
603
729
|
receiverObjectIds?: string[]
|
|
604
730
|
},
|
|
605
731
|
): Promise<CCIPExecution> {
|
|
606
|
-
const { execReport } = opts
|
|
607
|
-
if (!this.contractsDir.offRamp || !this.contractsDir.ccip) {
|
|
608
|
-
throw new CCIPContractNotRouterError(
|
|
609
|
-
'OffRamp or CCIP address not set in contracts directory',
|
|
610
|
-
'Sui',
|
|
611
|
-
)
|
|
612
|
-
}
|
|
732
|
+
const { execReport, offRamp } = opts
|
|
613
733
|
const wallet = opts.wallet as Keypair
|
|
614
|
-
|
|
615
|
-
|
|
734
|
+
|
|
735
|
+
// Discover the CCIP package from the offramp
|
|
736
|
+
const ccip = await getCcipStateAddress(offRamp, this.client)
|
|
737
|
+
|
|
738
|
+
const ccipObjectRef = await getObjectRef(ccip, this.client)
|
|
739
|
+
const offrampStateObject = await getObjectRef(offRamp, this.client)
|
|
616
740
|
const receiverConfig = await getReceiverModule(
|
|
617
741
|
this.client,
|
|
618
|
-
|
|
742
|
+
ccip,
|
|
619
743
|
ccipObjectRef,
|
|
620
744
|
execReport.message.receiver,
|
|
621
745
|
)
|
|
@@ -623,7 +747,7 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
623
747
|
if (execReport.message.tokenAmounts.length !== 0) {
|
|
624
748
|
tokenConfigs = await fetchTokenConfigs(
|
|
625
749
|
this.client,
|
|
626
|
-
|
|
750
|
+
ccip,
|
|
627
751
|
ccipObjectRef,
|
|
628
752
|
execReport.message.tokenAmounts as CCIPMessage<typeof CCIPVersion.V1_6>['tokenAmounts'],
|
|
629
753
|
)
|
|
@@ -631,8 +755,8 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
|
|
|
631
755
|
|
|
632
756
|
const input: SuiManuallyExecuteInput = {
|
|
633
757
|
executionReport: execReport as ExecutionReport<CCIPMessage_V1_6_Sui>,
|
|
634
|
-
offrampAddress:
|
|
635
|
-
ccipAddress:
|
|
758
|
+
offrampAddress: offRamp,
|
|
759
|
+
ccipAddress: ccip,
|
|
636
760
|
ccipObjectRef,
|
|
637
761
|
offrampStateObject,
|
|
638
762
|
receiverConfig,
|