@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
@@ -5,9 +5,17 @@ import type { SuiClient } from '@mysten/sui/client'
5
5
  import { Transaction } from '@mysten/sui/transactions'
6
6
  import { normalizeSuiAddress } from '@mysten/sui/utils'
7
7
  import { blake2b } from '@noble/hashes/blake2.js'
8
+ import { hexlify, toUtf8Bytes } from 'ethers'
9
+ import { memoize } from 'micro-memoize'
8
10
 
9
11
  import { CCIPDataFormatUnsupportedError } from '../errors/index.ts'
10
12
  import type { CCIPMessage, CCIPVersion } from '../types.ts'
13
+ import { toLeArray } from '../utils.ts'
14
+
15
+ const bcsBytes = (bytes: Uint8Array) => bcs.vector(bcs.u8()).serialize(bytes).toBytes()
16
+
17
+ const HASHING_INTENT_SCOPE_CHILD_OBJECT_ID = 0xf0
18
+ const SUI_FRAMEWORK_ADDRESS = '0x2'
11
19
 
12
20
  /**
13
21
  * Derive a dynamic field object ID using the Sui algorithm
@@ -19,27 +27,24 @@ export function deriveObjectID(parentAddress: string, keyBytes: Uint8Array): str
19
27
  const parentBytes = bcs.Address.serialize(normalizedParent).toBytes()
20
28
 
21
29
  // BCS serialize the key (vector<u8>)
22
- const bcsKeyBytes = bcs.vector(bcs.u8()).serialize(Array.from(keyBytes)).toBytes()
30
+ const bcsKeyBytes = bcsBytes(keyBytes)
31
+ const keyLenBytes = toLeArray(bcsKeyBytes.length, 8) // uint64
23
32
 
24
33
  // Construct TypeTag for DerivedObjectKey<vector<u8>>
25
- const suiFrameworkAddress = bcs.Address.serialize('0x2').toBytes()
34
+ const suiFrameworkAddress = bcs.Address.serialize(SUI_FRAMEWORK_ADDRESS).toBytes()
26
35
  const typeTagBytes = new Uint8Array([
27
36
  0x07, // TypeTag::Struct
28
37
  ...suiFrameworkAddress,
29
- 0x0e, // module length
30
- ...new TextEncoder().encode('derived_object'),
31
- 0x10, // struct name length
32
- ...new TextEncoder().encode('DerivedObjectKey'),
38
+ ...bcsBytes(toUtf8Bytes('derived_object')), //module
39
+ ...bcsBytes(toUtf8Bytes('DerivedObjectKey')), // struct name
33
40
  0x01, // type params count
34
- ...[0x06, 0x01], // vector<u8> TypeTag
41
+ 0x06, // TypeTag::Vector
42
+ 0x01, // TypeTag::U8
35
43
  ])
36
44
 
37
45
  // Build the hash input
38
- const keyLenBytes = new Uint8Array(8)
39
- new DataView(keyLenBytes.buffer).setBigUint64(0, BigInt(bcsKeyBytes.length), true)
40
-
41
46
  const hashInput = new Uint8Array([
42
- 0xf0, // HashingIntentScope::ChildObjectId
47
+ HASHING_INTENT_SCOPE_CHILD_OBJECT_ID,
43
48
  ...parentBytes,
44
49
  ...keyLenBytes,
45
50
  ...bcsKeyBytes,
@@ -49,101 +54,72 @@ export function deriveObjectID(parentAddress: string, keyBytes: Uint8Array): str
49
54
  // Hash with Blake2b-256
50
55
  const hash = blake2b(hashInput, { dkLen: 32 })
51
56
 
52
- // Convert to address string
53
- return normalizeSuiAddress('0x' + Buffer.from(hash).toString('hex'))
57
+ return hexlify(hash)
54
58
  }
55
59
 
56
60
  /**
57
- * Get the CCIPObjectRef ID for a CCIP package
61
+ * Finds the StatePointer object owned by a package.
62
+ * The StatePointer contains a reference to the parent object used for derivation.
58
63
  */
59
- export async function getCcipObjectRef(client: SuiClient, ccipPackageId: string): Promise<string> {
60
- // Get the pointer to find the CCIPObject ID
61
- const pointerResponse = await client.getOwnedObjects({
62
- owner: ccipPackageId,
63
- filter: {
64
- StructType: `${ccipPackageId}::state_object::CCIPObjectRefPointer`,
65
- },
66
- })
67
-
68
- if (!pointerResponse.data.length) {
69
- throw new CCIPDataFormatUnsupportedError(
70
- 'No CCIPObjectRefPointer found for the given packageId',
71
- )
72
- }
73
-
74
- // Get the pointer object to extract ccip_object_id
75
- const pointerId = pointerResponse.data[0]!.data?.objectId
76
- if (!pointerId) {
77
- throw new CCIPDataFormatUnsupportedError('Pointer does not have objectId')
78
- }
79
-
80
- const pointerObject = await client.getObject({
81
- id: pointerId,
82
- options: { showContent: true },
83
- })
84
-
85
- if (pointerObject.data?.content?.dataType !== 'moveObject') {
86
- throw new CCIPDataFormatUnsupportedError('Pointer object is not a Move object')
87
- }
88
-
89
- const ccipObjectId = (pointerObject.data.content.fields as Record<string, unknown>)[
90
- 'ccip_object_id'
91
- ] as string
92
-
93
- if (!ccipObjectId) {
94
- throw new CCIPDataFormatUnsupportedError('Could not find ccip_object_id in pointer')
95
- }
64
+ export const getObjectRef = memoize(
65
+ async function getPackageIds_(address: string, client: SuiClient): Promise<string> {
66
+ let stateObjectName
67
+ if (address.endsWith('::onramp')) stateObjectName = 'OnRampState'
68
+ else if (address.endsWith('::offramp')) stateObjectName = 'OffRampState'
69
+ else stateObjectName = 'CCIPObjectRef'
70
+
71
+ const fullStatePointerType = `${address}::${stateObjectName}Pointer`
72
+
73
+ const ownedObjects = await client.getOwnedObjects({
74
+ owner: address.split('::')[0]!,
75
+ filter: { StructType: fullStatePointerType },
76
+ options: { showContent: true },
77
+ })
96
78
 
97
- // Derive the CCIPObjectRef ID from the parent CCIPObject ID
98
- return deriveObjectID(ccipObjectId, new TextEncoder().encode('CCIPObjectRef'))
99
- }
79
+ const pointer = ownedObjects.data[0]?.data
80
+ if (!pointer?.objectId || pointer.content!.dataType !== 'moveObject')
81
+ throw new CCIPDataFormatUnsupportedError(
82
+ 'No CCIP ObjectRef Pointer found for the given packageId',
83
+ { context: { fullStatePointerType, pointer } },
84
+ )
85
+ // const statePointerObjectId = pointer.objectId
86
+ const parentObjectId = Object.entries(pointer.content!.fields).find(([key]) =>
87
+ key.endsWith('_object_id'),
88
+ )?.[1]
89
+ if (typeof parentObjectId !== 'string')
90
+ throw new CCIPDataFormatUnsupportedError('No parent object id found inthe given pointer', {
91
+ context: { fullStatePointerType, pointer },
92
+ })
93
+ return deriveObjectID(parentObjectId, toUtf8Bytes(stateObjectName))
94
+ },
95
+ { maxArgs: 1, expires: 300e3, async: true },
96
+ )
100
97
 
101
98
  /**
102
- * Get the OffRampState object ID for an offramp package
99
+ * Finds the StatePointer object owned by a package.
100
+ * The StatePointer contains a reference to the parent object used for derivation.
103
101
  */
104
- export async function getOffRampStateObject(
105
- client: SuiClient,
106
- offrampPackageId: string,
107
- ): Promise<string> {
108
- const offrampPointerResponse = await client.getOwnedObjects({
109
- owner: offrampPackageId,
110
- filter: {
111
- StructType: `${offrampPackageId}::offramp::OffRampStatePointer`,
112
- },
113
- })
114
-
115
- if (!offrampPointerResponse.data.length) {
116
- throw new CCIPDataFormatUnsupportedError(
117
- 'No OffRampStatePointer found for the given offramp package',
118
- )
119
- }
120
-
121
- const offrampPointerId = offrampPointerResponse.data[0]!.data?.objectId
122
-
123
- if (!offrampPointerId) {
124
- throw new CCIPDataFormatUnsupportedError('OffRampStatePointer does not have a valid objectId')
125
- }
126
-
127
- const offrampPointerObject = await client.getObject({
128
- id: offrampPointerId,
129
- options: { showContent: true },
130
- })
131
-
132
- if (offrampPointerObject.data?.content?.dataType !== 'moveObject') {
133
- throw new CCIPDataFormatUnsupportedError('OffRamp pointer object is not a Move object')
134
- }
135
-
136
- const offrampObjectId = (offrampPointerObject.data.content.fields as Record<string, unknown>)[
137
- 'off_ramp_object_id'
138
- ] as string
139
-
140
- if (!offrampObjectId) {
141
- throw new CCIPDataFormatUnsupportedError('Could not find off_ramp_object_id in pointer')
142
- }
143
-
144
- // Derive the OffRampState ID from the parent OffRamp Object ID
145
- return deriveObjectID(offrampObjectId, new TextEncoder().encode('OffRampState'))
146
- }
102
+ export const getLatestPackageId = memoize(
103
+ async function getLatestPackageId_(address: string, client: SuiClient): Promise<string> {
104
+ const suffix = address.split('::')[1]
105
+ try {
106
+ const stateObjectId = await getObjectRef(address, client)
107
+ const stateObject = await client.getObject({
108
+ id: stateObjectId,
109
+ options: { showContent: true },
110
+ })
111
+ const stateContent = stateObject.data?.content
112
+ if (stateContent?.dataType !== 'moveObject') return address
113
+ const packageIdsField = (stateContent.fields as Record<string, unknown>)['package_ids']
114
+ if (!Array.isArray(packageIdsField) || packageIdsField.length === 0) return address
115
+ const latest = packageIdsField[packageIdsField.length - 1] as string
116
+ return suffix ? `${latest}::${suffix}` : latest
117
+ } catch {
118
+ return address
119
+ }
120
+ },
121
+ { maxArgs: 1, expires: 60e3, async: true },
122
+ )
147
123
 
148
124
  /**
149
125
  * Get the receiver module configuration from the receiver registry.
package/src/sui/types.ts CHANGED
@@ -15,6 +15,16 @@ export const SuiExtraArgsV1Codec = bcs.struct('SuiExtraArgsV1', {
15
15
  receiverObjectIds: bcs.vector(bcs.vector(bcs.u8())),
16
16
  })
17
17
 
18
+ /** Token amount data structure for Sui CCIP messages. */
19
+ export type SuiTokenAmount = {
20
+ source_pool_address?: string
21
+ dest_token_address?: number[]
22
+ extra_data?: number[]
23
+ amount?: string | number
24
+ dest_exec_data?: number[]
25
+ dest_gas_amount?: string | number
26
+ }
27
+
18
28
  /**
19
29
  * Encodes Sui v1 extra arguments using BCS encoding.
20
30
  * @param args - Sui extra arguments to encode.
@@ -26,3 +36,28 @@ export function encodeSuiExtraArgsV1(args: SuiExtraArgsV1): string {
26
36
  const bcsData = SuiExtraArgsV1Codec.serialize({ ...args, tokenReceiver, receiverObjectIds })
27
37
  return concat([SuiExtraArgsV1Tag, bcsData.toBytes()])
28
38
  }
39
+
40
+ /**
41
+ * Sui-specific CCIP message log structure from events.
42
+ */
43
+ export type SuiCCIPMessageLog = {
44
+ dest_chain_selector: string
45
+ message: {
46
+ data: number[]
47
+ extra_args: number[]
48
+ fee_token: string
49
+ fee_token_amount: string
50
+ fee_value_juels: string
51
+ header: {
52
+ dest_chain_selector: string
53
+ message_id: number[]
54
+ nonce: string
55
+ sequence_number: string
56
+ source_chain_selector: string
57
+ }
58
+ receiver: number[]
59
+ sender: string
60
+ token_amounts: SuiTokenAmount[]
61
+ }
62
+ sequence_number: string
63
+ }
package/src/ton/index.ts CHANGED
@@ -8,7 +8,8 @@ import { type Memoized, memoize } from 'micro-memoize'
8
8
  import type { PickDeep } from 'type-fest'
9
9
 
10
10
  import { streamTransactionsForAddress } from './logs.ts'
11
- import { type ChainContext, type LogFilter, Chain } from '../chain.ts'
11
+ import { generateUnsignedCcipSend, getFee as getFeeImpl } from './send.ts'
12
+ import { type ChainContext, type GetBalanceOpts, type LogFilter, Chain } from '../chain.ts'
12
13
  import {
13
14
  CCIPArgumentInvalidError,
14
15
  CCIPExtraArgsInvalidError,
@@ -21,7 +22,8 @@ import {
21
22
  CCIPWalletInvalidError,
22
23
  } from '../errors/specialized.ts'
23
24
  import { type EVMExtraArgsV2, type ExtraArgs, EVMExtraArgsV2Tag } from '../extra-args.ts'
24
- import { getMessagesInTx } from '../requests.ts'
25
+ import type { LeafHasher } from '../hasher/common.ts'
26
+ import { buildMessageForDest } from '../requests.ts'
25
27
  import { supportedChains } from '../supported-chains.ts'
26
28
  import {
27
29
  type CCIPExecution,
@@ -35,6 +37,7 @@ import {
35
37
  type NetworkInfo,
36
38
  type OffchainTokenData,
37
39
  type WithLogger,
40
+ CCIPVersion,
38
41
  ChainFamily,
39
42
  ExecutionState,
40
43
  } from '../types.ts'
@@ -50,7 +53,6 @@ import { generateUnsignedExecuteReport as generateUnsignedExecuteReportImpl } fr
50
53
  import { getTONLeafHasher } from './hasher.ts'
51
54
  import { type CCIPMessage_V1_6_TON, type UnsignedTONTx, isTONWallet } from './types.ts'
52
55
  import { crc32, lookupTxByRawHash, parseJettonContent } from './utils.ts'
53
- import type { LeafHasher } from '../hasher/common.ts'
54
56
  export type { TONWallet, UnsignedTONTx } from './types.ts'
55
57
 
56
58
  /**
@@ -164,13 +166,13 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
164
166
  ctx?: ChainContext & { fetchFn?: typeof fetch },
165
167
  ): Promise<TONChain> {
166
168
  // Verify connection by getting the latest block
167
- const isTestnet =
169
+ const isMainnet =
168
170
  (
169
171
  await client.getContractState(
170
172
  Address.parse('EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'), // mainnet USDT
171
173
  )
172
- ).state !== 'active'
173
- return new TONChain(client, networkInfo(isTestnet ? 'ton-testnet' : 'ton-mainnet'), ctx)
174
+ ).state === 'active'
175
+ return new TONChain(client, networkInfo(isMainnet ? 'ton-mainnet' : 'ton-testnet'), ctx)
174
176
  }
175
177
 
176
178
  /**
@@ -180,6 +182,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
180
182
  * @param url - RPC endpoint URL for TonClient (v2).
181
183
  * @param ctx - Context containing logger.
182
184
  * @returns A new TONChain instance.
185
+ * @throws {@link CCIPHttpError} if connection to the RPC endpoint fails
183
186
  */
184
187
  static async fromUrl(url: string, ctx?: ChainContext): Promise<TONChain> {
185
188
  const { logger = console } = ctx ?? {}
@@ -220,6 +223,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
220
223
  *
221
224
  * @param block - Logical time (lt) as number, or 'finalized' for latest block timestamp
222
225
  * @returns Unix timestamp in seconds
226
+ * @throws {@link CCIPNotImplementedError} if lt is not in cache
223
227
  */
224
228
  async getBlockTimestamp(block: number | 'finalized'): Promise<number> {
225
229
  if (typeof block != 'number') {
@@ -247,6 +251,8 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
247
251
  * @param tx - Transaction identifier in either format
248
252
  * @returns ChainTransaction with transaction details
249
253
  * Note: `blockNumber` contains logical time (lt), not block seqno
254
+ * @throws {@link CCIPArgumentInvalidError} if hash format is invalid
255
+ * @throws {@link CCIPTransactionNotFoundError} if transaction not found
250
256
  */
251
257
  async getTransaction(tx: string | Transaction): Promise<ChainTransaction> {
252
258
  let address
@@ -264,7 +270,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
264
270
  )
265
271
  const txInfo = await lookupTxByRawHash(
266
272
  cleanHash,
267
- this.network.isTestnet,
273
+ this.network.networkType,
268
274
  this.rateLimitedFetch,
269
275
  this,
270
276
  )
@@ -341,6 +347,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
341
347
  * not block sequence numbers. This is because TON transaction APIs are indexed by lt.
342
348
  *
343
349
  * @param opts - Log filter options (startBlock/endBlock are interpreted as lt values)
350
+ * @throws {@link CCIPTopicsInvalidError} if topics format is invalid
344
351
  */
345
352
  async *getLogs(opts: LogFilter): AsyncIterableIterator<Log_> {
346
353
  let topics
@@ -363,12 +370,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
363
370
  }
364
371
  }
365
372
 
366
- /** {@inheritDoc Chain.getMessagesInTx} */
367
- override async getMessagesInTx(tx: string | ChainTransaction): Promise<CCIPRequest[]> {
368
- return getMessagesInTx(this, typeof tx === 'string' ? await this.getTransaction(tx) : tx)
369
- }
370
-
371
- /** {@inheritDoc Chain.getMessagesInBatch} */
373
+ /**
374
+ * {@inheritDoc Chain.getMessagesInBatch}
375
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
376
+ */
372
377
  override async getMessagesInBatch<
373
378
  R extends PickDeep<
374
379
  CCIPRequest,
@@ -426,9 +431,14 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
426
431
  return stack.readAddress().toRawString()
427
432
  }
428
433
 
429
- /** {@inheritDoc Chain.getNativeTokenForRouter} */
434
+ /**
435
+ * {@inheritDoc Chain.getNativeTokenForRouter}
436
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
437
+ */
430
438
  getNativeTokenForRouter(_router: string): Promise<string> {
431
- return Promise.reject(new CCIPNotImplementedError('getNativeTokenForRouter'))
439
+ // TON native token is represented as address 0:0...01 (workchain 0, hash = 1)
440
+ // This is a convention for representing native TON in CCIP
441
+ return Promise.resolve('0:0000000000000000000000000000000000000000000000000000000000000001')
432
442
  }
433
443
 
434
444
  /** {@inheritDoc Chain.getOffRampsForRouter} */
@@ -451,7 +461,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
451
461
  return stack.readAddress().toRawString()
452
462
  }
453
463
 
454
- /** {@inheritDoc Chain.getOnRampForOffRamp} */
464
+ /**
465
+ * {@inheritDoc Chain.getOnRampForOffRamp}
466
+ * @throws {@link CCIPSourceChainUnsupportedError} if source chain is not configured
467
+ */
455
468
  async getOnRampForOffRamp(offRamp: string, sourceChainSelector: bigint): Promise<string> {
456
469
  try {
457
470
  const offRampContract = this.provider.provider(Address.parse(offRamp))
@@ -497,7 +510,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
497
510
  return Promise.resolve(offRamp)
498
511
  }
499
512
 
500
- /** {@inheritDoc Chain.getTokenForTokenPool} */
513
+ /**
514
+ * {@inheritDoc Chain.getTokenForTokenPool}
515
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
516
+ */
501
517
  async getTokenForTokenPool(_tokenPool: string): Promise<string> {
502
518
  return Promise.reject(new CCIPNotImplementedError('getTokenForTokenPool'))
503
519
  }
@@ -525,7 +541,45 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
525
541
  }
526
542
  }
527
543
 
528
- /** {@inheritDoc Chain.getTokenAdminRegistryFor} */
544
+ /**
545
+ * {@inheritDoc Chain.getBalance}
546
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
547
+ */
548
+ async getBalance(opts: GetBalanceOpts): Promise<bigint> {
549
+ const { holder, token } = opts
550
+ const holderAddress = Address.parse(holder)
551
+
552
+ if (!token) {
553
+ // Get native TON balance
554
+ const state = await this.provider.getContractState(holderAddress)
555
+ return state.balance
556
+ }
557
+
558
+ // For jetton balance, we need to:
559
+ // 1. Derive the jetton wallet address for this holder
560
+ // 2. Query the balance from that wallet contract
561
+ const jettonMaster = Address.parse(token)
562
+ const { stack } = await this.provider.runMethod(jettonMaster, 'get_wallet_address', [
563
+ { type: 'slice', cell: beginCell().storeAddress(holderAddress).endCell() },
564
+ ])
565
+ const jettonWalletAddress = stack.readAddress()
566
+
567
+ try {
568
+ const { stack: balanceStack } = await this.provider.runMethod(
569
+ jettonWalletAddress,
570
+ 'get_wallet_data',
571
+ )
572
+ return balanceStack.readBigNumber() // First value is balance
573
+ } catch {
574
+ // Wallet doesn't exist yet = 0 balance
575
+ return 0n
576
+ }
577
+ }
578
+
579
+ /**
580
+ * {@inheritDoc Chain.getTokenAdminRegistryFor}
581
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
582
+ */
529
583
  getTokenAdminRegistryFor(_address: string): Promise<string> {
530
584
  return Promise.reject(new CCIPNotImplementedError('getTokenAdminRegistryFor'))
531
585
  }
@@ -833,6 +887,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
833
887
  * and raw format strings ("workchain:hash").
834
888
  * @param bytes - Bytes or string to convert.
835
889
  * @returns TON raw address string in format "workchain:hash".
890
+ * @throws {@link CCIPArgumentInvalidError} if bytes length is invalid
836
891
  */
837
892
  static getAddress(bytes: BytesLike): string {
838
893
  // If it's already a string address, try to parse and return raw format
@@ -952,20 +1007,107 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
952
1007
  }
953
1008
 
954
1009
  /** {@inheritDoc Chain.getFee} */
955
- async getFee(_opts: Parameters<Chain['getFee']>[0]): Promise<bigint> {
956
- return Promise.reject(new CCIPNotImplementedError('getFee'))
1010
+ async getFee({
1011
+ router,
1012
+ destChainSelector,
1013
+ message,
1014
+ }: Parameters<Chain['getFee']>[0]): Promise<bigint> {
1015
+ return getFeeImpl(
1016
+ this,
1017
+ router,
1018
+ destChainSelector,
1019
+ buildMessageForDest(message, networkInfo(destChainSelector).family),
1020
+ )
957
1021
  }
958
1022
 
959
1023
  /** {@inheritDoc Chain.generateUnsignedSendMessage} */
960
- generateUnsignedSendMessage(
961
- _opts: Parameters<Chain['generateUnsignedSendMessage']>[0],
962
- ): Promise<never> {
963
- return Promise.reject(new CCIPNotImplementedError('generateUnsignedSendMessage'))
1024
+ async generateUnsignedSendMessage({
1025
+ router,
1026
+ destChainSelector,
1027
+ message,
1028
+ sender,
1029
+ }: Parameters<Chain['generateUnsignedSendMessage']>[0]): Promise<UnsignedTONTx> {
1030
+ // Convert MessageInput to AnyMessage with defaults
1031
+ const populatedMessage = buildMessageForDest(message, networkInfo(destChainSelector).family)
1032
+
1033
+ // Calculate fee if not provided
1034
+ const fee =
1035
+ message.fee ??
1036
+ (await this.getFee({
1037
+ router,
1038
+ destChainSelector,
1039
+ message: populatedMessage,
1040
+ }))
1041
+
1042
+ const unsigned = generateUnsignedCcipSend(this, sender, router, destChainSelector, {
1043
+ ...populatedMessage,
1044
+ fee,
1045
+ })
1046
+
1047
+ return {
1048
+ family: ChainFamily.TON,
1049
+ ...unsigned,
1050
+ }
964
1051
  }
965
1052
 
966
1053
  /** {@inheritDoc Chain.sendMessage} */
967
- async sendMessage(_opts: Parameters<Chain['sendMessage']>[0]): Promise<CCIPRequest> {
968
- return Promise.reject(new CCIPNotImplementedError('sendMessage'))
1054
+ async sendMessage({
1055
+ router,
1056
+ destChainSelector,
1057
+ message,
1058
+ wallet,
1059
+ }: Parameters<Chain['sendMessage']>[0]): Promise<CCIPRequest> {
1060
+ if (!isTONWallet(wallet)) {
1061
+ throw new CCIPWalletInvalidError(wallet)
1062
+ }
1063
+
1064
+ const sender = await wallet.getAddress()
1065
+
1066
+ // Generate unsigned transaction with fee calculation if needed
1067
+ const { family: _, ...unsigned } = await this.generateUnsignedSendMessage({
1068
+ router,
1069
+ destChainSelector,
1070
+ message,
1071
+ sender,
1072
+ })
1073
+
1074
+ // Send transaction
1075
+ const startTime = Math.floor(Date.now() / 1000)
1076
+ const seqno = await wallet.sendTransaction(unsigned)
1077
+
1078
+ this.logger.info('CCIP send transaction submitted, seqno:', seqno)
1079
+
1080
+ // Wait for CCIPMessageSent event and extract the request
1081
+ // Query the OnRamp for the CCIPMessageSent event
1082
+ const onRamp = await this.getOnRampForRouter(router, destChainSelector)
1083
+
1084
+ // Poll for the message in recent logs
1085
+ for await (const log of this.getLogs({
1086
+ address: onRamp,
1087
+ topics: [crc32('CCIPMessageSent')],
1088
+ startTime,
1089
+ watch: sleep(5 * 60e3 /* 5m timeout */),
1090
+ })) {
1091
+ const msg = TONChain.decodeMessage(log)
1092
+ if (!msg) continue
1093
+
1094
+ // Found our message: construct and return the CCIPRequest
1095
+ const tx = log.tx ?? (await this.getTransaction(log.transactionHash))
1096
+
1097
+ return {
1098
+ lane: {
1099
+ sourceChainSelector: this.network.chainSelector,
1100
+ destChainSelector,
1101
+ onRamp,
1102
+ version: CCIPVersion.V1_6,
1103
+ },
1104
+ message: msg,
1105
+ log,
1106
+ tx,
1107
+ }
1108
+ }
1109
+
1110
+ throw new CCIPTransactionNotFoundError(seqno.toString())
969
1111
  }
970
1112
 
971
1113
  /** {@inheritDoc Chain.getOffchainTokenData} */
@@ -973,7 +1115,10 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
973
1115
  return Promise.resolve(request.message.tokenAmounts.map(() => undefined))
974
1116
  }
975
1117
 
976
- /** {@inheritDoc Chain.generateUnsignedExecuteReport} */
1118
+ /**
1119
+ * {@inheritDoc Chain.generateUnsignedExecuteReport}
1120
+ * @throws {@link CCIPExtraArgsInvalidError} if extra args are not EVMExtraArgsV2 format
1121
+ */
977
1122
  generateUnsignedExecuteReport({
978
1123
  offRamp,
979
1124
  execReport,
@@ -995,7 +1140,11 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
995
1140
  })
996
1141
  }
997
1142
 
998
- /** {@inheritDoc Chain.executeReport} */
1143
+ /**
1144
+ * {@inheritDoc Chain.executeReport}
1145
+ * @throws {@link CCIPWalletInvalidError} if wallet is not a valid TON wallet
1146
+ * @throws {@link CCIPReceiptNotFoundError} if execution receipt not found within timeout
1147
+ */
999
1148
  async executeReport(opts: Parameters<Chain['executeReport']>[0]): Promise<CCIPExecution> {
1000
1149
  const { offRamp, wallet } = opts
1001
1150
  if (!isTONWallet(wallet)) {
@@ -1040,27 +1189,42 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
1040
1189
  }
1041
1190
  }
1042
1191
 
1043
- /** {@inheritDoc Chain.getSupportedTokens} */
1192
+ /**
1193
+ * {@inheritDoc Chain.getSupportedTokens}
1194
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
1195
+ */
1044
1196
  async getSupportedTokens(_address: string): Promise<string[]> {
1045
1197
  return Promise.reject(new CCIPNotImplementedError('getSupportedTokens'))
1046
1198
  }
1047
1199
 
1048
- /** {@inheritDoc Chain.getRegistryTokenConfig} */
1200
+ /**
1201
+ * {@inheritDoc Chain.getRegistryTokenConfig}
1202
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
1203
+ */
1049
1204
  async getRegistryTokenConfig(_address: string, _tokenName: string): Promise<never> {
1050
1205
  return Promise.reject(new CCIPNotImplementedError('getRegistryTokenConfig'))
1051
1206
  }
1052
1207
 
1053
- /** {@inheritDoc Chain.getTokenPoolConfigs} */
1054
- async getTokenPoolConfigs(_tokenPool: string): Promise<never> {
1055
- return Promise.reject(new CCIPNotImplementedError('getTokenPoolConfigs'))
1208
+ /**
1209
+ * {@inheritDoc Chain.getTokenPoolConfig}
1210
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
1211
+ */
1212
+ async getTokenPoolConfig(_tokenPool: string): Promise<never> {
1213
+ return Promise.reject(new CCIPNotImplementedError('getTokenPoolConfig'))
1056
1214
  }
1057
1215
 
1058
- /** {@inheritDoc Chain.getTokenPoolRemotes} */
1216
+ /**
1217
+ * {@inheritDoc Chain.getTokenPoolRemotes}
1218
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
1219
+ */
1059
1220
  async getTokenPoolRemotes(_tokenPool: string): Promise<never> {
1060
1221
  return Promise.reject(new CCIPNotImplementedError('getTokenPoolRemotes'))
1061
1222
  }
1062
1223
 
1063
- /** {@inheritDoc Chain.getFeeTokens} */
1224
+ /**
1225
+ * {@inheritDoc Chain.getFeeTokens}
1226
+ * @throws {@link CCIPNotImplementedError} always (not implemented for TON)
1227
+ */
1064
1228
  async getFeeTokens(_router: string): Promise<never> {
1065
1229
  return Promise.reject(new CCIPNotImplementedError('getFeeTokens'))
1066
1230
  }