@chainlink/ccip-sdk 0.91.0 → 0.92.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 (273) hide show
  1. package/README.md +127 -80
  2. package/dist/aptos/hasher.d.ts.map +1 -1
  3. package/dist/aptos/hasher.js +7 -6
  4. package/dist/aptos/hasher.js.map +1 -1
  5. package/dist/aptos/index.d.ts +7 -2
  6. package/dist/aptos/index.d.ts.map +1 -1
  7. package/dist/aptos/index.js +29 -20
  8. package/dist/aptos/index.js.map +1 -1
  9. package/dist/aptos/logs.d.ts +5 -3
  10. package/dist/aptos/logs.d.ts.map +1 -1
  11. package/dist/aptos/logs.js +64 -27
  12. package/dist/aptos/logs.js.map +1 -1
  13. package/dist/aptos/token.d.ts.map +1 -1
  14. package/dist/aptos/token.js +2 -1
  15. package/dist/aptos/token.js.map +1 -1
  16. package/dist/aptos/types.js +6 -6
  17. package/dist/aptos/types.js.map +1 -1
  18. package/dist/chain.d.ts +36 -11
  19. package/dist/chain.d.ts.map +1 -1
  20. package/dist/chain.js +34 -2
  21. package/dist/chain.js.map +1 -1
  22. package/dist/commits.d.ts +2 -3
  23. package/dist/commits.d.ts.map +1 -1
  24. package/dist/commits.js +19 -8
  25. package/dist/commits.js.map +1 -1
  26. package/dist/errors/CCIPError.d.ts +48 -0
  27. package/dist/errors/CCIPError.d.ts.map +1 -0
  28. package/dist/errors/CCIPError.js +65 -0
  29. package/dist/errors/CCIPError.js.map +1 -0
  30. package/dist/errors/codes.d.ts +120 -0
  31. package/dist/errors/codes.d.ts.map +1 -0
  32. package/dist/errors/codes.js +156 -0
  33. package/dist/errors/codes.js.map +1 -0
  34. package/dist/errors/index.d.ts +26 -0
  35. package/dist/errors/index.d.ts.map +1 -0
  36. package/dist/errors/index.js +51 -0
  37. package/dist/errors/index.js.map +1 -0
  38. package/dist/errors/recovery.d.ts +6 -0
  39. package/dist/errors/recovery.d.ts.map +1 -0
  40. package/dist/errors/recovery.js +118 -0
  41. package/dist/errors/recovery.js.map +1 -0
  42. package/dist/errors/specialized.d.ts +637 -0
  43. package/dist/errors/specialized.d.ts.map +1 -0
  44. package/dist/errors/specialized.js +1298 -0
  45. package/dist/errors/specialized.js.map +1 -0
  46. package/dist/errors/utils.d.ts +11 -0
  47. package/dist/errors/utils.d.ts.map +1 -0
  48. package/dist/errors/utils.js +61 -0
  49. package/dist/errors/utils.js.map +1 -0
  50. package/dist/evm/abi/CommitStore_1_5.js +1 -1
  51. package/dist/evm/abi/LockReleaseTokenPool_1_5.js +1 -1
  52. package/dist/evm/abi/OffRamp_1_5.js +1 -1
  53. package/dist/evm/abi/OnRamp_1_5.js +1 -1
  54. package/dist/evm/abi/PriceRegistry_1_2.d.ts +443 -0
  55. package/dist/evm/abi/PriceRegistry_1_2.d.ts.map +1 -0
  56. package/dist/evm/abi/PriceRegistry_1_2.js +439 -0
  57. package/dist/evm/abi/PriceRegistry_1_2.js.map +1 -0
  58. package/dist/evm/const.d.ts +1 -0
  59. package/dist/evm/const.d.ts.map +1 -1
  60. package/dist/evm/const.js +2 -0
  61. package/dist/evm/const.js.map +1 -1
  62. package/dist/evm/hasher.d.ts.map +1 -1
  63. package/dist/evm/hasher.js +7 -6
  64. package/dist/evm/hasher.js.map +1 -1
  65. package/dist/evm/index.d.ts +9 -13
  66. package/dist/evm/index.d.ts.map +1 -1
  67. package/dist/evm/index.js +85 -68
  68. package/dist/evm/index.js.map +1 -1
  69. package/dist/evm/logs.d.ts.map +1 -1
  70. package/dist/evm/logs.js +47 -16
  71. package/dist/evm/logs.js.map +1 -1
  72. package/dist/evm/messages.d.ts +7 -6
  73. package/dist/evm/messages.d.ts.map +1 -1
  74. package/dist/evm/offchain.js +1 -1
  75. package/dist/evm/offchain.js.map +1 -1
  76. package/dist/evm/types.d.ts +10 -0
  77. package/dist/evm/types.d.ts.map +1 -0
  78. package/dist/evm/types.js +2 -0
  79. package/dist/evm/types.js.map +1 -0
  80. package/dist/execution.d.ts.map +1 -1
  81. package/dist/execution.js +9 -5
  82. package/dist/execution.js.map +1 -1
  83. package/dist/extra-args.d.ts.map +1 -1
  84. package/dist/extra-args.js +4 -3
  85. package/dist/extra-args.js.map +1 -1
  86. package/dist/gas.d.ts.map +1 -1
  87. package/dist/gas.js +3 -2
  88. package/dist/gas.js.map +1 -1
  89. package/dist/hasher/hasher.d.ts.map +1 -1
  90. package/dist/hasher/hasher.js +2 -1
  91. package/dist/hasher/hasher.js.map +1 -1
  92. package/dist/hasher/merklemulti.d.ts.map +1 -1
  93. package/dist/hasher/merklemulti.js +9 -8
  94. package/dist/hasher/merklemulti.js.map +1 -1
  95. package/dist/index.d.ts +5 -2
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +6 -2
  98. package/dist/index.js.map +1 -1
  99. package/dist/offchain.d.ts.map +1 -1
  100. package/dist/offchain.js +5 -8
  101. package/dist/offchain.js.map +1 -1
  102. package/dist/requests.d.ts +1 -1
  103. package/dist/requests.d.ts.map +1 -1
  104. package/dist/requests.js +37 -43
  105. package/dist/requests.js.map +1 -1
  106. package/dist/selectors.d.ts.map +1 -1
  107. package/dist/selectors.js +22 -0
  108. package/dist/selectors.js.map +1 -1
  109. package/dist/solana/cleanup.d.ts +2 -2
  110. package/dist/solana/cleanup.d.ts.map +1 -1
  111. package/dist/solana/cleanup.js +2 -3
  112. package/dist/solana/cleanup.js.map +1 -1
  113. package/dist/solana/exec.d.ts.map +1 -1
  114. package/dist/solana/exec.js +12 -12
  115. package/dist/solana/exec.js.map +1 -1
  116. package/dist/solana/hasher.d.ts.map +1 -1
  117. package/dist/solana/hasher.js +6 -5
  118. package/dist/solana/hasher.js.map +1 -1
  119. package/dist/solana/index.d.ts +30 -13
  120. package/dist/solana/index.d.ts.map +1 -1
  121. package/dist/solana/index.js +96 -143
  122. package/dist/solana/index.js.map +1 -1
  123. package/dist/solana/logs.d.ts +15 -0
  124. package/dist/solana/logs.d.ts.map +1 -0
  125. package/dist/solana/logs.js +106 -0
  126. package/dist/solana/logs.js.map +1 -0
  127. package/dist/solana/offchain.d.ts.map +1 -1
  128. package/dist/solana/offchain.js +6 -5
  129. package/dist/solana/offchain.js.map +1 -1
  130. package/dist/solana/patchBorsh.d.ts.map +1 -1
  131. package/dist/solana/patchBorsh.js +3 -2
  132. package/dist/solana/patchBorsh.js.map +1 -1
  133. package/dist/solana/send.d.ts.map +1 -1
  134. package/dist/solana/send.js +8 -7
  135. package/dist/solana/send.js.map +1 -1
  136. package/dist/solana/utils.d.ts +7 -8
  137. package/dist/solana/utils.d.ts.map +1 -1
  138. package/dist/solana/utils.js +23 -11
  139. package/dist/solana/utils.js.map +1 -1
  140. package/dist/sui/discovery.d.ts +18 -0
  141. package/dist/sui/discovery.d.ts.map +1 -0
  142. package/dist/sui/discovery.js +116 -0
  143. package/dist/sui/discovery.js.map +1 -0
  144. package/dist/sui/events.d.ts +36 -0
  145. package/dist/sui/events.d.ts.map +1 -0
  146. package/dist/sui/events.js +179 -0
  147. package/dist/sui/events.js.map +1 -0
  148. package/dist/sui/hasher.d.ts.map +1 -1
  149. package/dist/sui/hasher.js +6 -5
  150. package/dist/sui/hasher.js.map +1 -1
  151. package/dist/sui/index.d.ts +69 -41
  152. package/dist/sui/index.d.ts.map +1 -1
  153. package/dist/sui/index.js +402 -65
  154. package/dist/sui/index.js.map +1 -1
  155. package/dist/sui/manuallyExec/encoder.d.ts +8 -0
  156. package/dist/sui/manuallyExec/encoder.d.ts.map +1 -0
  157. package/dist/sui/manuallyExec/encoder.js +76 -0
  158. package/dist/sui/manuallyExec/encoder.js.map +1 -0
  159. package/dist/sui/manuallyExec/index.d.ts +37 -0
  160. package/dist/sui/manuallyExec/index.d.ts.map +1 -0
  161. package/dist/sui/manuallyExec/index.js +81 -0
  162. package/dist/sui/manuallyExec/index.js.map +1 -0
  163. package/dist/sui/objects.d.ts +46 -0
  164. package/dist/sui/objects.d.ts.map +1 -0
  165. package/dist/sui/objects.js +259 -0
  166. package/dist/sui/objects.js.map +1 -0
  167. package/dist/ton/bindings/offramp.d.ts +48 -0
  168. package/dist/ton/bindings/offramp.d.ts.map +1 -0
  169. package/dist/ton/bindings/offramp.js +63 -0
  170. package/dist/ton/bindings/offramp.js.map +1 -0
  171. package/dist/ton/bindings/onramp.d.ts +40 -0
  172. package/dist/ton/bindings/onramp.d.ts.map +1 -0
  173. package/dist/ton/bindings/onramp.js +51 -0
  174. package/dist/ton/bindings/onramp.js.map +1 -0
  175. package/dist/ton/bindings/router.d.ts +47 -0
  176. package/dist/ton/bindings/router.d.ts.map +1 -0
  177. package/dist/ton/bindings/router.js +51 -0
  178. package/dist/ton/bindings/router.js.map +1 -0
  179. package/dist/ton/exec.d.ts +18 -0
  180. package/dist/ton/exec.d.ts.map +1 -0
  181. package/dist/ton/exec.js +28 -0
  182. package/dist/ton/exec.js.map +1 -0
  183. package/dist/ton/hasher.d.ts +27 -0
  184. package/dist/ton/hasher.d.ts.map +1 -0
  185. package/dist/ton/hasher.js +134 -0
  186. package/dist/ton/hasher.js.map +1 -0
  187. package/dist/ton/index.d.ts +247 -0
  188. package/dist/ton/index.d.ts.map +1 -0
  189. package/dist/ton/index.js +781 -0
  190. package/dist/ton/index.js.map +1 -0
  191. package/dist/ton/logs.d.ts +26 -0
  192. package/dist/ton/logs.d.ts.map +1 -0
  193. package/dist/ton/logs.js +126 -0
  194. package/dist/ton/logs.js.map +1 -0
  195. package/dist/ton/types.d.ts +37 -0
  196. package/dist/ton/types.d.ts.map +1 -0
  197. package/dist/ton/types.js +92 -0
  198. package/dist/ton/types.js.map +1 -0
  199. package/dist/ton/utils.d.ts +67 -0
  200. package/dist/ton/utils.d.ts.map +1 -0
  201. package/dist/ton/utils.js +425 -0
  202. package/dist/ton/utils.js.map +1 -0
  203. package/dist/types.d.ts +4 -2
  204. package/dist/types.d.ts.map +1 -1
  205. package/dist/types.js +1 -0
  206. package/dist/types.js.map +1 -1
  207. package/dist/utils.d.ts +10 -0
  208. package/dist/utils.d.ts.map +1 -1
  209. package/dist/utils.js +52 -17
  210. package/dist/utils.js.map +1 -1
  211. package/package.json +12 -10
  212. package/src/aptos/hasher.ts +10 -6
  213. package/src/aptos/index.ts +50 -31
  214. package/src/aptos/logs.ts +85 -29
  215. package/src/aptos/token.ts +5 -1
  216. package/src/aptos/types.ts +6 -6
  217. package/src/chain.ts +83 -12
  218. package/src/commits.ts +23 -11
  219. package/src/errors/CCIPError.ts +86 -0
  220. package/src/errors/codes.ts +179 -0
  221. package/src/errors/index.ts +175 -0
  222. package/src/errors/recovery.ts +170 -0
  223. package/src/errors/specialized.ts +1655 -0
  224. package/src/errors/utils.ts +73 -0
  225. package/src/evm/abi/CommitStore_1_5.ts +1 -1
  226. package/src/evm/abi/LockReleaseTokenPool_1_5.ts +1 -1
  227. package/src/evm/abi/OffRamp_1_5.ts +1 -1
  228. package/src/evm/abi/OnRamp_1_5.ts +1 -1
  229. package/src/evm/abi/PriceRegistry_1_2.ts +438 -0
  230. package/src/evm/const.ts +2 -0
  231. package/src/evm/hasher.ts +7 -6
  232. package/src/evm/index.ts +104 -86
  233. package/src/evm/logs.ts +64 -16
  234. package/src/evm/messages.ts +14 -14
  235. package/src/evm/offchain.ts +1 -1
  236. package/src/evm/types.ts +11 -0
  237. package/src/execution.ts +13 -9
  238. package/src/extra-args.ts +4 -3
  239. package/src/gas.ts +10 -3
  240. package/src/hasher/hasher.ts +2 -1
  241. package/src/hasher/merklemulti.ts +18 -8
  242. package/src/index.ts +14 -2
  243. package/src/offchain.ts +10 -14
  244. package/src/requests.ts +51 -53
  245. package/src/selectors.ts +23 -0
  246. package/src/solana/cleanup.ts +2 -4
  247. package/src/solana/exec.ts +13 -13
  248. package/src/solana/hasher.ts +9 -5
  249. package/src/solana/index.ts +126 -200
  250. package/src/solana/logs.ts +155 -0
  251. package/src/solana/offchain.ts +10 -7
  252. package/src/solana/patchBorsh.ts +3 -2
  253. package/src/solana/send.ts +14 -7
  254. package/src/solana/utils.ts +31 -17
  255. package/src/sui/discovery.ts +163 -0
  256. package/src/sui/events.ts +328 -0
  257. package/src/sui/hasher.ts +6 -5
  258. package/src/sui/index.ts +528 -80
  259. package/src/sui/manuallyExec/encoder.ts +88 -0
  260. package/src/sui/manuallyExec/index.ts +137 -0
  261. package/src/sui/objects.ts +358 -0
  262. package/src/ton/bindings/offramp.ts +96 -0
  263. package/src/ton/bindings/onramp.ts +72 -0
  264. package/src/ton/bindings/router.ts +65 -0
  265. package/src/ton/exec.ts +44 -0
  266. package/src/ton/hasher.ts +184 -0
  267. package/src/ton/index.ts +989 -0
  268. package/src/ton/logs.ts +157 -0
  269. package/src/ton/types.ts +143 -0
  270. package/src/ton/utils.ts +514 -0
  271. package/src/types.ts +6 -2
  272. package/src/utils.ts +58 -23
  273. package/tsconfig.json +2 -1
@@ -0,0 +1,88 @@
1
+ import { bcs } from '@mysten/sui/bcs'
2
+ import type { BytesLike } from 'ethers'
3
+
4
+ import { CCIPMessageInvalidError } from '../../errors/specialized.ts'
5
+ import { decodeExtraArgs } from '../../extra-args.ts'
6
+ import type { CCIPMessage, CCIPVersion, ExecutionReport } from '../../types.ts'
7
+ import { bytesToBuffer, getAddressBytes, getDataBytes, networkInfo } from '../../utils.ts'
8
+
9
+ const Any2SuiTokenTransferBCS = bcs.struct('Any2SuiTokenTransfer', {
10
+ source_pool_address: bcs.vector(bcs.u8()),
11
+ dest_token_address: bcs.Address,
12
+ dest_gas_amount: bcs.u32(),
13
+ extra_data: bcs.vector(bcs.u8()),
14
+ amount: bcs.u256(),
15
+ })
16
+
17
+ const ExecutionReportBCS = bcs.struct('ExecutionReport', {
18
+ source_chain_selector: bcs.u64(),
19
+ message_id: bcs.fixedArray(32, bcs.u8()),
20
+ header_source_chain_selector: bcs.u64(),
21
+ dest_chain_selector: bcs.u64(),
22
+ sequence_number: bcs.u64(),
23
+ nonce: bcs.u64(),
24
+ sender: bcs.vector(bcs.u8()),
25
+ data: bcs.vector(bcs.u8()),
26
+ receiver: bcs.Address,
27
+ gas_limit: bcs.u256(),
28
+ token_receiver: bcs.Address,
29
+ token_amounts: bcs.vector(Any2SuiTokenTransferBCS),
30
+ offchain_token_data: bcs.vector(bcs.vector(bcs.u8())),
31
+ proofs: bcs.vector(bcs.fixedArray(32, bcs.u8())),
32
+ })
33
+
34
+ /**
35
+ * Serializes an execution report for Sui manual execution.
36
+ * @param executionReport - The execution report to serialize.
37
+ * @returns Serialized execution report as Uint8Array.
38
+ */
39
+ export function serializeExecutionReport(
40
+ executionReport: ExecutionReport<CCIPMessage<typeof CCIPVersion.V1_6>>,
41
+ ): Uint8Array {
42
+ const { message, offchainTokenData, proofs } = executionReport
43
+
44
+ if (!message) {
45
+ throw new CCIPMessageInvalidError('Message is undefined in execution report')
46
+ }
47
+
48
+ let gasLimit, tokenReceiver
49
+ if ('receiverObjectIds' in message) {
50
+ ;({ gasLimit, tokenReceiver } = message)
51
+ } else {
52
+ const decodedExtraArgs = decodeExtraArgs(
53
+ message.extraArgs,
54
+ networkInfo(message.sourceChainSelector).family,
55
+ )
56
+ if (decodedExtraArgs?._tag !== 'SuiExtraArgsV1')
57
+ throw new CCIPMessageInvalidError('Expected Sui extra args')
58
+ ;({ gasLimit, tokenReceiver } = decodedExtraArgs)
59
+ }
60
+
61
+ const reportData = {
62
+ source_chain_selector: message.sourceChainSelector,
63
+ message_id: Array.from(bytesToBuffer(message.messageId)),
64
+ header_source_chain_selector: message.sourceChainSelector,
65
+ dest_chain_selector: message.destChainSelector,
66
+ sequence_number: message.sequenceNumber,
67
+ nonce: message.nonce,
68
+ sender: Array.from(bytesToBuffer(message.sender)),
69
+ data: Array.from(bytesToBuffer(message.data)),
70
+ receiver: message.receiver,
71
+ gas_limit: gasLimit,
72
+ token_receiver: tokenReceiver,
73
+ token_amounts: message.tokenAmounts.map((token) => ({
74
+ source_pool_address: Array.from(getAddressBytes(token.sourcePoolAddress)),
75
+ dest_token_address: token.destTokenAddress,
76
+ dest_gas_amount: Number(token.destGasAmount || 0n), // Use actual destGasAmount from token data
77
+ extra_data: Array.from(getDataBytes(token.extraData)),
78
+ amount: BigInt(token.amount),
79
+ })),
80
+ // TODO: encode as per CCTP/LBTC TPs, when available
81
+ offchain_token_data: offchainTokenData.map(() => Buffer.from([])),
82
+ proofs: proofs.map((proof: BytesLike) => {
83
+ return Array.from(getDataBytes(proof))
84
+ }),
85
+ }
86
+
87
+ return ExecutionReportBCS.serialize(reportData).toBytes()
88
+ }
@@ -0,0 +1,137 @@
1
+ import { Transaction } from '@mysten/sui/transactions'
2
+
3
+ import { serializeExecutionReport } from './encoder.ts'
4
+ import { CCIPMessageInvalidError } from '../../errors/specialized.ts'
5
+ import { decodeExtraArgs } from '../../extra-args.ts'
6
+ import type { ExecutionReport } from '../../types.ts'
7
+ import { networkInfo } from '../../utils.ts'
8
+ import type { CCIPMessage_V1_6_Sui } from '../types.ts'
9
+
10
+ /** Configuration for manually executing a Sui receiver module. */
11
+ export type ManuallyExecuteSuiReceiverConfig = {
12
+ moduleName: string
13
+ packageId: string
14
+ }
15
+
16
+ /** Configuration for a token pool in manual execution. */
17
+ export type TokenConfig = {
18
+ tokenPoolPackageId: string
19
+ tokenPoolModule: string
20
+ tokenType: string
21
+ administrator: string
22
+ pendingAdministrator: string
23
+ tokenPoolTypeProof: string
24
+ lockOrBurnParams: string[]
25
+ releaseOrMintParams: string[]
26
+ }
27
+
28
+ /** Input parameters for building a Sui manual execution transaction. */
29
+ export type SuiManuallyExecuteInput = {
30
+ offrampAddress: string
31
+ executionReport: ExecutionReport<CCIPMessage_V1_6_Sui>
32
+ ccipAddress: string
33
+ ccipObjectRef: string
34
+ offrampStateObject: string
35
+ receiverConfig: ManuallyExecuteSuiReceiverConfig
36
+ tokenConfigs?: TokenConfig[]
37
+ overrideReceiverObjectIds?: string[]
38
+ }
39
+
40
+ /**
41
+ * Builds a Sui Programmable Transaction Block for manual CCIP message execution.
42
+ * @param params - Input parameters for building the manual execution transaction.
43
+ * @returns A Transaction object ready to be signed and executed.
44
+ */
45
+ export function buildManualExecutionPTB({
46
+ offrampAddress,
47
+ executionReport,
48
+ ccipAddress,
49
+ ccipObjectRef,
50
+ offrampStateObject,
51
+ receiverConfig,
52
+ tokenConfigs,
53
+ overrideReceiverObjectIds,
54
+ }: SuiManuallyExecuteInput): Transaction {
55
+ const reportBytes = serializeExecutionReport(executionReport)
56
+
57
+ // Create transaction
58
+ const tx = new Transaction()
59
+
60
+ // Step 1: Call manually_init_execute to prepare the execution
61
+ const receiverParamsArg = tx.moveCall({
62
+ target: `${offrampAddress}::offramp::manually_init_execute`,
63
+ arguments: [
64
+ tx.object(ccipObjectRef),
65
+ tx.object(offrampStateObject),
66
+ tx.object('0x6'), // Clock object
67
+ tx.pure.vector('u8', Array.from(reportBytes)),
68
+ ],
69
+ })
70
+
71
+ // Get the message from the from the report using the offramp helper
72
+ const messageArg = tx.moveCall({
73
+ target: `${ccipAddress}::offramp_state_helper::extract_any2sui_message`,
74
+ arguments: [receiverParamsArg],
75
+ })
76
+
77
+ // Process token pool transfers
78
+ if (tokenConfigs && tokenConfigs.length > 0) {
79
+ if (executionReport.message.tokenAmounts.length !== tokenConfigs.length) {
80
+ throw new CCIPMessageInvalidError('Token amounts length does not match token configs length')
81
+ }
82
+
83
+ // Process each token transfer
84
+ for (let i = 0; i < tokenConfigs.length; i++) {
85
+ const tokenConfig = tokenConfigs[i]
86
+
87
+ tx.moveCall({
88
+ target: `${tokenConfig.tokenPoolPackageId}::${tokenConfig.tokenPoolModule}::release_or_mint`,
89
+ typeArguments: [tokenConfig.tokenType],
90
+ arguments: [
91
+ tx.object(ccipObjectRef), // CCIPObjectRef
92
+ receiverParamsArg, // ReceiverParams (mutable)
93
+ ...tokenConfig.releaseOrMintParams.map((param) => tx.object(param)), // Pool-specific objects (clock, deny_list, token_state, state, etc.)
94
+ ],
95
+ })
96
+ }
97
+ }
98
+
99
+ // Decode extraArgs to get receiverObjectIds
100
+ const decodedExtraArgs = decodeExtraArgs(
101
+ executionReport.message.extraArgs,
102
+ networkInfo(executionReport.message.destChainSelector).family,
103
+ )
104
+
105
+ if (!decodedExtraArgs || decodedExtraArgs._tag !== 'SuiExtraArgsV1') {
106
+ throw new CCIPMessageInvalidError('Expected Sui extra args')
107
+ }
108
+
109
+ if (decodedExtraArgs.receiverObjectIds.length === 0) {
110
+ throw new CCIPMessageInvalidError('No receiverObjectIds provided in SUIExtraArgsV1')
111
+ }
112
+ // Call the receiver contract
113
+ tx.moveCall({
114
+ target: `${receiverConfig.packageId}::${receiverConfig.moduleName}::ccip_receive`,
115
+ arguments: [
116
+ tx.pure.vector('u8', Buffer.from(executionReport.message.messageId.slice(2), 'hex')),
117
+ tx.object(ccipObjectRef),
118
+ messageArg,
119
+ // if overrideReceiverObjectIds is provided, use them; otherwise, use the ones from decodedExtraArgs (original message)
120
+ ...(overrideReceiverObjectIds && overrideReceiverObjectIds.length > 0
121
+ ? overrideReceiverObjectIds.map(tx.object)
122
+ : decodedExtraArgs.receiverObjectIds.map(tx.object)),
123
+ ],
124
+ })
125
+
126
+ // Step 2: Call finish_execute to complete the execution
127
+ tx.moveCall({
128
+ target: `${offrampAddress}::offramp::finish_execute`,
129
+ arguments: [
130
+ tx.object(ccipObjectRef),
131
+ tx.object(offrampStateObject),
132
+ receiverParamsArg, // ReceiverParams from manually_init_execute
133
+ ],
134
+ })
135
+
136
+ return tx
137
+ }
@@ -0,0 +1,358 @@
1
+ import { bcs } from '@mysten/sui/bcs'
2
+ import type { SuiClient } from '@mysten/sui/client'
3
+ import { Transaction } from '@mysten/sui/transactions'
4
+ import { normalizeSuiAddress } from '@mysten/sui/utils'
5
+ import { blake2b } from '@noble/hashes/blake2'
6
+
7
+ import { CCIPDataFormatUnsupportedError } from '../errors/index.ts'
8
+ import type { CCIPMessage, CCIPVersion } from '../types.ts'
9
+
10
+ /**
11
+ * Derive a dynamic field object ID using the Sui algorithm
12
+ * This matches the Go implementation in chainlink-sui
13
+ */
14
+ export function deriveObjectID(parentAddress: string, keyBytes: Uint8Array): string {
15
+ // Normalize parent address to 32 bytes
16
+ const normalizedParent = normalizeSuiAddress(parentAddress)
17
+ const parentBytes = bcs.Address.serialize(normalizedParent).toBytes()
18
+
19
+ // BCS serialize the key (vector<u8>)
20
+ const bcsKeyBytes = bcs.vector(bcs.u8()).serialize(Array.from(keyBytes)).toBytes()
21
+
22
+ // Construct TypeTag for DerivedObjectKey<vector<u8>>
23
+ const suiFrameworkAddress = bcs.Address.serialize('0x2').toBytes()
24
+ const typeTagBytes = new Uint8Array([
25
+ 0x07, // TypeTag::Struct
26
+ ...suiFrameworkAddress,
27
+ 0x0e, // module length
28
+ ...new TextEncoder().encode('derived_object'),
29
+ 0x10, // struct name length
30
+ ...new TextEncoder().encode('DerivedObjectKey'),
31
+ 0x01, // type params count
32
+ ...[0x06, 0x01], // vector<u8> TypeTag
33
+ ])
34
+
35
+ // Build the hash input
36
+ const keyLenBytes = new Uint8Array(8)
37
+ new DataView(keyLenBytes.buffer).setBigUint64(0, BigInt(bcsKeyBytes.length), true)
38
+
39
+ const hashInput = new Uint8Array([
40
+ 0xf0, // HashingIntentScope::ChildObjectId
41
+ ...parentBytes,
42
+ ...keyLenBytes,
43
+ ...bcsKeyBytes,
44
+ ...typeTagBytes,
45
+ ])
46
+
47
+ // Hash with Blake2b-256
48
+ const hash = blake2b(hashInput, { dkLen: 32 })
49
+
50
+ // Convert to address string
51
+ return normalizeSuiAddress('0x' + Buffer.from(hash).toString('hex'))
52
+ }
53
+
54
+ /**
55
+ * Get the CCIPObjectRef ID for a CCIP package
56
+ */
57
+ export async function getCcipObjectRef(client: SuiClient, ccipPackageId: string): Promise<string> {
58
+ // Get the pointer to find the CCIPObject ID
59
+ const pointerResponse = await client.getOwnedObjects({
60
+ owner: ccipPackageId,
61
+ filter: {
62
+ StructType: `${ccipPackageId}::state_object::CCIPObjectRefPointer`,
63
+ },
64
+ })
65
+
66
+ if (pointerResponse.data.length === 0) {
67
+ throw new CCIPDataFormatUnsupportedError(
68
+ 'No CCIPObjectRefPointer found for the given packageId',
69
+ )
70
+ }
71
+
72
+ // Get the pointer object to extract ccip_object_id
73
+ const pointerId = pointerResponse.data[0].data?.objectId
74
+ if (!pointerId) {
75
+ throw new CCIPDataFormatUnsupportedError('Pointer does not have objectId')
76
+ }
77
+
78
+ const pointerObject = await client.getObject({
79
+ id: pointerId,
80
+ options: { showContent: true },
81
+ })
82
+
83
+ if (pointerObject.data?.content?.dataType !== 'moveObject') {
84
+ throw new CCIPDataFormatUnsupportedError('Pointer object is not a Move object')
85
+ }
86
+
87
+ const ccipObjectId = (pointerObject.data.content.fields as Record<string, unknown>)[
88
+ 'ccip_object_id'
89
+ ] as string
90
+
91
+ if (!ccipObjectId) {
92
+ throw new CCIPDataFormatUnsupportedError('Could not find ccip_object_id in pointer')
93
+ }
94
+
95
+ // Derive the CCIPObjectRef ID from the parent CCIPObject ID
96
+ return deriveObjectID(ccipObjectId, new TextEncoder().encode('CCIPObjectRef'))
97
+ }
98
+
99
+ /**
100
+ * Get the OffRampState object ID for an offramp package
101
+ */
102
+ export async function getOffRampStateObject(
103
+ client: SuiClient,
104
+ offrampPackageId: string,
105
+ ): Promise<string> {
106
+ const offrampPointerResponse = await client.getOwnedObjects({
107
+ owner: offrampPackageId,
108
+ filter: {
109
+ StructType: `${offrampPackageId}::offramp::OffRampStatePointer`,
110
+ },
111
+ })
112
+
113
+ if (offrampPointerResponse.data.length === 0) {
114
+ throw new CCIPDataFormatUnsupportedError(
115
+ 'No OffRampStatePointer found for the given offramp package',
116
+ )
117
+ }
118
+
119
+ const offrampPointerId = offrampPointerResponse.data[0].data?.objectId
120
+
121
+ if (!offrampPointerId) {
122
+ throw new CCIPDataFormatUnsupportedError('OffRampStatePointer does not have a valid objectId')
123
+ }
124
+
125
+ const offrampPointerObject = await client.getObject({
126
+ id: offrampPointerId,
127
+ options: { showContent: true },
128
+ })
129
+
130
+ if (offrampPointerObject.data?.content?.dataType !== 'moveObject') {
131
+ throw new CCIPDataFormatUnsupportedError('OffRamp pointer object is not a Move object')
132
+ }
133
+
134
+ const offrampObjectId = (offrampPointerObject.data.content.fields as Record<string, unknown>)[
135
+ 'off_ramp_object_id'
136
+ ] as string
137
+
138
+ if (!offrampObjectId) {
139
+ throw new CCIPDataFormatUnsupportedError('Could not find off_ramp_object_id in pointer')
140
+ }
141
+
142
+ // Derive the OffRampState ID from the parent OffRamp Object ID
143
+ return deriveObjectID(offrampObjectId, new TextEncoder().encode('OffRampState'))
144
+ }
145
+
146
+ /**
147
+ * Get the receiver module configuration from the receiver registry.
148
+ * @param provider - Sui client
149
+ * @param ccipPackageId - CCIP package ID
150
+ * @param ccipObjectRef - CCIP object reference
151
+ * @param receiverPackageId - Receiver package ID
152
+ * @returns Receiver module name and package ID
153
+ */
154
+ export async function getReceiverModule(
155
+ provider: SuiClient,
156
+ ccipPackageId: string,
157
+ ccipObjectRef: string,
158
+ receiverPackageId: string,
159
+ ) {
160
+ // Call get_receiver_config from receiver_registry contract
161
+ const tx = new Transaction()
162
+
163
+ tx.moveCall({
164
+ target: `${ccipPackageId}::receiver_registry::get_receiver_config`,
165
+ arguments: [tx.object(ccipObjectRef), tx.pure.address(receiverPackageId)],
166
+ })
167
+
168
+ const result = await provider.devInspectTransactionBlock({
169
+ transactionBlock: tx,
170
+ sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
171
+ })
172
+
173
+ if (result.error) {
174
+ throw new CCIPDataFormatUnsupportedError(`Failed to call get_receiver_config: ${result.error}`)
175
+ }
176
+
177
+ if (!result.results || result.results.length === 0) {
178
+ throw new CCIPDataFormatUnsupportedError('No results returned from get_receiver_config')
179
+ }
180
+
181
+ const returnValues = result.results[0]?.returnValues
182
+
183
+ if (!returnValues || returnValues.length === 0) {
184
+ throw new CCIPDataFormatUnsupportedError('No return values from get_receiver_config')
185
+ }
186
+
187
+ // Decode the ReceiverConfig struct
188
+ // ReceiverConfig has two fields: module_name (String) and proof_typename (ascii::String)
189
+ // The struct is returned as a BCS-encoded byte array
190
+ const receiverConfigBytes = returnValues[0][0]
191
+
192
+ // Parse the struct:
193
+ // First field is module_name (String = vector<u8> with length prefix)
194
+ let offset = 0
195
+ const moduleNameLength = receiverConfigBytes[offset]
196
+ offset += 1
197
+ const moduleName = new TextDecoder().decode(
198
+ new Uint8Array(receiverConfigBytes.slice(offset, offset + moduleNameLength)),
199
+ )
200
+
201
+ return {
202
+ moduleName,
203
+ packageId: receiverPackageId,
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Fetch token configurations for the given token amounts.
209
+ * @param client - Sui client
210
+ * @param ccipPackageId - CCIP package ID
211
+ * @param ccipObjectRef - CCIP object reference
212
+ * @param tokenAmounts - Token amounts from CCIP message
213
+ * @returns Array of token configurations
214
+ */
215
+ export async function fetchTokenConfigs(
216
+ client: SuiClient,
217
+ ccipPackageId: string,
218
+ ccipObjectRef: string,
219
+ tokenAmounts: CCIPMessage<typeof CCIPVersion.V1_6>['tokenAmounts'],
220
+ ) {
221
+ if (tokenAmounts.length === 0) {
222
+ return []
223
+ }
224
+ const tokenConfigs = []
225
+ const tokenAddresses = [
226
+ ...new Set(
227
+ tokenAmounts.map((token) => token.destTokenAddress).filter((addr) => addr && addr !== '0x0'),
228
+ ),
229
+ ]
230
+
231
+ // Fetch token config for each unique token address
232
+ for (const tokenAddress of tokenAddresses) {
233
+ const tx = new Transaction()
234
+
235
+ // Call get_token_config_struct from token_admin_registry
236
+ tx.moveCall({
237
+ target: `${ccipPackageId}::token_admin_registry::get_token_config_struct`,
238
+ arguments: [tx.object(ccipObjectRef), tx.pure.address(tokenAddress)],
239
+ })
240
+
241
+ const result = await client.devInspectTransactionBlock({
242
+ transactionBlock: tx,
243
+ sender: '0x0000000000000000000000000000000000000000000000000000000000000000',
244
+ })
245
+
246
+ if (result.error) {
247
+ throw new CCIPDataFormatUnsupportedError(
248
+ `Failed to fetch token config for ${tokenAddress}: ${result.error}`,
249
+ )
250
+ }
251
+
252
+ if (!result.results || result.results.length === 0) {
253
+ throw new CCIPDataFormatUnsupportedError(
254
+ `No results returned from get_token_config_struct for ${tokenAddress}`,
255
+ )
256
+ }
257
+
258
+ const returnValues = result.results[0]?.returnValues
259
+
260
+ if (!returnValues || returnValues.length === 0) {
261
+ throw new CCIPDataFormatUnsupportedError(
262
+ `No return values from get_token_config_struct for ${tokenAddress}`,
263
+ )
264
+ }
265
+
266
+ // Parse the TokenConfig struct from BCS-encoded bytes
267
+ const configBytes = returnValues[0][0]
268
+
269
+ // TokenConfig structure (from token_admin_registry.go):
270
+ // - TokenPoolPackageId (address = 32 bytes)
271
+ // - TokenPoolModule (String = length + bytes)
272
+ // - TokenType (ascii::String = length + bytes)
273
+ // - Administrator (address = 32 bytes)
274
+ // - PendingAdministrator (address = 32 bytes)
275
+ // - TokenPoolTypeProof (ascii::String = length + bytes)
276
+ // - LockOrBurnParams (vector<address> = length + N * 32 bytes)
277
+ // - ReleaseOrMintParams (vector<address> = length + N * 32 bytes)
278
+
279
+ let offset = 0
280
+
281
+ // TokenPoolPackageId (32 bytes)
282
+ const tokenPoolPackageIdBytes = configBytes.slice(offset, offset + 32)
283
+ const tokenPoolPackageId = normalizeSuiAddress(
284
+ '0x' + Buffer.from(tokenPoolPackageIdBytes).toString('hex'),
285
+ )
286
+ offset += 32
287
+
288
+ // TokenPoolModule (String)
289
+ const moduleNameLength = configBytes[offset]
290
+ offset += 1
291
+ const tokenPoolModule = new TextDecoder().decode(
292
+ new Uint8Array(configBytes.slice(offset, offset + moduleNameLength)),
293
+ )
294
+ offset += moduleNameLength
295
+
296
+ // TokenType (ascii::String)
297
+ const tokenTypeLength = configBytes[offset]
298
+ offset += 1
299
+ const tokenType = new TextDecoder().decode(
300
+ new Uint8Array(configBytes.slice(offset, offset + tokenTypeLength)),
301
+ )
302
+ offset += tokenTypeLength
303
+
304
+ // Administrator (32 bytes)
305
+ const administratorBytes = configBytes.slice(offset, offset + 32)
306
+ const administrator = normalizeSuiAddress(
307
+ '0x' + Buffer.from(administratorBytes).toString('hex'),
308
+ )
309
+ offset += 32
310
+
311
+ // PendingAdministrator (32 bytes)
312
+ const pendingAdminBytes = configBytes.slice(offset, offset + 32)
313
+ const pendingAdministrator = normalizeSuiAddress(
314
+ '0x' + Buffer.from(pendingAdminBytes).toString('hex'),
315
+ )
316
+ offset += 32
317
+
318
+ // TokenPoolTypeProof (ascii::String)
319
+ const proofLength = configBytes[offset]
320
+ offset += 1
321
+ const tokenPoolTypeProof = new TextDecoder().decode(
322
+ new Uint8Array(configBytes.slice(offset, offset + proofLength)),
323
+ )
324
+ offset += proofLength
325
+
326
+ // LockOrBurnParams (vector<address>)
327
+ const lockOrBurnCount = configBytes[offset]
328
+ offset += 1
329
+ const lockOrBurnParams: string[] = []
330
+ for (let i = 0; i < lockOrBurnCount; i++) {
331
+ const addrBytes = configBytes.slice(offset, offset + 32)
332
+ lockOrBurnParams.push(normalizeSuiAddress('0x' + Buffer.from(addrBytes).toString('hex')))
333
+ offset += 32
334
+ }
335
+
336
+ // ReleaseOrMintParams (vector<address>)
337
+ const releaseOrMintCount = configBytes[offset]
338
+ offset += 1
339
+ const releaseOrMintParams: string[] = []
340
+ for (let i = 0; i < releaseOrMintCount; i++) {
341
+ const addrBytes = configBytes.slice(offset, offset + 32)
342
+ releaseOrMintParams.push(normalizeSuiAddress('0x' + Buffer.from(addrBytes).toString('hex')))
343
+ offset += 32
344
+ }
345
+
346
+ tokenConfigs.push({
347
+ tokenPoolPackageId,
348
+ tokenPoolModule,
349
+ tokenType,
350
+ administrator,
351
+ pendingAdministrator,
352
+ tokenPoolTypeProof,
353
+ lockOrBurnParams,
354
+ releaseOrMintParams,
355
+ })
356
+ }
357
+ return tokenConfigs
358
+ }
@@ -0,0 +1,96 @@
1
+ // TODO: FIXME: Remove local copies and import when chainlink-ton is published as npm package
2
+ import type { Address, ContractProvider } from '@ton/core'
3
+
4
+ /**
5
+ * Configuration for a source chain on the TON OffRamp contract.
6
+ */
7
+ export interface SourceChainConfig {
8
+ router: Address
9
+ isEnabled: boolean
10
+ minSeqNr: bigint
11
+ isRMNVerificationDisabled: boolean
12
+ onRamp: Buffer
13
+ }
14
+
15
+ /**
16
+ * Dynamic configuration for the OffRamp contract.
17
+ * Contains addresses that can be updated without redeploying the contract.
18
+ */
19
+ export interface DynamicConfig {
20
+ router: Address
21
+ feeQuoter: Address
22
+ permissionlessExecutionThresholdSeconds: number
23
+ }
24
+
25
+ /**
26
+ * TON OffRamp contract binding.
27
+ * The OffRamp receives and executes cross-chain messages on the destination (TON) chain.
28
+ */
29
+ export class OffRamp {
30
+ readonly address: Address
31
+
32
+ /**
33
+ * Creates a new OffRamp instance.
34
+ * @param address - The OffRamp contract address on TON.
35
+ */
36
+ constructor(address: Address) {
37
+ this.address = address
38
+ }
39
+
40
+ /**
41
+ * Creates an OffRamp instance from a contract address.
42
+ * @param address - The OffRamp contract address on TON.
43
+ * @returns A new OffRamp instance.
44
+ */
45
+ static createFromAddress(address: Address): OffRamp {
46
+ return new OffRamp(address)
47
+ }
48
+
49
+ /**
50
+ * Retrieves the source chain configuration for a given chain selector.
51
+ *
52
+ * @param provider - TON contract provider for making RPC calls.
53
+ * @param sourceChainSelector - The CCIP chain selector of the source chain.
54
+ * @returns The source chain configuration.
55
+ * @throws Error with exitCode 266 if the source chain is not enabled.
56
+ */
57
+ async getSourceChainConfig(
58
+ provider: ContractProvider,
59
+ sourceChainSelector: bigint,
60
+ ): Promise<SourceChainConfig> {
61
+ const result = await provider.get('sourceChainConfig', [
62
+ { type: 'int', value: sourceChainSelector },
63
+ ])
64
+
65
+ // Tolk returns struct as tuple
66
+ const router = result.stack.readAddress()
67
+ const isEnabled = result.stack.readBoolean()
68
+ const minSeqNr = result.stack.readBigNumber()
69
+ const isRMNVerificationDisabled = result.stack.readBoolean()
70
+
71
+ // onRamp is stored as CrossChainAddress cell
72
+ const onRampCell = result.stack.readCell()
73
+ const onRampSlice = onRampCell.beginParse()
74
+
75
+ // Check if length-prefixed or raw format based on cell bit length
76
+ const cellBits = onRampCell.bits.length
77
+ let onRamp: Buffer
78
+
79
+ if (cellBits === 160) {
80
+ // Raw 20-byte EVM address (no length prefix)
81
+ onRamp = onRampSlice.loadBuffer(20)
82
+ } else {
83
+ // Length-prefixed format: 8-bit length + data
84
+ const onRampLength = onRampSlice.loadUint(8)
85
+ onRamp = onRampSlice.loadBuffer(onRampLength)
86
+ }
87
+
88
+ return {
89
+ router,
90
+ isEnabled,
91
+ minSeqNr,
92
+ isRMNVerificationDisabled,
93
+ onRamp,
94
+ }
95
+ }
96
+ }