@chainlink/ccip-sdk 0.94.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 +86 -7
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +270 -10
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +134 -13
- package/dist/api/types.d.ts.map +1 -1
- package/dist/aptos/index.d.ts +38 -17
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +91 -61
- 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 +300 -42
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +160 -9
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +9 -3
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +10 -3
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +8 -8
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +8 -8
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.d.ts.map +1 -1
- package/dist/errors/recovery.js +10 -4
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +62 -21
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +128 -41
- 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 +14 -0
- package/dist/evm/gas.d.ts.map +1 -0
- package/dist/evm/gas.js +92 -0
- package/dist/evm/gas.js.map +1 -0
- package/dist/evm/index.d.ts +76 -32
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +94 -104
- 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 +24 -3
- 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 +46 -19
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +56 -68
- package/dist/gas.js.map +1 -1
- package/dist/index.d.ts +18 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -13
- 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 +30 -20
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +86 -56
- 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 +625 -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 +73 -22
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +91 -28
- 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 +57 -41
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +286 -159
- 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 +67 -21
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +159 -30
- 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/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 +126 -9
- 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 +67 -4
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +126 -17
- package/dist/utils.js.map +1 -1
- package/package.json +14 -9
- package/src/all-chains.ts +26 -0
- package/src/api/index.ts +348 -13
- package/src/api/types.ts +160 -13
- package/src/aptos/index.ts +98 -76
- package/src/aptos/logs.ts +3 -3
- package/src/chain.ts +408 -51
- package/src/errors/codes.ts +10 -3
- package/src/errors/index.ts +8 -5
- package/src/errors/recovery.ts +18 -5
- package/src/errors/specialized.ts +168 -49
- package/src/evm/extra-args.ts +377 -0
- package/src/evm/gas.ts +150 -0
- package/src/evm/index.ts +123 -155
- package/src/evm/offchain.ts +15 -9
- package/src/execution.ts +26 -3
- package/src/extra-args.ts +108 -4
- package/src/gas.ts +101 -115
- package/src/index.ts +27 -14
- package/src/offchain.ts +12 -6
- package/src/requests.ts +117 -67
- package/src/selectors.ts +632 -280
- package/src/solana/exec.ts +3 -1
- package/src/solana/index.ts +97 -37
- 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 +365 -212
- package/src/sui/objects.ts +74 -98
- package/src/sui/types.ts +35 -0
- package/src/ton/index.ts +199 -35
- package/src/ton/send.ts +222 -0
- package/src/ton/utils.ts +7 -6
- package/src/types.ts +128 -9
- package/src/utils.ts +169 -21
package/dist/sui/index.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bcs } from '@mysten/sui/bcs';
|
|
2
2
|
import { SuiClient } from '@mysten/sui/client';
|
|
3
3
|
import { SuiGraphQLClient } from '@mysten/sui/graphql';
|
|
4
4
|
import { Transaction } from '@mysten/sui/transactions';
|
|
5
|
-
import {
|
|
5
|
+
import { isValidSuiAddress, isValidTransactionDigest, normalizeSuiAddress } from '@mysten/sui/utils';
|
|
6
|
+
import { dataLength, hexlify, isBytesLike, isHexString } from 'ethers';
|
|
6
7
|
import { AptosChain } from "../aptos/index.js";
|
|
7
|
-
import { Chain } from "../chain.js";
|
|
8
|
-
import { CCIPContractNotRouterError, CCIPDataFormatUnsupportedError, CCIPError, CCIPErrorCode, CCIPExecTxRevertedError, CCIPNotImplementedError,
|
|
9
|
-
import {
|
|
8
|
+
import { Chain, } from "../chain.js";
|
|
9
|
+
import { CCIPContractNotRouterError, CCIPDataFormatUnsupportedError, CCIPError, CCIPErrorCode, CCIPExecTxRevertedError, CCIPNotImplementedError, } from "../errors/index.js";
|
|
10
|
+
import { CCIPLogsAddressRequiredError, CCIPSuiLogInvalidError, CCIPTopicsInvalidError, } from "../errors/specialized.js";
|
|
11
|
+
import { decodeMessage, getMessagesInBatch } from "../requests.js";
|
|
10
12
|
import { supportedChains } from "../supported-chains.js";
|
|
13
|
+
import { getSuiLeafHasher } from "./hasher.js";
|
|
11
14
|
import { ChainFamily, } from "../types.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
+
import { decodeAddress, decodeOnRampAddress, getDataBytes, networkInfo, parseTypeAndVersion, util, } from "../utils.js";
|
|
16
|
+
import { getCcipStateAddress, getOffRampForCcip } from "./discovery.js";
|
|
17
|
+
import { streamSuiLogs } from "./events.js";
|
|
15
18
|
import { buildManualExecutionPTB, } from "./manuallyExec/index.js";
|
|
16
|
-
import { fetchTokenConfigs,
|
|
17
|
-
export const SUI_EXTRA_ARGS_V1_TAG = '21ea4ca9';
|
|
19
|
+
import { deriveObjectID, fetchTokenConfigs, getLatestPackageId, getObjectRef, getReceiverModule, } from "./objects.js";
|
|
18
20
|
const DEFAULT_GAS_LIMIT = 1000000n;
|
|
19
21
|
/**
|
|
20
22
|
* Sui chain implementation supporting Sui networks.
|
|
@@ -29,8 +31,6 @@ export class SuiChain extends Chain {
|
|
|
29
31
|
network;
|
|
30
32
|
client;
|
|
31
33
|
graphqlClient;
|
|
32
|
-
// contracts dir <chainSelectorName, SuiContractDir>
|
|
33
|
-
contractsDir;
|
|
34
34
|
/**
|
|
35
35
|
* Creates a new SuiChain instance.
|
|
36
36
|
* @param client - Sui client for interacting with the Sui network.
|
|
@@ -40,7 +40,6 @@ export class SuiChain extends Chain {
|
|
|
40
40
|
super(network, ctx);
|
|
41
41
|
this.client = client;
|
|
42
42
|
this.network = network;
|
|
43
|
-
this.contractsDir = {};
|
|
44
43
|
// TODO: Graphql client should come from config
|
|
45
44
|
let graphqlUrl;
|
|
46
45
|
if (this.network.name === 'sui-mainnet') {
|
|
@@ -63,6 +62,8 @@ export class SuiChain extends Chain {
|
|
|
63
62
|
* Creates a SuiChain instance from an RPC URL.
|
|
64
63
|
* @param url - HTTP or WebSocket endpoint URL for the Sui network.
|
|
65
64
|
* @returns A new SuiChain instance.
|
|
65
|
+
* @throws {@link CCIPDataFormatUnsupportedError} if unable to fetch chain identifier
|
|
66
|
+
* @throws {@link CCIPError} if chain identifier is not supported
|
|
66
67
|
*/
|
|
67
68
|
static async fromUrl(url, ctx) {
|
|
68
69
|
const client = new SuiClient({ url });
|
|
@@ -84,13 +85,16 @@ export class SuiChain extends Chain {
|
|
|
84
85
|
chainId = 'sui:4'; // devnet
|
|
85
86
|
}
|
|
86
87
|
else {
|
|
87
|
-
throw new CCIPError(CCIPErrorCode.
|
|
88
|
+
throw new CCIPError(CCIPErrorCode.CHAIN_FAMILY_UNSUPPORTED, `Unsupported Sui chain identifier: ${rawChainId}`);
|
|
88
89
|
}
|
|
89
90
|
const network = networkInfo(chainId);
|
|
90
|
-
|
|
91
|
+
const chain = new SuiChain(client, network, ctx);
|
|
92
|
+
return Object.assign(chain, { url });
|
|
91
93
|
}
|
|
92
94
|
/** {@inheritDoc Chain.getBlockTimestamp} */
|
|
93
95
|
async getBlockTimestamp(block) {
|
|
96
|
+
if (typeof block !== 'number' || block <= 0)
|
|
97
|
+
return Math.floor(Date.now() / 1000);
|
|
94
98
|
const checkpoint = await this.client.getCheckpoint({
|
|
95
99
|
id: String(block),
|
|
96
100
|
});
|
|
@@ -113,11 +117,11 @@ export class SuiChain extends Chain {
|
|
|
113
117
|
if (txResponse.events?.length) {
|
|
114
118
|
for (const [i, event] of txResponse.events.entries()) {
|
|
115
119
|
const eventType = event.type;
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
const eventName = eventType.
|
|
120
|
+
const splitIdx = eventType.lastIndexOf('::');
|
|
121
|
+
const address = eventType.substring(0, splitIdx);
|
|
122
|
+
const eventName = eventType.substring(splitIdx + 2);
|
|
119
123
|
events.push({
|
|
120
|
-
address:
|
|
124
|
+
address: address,
|
|
121
125
|
transactionHash: digest,
|
|
122
126
|
index: i,
|
|
123
127
|
blockNumber: Number(txResponse.checkpoint || 0),
|
|
@@ -134,89 +138,106 @@ export class SuiChain extends Chain {
|
|
|
134
138
|
from: txResponse.transaction?.data.sender || '',
|
|
135
139
|
};
|
|
136
140
|
}
|
|
137
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* {@inheritDoc Chain.getLogs}
|
|
143
|
+
* @throws {@link CCIPLogsAddressRequiredError} if address is not provided
|
|
144
|
+
* @throws {@link CCIPTopicsInvalidError} if topics format is invalid
|
|
145
|
+
*/
|
|
138
146
|
async *getLogs(opts) {
|
|
139
|
-
if (!
|
|
140
|
-
throw new
|
|
141
|
-
}
|
|
147
|
+
if (!opts.address)
|
|
148
|
+
throw new CCIPLogsAddressRequiredError();
|
|
142
149
|
// Extract the event type from topics
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.logger.info(`Fetching Sui events of type ${topic} from ${startTime.toISOString()} to ${endTime.toISOString()}`);
|
|
152
|
-
const events = await getSuiEventsInTimeRange(this.client, this.graphqlClient, `${this.contractsDir.offRamp}::offramp::CommitReportAccepted`, startTime, endTime);
|
|
153
|
-
for (const event of events) {
|
|
154
|
-
const eventData = event.contents.json;
|
|
150
|
+
if (opts.topics?.length !== 1 || typeof opts.topics[0] !== 'string') {
|
|
151
|
+
throw new CCIPTopicsInvalidError(opts.topics);
|
|
152
|
+
}
|
|
153
|
+
const topic = opts.topics[0];
|
|
154
|
+
for await (const event of streamSuiLogs(this, opts)) {
|
|
155
|
+
const eventData = event.contents?.json;
|
|
156
|
+
if (!eventData)
|
|
157
|
+
continue;
|
|
155
158
|
yield {
|
|
156
|
-
address:
|
|
157
|
-
transactionHash: event.transaction
|
|
158
|
-
index:
|
|
159
|
+
address: opts.address,
|
|
160
|
+
transactionHash: event.transaction.digest,
|
|
161
|
+
index: Number(event.sequenceNumber) || 0,
|
|
159
162
|
blockNumber: Number(event.transaction?.effects.checkpoint.sequenceNumber || 0),
|
|
160
163
|
data: eventData,
|
|
161
164
|
topics: [topic],
|
|
162
165
|
};
|
|
163
166
|
}
|
|
164
167
|
}
|
|
165
|
-
/** {@inheritDoc Chain.getMessagesInTx} */
|
|
166
|
-
async getMessagesInTx(_tx) {
|
|
167
|
-
return Promise.reject(new CCIPNotImplementedError('SuiChain.getMessagesInTx'));
|
|
168
|
-
}
|
|
169
168
|
/** {@inheritDoc Chain.getMessagesInBatch} */
|
|
170
|
-
async getMessagesInBatch(
|
|
171
|
-
return
|
|
169
|
+
async getMessagesInBatch(request, commit, opts) {
|
|
170
|
+
return getMessagesInBatch(this, request, commit, opts);
|
|
172
171
|
}
|
|
173
|
-
/**
|
|
174
|
-
|
|
175
|
-
|
|
172
|
+
/**
|
|
173
|
+
* {@inheritDoc Chain.typeAndVersion}
|
|
174
|
+
* @throws {@link CCIPDataFormatUnsupportedError} if view call fails
|
|
175
|
+
*/
|
|
176
|
+
async typeAndVersion(address) {
|
|
177
|
+
// requires address to have `::<module>` suffix
|
|
178
|
+
address = await getLatestPackageId(address, this.client);
|
|
179
|
+
const target = `${address}::type_and_version`;
|
|
180
|
+
// Use the Transaction builder to create a move call
|
|
181
|
+
const tx = new Transaction();
|
|
182
|
+
// Add move call to the transaction
|
|
183
|
+
tx.moveCall({ target, arguments: [] });
|
|
184
|
+
// Execute with devInspectTransactionBlock for read-only call
|
|
185
|
+
const result = await this.client.devInspectTransactionBlock({
|
|
186
|
+
sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
187
|
+
transactionBlock: tx,
|
|
188
|
+
});
|
|
189
|
+
if (result.effects.status.status !== 'success' || !result.results?.[0]?.returnValues?.[0]) {
|
|
190
|
+
throw new CCIPDataFormatUnsupportedError(`Failed to call ${target}: ${result.effects.status.error || 'No return value'}`);
|
|
191
|
+
}
|
|
192
|
+
const [data] = result.results[0].returnValues[0];
|
|
193
|
+
const res = bcs.String.parse(getDataBytes(data));
|
|
194
|
+
return parseTypeAndVersion(res);
|
|
176
195
|
}
|
|
177
196
|
/** {@inheritDoc Chain.getRouterForOnRamp} */
|
|
178
197
|
async getRouterForOnRamp(onRamp, _destChainSelector) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.contractsDir.onRamp = onRamp;
|
|
182
|
-
}
|
|
183
|
-
return Promise.resolve(this.contractsDir.onRamp);
|
|
198
|
+
// In Sui, the router is the onRamp package itself
|
|
199
|
+
return Promise.resolve(onRamp);
|
|
184
200
|
}
|
|
185
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* {@inheritDoc Chain.getRouterForOffRamp}
|
|
203
|
+
* @throws {@link CCIPContractNotRouterError} always (Sui architecture doesn't have separate router)
|
|
204
|
+
*/
|
|
186
205
|
getRouterForOffRamp(offRamp, _sourceChainSelector) {
|
|
187
206
|
throw new CCIPContractNotRouterError(offRamp, 'unknown');
|
|
188
207
|
}
|
|
189
208
|
/** {@inheritDoc Chain.getNativeTokenForRouter} */
|
|
190
|
-
getNativeTokenForRouter(
|
|
209
|
+
getNativeTokenForRouter() {
|
|
191
210
|
// SUI native token is always 0x2::sui::SUI
|
|
192
211
|
return Promise.resolve('0x2::sui::SUI');
|
|
193
212
|
}
|
|
194
213
|
/** {@inheritDoc Chain.getOffRampsForRouter} */
|
|
195
214
|
async getOffRampsForRouter(router, _sourceChainSelector) {
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
this.contractsDir.ccip = ccip;
|
|
215
|
+
router = await getLatestPackageId(router, this.client);
|
|
216
|
+
const ccip = await getCcipStateAddress(router, this.client);
|
|
217
|
+
const offramp = await getOffRampForCcip(ccip, this.client);
|
|
200
218
|
return [offramp];
|
|
201
219
|
}
|
|
202
220
|
/** {@inheritDoc Chain.getOnRampForRouter} */
|
|
203
|
-
getOnRampForRouter(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
return Promise.resolve(this.contractsDir.onRamp);
|
|
221
|
+
getOnRampForRouter(router, _destChainSelector) {
|
|
222
|
+
// For Sui, the router is the onramp package address
|
|
223
|
+
return Promise.resolve(router);
|
|
208
224
|
}
|
|
209
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* {@inheritDoc Chain.getOnRampForOffRamp}
|
|
227
|
+
* @throws {@link CCIPDataFormatUnsupportedError} if view call fails
|
|
228
|
+
*/
|
|
210
229
|
async getOnRampForOffRamp(offRamp, sourceChainSelector) {
|
|
211
|
-
|
|
212
|
-
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'CCIP address not set in contracts directory');
|
|
213
|
-
}
|
|
214
|
-
const offrampPackageId = offRamp;
|
|
230
|
+
offRamp = await getLatestPackageId(offRamp, this.client);
|
|
215
231
|
const functionName = 'get_source_chain_config';
|
|
216
|
-
|
|
232
|
+
// Preserve module suffix if present, otherwise add it
|
|
233
|
+
const target = offRamp.includes('::')
|
|
234
|
+
? `${offRamp}::${functionName}`
|
|
235
|
+
: `${offRamp}::offramp::${functionName}`;
|
|
236
|
+
// Discover the CCIP package from the offramp
|
|
237
|
+
const ccip = await getCcipStateAddress(offRamp, this.client);
|
|
217
238
|
// Get the OffRampState object
|
|
218
|
-
const offrampStateObject = await
|
|
219
|
-
const ccipObjectRef = await
|
|
239
|
+
const offrampStateObject = await getObjectRef(offRamp, this.client);
|
|
240
|
+
const ccipObjectRef = await getObjectRef(ccip, this.client);
|
|
220
241
|
// Use the Transaction builder to create a move call
|
|
221
242
|
const tx = new Transaction();
|
|
222
243
|
// Add move call to the transaction with OffRampState object and source chain selector
|
|
@@ -265,39 +286,142 @@ export class SuiChain extends Chain {
|
|
|
265
286
|
getCommitStoreForOffRamp(offRamp) {
|
|
266
287
|
return Promise.resolve(offRamp);
|
|
267
288
|
}
|
|
268
|
-
/**
|
|
269
|
-
|
|
270
|
-
|
|
289
|
+
/**
|
|
290
|
+
* {@inheritDoc Chain.getTokenForTokenPool}
|
|
291
|
+
* @throws {@link CCIPError} if token pool type is invalid or state not found
|
|
292
|
+
* @throws {@link CCIPDataFormatUnsupportedError} if view call fails
|
|
293
|
+
*/
|
|
294
|
+
async getTokenForTokenPool(tokenPool) {
|
|
295
|
+
const normalizedTokenPool = normalizeSuiAddress(tokenPool);
|
|
296
|
+
// Get objects owned by this package (looking for state pointers)
|
|
297
|
+
const objects = await this.client.getOwnedObjects({
|
|
298
|
+
owner: normalizedTokenPool,
|
|
299
|
+
options: { showType: true, showContent: true },
|
|
300
|
+
});
|
|
301
|
+
const tpType = objects.data
|
|
302
|
+
.find((obj) => obj.data?.type?.includes('token_pool::'))
|
|
303
|
+
?.data?.type?.split('::')[1];
|
|
304
|
+
const allowedTps = ['managed_token_pool', 'burn_mint_token_pool', 'lock_release_token_pool'];
|
|
305
|
+
if (!tpType || !allowedTps.includes(tpType)) {
|
|
306
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid token pool type: ${tpType}`);
|
|
307
|
+
}
|
|
308
|
+
// Find the state pointer object
|
|
309
|
+
let stateObjectPointerId;
|
|
310
|
+
for (const obj of objects.data) {
|
|
311
|
+
const content = obj.data?.content;
|
|
312
|
+
if (content?.dataType !== 'moveObject')
|
|
313
|
+
continue;
|
|
314
|
+
const fields = content.fields;
|
|
315
|
+
// Look for a pointer field that references the state object
|
|
316
|
+
stateObjectPointerId = fields[`${tpType}_object_id`];
|
|
317
|
+
}
|
|
318
|
+
if (!stateObjectPointerId) {
|
|
319
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `No token pool state pointer found for ${tokenPool}`);
|
|
320
|
+
}
|
|
321
|
+
const stateNamesPerTP = {
|
|
322
|
+
managed_token_pool: 'ManagedTokenPoolState',
|
|
323
|
+
burn_mint_token_pool: 'BurnMintTokenPoolState',
|
|
324
|
+
lock_release_token_pool: 'LockReleaseTokenPoolState',
|
|
325
|
+
};
|
|
326
|
+
const poolStateObject = deriveObjectID(stateObjectPointerId, new TextEncoder().encode(stateNamesPerTP[tpType]));
|
|
327
|
+
// Get object info to get the coin type
|
|
328
|
+
const info = await this.client.getObject({
|
|
329
|
+
id: poolStateObject,
|
|
330
|
+
options: { showType: true, showContent: true },
|
|
331
|
+
});
|
|
332
|
+
const type = info.data?.type;
|
|
333
|
+
if (!type) {
|
|
334
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading token pool state object type');
|
|
335
|
+
}
|
|
336
|
+
// Extract the type parameter T from ManagedTokenPoolState<T>
|
|
337
|
+
const typeMatch = type.match(/(?:Managed|BurnMint|LockRelease)TokenPoolState<(.+)>$/);
|
|
338
|
+
if (!typeMatch || !typeMatch[1]) {
|
|
339
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid pool state type format: ${type}`);
|
|
340
|
+
}
|
|
341
|
+
const tokenType = typeMatch[1];
|
|
342
|
+
// Call get_token function from managed_token_pool contract with the type parameter
|
|
343
|
+
const target = type.split('<')[0]?.split('::').slice(0, 2).join('::') + '::get_token';
|
|
344
|
+
if (!target) {
|
|
345
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid pool state type format: ${type}`);
|
|
346
|
+
}
|
|
347
|
+
const tx = new Transaction();
|
|
348
|
+
tx.moveCall({
|
|
349
|
+
target,
|
|
350
|
+
typeArguments: [tokenType],
|
|
351
|
+
arguments: [tx.object(poolStateObject)],
|
|
352
|
+
});
|
|
353
|
+
const result = await this.client.devInspectTransactionBlock({
|
|
354
|
+
sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
|
|
355
|
+
transactionBlock: tx,
|
|
356
|
+
});
|
|
357
|
+
if (result.effects.status.status !== 'success' || !result.results?.[0]?.returnValues?.[0]) {
|
|
358
|
+
throw new CCIPDataFormatUnsupportedError(`Failed to call ${target}: ${result.effects.status.error || 'No return value'}`);
|
|
359
|
+
}
|
|
360
|
+
// Parse the return value to get the coin metadata address (32 bytes)
|
|
361
|
+
const returnValue = result.results[0].returnValues[0];
|
|
362
|
+
const [data] = returnValue;
|
|
363
|
+
const coinMetadataBytes = new Uint8Array(data);
|
|
364
|
+
const coinMetadataAddress = normalizeSuiAddress(hexlify(coinMetadataBytes));
|
|
365
|
+
return coinMetadataAddress;
|
|
271
366
|
}
|
|
272
|
-
/**
|
|
367
|
+
/**
|
|
368
|
+
* {@inheritDoc Chain.getTokenInfo}
|
|
369
|
+
* @throws {@link CCIPError} if token address is invalid or metadata cannot be loaded
|
|
370
|
+
*/
|
|
273
371
|
async getTokenInfo(token) {
|
|
274
|
-
|
|
275
|
-
if (
|
|
276
|
-
|
|
372
|
+
const normalizedTokenAddress = normalizeSuiAddress(token);
|
|
373
|
+
if (!isValidSuiAddress(normalizedTokenAddress)) {
|
|
374
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata');
|
|
277
375
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
};
|
|
376
|
+
const objectResponse = await this.client.getObject({
|
|
377
|
+
id: normalizedTokenAddress,
|
|
378
|
+
options: { showType: true },
|
|
379
|
+
});
|
|
380
|
+
const getCoinFromMetadata = (metadata) => {
|
|
381
|
+
// Extract the type parameter from CoinMetadata<...>
|
|
382
|
+
const match = metadata.match(/CoinMetadata<(.+)>$/);
|
|
383
|
+
if (!match || !match[1]) {
|
|
384
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid metadata format: ${metadata}`);
|
|
287
385
|
}
|
|
386
|
+
return match[1];
|
|
387
|
+
};
|
|
388
|
+
let coinType;
|
|
389
|
+
const objectType = objectResponse.data?.type;
|
|
390
|
+
// Check if this is a CoinMetadata object or a coin type string
|
|
391
|
+
if (objectType?.includes('CoinMetadata')) {
|
|
392
|
+
coinType = getCoinFromMetadata(objectType);
|
|
393
|
+
}
|
|
394
|
+
else if (token.includes('::')) {
|
|
395
|
+
// This is a coin type string (e.g., "0xabc::coin::COIN")
|
|
396
|
+
coinType = token;
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
// This is a package address or unknown format
|
|
400
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, `Token address ${token} is not a CoinMetadata object or coin type. Expected format: package::module::Type`);
|
|
401
|
+
}
|
|
402
|
+
if (coinType.split('::').length < 3) {
|
|
403
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata');
|
|
404
|
+
}
|
|
405
|
+
let metadata = null;
|
|
406
|
+
try {
|
|
407
|
+
metadata = await this.client.getCoinMetadata({ coinType });
|
|
408
|
+
}
|
|
409
|
+
catch (e) {
|
|
410
|
+
console.error('Error fetching coin metadata:', e);
|
|
411
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata');
|
|
288
412
|
}
|
|
289
|
-
|
|
290
|
-
|
|
413
|
+
if (!metadata) {
|
|
414
|
+
throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata');
|
|
291
415
|
}
|
|
292
|
-
// Fallback: parse from token type string if possible
|
|
293
|
-
const parts = token.split('::');
|
|
294
|
-
const symbol = parts[parts.length - 1] || 'UNKNOWN';
|
|
295
416
|
return {
|
|
296
|
-
symbol: symbol
|
|
297
|
-
decimals:
|
|
417
|
+
symbol: metadata.symbol,
|
|
418
|
+
decimals: metadata.decimals,
|
|
298
419
|
};
|
|
299
420
|
}
|
|
300
|
-
/** {@inheritDoc Chain.
|
|
421
|
+
/** {@inheritDoc Chain.getBalance} */
|
|
422
|
+
async getBalance(_opts) {
|
|
423
|
+
return Promise.reject(new CCIPNotImplementedError('SuiChain.getBalance'));
|
|
424
|
+
}
|
|
301
425
|
/** {@inheritDoc Chain.getTokenAdminRegistryFor} */
|
|
302
426
|
getTokenAdminRegistryFor(_address) {
|
|
303
427
|
return Promise.reject(new CCIPNotImplementedError());
|
|
@@ -305,11 +429,22 @@ export class SuiChain extends Chain {
|
|
|
305
429
|
// Static methods for decoding
|
|
306
430
|
/**
|
|
307
431
|
* Decodes a CCIP message from a Sui log event.
|
|
308
|
-
* @param
|
|
432
|
+
* @param log - Log event data.
|
|
309
433
|
* @returns Decoded CCIPMessage or undefined if not valid.
|
|
434
|
+
* @throws {@link CCIPSuiLogInvalidError} if log data format is invalid
|
|
310
435
|
*/
|
|
311
|
-
static decodeMessage(
|
|
312
|
-
|
|
436
|
+
static decodeMessage(log) {
|
|
437
|
+
const { data } = log;
|
|
438
|
+
if ((typeof data !== 'string' || !data.startsWith('{')) &&
|
|
439
|
+
(typeof data !== 'object' || isBytesLike(data)))
|
|
440
|
+
throw new CCIPSuiLogInvalidError(util.inspect(log));
|
|
441
|
+
// offload massaging to generic decodeJsonMessage
|
|
442
|
+
try {
|
|
443
|
+
return decodeMessage(data);
|
|
444
|
+
}
|
|
445
|
+
catch (_) {
|
|
446
|
+
// return undefined
|
|
447
|
+
}
|
|
313
448
|
}
|
|
314
449
|
/**
|
|
315
450
|
* Decodes extra arguments from Sui CCIP messages.
|
|
@@ -323,6 +458,7 @@ export class SuiChain extends Chain {
|
|
|
323
458
|
* Encodes extra arguments for CCIP messages.
|
|
324
459
|
* @param _extraArgs - Extra arguments to encode.
|
|
325
460
|
* @returns Encoded extra arguments as a hex string.
|
|
461
|
+
* @throws {@link CCIPNotImplementedError} always (not yet implemented)
|
|
326
462
|
*/
|
|
327
463
|
static encodeExtraArgs(_extraArgs) {
|
|
328
464
|
throw new CCIPNotImplementedError();
|
|
@@ -330,65 +466,52 @@ export class SuiChain extends Chain {
|
|
|
330
466
|
/**
|
|
331
467
|
* Decodes commit reports from a log entry.
|
|
332
468
|
* @param log - The log entry to decode.
|
|
333
|
-
* @param
|
|
469
|
+
* @param lane - Optional lane information.
|
|
334
470
|
* @returns Array of decoded commit reports or undefined.
|
|
335
471
|
*/
|
|
336
|
-
static decodeCommits(
|
|
337
|
-
|
|
472
|
+
static decodeCommits({ data, topics }, lane) {
|
|
473
|
+
// Check if this is an CommitReportAccepted event
|
|
474
|
+
if (topics?.[0] && topics[0] !== 'CommitReportAccepted')
|
|
338
475
|
return;
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const eventData = log.data;
|
|
342
|
-
const unblessedRoots = eventData.unblessed_merkle_roots;
|
|
343
|
-
if (!Array.isArray(unblessedRoots) || unblessedRoots.length === 0) {
|
|
476
|
+
// Basic log data structure validation
|
|
477
|
+
if (!data || typeof data !== 'object' || !('unblessed_merkle_roots' in data))
|
|
344
478
|
return;
|
|
345
|
-
|
|
346
|
-
|
|
479
|
+
const eventData = data;
|
|
480
|
+
const rootsRaw = eventData.blessed_merkle_roots.concat(eventData.unblessed_merkle_roots);
|
|
481
|
+
return rootsRaw
|
|
482
|
+
.map((root) => {
|
|
347
483
|
return {
|
|
348
484
|
sourceChainSelector: BigInt(root.source_chain_selector),
|
|
349
|
-
onRampAddress:
|
|
485
|
+
onRampAddress: decodeOnRampAddress(root.on_ramp_address),
|
|
350
486
|
minSeqNr: BigInt(root.min_seq_nr),
|
|
351
487
|
maxSeqNr: BigInt(root.max_seq_nr),
|
|
352
|
-
merkleRoot:
|
|
488
|
+
merkleRoot: hexlify(getDataBytes(root.merkle_root)),
|
|
353
489
|
};
|
|
354
|
-
})
|
|
490
|
+
})
|
|
491
|
+
.filter((r) => lane
|
|
492
|
+
? r.sourceChainSelector === lane.sourceChainSelector && r.onRampAddress === lane.onRamp
|
|
493
|
+
: true);
|
|
355
494
|
}
|
|
356
495
|
/**
|
|
357
496
|
* Decodes an execution receipt from a log entry.
|
|
358
497
|
* @param log - The log entry to decode.
|
|
359
498
|
* @returns Decoded execution receipt or undefined.
|
|
360
499
|
*/
|
|
361
|
-
static decodeReceipt(
|
|
500
|
+
static decodeReceipt({ data, topics, }) {
|
|
362
501
|
// Check if this is an ExecutionStateChanged event
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
const eventData = log.data;
|
|
372
|
-
// Verify required fields exist
|
|
373
|
-
if (!eventData.message_id ||
|
|
374
|
-
!Array.isArray(eventData.message_id) ||
|
|
375
|
-
eventData.sequence_number === undefined ||
|
|
376
|
-
eventData.state === undefined) {
|
|
377
|
-
return undefined;
|
|
378
|
-
}
|
|
379
|
-
const toHex = (bytes) => hexlify(bytesToBuffer(bytes));
|
|
380
|
-
// Convert message_id bytes array to hex string
|
|
381
|
-
const messageId = toHex(eventData.message_id);
|
|
382
|
-
// Convert message_hash bytes array to hex string (if present)
|
|
383
|
-
const messageHash = eventData.message_hash ? toHex(eventData.message_hash) : undefined;
|
|
502
|
+
if (topics?.[0] && topics[0] !== 'ExecutionStateChanged')
|
|
503
|
+
return;
|
|
504
|
+
// Basic log data structure validation
|
|
505
|
+
if (!data || typeof data !== 'object' || !('message_id' in data) || !('state' in data)) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const eventData = data;
|
|
384
509
|
return {
|
|
385
|
-
messageId,
|
|
510
|
+
messageId: hexlify(getDataBytes(eventData.message_id)),
|
|
386
511
|
sequenceNumber: BigInt(eventData.sequence_number),
|
|
387
|
-
state: eventData.state,
|
|
388
|
-
sourceChainSelector: eventData.source_chain_selector
|
|
389
|
-
|
|
390
|
-
: undefined,
|
|
391
|
-
messageHash,
|
|
512
|
+
state: Number(eventData.state),
|
|
513
|
+
sourceChainSelector: BigInt(eventData.source_chain_selector),
|
|
514
|
+
messageHash: hexlify(getDataBytes(eventData.message_hash)),
|
|
392
515
|
};
|
|
393
516
|
}
|
|
394
517
|
/**
|
|
@@ -402,8 +525,11 @@ export class SuiChain extends Chain {
|
|
|
402
525
|
/**
|
|
403
526
|
* Validates a transaction hash format for Sui
|
|
404
527
|
*/
|
|
405
|
-
static isTxHash(
|
|
406
|
-
|
|
528
|
+
static isTxHash(v) {
|
|
529
|
+
if (typeof v !== 'string')
|
|
530
|
+
return false;
|
|
531
|
+
// check in both hex and base58 formats
|
|
532
|
+
return isHexString(v, 32) || isValidTransactionDigest(v);
|
|
407
533
|
}
|
|
408
534
|
/**
|
|
409
535
|
* Gets the leaf hasher for Sui destination chains.
|
|
@@ -427,9 +553,6 @@ export class SuiChain extends Chain {
|
|
|
427
553
|
}
|
|
428
554
|
/** {@inheritDoc Chain.getOffchainTokenData} */
|
|
429
555
|
getOffchainTokenData(request) {
|
|
430
|
-
if (!('receiverObjectIds' in request.message)) {
|
|
431
|
-
throw new CCIPSuiMessageVersionInvalidError();
|
|
432
|
-
}
|
|
433
556
|
// default offchain token data
|
|
434
557
|
return Promise.resolve(request.message.tokenAmounts.map(() => undefined));
|
|
435
558
|
}
|
|
@@ -437,24 +560,27 @@ export class SuiChain extends Chain {
|
|
|
437
560
|
generateUnsignedExecuteReport(_opts) {
|
|
438
561
|
return Promise.reject(new CCIPNotImplementedError('SuiChain.generateUnsignedExecuteReport'));
|
|
439
562
|
}
|
|
440
|
-
/**
|
|
563
|
+
/**
|
|
564
|
+
* {@inheritDoc Chain.executeReport}
|
|
565
|
+
* @throws {@link CCIPError} if transaction submission fails
|
|
566
|
+
* @throws {@link CCIPExecTxRevertedError} if transaction reverts
|
|
567
|
+
*/
|
|
441
568
|
async executeReport(opts) {
|
|
442
|
-
const { execReport } = opts;
|
|
443
|
-
if (!this.contractsDir.offRamp || !this.contractsDir.ccip) {
|
|
444
|
-
throw new CCIPContractNotRouterError('OffRamp or CCIP address not set in contracts directory', 'Sui');
|
|
445
|
-
}
|
|
569
|
+
const { execReport, offRamp } = opts;
|
|
446
570
|
const wallet = opts.wallet;
|
|
447
|
-
|
|
448
|
-
const
|
|
449
|
-
const
|
|
571
|
+
// Discover the CCIP package from the offramp
|
|
572
|
+
const ccip = await getCcipStateAddress(offRamp, this.client);
|
|
573
|
+
const ccipObjectRef = await getObjectRef(ccip, this.client);
|
|
574
|
+
const offrampStateObject = await getObjectRef(offRamp, this.client);
|
|
575
|
+
const receiverConfig = await getReceiverModule(this.client, ccip, ccipObjectRef, execReport.message.receiver);
|
|
450
576
|
let tokenConfigs = [];
|
|
451
577
|
if (execReport.message.tokenAmounts.length !== 0) {
|
|
452
|
-
tokenConfigs = await fetchTokenConfigs(this.client,
|
|
578
|
+
tokenConfigs = await fetchTokenConfigs(this.client, ccip, ccipObjectRef, execReport.message.tokenAmounts);
|
|
453
579
|
}
|
|
454
580
|
const input = {
|
|
455
581
|
executionReport: execReport,
|
|
456
|
-
offrampAddress:
|
|
457
|
-
ccipAddress:
|
|
582
|
+
offrampAddress: offRamp,
|
|
583
|
+
ccipAddress: ccip,
|
|
458
584
|
ccipObjectRef,
|
|
459
585
|
offrampStateObject,
|
|
460
586
|
receiverConfig,
|
|
@@ -523,9 +649,9 @@ export class SuiChain extends Chain {
|
|
|
523
649
|
async getRegistryTokenConfig(_address, _tokenName) {
|
|
524
650
|
return Promise.reject(new CCIPNotImplementedError('SuiChain.getRegistryTokenConfig'));
|
|
525
651
|
}
|
|
526
|
-
/** {@inheritDoc Chain.
|
|
527
|
-
async
|
|
528
|
-
return Promise.reject(new CCIPNotImplementedError('SuiChain.
|
|
652
|
+
/** {@inheritDoc Chain.getTokenPoolConfig} */
|
|
653
|
+
async getTokenPoolConfig(_tokenPool) {
|
|
654
|
+
return Promise.reject(new CCIPNotImplementedError('SuiChain.getTokenPoolConfig'));
|
|
529
655
|
}
|
|
530
656
|
/** {@inheritDoc Chain.getTokenPoolRemotes} */
|
|
531
657
|
async getTokenPoolRemotes(_tokenPool) {
|
|
@@ -549,7 +675,8 @@ export class SuiChain extends Chain {
|
|
|
549
675
|
: true;
|
|
550
676
|
const tokenReceiver = message.extraArgs &&
|
|
551
677
|
'tokenReceiver' in message.extraArgs &&
|
|
552
|
-
message.extraArgs.tokenReceiver != null
|
|
678
|
+
message.extraArgs.tokenReceiver != null &&
|
|
679
|
+
typeof message.extraArgs.tokenReceiver === 'string'
|
|
553
680
|
? message.extraArgs.tokenReceiver
|
|
554
681
|
: message.tokenAmounts?.length
|
|
555
682
|
? this.getAddress(message.receiver)
|