@chainlink/ccip-sdk 0.94.0 → 0.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.d.ts +80 -4
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +262 -6
- package/dist/api/index.js.map +1 -1
- package/dist/api/types.d.ts +138 -13
- package/dist/api/types.d.ts.map +1 -1
- package/dist/aptos/index.d.ts +5 -9
- package/dist/aptos/index.d.ts.map +1 -1
- package/dist/aptos/index.js +18 -21
- package/dist/aptos/index.js.map +1 -1
- package/dist/aptos/logs.js +3 -3
- package/dist/aptos/logs.js.map +1 -1
- package/dist/chain.d.ts +84 -5
- package/dist/chain.d.ts.map +1 -1
- package/dist/chain.js +63 -2
- package/dist/chain.js.map +1 -1
- package/dist/errors/codes.d.ts +7 -3
- package/dist/errors/codes.d.ts.map +1 -1
- package/dist/errors/codes.js +8 -3
- package/dist/errors/codes.js.map +1 -1
- package/dist/errors/index.d.ts +7 -7
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +7 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/recovery.d.ts.map +1 -1
- package/dist/errors/recovery.js +8 -4
- package/dist/errors/recovery.js.map +1 -1
- package/dist/errors/specialized.d.ts +53 -18
- package/dist/errors/specialized.d.ts.map +1 -1
- package/dist/errors/specialized.js +112 -37
- package/dist/errors/specialized.js.map +1 -1
- package/dist/evm/gas.d.ts +14 -0
- package/dist/evm/gas.d.ts.map +1 -0
- package/dist/evm/gas.js +97 -0
- package/dist/evm/gas.js.map +1 -0
- package/dist/evm/index.d.ts +6 -8
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +23 -14
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/offchain.d.ts.map +1 -1
- package/dist/evm/offchain.js +8 -8
- package/dist/evm/offchain.js.map +1 -1
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +8 -1
- package/dist/execution.js.map +1 -1
- package/dist/gas.d.ts +43 -19
- package/dist/gas.d.ts.map +1 -1
- package/dist/gas.js +48 -68
- package/dist/gas.js.map +1 -1
- package/dist/index.d.ts +15 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/offchain.d.ts +5 -4
- package/dist/offchain.d.ts.map +1 -1
- package/dist/offchain.js +7 -6
- package/dist/offchain.js.map +1 -1
- package/dist/requests.d.ts +13 -11
- package/dist/requests.d.ts.map +1 -1
- package/dist/requests.js +69 -47
- package/dist/requests.js.map +1 -1
- package/dist/selectors.d.ts +2 -1
- package/dist/selectors.d.ts.map +1 -1
- package/dist/selectors.js +613 -278
- package/dist/selectors.js.map +1 -1
- package/dist/solana/exec.d.ts.map +1 -1
- package/dist/solana/exec.js +2 -1
- package/dist/solana/exec.js.map +1 -1
- package/dist/solana/index.d.ts +4 -8
- package/dist/solana/index.d.ts.map +1 -1
- package/dist/solana/index.js +20 -13
- package/dist/solana/index.js.map +1 -1
- package/dist/solana/offchain.js +2 -2
- package/dist/solana/offchain.js.map +1 -1
- package/dist/solana/send.d.ts.map +1 -1
- package/dist/solana/send.js +6 -9
- package/dist/solana/send.js.map +1 -1
- package/dist/solana/utils.d.ts +29 -1
- package/dist/solana/utils.d.ts.map +1 -1
- package/dist/solana/utils.js +39 -1
- package/dist/solana/utils.js.map +1 -1
- package/dist/sui/discovery.d.ts +7 -4
- package/dist/sui/discovery.d.ts.map +1 -1
- package/dist/sui/discovery.js +66 -19
- package/dist/sui/discovery.js.map +1 -1
- package/dist/sui/events.d.ts +23 -12
- package/dist/sui/events.d.ts.map +1 -1
- package/dist/sui/events.js +267 -128
- package/dist/sui/events.js.map +1 -1
- package/dist/sui/index.d.ts +20 -32
- package/dist/sui/index.d.ts.map +1 -1
- package/dist/sui/index.js +246 -148
- package/dist/sui/index.js.map +1 -1
- package/dist/sui/objects.d.ts +14 -4
- package/dist/sui/objects.d.ts.map +1 -1
- package/dist/sui/objects.js +61 -68
- package/dist/sui/objects.js.map +1 -1
- package/dist/sui/types.d.ts +33 -0
- package/dist/sui/types.d.ts.map +1 -1
- package/dist/sui/types.js.map +1 -1
- package/dist/ton/index.d.ts +4 -4
- package/dist/ton/index.d.ts.map +1 -1
- package/dist/ton/index.js +7 -8
- package/dist/ton/index.js.map +1 -1
- package/dist/ton/utils.d.ts +3 -3
- package/dist/ton/utils.d.ts.map +1 -1
- package/dist/ton/utils.js +6 -5
- package/dist/ton/utils.js.map +1 -1
- package/dist/types.d.ts +24 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +19 -5
- package/dist/types.js.map +1 -1
- package/dist/utils.d.ts +52 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +108 -12
- package/dist/utils.js.map +1 -1
- package/package.json +8 -8
- package/src/api/index.ts +343 -9
- package/src/api/types.ts +165 -13
- package/src/aptos/index.ts +19 -33
- package/src/aptos/logs.ts +3 -3
- package/src/chain.ts +139 -10
- package/src/errors/codes.ts +8 -3
- package/src/errors/index.ts +7 -4
- package/src/errors/recovery.ts +16 -5
- package/src/errors/specialized.ts +147 -45
- package/src/evm/gas.ts +149 -0
- package/src/evm/index.ts +47 -30
- package/src/evm/offchain.ts +15 -9
- package/src/execution.ts +8 -1
- package/src/gas.ts +95 -116
- package/src/index.ts +16 -6
- package/src/offchain.ts +12 -6
- package/src/requests.ts +100 -58
- package/src/selectors.ts +620 -280
- package/src/solana/exec.ts +3 -1
- package/src/solana/index.ts +26 -22
- package/src/solana/offchain.ts +2 -2
- package/src/solana/send.ts +5 -23
- package/src/solana/utils.ts +66 -0
- package/src/sui/discovery.ts +92 -31
- package/src/sui/events.ts +346 -239
- package/src/sui/index.ts +325 -201
- package/src/sui/objects.ts +74 -98
- package/src/sui/types.ts +35 -0
- package/src/ton/index.ts +10 -11
- package/src/ton/utils.ts +7 -6
- package/src/types.ts +25 -8
- package/src/utils.ts +151 -16
package/src/sui/objects.ts
CHANGED
|
@@ -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 =
|
|
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(
|
|
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
|
-
|
|
30
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
return normalizeSuiAddress('0x' + Buffer.from(hash).toString('hex'))
|
|
57
|
+
return hexlify(hash)
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
/**
|
|
57
|
-
*
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
*
|
|
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
|
|
105
|
-
client: SuiClient
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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,7 @@ 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 { type ChainContext, type GetBalanceOpts, type LogFilter, Chain } from '../chain.ts'
|
|
12
12
|
import {
|
|
13
13
|
CCIPArgumentInvalidError,
|
|
14
14
|
CCIPExtraArgsInvalidError,
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
CCIPWalletInvalidError,
|
|
22
22
|
} from '../errors/specialized.ts'
|
|
23
23
|
import { type EVMExtraArgsV2, type ExtraArgs, EVMExtraArgsV2Tag } from '../extra-args.ts'
|
|
24
|
-
import { getMessagesInTx } from '../requests.ts'
|
|
25
24
|
import { supportedChains } from '../supported-chains.ts'
|
|
26
25
|
import {
|
|
27
26
|
type CCIPExecution,
|
|
@@ -164,13 +163,13 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
164
163
|
ctx?: ChainContext & { fetchFn?: typeof fetch },
|
|
165
164
|
): Promise<TONChain> {
|
|
166
165
|
// Verify connection by getting the latest block
|
|
167
|
-
const
|
|
166
|
+
const isMainnet =
|
|
168
167
|
(
|
|
169
168
|
await client.getContractState(
|
|
170
169
|
Address.parse('EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs'), // mainnet USDT
|
|
171
170
|
)
|
|
172
|
-
).state
|
|
173
|
-
return new TONChain(client, networkInfo(
|
|
171
|
+
).state === 'active'
|
|
172
|
+
return new TONChain(client, networkInfo(isMainnet ? 'ton-mainnet' : 'ton-testnet'), ctx)
|
|
174
173
|
}
|
|
175
174
|
|
|
176
175
|
/**
|
|
@@ -264,7 +263,7 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
264
263
|
)
|
|
265
264
|
const txInfo = await lookupTxByRawHash(
|
|
266
265
|
cleanHash,
|
|
267
|
-
this.network.
|
|
266
|
+
this.network.networkType,
|
|
268
267
|
this.rateLimitedFetch,
|
|
269
268
|
this,
|
|
270
269
|
)
|
|
@@ -363,11 +362,6 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
363
362
|
}
|
|
364
363
|
}
|
|
365
364
|
|
|
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
365
|
/** {@inheritDoc Chain.getMessagesInBatch} */
|
|
372
366
|
override async getMessagesInBatch<
|
|
373
367
|
R extends PickDeep<
|
|
@@ -525,6 +519,11 @@ export class TONChain extends Chain<typeof ChainFamily.TON> {
|
|
|
525
519
|
}
|
|
526
520
|
}
|
|
527
521
|
|
|
522
|
+
/** {@inheritDoc Chain.getBalance} */
|
|
523
|
+
async getBalance(_opts: GetBalanceOpts): Promise<bigint> {
|
|
524
|
+
return Promise.reject(new CCIPNotImplementedError('TONChain.getBalance'))
|
|
525
|
+
}
|
|
526
|
+
|
|
528
527
|
/** {@inheritDoc Chain.getTokenAdminRegistryFor} */
|
|
529
528
|
getTokenAdminRegistryFor(_address: string): Promise<string> {
|
|
530
529
|
return Promise.reject(new CCIPNotImplementedError('getTokenAdminRegistryFor'))
|
package/src/ton/utils.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Cell, Dictionary, beginCell } from '@ton/core'
|
|
|
2
2
|
import { hexlify, toBeHex } from 'ethers'
|
|
3
3
|
|
|
4
4
|
import { CCIPTransactionNotFoundError } from '../errors/specialized.ts'
|
|
5
|
-
import type
|
|
5
|
+
import { type WithLogger, NetworkType } from '../types.ts'
|
|
6
6
|
import { bytesToBuffer } from '../utils.ts'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -340,14 +340,14 @@ export async function parseJettonContent(
|
|
|
340
340
|
* TonCenter V3 provides an index that allows hash-only lookups.
|
|
341
341
|
*
|
|
342
342
|
* @param hash - Raw 64-char hex transaction hash
|
|
343
|
-
* @param
|
|
343
|
+
* @param networkType - Network type (mainnet or testnet)
|
|
344
344
|
* @param fetch - Rate-limited fetch function
|
|
345
345
|
* @param logger - Logger instance
|
|
346
346
|
* @returns Transaction identifier components needed for V4 API lookup
|
|
347
347
|
*/
|
|
348
348
|
export async function lookupTxByRawHash(
|
|
349
349
|
hash: string,
|
|
350
|
-
|
|
350
|
+
networkType: NetworkType,
|
|
351
351
|
fetch = globalThis.fetch,
|
|
352
352
|
{ logger = console }: WithLogger = {},
|
|
353
353
|
): Promise<{
|
|
@@ -355,9 +355,10 @@ export async function lookupTxByRawHash(
|
|
|
355
355
|
lt: string
|
|
356
356
|
hash: string
|
|
357
357
|
}> {
|
|
358
|
-
const baseUrl =
|
|
359
|
-
|
|
360
|
-
|
|
358
|
+
const baseUrl =
|
|
359
|
+
networkType === NetworkType.Mainnet
|
|
360
|
+
? 'https://toncenter.com/api/v3/transactions'
|
|
361
|
+
: 'https://testnet.toncenter.com/api/v3/transactions'
|
|
361
362
|
|
|
362
363
|
// TonCenter V3 accepts hex directly
|
|
363
364
|
const cleanHash = bytesToBuffer(hash).toString('hex')
|
package/src/types.ts
CHANGED
|
@@ -60,15 +60,26 @@ export type MergeArrayElements<T, U> = {
|
|
|
60
60
|
* Enumeration of supported blockchain families.
|
|
61
61
|
*/
|
|
62
62
|
export const ChainFamily = {
|
|
63
|
-
EVM: '
|
|
64
|
-
Solana: '
|
|
65
|
-
Aptos: '
|
|
66
|
-
Sui: '
|
|
67
|
-
TON: '
|
|
63
|
+
EVM: 'EVM',
|
|
64
|
+
Solana: 'SVM',
|
|
65
|
+
Aptos: 'APTOS',
|
|
66
|
+
Sui: 'SUI',
|
|
67
|
+
TON: 'TON',
|
|
68
|
+
Unknown: 'UNKNOWN',
|
|
68
69
|
} as const
|
|
69
70
|
/** Type representing one of the supported chain families. */
|
|
70
71
|
export type ChainFamily = (typeof ChainFamily)[keyof typeof ChainFamily]
|
|
71
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Enumeration of network types (mainnet vs testnet).
|
|
75
|
+
*/
|
|
76
|
+
export const NetworkType = {
|
|
77
|
+
Mainnet: 'MAINNET',
|
|
78
|
+
Testnet: 'TESTNET',
|
|
79
|
+
} as const
|
|
80
|
+
/** Type representing the network environment type. */
|
|
81
|
+
export type NetworkType = (typeof NetworkType)[keyof typeof NetworkType]
|
|
82
|
+
|
|
72
83
|
/**
|
|
73
84
|
* Enumeration of supported CCIP protocol versions.
|
|
74
85
|
*/
|
|
@@ -88,7 +99,7 @@ type ChainFamilyWithId<F extends ChainFamily> = F extends
|
|
|
88
99
|
: F extends typeof ChainFamily.Solana
|
|
89
100
|
? { readonly family: F; readonly chainId: string }
|
|
90
101
|
: F extends typeof ChainFamily.Aptos | typeof ChainFamily.Sui
|
|
91
|
-
? { readonly family: F; readonly chainId: `${F}:${number}` }
|
|
102
|
+
? { readonly family: F; readonly chainId: `${Lowercase<F>}:${number}` }
|
|
92
103
|
: never
|
|
93
104
|
|
|
94
105
|
/**
|
|
@@ -99,8 +110,8 @@ export type NetworkInfo<F extends ChainFamily = ChainFamily> = {
|
|
|
99
110
|
readonly chainSelector: bigint
|
|
100
111
|
/** Human-readable network name. */
|
|
101
112
|
readonly name: string
|
|
102
|
-
/**
|
|
103
|
-
readonly
|
|
113
|
+
/** Network environment type. */
|
|
114
|
+
readonly networkType: NetworkType
|
|
104
115
|
} & ChainFamilyWithId<F>
|
|
105
116
|
|
|
106
117
|
/**
|
|
@@ -216,6 +227,12 @@ export const MessageStatus = {
|
|
|
216
227
|
Success: 'SUCCESS',
|
|
217
228
|
/** Message execution failed on destination. */
|
|
218
229
|
Failed: 'FAILED',
|
|
230
|
+
/** Message is being verified by the CCIP network */
|
|
231
|
+
Verifying: 'VERIFYING',
|
|
232
|
+
/** Message has been verified by the CCIP network */
|
|
233
|
+
Verified: 'VERIFIED',
|
|
234
|
+
/** Unknown status returned by API */
|
|
235
|
+
Unknown: 'UNKNOWN',
|
|
219
236
|
} as const
|
|
220
237
|
/** Type representing a CCIP message lifecycle status. */
|
|
221
238
|
export type MessageStatus = (typeof MessageStatus)[keyof typeof MessageStatus]
|
package/src/utils.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
toBigInt,
|
|
13
13
|
} from 'ethers'
|
|
14
14
|
import { memoize } from 'micro-memoize'
|
|
15
|
+
import yaml from 'yaml'
|
|
15
16
|
|
|
16
17
|
import type { Chain, ChainStatic } from './chain.ts'
|
|
17
18
|
import {
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
CCIPTypeVersionInvalidError,
|
|
25
26
|
HttpStatus,
|
|
26
27
|
} from './errors/index.ts'
|
|
28
|
+
import { getRetryDelay, shouldRetry } from './errors/utils.ts'
|
|
27
29
|
import SELECTORS from './selectors.ts'
|
|
28
30
|
import { supportedChains } from './supported-chains.ts'
|
|
29
31
|
import { type NetworkInfo, type WithLogger, ChainFamily } from './types.ts'
|
|
@@ -103,6 +105,9 @@ export async function getSomeBlockNumberBefore(
|
|
|
103
105
|
return beforeBlockNumber
|
|
104
106
|
}
|
|
105
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Checks if a chain is a testnet
|
|
110
|
+
*/
|
|
106
111
|
// memoized so we always output the same object for a given chainId
|
|
107
112
|
const networkInfoFromChainId = memoize((chainId: NetworkInfo['chainId']): NetworkInfo => {
|
|
108
113
|
const sel = SELECTORS[chainId]
|
|
@@ -112,7 +117,7 @@ const networkInfoFromChainId = memoize((chainId: NetworkInfo['chainId']): Networ
|
|
|
112
117
|
chainSelector: sel.selector,
|
|
113
118
|
name: sel.name,
|
|
114
119
|
family: sel.family,
|
|
115
|
-
|
|
120
|
+
networkType: sel.network_type,
|
|
116
121
|
} as NetworkInfo
|
|
117
122
|
})
|
|
118
123
|
|
|
@@ -220,13 +225,23 @@ export function bigIntReviver(_key: string, value: unknown): unknown {
|
|
|
220
225
|
return value
|
|
221
226
|
}
|
|
222
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Parses JSON text with BigInt support for large integers.
|
|
230
|
+
* Uses yaml parser which handles integers as BigInt when they exceed safe integer range.
|
|
231
|
+
* @param text - JSON string to parse
|
|
232
|
+
* @returns Parsed object with large integers as BigInt
|
|
233
|
+
*/
|
|
234
|
+
export function parseJson<T = unknown>(text: string): T {
|
|
235
|
+
return yaml.parse(text, { intAsBigInt: true }) as T
|
|
236
|
+
}
|
|
237
|
+
|
|
223
238
|
/**
|
|
224
239
|
* Decode address from a 32-byte hex string
|
|
225
240
|
**/
|
|
226
241
|
export function decodeAddress(address: BytesLike, family: ChainFamily = ChainFamily.EVM): string {
|
|
227
242
|
const chain = supportedChains[family]
|
|
228
243
|
if (!chain) throw new CCIPChainFamilyUnsupportedError(family)
|
|
229
|
-
return chain.getAddress(
|
|
244
|
+
return chain.getAddress(address)
|
|
230
245
|
}
|
|
231
246
|
|
|
232
247
|
/**
|
|
@@ -255,7 +270,7 @@ export function decodeOnRampAddress(
|
|
|
255
270
|
family: ChainFamily = ChainFamily.EVM,
|
|
256
271
|
): string {
|
|
257
272
|
let decoded = decodeAddress(address, family)
|
|
258
|
-
if (family === ChainFamily.Aptos) decoded += '::onramp'
|
|
273
|
+
if (family === ChainFamily.Aptos || family === ChainFamily.Sui) decoded += '::onramp'
|
|
259
274
|
return decoded
|
|
260
275
|
}
|
|
261
276
|
|
|
@@ -299,6 +314,8 @@ export function getDataBytes(data: BytesLike | readonly number[]): Uint8Array {
|
|
|
299
314
|
if (Array.isArray(data)) return new Uint8Array(data)
|
|
300
315
|
if (typeof data === 'string' && data.match(/^[0-9a-f]+[a-f][0-9a-f]+$/i)) data = '0x' + data
|
|
301
316
|
else if (typeof data === 'string' && data.match(/^0X[0-9a-fA-F]+$/)) data = data.toLowerCase()
|
|
317
|
+
if (typeof data === 'string' && data.startsWith('0x') && data.length % 2)
|
|
318
|
+
data = '0x0' + data.slice(2)
|
|
302
319
|
if (isBytesLike(data)) {
|
|
303
320
|
return getBytes(data)
|
|
304
321
|
} else if (isBase64(data)) {
|
|
@@ -322,20 +339,33 @@ export function bytesToBuffer(bytes: BytesLike | readonly number[]): Buffer {
|
|
|
322
339
|
* @param address - Address in hex or Base58 format.
|
|
323
340
|
* @returns Address bytes as Uint8Array.
|
|
324
341
|
*/
|
|
325
|
-
export function getAddressBytes(address: BytesLike): Uint8Array {
|
|
326
|
-
let bytes
|
|
327
|
-
if (
|
|
328
|
-
bytes =
|
|
342
|
+
export function getAddressBytes(address: BytesLike | readonly number[]): Uint8Array {
|
|
343
|
+
let bytes
|
|
344
|
+
if (address instanceof Uint8Array) {
|
|
345
|
+
bytes = address
|
|
346
|
+
} else if (Array.isArray(address)) {
|
|
347
|
+
bytes = new Uint8Array(address)
|
|
348
|
+
} else if (
|
|
349
|
+
typeof address === 'string' &&
|
|
350
|
+
address.match(/^((0x[0-9a-f]*)|[0-9a-f]{40,})(::.*)?$/i)
|
|
351
|
+
) {
|
|
352
|
+
address = address.split('::')[0]! // discard possible Aptos/Sui module suffix
|
|
353
|
+
// supports with or without (long>=20B) 0x-prefix, odd or even length
|
|
354
|
+
bytes = getBytes(
|
|
355
|
+
address.length % 2
|
|
356
|
+
? '0x0' + (address.toLowerCase().startsWith('0x') ? address.slice(2) : address)
|
|
357
|
+
: !address.toLowerCase().startsWith('0x')
|
|
358
|
+
? '0x' + address
|
|
359
|
+
: address,
|
|
360
|
+
)
|
|
329
361
|
} else {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
bytes.slice(-20).some((b) => b !== 0)
|
|
336
|
-
) {
|
|
337
|
-
bytes = bytes.slice(-20)
|
|
362
|
+
try {
|
|
363
|
+
const bytes_ = bs58.decode(address as string)
|
|
364
|
+
if (bytes_.length % 32 === 0) bytes = bytes_
|
|
365
|
+
} catch (_) {
|
|
366
|
+
// pass
|
|
338
367
|
}
|
|
368
|
+
if (!bytes) bytes = decodeBase64(address as string)
|
|
339
369
|
}
|
|
340
370
|
return bytes
|
|
341
371
|
}
|
|
@@ -356,7 +386,9 @@ export function convertKeysToCamelCase(
|
|
|
356
386
|
mapValues?: (value: unknown, key?: string) => unknown,
|
|
357
387
|
key?: string,
|
|
358
388
|
): unknown {
|
|
359
|
-
if (Array.isArray(obj)) {
|
|
389
|
+
if (Array.isArray(obj) && obj.every((v) => typeof v === 'number')) {
|
|
390
|
+
return mapValues ? mapValues(obj, key) : obj
|
|
391
|
+
} else if (Array.isArray(obj)) {
|
|
360
392
|
return obj.map((v) => convertKeysToCamelCase(v, mapValues, key))
|
|
361
393
|
}
|
|
362
394
|
|
|
@@ -379,6 +411,109 @@ export function convertKeysToCamelCase(
|
|
|
379
411
|
*/
|
|
380
412
|
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms).unref())
|
|
381
413
|
|
|
414
|
+
/**
|
|
415
|
+
* Configuration for the withRetry utility.
|
|
416
|
+
*/
|
|
417
|
+
export type WithRetryConfig = {
|
|
418
|
+
/** Maximum number of retry attempts */
|
|
419
|
+
maxRetries: number
|
|
420
|
+
/** Initial delay in milliseconds before the first retry */
|
|
421
|
+
initialDelayMs: number
|
|
422
|
+
/** Multiplier applied to delay after each retry */
|
|
423
|
+
backoffMultiplier: number
|
|
424
|
+
/** Maximum delay in milliseconds between retries */
|
|
425
|
+
maxDelayMs: number
|
|
426
|
+
/** Whether to respect the error's retryAfterMs hint */
|
|
427
|
+
respectRetryAfterHint: boolean
|
|
428
|
+
/** Optional logger for retry attempts */
|
|
429
|
+
logger?: { debug: (...args: unknown[]) => void; warn: (...args: unknown[]) => void }
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Executes an async operation with retry logic and exponential backoff.
|
|
434
|
+
* Only retries on transient errors (as determined by shouldRetry from errors/utils).
|
|
435
|
+
*
|
|
436
|
+
* @param operation - Async function to execute
|
|
437
|
+
* @param config - Retry configuration
|
|
438
|
+
* @returns Promise resolving to the operation result
|
|
439
|
+
* @throws The last error encountered after all retries are exhausted
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```typescript
|
|
443
|
+
* const result = await withRetry(
|
|
444
|
+
* () => apiClient.getMessageById(id),
|
|
445
|
+
* {
|
|
446
|
+
* maxRetries: 3,
|
|
447
|
+
* initialDelayMs: 1000,
|
|
448
|
+
* backoffMultiplier: 2,
|
|
449
|
+
* maxDelayMs: 30000,
|
|
450
|
+
* respectRetryAfterHint: true,
|
|
451
|
+
* }
|
|
452
|
+
* )
|
|
453
|
+
* ```
|
|
454
|
+
*/
|
|
455
|
+
export async function withRetry<T>(
|
|
456
|
+
operation: () => Promise<T>,
|
|
457
|
+
config: WithRetryConfig,
|
|
458
|
+
): Promise<T> {
|
|
459
|
+
const {
|
|
460
|
+
maxRetries,
|
|
461
|
+
initialDelayMs,
|
|
462
|
+
backoffMultiplier,
|
|
463
|
+
maxDelayMs,
|
|
464
|
+
respectRetryAfterHint,
|
|
465
|
+
logger,
|
|
466
|
+
} = config
|
|
467
|
+
|
|
468
|
+
let lastError: CCIPError | undefined
|
|
469
|
+
let delay = initialDelayMs
|
|
470
|
+
|
|
471
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
472
|
+
try {
|
|
473
|
+
return await operation()
|
|
474
|
+
} catch (err) {
|
|
475
|
+
lastError = CCIPError.isCCIPError(err) ? err : CCIPError.from(err, 'UNKNOWN')
|
|
476
|
+
|
|
477
|
+
// Only retry on transient errors
|
|
478
|
+
if (!shouldRetry(lastError)) {
|
|
479
|
+
throw lastError
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Don't sleep after the last attempt
|
|
483
|
+
if (attempt >= maxRetries) {
|
|
484
|
+
logger?.warn(`All ${maxRetries} retries exhausted:`, lastError.message)
|
|
485
|
+
break
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Calculate delay for next retry
|
|
489
|
+
let nextDelay = delay
|
|
490
|
+
|
|
491
|
+
// Respect error's retryAfterMs hint if configured
|
|
492
|
+
if (respectRetryAfterHint) {
|
|
493
|
+
const hintDelay = getRetryDelay(lastError)
|
|
494
|
+
if (hintDelay !== null) {
|
|
495
|
+
nextDelay = Math.max(delay, hintDelay)
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Cap at maxDelayMs
|
|
500
|
+
nextDelay = Math.min(nextDelay, maxDelayMs)
|
|
501
|
+
|
|
502
|
+
logger?.debug(
|
|
503
|
+
`Retry attempt ${attempt + 1}/${maxRetries} after ${nextDelay}ms:`,
|
|
504
|
+
lastError.message,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
await sleep(nextDelay)
|
|
508
|
+
|
|
509
|
+
// Apply exponential backoff for next iteration
|
|
510
|
+
delay = Math.min(delay * backoffMultiplier, maxDelayMs)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
throw lastError!
|
|
515
|
+
}
|
|
516
|
+
|
|
382
517
|
/**
|
|
383
518
|
* Parses a typeAndVersion string into its components.
|
|
384
519
|
* @param typeAndVersion - String in format "TypeName vX.Y.Z".
|