@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/src/sui/index.ts CHANGED
@@ -1,14 +1,20 @@
1
- import { Buffer } from 'buffer'
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 { type BytesLike, dataLength, hexlify, isBytesLike } from 'ethers'
8
- import type { PickDeep } from 'type-fest'
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 { type ChainContext, type ChainStatic, type LogFilter, Chain } from '../chain.ts'
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 { discoverCCIP, discoverOfframp } from './discovery.ts'
45
- import type { CCIPMessage_V1_6_Sui } from './types.ts'
46
- import { bytesToBuffer, decodeAddress, networkInfo } from '../utils.ts'
47
- import { type CommitEvent, getSuiEventsInTimeRange } from './events.ts'
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
- getCcipObjectRef,
56
- getOffRampStateObject,
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
@@ -119,6 +125,8 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
119
125
  * Creates a SuiChain instance from an RPC URL.
120
126
  * @param url - HTTP or WebSocket endpoint URL for the Sui network.
121
127
  * @returns A new SuiChain instance.
128
+ * @throws {@link CCIPDataFormatUnsupportedError} if unable to fetch chain identifier
129
+ * @throws {@link CCIPError} if chain identifier is not supported
122
130
  */
123
131
  static async fromUrl(url: string, ctx?: ChainContext): Promise<SuiChain> {
124
132
  const client = new SuiClient({ url })
@@ -140,17 +148,19 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
140
148
  chainId = 'sui:4' // devnet
141
149
  } else {
142
150
  throw new CCIPError(
143
- CCIPErrorCode.NETWORK_FAMILY_UNSUPPORTED,
151
+ CCIPErrorCode.CHAIN_FAMILY_UNSUPPORTED,
144
152
  `Unsupported Sui chain identifier: ${rawChainId}`,
145
153
  )
146
154
  }
147
155
 
148
156
  const network = networkInfo(chainId) as NetworkInfo<typeof ChainFamily.Sui>
149
- return new SuiChain(client, network, ctx)
157
+ const chain = new SuiChain(client, network, ctx)
158
+ return Object.assign(chain, { url })
150
159
  }
151
160
 
152
161
  /** {@inheritDoc Chain.getBlockTimestamp} */
153
- async getBlockTimestamp(block: number): Promise<number> {
162
+ async getBlockTimestamp(block: number | 'finalized'): Promise<number> {
163
+ if (typeof block !== 'number' || block <= 0) return Math.floor(Date.now() / 1000)
154
164
  const checkpoint = await this.client.getCheckpoint({
155
165
  id: String(block),
156
166
  })
@@ -176,12 +186,12 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
176
186
  if (txResponse.events?.length) {
177
187
  for (const [i, event] of txResponse.events.entries()) {
178
188
  const eventType = event.type
179
- const packageId = eventType.split('::')[0]
180
- const moduleName = eventType.split('::')[1]
181
- const eventName = eventType.split('::')[2]!
189
+ const splitIdx = eventType.lastIndexOf('::')
190
+ const address = eventType.substring(0, splitIdx)
191
+ const eventName = eventType.substring(splitIdx + 2)
182
192
 
183
193
  events.push({
184
- address: `${packageId}::${moduleName}`,
194
+ address: address,
185
195
  transactionHash: digest,
186
196
  index: i,
187
197
  blockNumber: Number(txResponse.checkpoint || 0),
@@ -200,43 +210,27 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
200
210
  }
201
211
  }
202
212
 
203
- /** {@inheritDoc Chain.getLogs} */
213
+ /**
214
+ * {@inheritDoc Chain.getLogs}
215
+ * @throws {@link CCIPLogsAddressRequiredError} if address is not provided
216
+ * @throws {@link CCIPTopicsInvalidError} if topics format is invalid
217
+ */
204
218
  async *getLogs(opts: LogFilter & { versionAsHash?: boolean }) {
205
- if (!this.contractsDir.offRamp) {
206
- throw new CCIPContractNotRouterError('OffRamp address not set in contracts directory', 'Sui')
207
- }
219
+ if (!opts.address) throw new CCIPLogsAddressRequiredError()
220
+
208
221
  // Extract the event type from topics
209
- const topic = Array.isArray(opts.topics?.[0]) ? opts.topics[0][0] : opts.topics?.[0] || ''
210
- if (!topic || topic !== 'CommitReportAccepted') {
211
- throw new CCIPVersionFeatureUnavailableError(
212
- 'Event type',
213
- topic || 'unknown',
214
- 'CommitReportAccepted',
215
- )
222
+ if (opts.topics?.length !== 1 || typeof opts.topics[0] !== 'string') {
223
+ throw new CCIPTopicsInvalidError(opts.topics!)
216
224
  }
225
+ const topic = opts.topics[0]
217
226
 
218
- const startTime = opts.startTime ? new Date(opts.startTime * 1000) : new Date(0)
219
- const endTime = opts.endBlock
220
- ? new Date(opts.endBlock)
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
227
+ for await (const event of streamSuiLogs<Record<string, unknown>>(this, opts)) {
228
+ const eventData = event.contents?.json
229
+ if (!eventData) continue
236
230
  yield {
237
- address: this.contractsDir.offRamp,
238
- transactionHash: event.transaction?.digest || '',
239
- index: 0, // Sui events do not have an index, set to 0
231
+ address: opts.address,
232
+ transactionHash: event.transaction!.digest,
233
+ index: Number(event.sequenceNumber) || 0,
240
234
  blockNumber: Number(event.transaction?.effects.checkpoint.sequenceNumber || 0),
241
235
  data: eventData,
242
236
  topics: [topic],
@@ -244,11 +238,6 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
244
238
  }
245
239
  }
246
240
 
247
- /** {@inheritDoc Chain.getMessagesInTx} */
248
- override async getMessagesInTx(_tx: string | ChainTransaction): Promise<CCIPRequest[]> {
249
- return Promise.reject(new CCIPNotImplementedError('SuiChain.getMessagesInTx'))
250
- }
251
-
252
241
  /** {@inheritDoc Chain.getMessagesInBatch} */
253
242
  override async getMessagesInBatch<
254
243
  R extends PickDeep<
@@ -256,67 +245,96 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
256
245
  'lane' | `log.${'topics' | 'address' | 'blockNumber'}` | 'message.sequenceNumber'
257
246
  >,
258
247
  >(
259
- _request: R,
260
- _commit: Pick<CommitReport, 'minSeqNr' | 'maxSeqNr'>,
261
- _opts?: { page?: number },
248
+ request: R,
249
+ commit: Pick<CommitReport, 'minSeqNr' | 'maxSeqNr'>,
250
+ opts?: { page?: number },
262
251
  ): Promise<R['message'][]> {
263
- return Promise.reject(new CCIPNotImplementedError('SuiChain.getMessagesInBatch'))
252
+ return getMessagesInBatch(this, request, commit, opts)
264
253
  }
265
254
 
266
- /** {@inheritDoc Chain.typeAndVersion} */
267
- async typeAndVersion(_address: string) {
268
- return Promise.reject(new CCIPNotImplementedError('SuiChain.typeAndVersion'))
255
+ /**
256
+ * {@inheritDoc Chain.typeAndVersion}
257
+ * @throws {@link CCIPDataFormatUnsupportedError} if view call fails
258
+ */
259
+ async typeAndVersion(address: string) {
260
+ // requires address to have `::<module>` suffix
261
+ address = await getLatestPackageId(address, this.client)
262
+ const target = `${address}::type_and_version`
263
+
264
+ // Use the Transaction builder to create a move call
265
+ const tx = new Transaction()
266
+ // Add move call to the transaction
267
+ tx.moveCall({ target, arguments: [] })
268
+
269
+ // Execute with devInspectTransactionBlock for read-only call
270
+ const result = await this.client.devInspectTransactionBlock({
271
+ sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
272
+ transactionBlock: tx,
273
+ })
274
+
275
+ if (result.effects.status.status !== 'success' || !result.results?.[0]?.returnValues?.[0]) {
276
+ throw new CCIPDataFormatUnsupportedError(
277
+ `Failed to call ${target}: ${result.effects.status.error || 'No return value'}`,
278
+ )
279
+ }
280
+
281
+ const [data] = result.results[0].returnValues[0]
282
+ const res = bcs.String.parse(getDataBytes(data))
283
+ return parseTypeAndVersion(res)
269
284
  }
270
285
 
271
286
  /** {@inheritDoc Chain.getRouterForOnRamp} */
272
287
  async getRouterForOnRamp(onRamp: string, _destChainSelector: bigint): Promise<string> {
273
- this.contractsDir.onRamp = onRamp
274
- if (onRamp !== this.contractsDir.onRamp) {
275
- this.contractsDir.onRamp = onRamp
276
- }
277
- return Promise.resolve(this.contractsDir.onRamp)
288
+ // In Sui, the router is the onRamp package itself
289
+ return Promise.resolve(onRamp)
278
290
  }
279
291
 
280
- /** {@inheritDoc Chain.getRouterForOffRamp} */
292
+ /**
293
+ * {@inheritDoc Chain.getRouterForOffRamp}
294
+ * @throws {@link CCIPContractNotRouterError} always (Sui architecture doesn't have separate router)
295
+ */
281
296
  getRouterForOffRamp(offRamp: string, _sourceChainSelector: bigint): Promise<string> {
282
297
  throw new CCIPContractNotRouterError(offRamp, 'unknown')
283
298
  }
284
299
 
285
300
  /** {@inheritDoc Chain.getNativeTokenForRouter} */
286
- getNativeTokenForRouter(_router: string): Promise<string> {
301
+ getNativeTokenForRouter(): Promise<string> {
287
302
  // SUI native token is always 0x2::sui::SUI
288
303
  return Promise.resolve('0x2::sui::SUI')
289
304
  }
290
305
 
291
306
  /** {@inheritDoc Chain.getOffRampsForRouter} */
292
307
  async getOffRampsForRouter(router: string, _sourceChainSelector: bigint): Promise<string[]> {
293
- const ccip = await discoverCCIP(this.client, router)
294
- const offramp = await discoverOfframp(this.client, ccip)
295
- this.contractsDir.offRamp = offramp
296
- this.contractsDir.ccip = ccip
308
+ router = await getLatestPackageId(router, this.client)
309
+ const ccip = await getCcipStateAddress(router, this.client)
310
+ const offramp = await getOffRampForCcip(ccip, this.client)
297
311
  return [offramp]
298
312
  }
299
313
 
300
314
  /** {@inheritDoc Chain.getOnRampForRouter} */
301
- getOnRampForRouter(_router: string, _destChainSelector: bigint): Promise<string> {
302
- if (!this.contractsDir.onRamp) {
303
- throw new CCIPContractNotRouterError('OnRamp address not set in contracts directory', 'Sui')
304
- }
305
- return Promise.resolve(this.contractsDir.onRamp)
315
+ getOnRampForRouter(router: string, _destChainSelector: bigint): Promise<string> {
316
+ // For Sui, the router is the onramp package address
317
+ return Promise.resolve(router)
306
318
  }
307
319
 
308
- /** {@inheritDoc Chain.getOnRampForOffRamp} */
320
+ /**
321
+ * {@inheritDoc Chain.getOnRampForOffRamp}
322
+ * @throws {@link CCIPDataFormatUnsupportedError} if view call fails
323
+ */
309
324
  async getOnRampForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
310
- if (!this.contractsDir.ccip) {
311
- throw new CCIPError(CCIPErrorCode.UNKNOWN, 'CCIP address not set in contracts directory')
312
- }
313
- const offrampPackageId = offRamp
325
+ offRamp = await getLatestPackageId(offRamp, this.client)
314
326
  const functionName = 'get_source_chain_config'
315
- const target = `${offrampPackageId}::offramp::${functionName}`
327
+ // Preserve module suffix if present, otherwise add it
328
+ const target = offRamp.includes('::')
329
+ ? `${offRamp}::${functionName}`
330
+ : `${offRamp}::offramp::${functionName}`
331
+
332
+ // Discover the CCIP package from the offramp
333
+ const ccip = await getCcipStateAddress(offRamp, this.client)
316
334
 
317
335
  // Get the OffRampState object
318
- const offrampStateObject = await getOffRampStateObject(this.client, offrampPackageId)
319
- const ccipObjectRef = await getCcipObjectRef(this.client, this.contractsDir.ccip)
336
+ const offrampStateObject = await getObjectRef(offRamp, this.client)
337
+ const ccipObjectRef = await getObjectRef(ccip, this.client)
320
338
  // Use the Transaction builder to create a move call
321
339
  const tx = new Transaction()
322
340
 
@@ -380,44 +398,178 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
380
398
  return Promise.resolve(offRamp)
381
399
  }
382
400
 
383
- /** {@inheritDoc Chain.getTokenForTokenPool} */
384
- getTokenForTokenPool(_tokenPool: string): Promise<string> {
385
- throw new CCIPNotImplementedError()
401
+ /**
402
+ * {@inheritDoc Chain.getTokenForTokenPool}
403
+ * @throws {@link CCIPError} if token pool type is invalid or state not found
404
+ * @throws {@link CCIPDataFormatUnsupportedError} if view call fails
405
+ */
406
+ async getTokenForTokenPool(tokenPool: string): Promise<string> {
407
+ const normalizedTokenPool = normalizeSuiAddress(tokenPool)
408
+
409
+ // Get objects owned by this package (looking for state pointers)
410
+ const objects = await this.client.getOwnedObjects({
411
+ owner: normalizedTokenPool,
412
+ options: { showType: true, showContent: true },
413
+ })
414
+
415
+ const tpType = objects.data
416
+ .find((obj) => obj.data?.type?.includes('token_pool::'))
417
+ ?.data?.type?.split('::')[1]
418
+
419
+ const allowedTps = ['managed_token_pool', 'burn_mint_token_pool', 'lock_release_token_pool']
420
+ if (!tpType || !allowedTps.includes(tpType)) {
421
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid token pool type: ${tpType}`)
422
+ }
423
+
424
+ // Find the state pointer object
425
+ let stateObjectPointerId: string | undefined
426
+ for (const obj of objects.data) {
427
+ const content = obj.data?.content
428
+ if (content?.dataType !== 'moveObject') continue
429
+
430
+ const fields = content.fields as Record<string, unknown>
431
+ // Look for a pointer field that references the state object
432
+ stateObjectPointerId = fields[`${tpType}_object_id`] as string
433
+ }
434
+
435
+ if (!stateObjectPointerId) {
436
+ throw new CCIPError(
437
+ CCIPErrorCode.UNKNOWN,
438
+ `No token pool state pointer found for ${tokenPool}`,
439
+ )
440
+ }
441
+
442
+ const stateNamesPerTP: Record<string, string> = {
443
+ managed_token_pool: 'ManagedTokenPoolState',
444
+ burn_mint_token_pool: 'BurnMintTokenPoolState',
445
+ lock_release_token_pool: 'LockReleaseTokenPoolState',
446
+ }
447
+
448
+ const poolStateObject = deriveObjectID(
449
+ stateObjectPointerId,
450
+ new TextEncoder().encode(stateNamesPerTP[tpType]),
451
+ )
452
+
453
+ // Get object info to get the coin type
454
+ const info = await this.client.getObject({
455
+ id: poolStateObject,
456
+ options: { showType: true, showContent: true },
457
+ })
458
+
459
+ const type = info.data?.type
460
+ if (!type) {
461
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading token pool state object type')
462
+ }
463
+
464
+ // Extract the type parameter T from ManagedTokenPoolState<T>
465
+ const typeMatch = type.match(/(?:Managed|BurnMint|LockRelease)TokenPoolState<(.+)>$/)
466
+ if (!typeMatch || !typeMatch[1]) {
467
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid pool state type format: ${type}`)
468
+ }
469
+ const tokenType = typeMatch[1]
470
+
471
+ // Call get_token function from managed_token_pool contract with the type parameter
472
+ const target = type.split('<')[0]?.split('::').slice(0, 2).join('::') + '::get_token'
473
+ if (!target) {
474
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid pool state type format: ${type}`)
475
+ }
476
+ const tx = new Transaction()
477
+ tx.moveCall({
478
+ target,
479
+ typeArguments: [tokenType],
480
+ arguments: [tx.object(poolStateObject)],
481
+ })
482
+
483
+ const result = await this.client.devInspectTransactionBlock({
484
+ sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
485
+ transactionBlock: tx,
486
+ })
487
+
488
+ if (result.effects.status.status !== 'success' || !result.results?.[0]?.returnValues?.[0]) {
489
+ throw new CCIPDataFormatUnsupportedError(
490
+ `Failed to call ${target}: ${result.effects.status.error || 'No return value'}`,
491
+ )
492
+ }
493
+
494
+ // Parse the return value to get the coin metadata address (32 bytes)
495
+ const returnValue = result.results[0].returnValues[0]
496
+ const [data] = returnValue
497
+ const coinMetadataBytes = new Uint8Array(data)
498
+ const coinMetadataAddress = normalizeSuiAddress(hexlify(coinMetadataBytes))
499
+
500
+ return coinMetadataAddress
386
501
  }
387
502
 
388
- /** {@inheritDoc Chain.getTokenInfo} */
503
+ /**
504
+ * {@inheritDoc Chain.getTokenInfo}
505
+ * @throws {@link CCIPError} if token address is invalid or metadata cannot be loaded
506
+ */
389
507
  async getTokenInfo(token: string): Promise<{ symbol: string; decimals: number }> {
390
- // Handle native SUI token
391
- if (token === '0x2::sui::SUI' || token.includes('::sui::SUI')) {
392
- return { symbol: 'SUI', decimals: 9 }
508
+ const normalizedTokenAddress = normalizeSuiAddress(token)
509
+ if (!isValidSuiAddress(normalizedTokenAddress)) {
510
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
393
511
  }
394
512
 
395
- try {
396
- // For Coin types, try to fetch metadata from the coin metadata object
397
- // Format: 0xPACKAGE::module::TYPE
398
- const coinMetadata = await this.client.getCoinMetadata({ coinType: token })
513
+ const objectResponse = await this.client.getObject({
514
+ id: normalizedTokenAddress,
515
+ options: { showType: true },
516
+ })
399
517
 
400
- if (coinMetadata) {
401
- return {
402
- symbol: coinMetadata.symbol || 'UNKNOWN',
403
- decimals: coinMetadata.decimals,
404
- }
518
+ const getCoinFromMetadata = (metadata: string) => {
519
+ // Extract the type parameter from CoinMetadata<...>
520
+ const match = metadata.match(/CoinMetadata<(.+)>$/)
521
+
522
+ if (!match || !match[1]) {
523
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, `Invalid metadata format: ${metadata}`)
405
524
  }
406
- } catch (error) {
407
- console.log(`Failed to fetch coin metadata for ${token}:`, error)
525
+
526
+ return match[1]
408
527
  }
409
528
 
410
- // Fallback: parse from token type string if possible
411
- const parts = token.split('::')
412
- const symbol = parts[parts.length - 1] || 'UNKNOWN'
529
+ let coinType: string
530
+ const objectType = objectResponse.data?.type
531
+
532
+ // Check if this is a CoinMetadata object or a coin type string
533
+ if (objectType?.includes('CoinMetadata')) {
534
+ coinType = getCoinFromMetadata(objectType)
535
+ } else if (token.includes('::')) {
536
+ // This is a coin type string (e.g., "0xabc::coin::COIN")
537
+ coinType = token
538
+ } else {
539
+ // This is a package address or unknown format
540
+ throw new CCIPError(
541
+ CCIPErrorCode.UNKNOWN,
542
+ `Token address ${token} is not a CoinMetadata object or coin type. Expected format: package::module::Type`,
543
+ )
544
+ }
545
+
546
+ if (coinType.split('::').length < 3) {
547
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
548
+ }
549
+
550
+ let metadata = null
551
+ try {
552
+ metadata = await this.client.getCoinMetadata({ coinType })
553
+ } catch (e) {
554
+ console.error('Error fetching coin metadata:', e)
555
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
556
+ }
557
+
558
+ if (!metadata) {
559
+ throw new CCIPError(CCIPErrorCode.UNKNOWN, 'Error loading Sui token metadata')
560
+ }
413
561
 
414
562
  return {
415
- symbol: symbol.toUpperCase(),
416
- decimals: 9, // Default to 9 decimals (SUI standard)
563
+ symbol: metadata.symbol,
564
+ decimals: metadata.decimals,
417
565
  }
418
566
  }
419
567
 
420
- /** {@inheritDoc Chain.getTokenAdminRegistryFor} */
568
+ /** {@inheritDoc Chain.getBalance} */
569
+ async getBalance(_opts: GetBalanceOpts): Promise<bigint> {
570
+ return Promise.reject(new CCIPNotImplementedError('SuiChain.getBalance'))
571
+ }
572
+
421
573
  /** {@inheritDoc Chain.getTokenAdminRegistryFor} */
422
574
  getTokenAdminRegistryFor(_address: string): Promise<string> {
423
575
  return Promise.reject(new CCIPNotImplementedError())
@@ -426,11 +578,23 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
426
578
  // Static methods for decoding
427
579
  /**
428
580
  * Decodes a CCIP message from a Sui log event.
429
- * @param _log - Log event data.
581
+ * @param log - Log event data.
430
582
  * @returns Decoded CCIPMessage or undefined if not valid.
583
+ * @throws {@link CCIPSuiLogInvalidError} if log data format is invalid
431
584
  */
432
- static decodeMessage(_log: Log_): CCIPMessage_V1_6_Sui | undefined {
433
- throw new CCIPNotImplementedError()
585
+ static decodeMessage(log: Log_): CCIPMessage | undefined {
586
+ const { data } = log
587
+ if (
588
+ (typeof data !== 'string' || !data.startsWith('{')) &&
589
+ (typeof data !== 'object' || isBytesLike(data))
590
+ )
591
+ throw new CCIPSuiLogInvalidError(util.inspect(log))
592
+ // offload massaging to generic decodeJsonMessage
593
+ try {
594
+ return decodeMessage(data)
595
+ } catch (_) {
596
+ // return undefined
597
+ }
434
598
  }
435
599
 
436
600
  /**
@@ -451,6 +615,7 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
451
615
  * Encodes extra arguments for CCIP messages.
452
616
  * @param _extraArgs - Extra arguments to encode.
453
617
  * @returns Encoded extra arguments as a hex string.
618
+ * @throws {@link CCIPNotImplementedError} always (not yet implemented)
454
619
  */
455
620
  static encodeExtraArgs(_extraArgs: ExtraArgs): string {
456
621
  throw new CCIPNotImplementedError()
@@ -459,30 +624,36 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
459
624
  /**
460
625
  * Decodes commit reports from a log entry.
461
626
  * @param log - The log entry to decode.
462
- * @param _lane - Optional lane information.
627
+ * @param lane - Optional lane information.
463
628
  * @returns Array of decoded commit reports or undefined.
464
629
  */
465
- static decodeCommits(log: Log_, _lane?: Lane): CommitReport[] | undefined {
466
- if (!log.data || typeof log.data !== 'object' || !('unblessed_merkle_roots' in log.data)) {
467
- return
468
- }
469
- const toHexFromBase64 = (b64: string) => '0x' + Buffer.from(b64, 'base64').toString('hex')
470
-
471
- const eventData = log.data as CommitEvent
472
- const unblessedRoots = eventData.unblessed_merkle_roots
473
- if (!Array.isArray(unblessedRoots) || unblessedRoots.length === 0) {
474
- return
475
- }
476
-
477
- return unblessedRoots.map((root) => {
478
- return {
479
- sourceChainSelector: BigInt(root.source_chain_selector),
480
- onRampAddress: toHexFromBase64(root.on_ramp_address),
481
- minSeqNr: BigInt(root.min_seq_nr),
482
- maxSeqNr: BigInt(root.max_seq_nr),
483
- merkleRoot: toHexFromBase64(root.merkle_root),
484
- }
485
- })
630
+ static decodeCommits(
631
+ { data, topics }: SetOptional<Pick<Log_, 'data' | 'topics'>, 'topics'>,
632
+ lane?: Lane,
633
+ ): CommitReport[] | undefined {
634
+ // Check if this is an CommitReportAccepted event
635
+ if (topics?.[0] && topics[0] !== 'CommitReportAccepted') return
636
+
637
+ // Basic log data structure validation
638
+ if (!data || typeof data !== 'object' || !('unblessed_merkle_roots' in data)) return
639
+
640
+ const eventData = data as CommitEvent
641
+ const rootsRaw = eventData.blessed_merkle_roots.concat(eventData.unblessed_merkle_roots)
642
+ return rootsRaw
643
+ .map((root) => {
644
+ return {
645
+ sourceChainSelector: BigInt(root.source_chain_selector),
646
+ onRampAddress: decodeOnRampAddress(root.on_ramp_address),
647
+ minSeqNr: BigInt(root.min_seq_nr),
648
+ maxSeqNr: BigInt(root.max_seq_nr),
649
+ merkleRoot: hexlify(getDataBytes(root.merkle_root)),
650
+ }
651
+ })
652
+ .filter((r) =>
653
+ lane
654
+ ? r.sourceChainSelector === lane.sourceChainSelector && r.onRampAddress === lane.onRamp
655
+ : true,
656
+ )
486
657
  }
487
658
 
488
659
  /**
@@ -490,52 +661,32 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
490
661
  * @param log - The log entry to decode.
491
662
  * @returns Decoded execution receipt or undefined.
492
663
  */
493
- static decodeReceipt(log: Log_): ExecutionReceipt | undefined {
664
+ static decodeReceipt({
665
+ data,
666
+ topics,
667
+ }: SetOptional<Pick<Log_, 'data' | 'topics'>, 'topics'>): ExecutionReceipt | undefined {
494
668
  // Check if this is an ExecutionStateChanged event
495
- const topic = (Array.isArray(log.topics) ? log.topics[0] : log.topics) as string
496
- if (topic !== 'ExecutionStateChanged') {
497
- return undefined
498
- }
669
+ if (topics?.[0] && topics[0] !== 'ExecutionStateChanged') return
499
670
 
500
- // Validate log data structure
501
- if (!log.data || typeof log.data !== 'object') {
502
- return undefined
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
671
+ // Basic log data structure validation
672
+ if (!data || typeof data !== 'object' || !('message_id' in data) || !('state' in data)) {
673
+ return
511
674
  }
512
675
 
513
- // Verify required fields exist
514
- if (
515
- !eventData.message_id ||
516
- !Array.isArray(eventData.message_id) ||
517
- eventData.sequence_number === undefined ||
518
- eventData.state === undefined
519
- ) {
520
- return undefined
676
+ const eventData = data as {
677
+ message_hash: BytesLike
678
+ message_id: BytesLike
679
+ sequence_number: string
680
+ source_chain_selector: string
681
+ state: number
521
682
  }
522
683
 
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
684
  return {
532
- messageId,
685
+ messageId: hexlify(getDataBytes(eventData.message_id)),
533
686
  sequenceNumber: BigInt(eventData.sequence_number),
534
- state: eventData.state as ExecutionState,
535
- sourceChainSelector: eventData.source_chain_selector
536
- ? BigInt(eventData.source_chain_selector)
537
- : undefined,
538
- messageHash,
687
+ state: Number(eventData.state) as ExecutionState,
688
+ sourceChainSelector: BigInt(eventData.source_chain_selector),
689
+ messageHash: hexlify(getDataBytes(eventData.message_hash)),
539
690
  }
540
691
  }
541
692
 
@@ -544,15 +695,17 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
544
695
  * @param bytes - Bytes to convert.
545
696
  * @returns Sui address.
546
697
  */
547
- static getAddress(bytes: BytesLike): string {
698
+ static getAddress(bytes: BytesLike | readonly number[]): string {
548
699
  return AptosChain.getAddress(bytes)
549
700
  }
550
701
 
551
702
  /**
552
703
  * Validates a transaction hash format for Sui
553
704
  */
554
- static isTxHash(_v: unknown): _v is string {
555
- return false
705
+ static isTxHash(v: unknown): v is string {
706
+ if (typeof v !== 'string') return false
707
+ // check in both hex and base58 formats
708
+ return isHexString(v, 32) || isValidTransactionDigest(v)
556
709
  }
557
710
 
558
711
  /**
@@ -583,9 +736,6 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
583
736
 
584
737
  /** {@inheritDoc Chain.getOffchainTokenData} */
585
738
  getOffchainTokenData(request: CCIPRequest): Promise<OffchainTokenData[]> {
586
- if (!('receiverObjectIds' in request.message)) {
587
- throw new CCIPSuiMessageVersionInvalidError()
588
- }
589
739
  // default offchain token data
590
740
  return Promise.resolve(request.message.tokenAmounts.map(() => undefined))
591
741
  }
@@ -597,25 +747,27 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
597
747
  return Promise.reject(new CCIPNotImplementedError('SuiChain.generateUnsignedExecuteReport'))
598
748
  }
599
749
 
600
- /** {@inheritDoc Chain.executeReport} */
750
+ /**
751
+ * {@inheritDoc Chain.executeReport}
752
+ * @throws {@link CCIPError} if transaction submission fails
753
+ * @throws {@link CCIPExecTxRevertedError} if transaction reverts
754
+ */
601
755
  async executeReport(
602
756
  opts: Parameters<Chain['executeReport']>[0] & {
603
757
  receiverObjectIds?: string[]
604
758
  },
605
759
  ): 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
- }
760
+ const { execReport, offRamp } = opts
613
761
  const wallet = opts.wallet as Keypair
614
- const ccipObjectRef = await getCcipObjectRef(this.client, this.contractsDir.ccip)
615
- const offrampStateObject = await getOffRampStateObject(this.client, this.contractsDir.offRamp)
762
+
763
+ // Discover the CCIP package from the offramp
764
+ const ccip = await getCcipStateAddress(offRamp, this.client)
765
+
766
+ const ccipObjectRef = await getObjectRef(ccip, this.client)
767
+ const offrampStateObject = await getObjectRef(offRamp, this.client)
616
768
  const receiverConfig = await getReceiverModule(
617
769
  this.client,
618
- this.contractsDir.ccip,
770
+ ccip,
619
771
  ccipObjectRef,
620
772
  execReport.message.receiver,
621
773
  )
@@ -623,7 +775,7 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
623
775
  if (execReport.message.tokenAmounts.length !== 0) {
624
776
  tokenConfigs = await fetchTokenConfigs(
625
777
  this.client,
626
- this.contractsDir.ccip,
778
+ ccip,
627
779
  ccipObjectRef,
628
780
  execReport.message.tokenAmounts as CCIPMessage<typeof CCIPVersion.V1_6>['tokenAmounts'],
629
781
  )
@@ -631,8 +783,8 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
631
783
 
632
784
  const input: SuiManuallyExecuteInput = {
633
785
  executionReport: execReport as ExecutionReport<CCIPMessage_V1_6_Sui>,
634
- offrampAddress: this.contractsDir.offRamp,
635
- ccipAddress: this.contractsDir.ccip,
786
+ offrampAddress: offRamp,
787
+ ccipAddress: ccip,
636
788
  ccipObjectRef,
637
789
  offrampStateObject,
638
790
  receiverConfig,
@@ -714,9 +866,9 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
714
866
  return Promise.reject(new CCIPNotImplementedError('SuiChain.getRegistryTokenConfig'))
715
867
  }
716
868
 
717
- /** {@inheritDoc Chain.getTokenPoolConfigs} */
718
- async getTokenPoolConfigs(_tokenPool: string): Promise<never> {
719
- return Promise.reject(new CCIPNotImplementedError('SuiChain.getTokenPoolConfigs'))
869
+ /** {@inheritDoc Chain.getTokenPoolConfig} */
870
+ async getTokenPoolConfig(_tokenPool: string): Promise<never> {
871
+ return Promise.reject(new CCIPNotImplementedError('SuiChain.getTokenPoolConfig'))
720
872
  }
721
873
 
722
874
  /** {@inheritDoc Chain.getTokenPoolRemotes} */
@@ -748,7 +900,8 @@ export class SuiChain extends Chain<typeof ChainFamily.Sui> {
748
900
  const tokenReceiver =
749
901
  message.extraArgs &&
750
902
  'tokenReceiver' in message.extraArgs &&
751
- message.extraArgs.tokenReceiver != null
903
+ message.extraArgs.tokenReceiver != null &&
904
+ typeof message.extraArgs.tokenReceiver === 'string'
752
905
  ? message.extraArgs.tokenReceiver
753
906
  : message.tokenAmounts?.length
754
907
  ? this.getAddress(message.receiver)