@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.
Files changed (170) hide show
  1. package/README.md +2 -2
  2. package/dist/all-chains.d.ts +23 -0
  3. package/dist/all-chains.d.ts.map +1 -0
  4. package/dist/all-chains.js +24 -0
  5. package/dist/all-chains.js.map +1 -0
  6. package/dist/api/index.d.ts +86 -7
  7. package/dist/api/index.d.ts.map +1 -1
  8. package/dist/api/index.js +270 -10
  9. package/dist/api/index.js.map +1 -1
  10. package/dist/api/types.d.ts +134 -13
  11. package/dist/api/types.d.ts.map +1 -1
  12. package/dist/aptos/index.d.ts +38 -17
  13. package/dist/aptos/index.d.ts.map +1 -1
  14. package/dist/aptos/index.js +91 -61
  15. package/dist/aptos/index.js.map +1 -1
  16. package/dist/aptos/logs.js +3 -3
  17. package/dist/aptos/logs.js.map +1 -1
  18. package/dist/chain.d.ts +300 -42
  19. package/dist/chain.d.ts.map +1 -1
  20. package/dist/chain.js +160 -9
  21. package/dist/chain.js.map +1 -1
  22. package/dist/errors/codes.d.ts +9 -3
  23. package/dist/errors/codes.d.ts.map +1 -1
  24. package/dist/errors/codes.js +10 -3
  25. package/dist/errors/codes.js.map +1 -1
  26. package/dist/errors/index.d.ts +8 -8
  27. package/dist/errors/index.d.ts.map +1 -1
  28. package/dist/errors/index.js +8 -8
  29. package/dist/errors/index.js.map +1 -1
  30. package/dist/errors/recovery.d.ts.map +1 -1
  31. package/dist/errors/recovery.js +10 -4
  32. package/dist/errors/recovery.js.map +1 -1
  33. package/dist/errors/specialized.d.ts +62 -21
  34. package/dist/errors/specialized.d.ts.map +1 -1
  35. package/dist/errors/specialized.js +128 -41
  36. package/dist/errors/specialized.js.map +1 -1
  37. package/dist/evm/extra-args.d.ts +25 -0
  38. package/dist/evm/extra-args.d.ts.map +1 -0
  39. package/dist/evm/extra-args.js +328 -0
  40. package/dist/evm/extra-args.js.map +1 -0
  41. package/dist/evm/gas.d.ts +14 -0
  42. package/dist/evm/gas.d.ts.map +1 -0
  43. package/dist/evm/gas.js +92 -0
  44. package/dist/evm/gas.js.map +1 -0
  45. package/dist/evm/index.d.ts +76 -32
  46. package/dist/evm/index.d.ts.map +1 -1
  47. package/dist/evm/index.js +94 -104
  48. package/dist/evm/index.js.map +1 -1
  49. package/dist/evm/offchain.d.ts.map +1 -1
  50. package/dist/evm/offchain.js +8 -8
  51. package/dist/evm/offchain.js.map +1 -1
  52. package/dist/execution.d.ts.map +1 -1
  53. package/dist/execution.js +24 -3
  54. package/dist/execution.js.map +1 -1
  55. package/dist/extra-args.d.ts +103 -4
  56. package/dist/extra-args.d.ts.map +1 -1
  57. package/dist/extra-args.js +28 -3
  58. package/dist/extra-args.js.map +1 -1
  59. package/dist/gas.d.ts +46 -19
  60. package/dist/gas.d.ts.map +1 -1
  61. package/dist/gas.js +56 -68
  62. package/dist/gas.js.map +1 -1
  63. package/dist/index.d.ts +18 -15
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +14 -13
  66. package/dist/index.js.map +1 -1
  67. package/dist/offchain.d.ts +5 -4
  68. package/dist/offchain.d.ts.map +1 -1
  69. package/dist/offchain.js +7 -6
  70. package/dist/offchain.js.map +1 -1
  71. package/dist/requests.d.ts +30 -20
  72. package/dist/requests.d.ts.map +1 -1
  73. package/dist/requests.js +86 -56
  74. package/dist/requests.js.map +1 -1
  75. package/dist/selectors.d.ts +2 -1
  76. package/dist/selectors.d.ts.map +1 -1
  77. package/dist/selectors.js +625 -278
  78. package/dist/selectors.js.map +1 -1
  79. package/dist/solana/exec.d.ts.map +1 -1
  80. package/dist/solana/exec.js +2 -1
  81. package/dist/solana/exec.js.map +1 -1
  82. package/dist/solana/index.d.ts +73 -22
  83. package/dist/solana/index.d.ts.map +1 -1
  84. package/dist/solana/index.js +91 -28
  85. package/dist/solana/index.js.map +1 -1
  86. package/dist/solana/offchain.js +2 -2
  87. package/dist/solana/offchain.js.map +1 -1
  88. package/dist/solana/send.d.ts.map +1 -1
  89. package/dist/solana/send.js +6 -9
  90. package/dist/solana/send.js.map +1 -1
  91. package/dist/solana/utils.d.ts +29 -1
  92. package/dist/solana/utils.d.ts.map +1 -1
  93. package/dist/solana/utils.js +39 -1
  94. package/dist/solana/utils.js.map +1 -1
  95. package/dist/sui/discovery.d.ts +7 -4
  96. package/dist/sui/discovery.d.ts.map +1 -1
  97. package/dist/sui/discovery.js +66 -19
  98. package/dist/sui/discovery.js.map +1 -1
  99. package/dist/sui/events.d.ts +23 -12
  100. package/dist/sui/events.d.ts.map +1 -1
  101. package/dist/sui/events.js +267 -128
  102. package/dist/sui/events.js.map +1 -1
  103. package/dist/sui/index.d.ts +57 -41
  104. package/dist/sui/index.d.ts.map +1 -1
  105. package/dist/sui/index.js +286 -159
  106. package/dist/sui/index.js.map +1 -1
  107. package/dist/sui/objects.d.ts +14 -4
  108. package/dist/sui/objects.d.ts.map +1 -1
  109. package/dist/sui/objects.js +61 -68
  110. package/dist/sui/objects.js.map +1 -1
  111. package/dist/sui/types.d.ts +33 -0
  112. package/dist/sui/types.d.ts.map +1 -1
  113. package/dist/sui/types.js.map +1 -1
  114. package/dist/ton/index.d.ts +67 -21
  115. package/dist/ton/index.d.ts.map +1 -1
  116. package/dist/ton/index.js +159 -30
  117. package/dist/ton/index.js.map +1 -1
  118. package/dist/ton/send.d.ts +52 -0
  119. package/dist/ton/send.d.ts.map +1 -0
  120. package/dist/ton/send.js +166 -0
  121. package/dist/ton/send.js.map +1 -0
  122. package/dist/ton/utils.d.ts +3 -3
  123. package/dist/ton/utils.d.ts.map +1 -1
  124. package/dist/ton/utils.js +6 -5
  125. package/dist/ton/utils.js.map +1 -1
  126. package/dist/types.d.ts +126 -9
  127. package/dist/types.d.ts.map +1 -1
  128. package/dist/types.js +19 -5
  129. package/dist/types.js.map +1 -1
  130. package/dist/utils.d.ts +67 -4
  131. package/dist/utils.d.ts.map +1 -1
  132. package/dist/utils.js +126 -17
  133. package/dist/utils.js.map +1 -1
  134. package/package.json +14 -9
  135. package/src/all-chains.ts +26 -0
  136. package/src/api/index.ts +348 -13
  137. package/src/api/types.ts +160 -13
  138. package/src/aptos/index.ts +98 -76
  139. package/src/aptos/logs.ts +3 -3
  140. package/src/chain.ts +408 -51
  141. package/src/errors/codes.ts +10 -3
  142. package/src/errors/index.ts +8 -5
  143. package/src/errors/recovery.ts +18 -5
  144. package/src/errors/specialized.ts +168 -49
  145. package/src/evm/extra-args.ts +377 -0
  146. package/src/evm/gas.ts +150 -0
  147. package/src/evm/index.ts +123 -155
  148. package/src/evm/offchain.ts +15 -9
  149. package/src/execution.ts +26 -3
  150. package/src/extra-args.ts +108 -4
  151. package/src/gas.ts +101 -115
  152. package/src/index.ts +27 -14
  153. package/src/offchain.ts +12 -6
  154. package/src/requests.ts +117 -67
  155. package/src/selectors.ts +632 -280
  156. package/src/solana/exec.ts +3 -1
  157. package/src/solana/index.ts +97 -37
  158. package/src/solana/offchain.ts +2 -2
  159. package/src/solana/send.ts +5 -23
  160. package/src/solana/utils.ts +66 -0
  161. package/src/sui/discovery.ts +92 -31
  162. package/src/sui/events.ts +346 -239
  163. package/src/sui/index.ts +365 -212
  164. package/src/sui/objects.ts +74 -98
  165. package/src/sui/types.ts +35 -0
  166. package/src/ton/index.ts +199 -35
  167. package/src/ton/send.ts +222 -0
  168. package/src/ton/utils.ts +7 -6
  169. package/src/types.ts +128 -9
  170. package/src/utils.ts +169 -21
package/dist/sui/index.js CHANGED
@@ -1,20 +1,22 @@
1
- import { Buffer } from 'buffer';
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 { dataLength, hexlify, isBytesLike } from 'ethers';
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, CCIPSuiMessageVersionInvalidError, CCIPVersionFeatureUnavailableError, } from "../errors/index.js";
9
- import { getSuiLeafHasher } from "./hasher.js";
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 { discoverCCIP, discoverOfframp } from "./discovery.js";
13
- import { bytesToBuffer, decodeAddress, networkInfo } from "../utils.js";
14
- import { getSuiEventsInTimeRange } from "./events.js";
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, getCcipObjectRef, getOffRampStateObject, getReceiverModule, } from "./objects.js";
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.NETWORK_FAMILY_UNSUPPORTED, `Unsupported Sui chain identifier: ${rawChainId}`);
88
+ throw new CCIPError(CCIPErrorCode.CHAIN_FAMILY_UNSUPPORTED, `Unsupported Sui chain identifier: ${rawChainId}`);
88
89
  }
89
90
  const network = networkInfo(chainId);
90
- return new SuiChain(client, network, ctx);
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 packageId = eventType.split('::')[0];
117
- const moduleName = eventType.split('::')[1];
118
- const eventName = eventType.split('::')[2];
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: `${packageId}::${moduleName}`,
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
- /** {@inheritDoc Chain.getLogs} */
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 (!this.contractsDir.offRamp) {
140
- throw new CCIPContractNotRouterError('OffRamp address not set in contracts directory', 'Sui');
141
- }
147
+ if (!opts.address)
148
+ throw new CCIPLogsAddressRequiredError();
142
149
  // Extract the event type from topics
143
- const topic = Array.isArray(opts.topics?.[0]) ? opts.topics[0][0] : opts.topics?.[0] || '';
144
- if (!topic || topic !== 'CommitReportAccepted') {
145
- throw new CCIPVersionFeatureUnavailableError('Event type', topic || 'unknown', 'CommitReportAccepted');
146
- }
147
- const startTime = opts.startTime ? new Date(opts.startTime * 1000) : new Date(0);
148
- const endTime = opts.endBlock
149
- ? new Date(opts.endBlock)
150
- : new Date(startTime.getTime() + 1 * 24 * 60 * 60 * 1000); // default to +24h
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: this.contractsDir.offRamp,
157
- transactionHash: event.transaction?.digest || '',
158
- index: 0, // Sui events do not have an index, set to 0
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(_request, _commit, _opts) {
171
- return Promise.reject(new CCIPNotImplementedError('SuiChain.getMessagesInBatch'));
169
+ async getMessagesInBatch(request, commit, opts) {
170
+ return getMessagesInBatch(this, request, commit, opts);
172
171
  }
173
- /** {@inheritDoc Chain.typeAndVersion} */
174
- async typeAndVersion(_address) {
175
- return Promise.reject(new CCIPNotImplementedError('SuiChain.typeAndVersion'));
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
- this.contractsDir.onRamp = onRamp;
180
- if (onRamp !== this.contractsDir.onRamp) {
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
- /** {@inheritDoc Chain.getRouterForOffRamp} */
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(_router) {
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
- const ccip = await discoverCCIP(this.client, router);
197
- const offramp = await discoverOfframp(this.client, ccip);
198
- this.contractsDir.offRamp = offramp;
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(_router, _destChainSelector) {
204
- if (!this.contractsDir.onRamp) {
205
- throw new CCIPContractNotRouterError('OnRamp address not set in contracts directory', 'Sui');
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
- /** {@inheritDoc Chain.getOnRampForOffRamp} */
225
+ /**
226
+ * {@inheritDoc Chain.getOnRampForOffRamp}
227
+ * @throws {@link CCIPDataFormatUnsupportedError} if view call fails
228
+ */
210
229
  async getOnRampForOffRamp(offRamp, sourceChainSelector) {
211
- if (!this.contractsDir.ccip) {
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
- const target = `${offrampPackageId}::offramp::${functionName}`;
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 getOffRampStateObject(this.client, offrampPackageId);
219
- const ccipObjectRef = await getCcipObjectRef(this.client, this.contractsDir.ccip);
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
- /** {@inheritDoc Chain.getTokenForTokenPool} */
269
- getTokenForTokenPool(_tokenPool) {
270
- throw new CCIPNotImplementedError();
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
- /** {@inheritDoc Chain.getTokenInfo} */
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
- // Handle native SUI token
275
- if (token === '0x2::sui::SUI' || token.includes('::sui::SUI')) {
276
- return { symbol: 'SUI', decimals: 9 };
372
+ const normalizedTokenAddress = normalizeSuiAddress(token);
373
+ if (!isValidSuiAddress(normalizedTokenAddress)) {
374
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata');
277
375
  }
278
- try {
279
- // For Coin types, try to fetch metadata from the coin metadata object
280
- // Format: 0xPACKAGE::module::TYPE
281
- const coinMetadata = await this.client.getCoinMetadata({ coinType: token });
282
- if (coinMetadata) {
283
- return {
284
- symbol: coinMetadata.symbol || 'UNKNOWN',
285
- decimals: coinMetadata.decimals,
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
- catch (error) {
290
- console.log(`Failed to fetch coin metadata for ${token}:`, error);
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.toUpperCase(),
297
- decimals: 9, // Default to 9 decimals (SUI standard)
417
+ symbol: metadata.symbol,
418
+ decimals: metadata.decimals,
298
419
  };
299
420
  }
300
- /** {@inheritDoc Chain.getTokenAdminRegistryFor} */
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 _log - Log event data.
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(_log) {
312
- throw new CCIPNotImplementedError();
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 _lane - Optional lane information.
469
+ * @param lane - Optional lane information.
334
470
  * @returns Array of decoded commit reports or undefined.
335
471
  */
336
- static decodeCommits(log, _lane) {
337
- if (!log.data || typeof log.data !== 'object' || !('unblessed_merkle_roots' in log.data)) {
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
- const toHexFromBase64 = (b64) => '0x' + Buffer.from(b64, 'base64').toString('hex');
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
- return unblessedRoots.map((root) => {
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: toHexFromBase64(root.on_ramp_address),
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: toHexFromBase64(root.merkle_root),
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(log) {
500
+ static decodeReceipt({ data, topics, }) {
362
501
  // Check if this is an ExecutionStateChanged event
363
- const topic = (Array.isArray(log.topics) ? log.topics[0] : log.topics);
364
- if (topic !== 'ExecutionStateChanged') {
365
- return undefined;
366
- }
367
- // Validate log data structure
368
- if (!log.data || typeof log.data !== 'object') {
369
- return undefined;
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
- ? BigInt(eventData.source_chain_selector)
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(_v) {
406
- return false;
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
- /** {@inheritDoc Chain.executeReport} */
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
- const ccipObjectRef = await getCcipObjectRef(this.client, this.contractsDir.ccip);
448
- const offrampStateObject = await getOffRampStateObject(this.client, this.contractsDir.offRamp);
449
- const receiverConfig = await getReceiverModule(this.client, this.contractsDir.ccip, ccipObjectRef, execReport.message.receiver);
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, this.contractsDir.ccip, ccipObjectRef, execReport.message.tokenAmounts);
578
+ tokenConfigs = await fetchTokenConfigs(this.client, ccip, ccipObjectRef, execReport.message.tokenAmounts);
453
579
  }
454
580
  const input = {
455
581
  executionReport: execReport,
456
- offrampAddress: this.contractsDir.offRamp,
457
- ccipAddress: this.contractsDir.ccip,
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.getTokenPoolConfigs} */
527
- async getTokenPoolConfigs(_tokenPool) {
528
- return Promise.reject(new CCIPNotImplementedError('SuiChain.getTokenPoolConfigs'));
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)