@chainlink/ccip-cli 0.92.0 → 0.93.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/commands/lane-latency.d.ts +26 -0
- package/dist/commands/lane-latency.d.ts.map +1 -0
- package/dist/commands/lane-latency.js +73 -0
- package/dist/commands/lane-latency.js.map +1 -0
- package/dist/commands/manual-exec.d.ts.map +1 -1
- package/dist/commands/manual-exec.js +20 -269
- package/dist/commands/manual-exec.js.map +1 -1
- package/dist/commands/send.d.ts +1 -1
- package/dist/commands/send.js +11 -2
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +36 -16
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/types.d.ts +1 -1
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +2 -1
- package/dist/commands/utils.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/aptos.js +1 -1
- package/dist/providers/aptos.js.map +1 -1
- package/dist/providers/evm.js +1 -1
- package/dist/providers/evm.js.map +1 -1
- package/dist/providers/index.d.ts +6 -1
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +11 -8
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/sui.d.ts.map +1 -1
- package/dist/providers/sui.js +1 -2
- package/dist/providers/sui.js.map +1 -1
- package/dist/providers/ton.d.ts +9 -3
- package/dist/providers/ton.d.ts.map +1 -1
- package/dist/providers/ton.js +100 -26
- package/dist/providers/ton.js.map +1 -1
- package/package.json +10 -6
- package/src/commands/lane-latency.ts +93 -0
- package/src/commands/manual-exec.ts +18 -267
- package/src/commands/send.ts +11 -8
- package/src/commands/show.ts +40 -22
- package/src/commands/types.ts +1 -1
- package/src/commands/utils.ts +6 -4
- package/src/index.ts +8 -4
- package/src/providers/aptos.ts +1 -1
- package/src/providers/evm.ts +1 -1
- package/src/providers/index.ts +18 -14
- package/src/providers/sui.ts +1 -2
- package/src/providers/ton.ts +106 -30
- package/tsconfig.json +3 -2
package/src/commands/send.ts
CHANGED
|
@@ -296,7 +296,11 @@ async function sendMessage(
|
|
|
296
296
|
}
|
|
297
297
|
|
|
298
298
|
// calculate fee
|
|
299
|
-
const fee = await source.getFee(
|
|
299
|
+
const fee = await source.getFee({
|
|
300
|
+
...argv,
|
|
301
|
+
destChainSelector: destNetwork.chainSelector,
|
|
302
|
+
message,
|
|
303
|
+
})
|
|
300
304
|
|
|
301
305
|
logger.info(
|
|
302
306
|
'Fee:',
|
|
@@ -310,12 +314,12 @@ async function sendMessage(
|
|
|
310
314
|
if (argv.onlyGetFee) return
|
|
311
315
|
|
|
312
316
|
if (!walletAddress) [walletAddress, wallet] = await loadChainWallet(source, argv)
|
|
313
|
-
const request = await source.sendMessage(
|
|
314
|
-
argv
|
|
315
|
-
destNetwork.chainSelector,
|
|
316
|
-
{ ...message, fee },
|
|
317
|
-
|
|
318
|
-
)
|
|
317
|
+
const request = await source.sendMessage({
|
|
318
|
+
...argv,
|
|
319
|
+
destChainSelector: destNetwork.chainSelector,
|
|
320
|
+
message: { ...message, fee },
|
|
321
|
+
wallet,
|
|
322
|
+
})
|
|
319
323
|
logger.info(
|
|
320
324
|
'🚀 Sending message to',
|
|
321
325
|
tokenReceiver && tokenReceiver !== '11111111111111111111111111111111'
|
|
@@ -328,7 +332,6 @@ async function sendMessage(
|
|
|
328
332
|
', messageId =>',
|
|
329
333
|
request.message.messageId,
|
|
330
334
|
)
|
|
331
|
-
|
|
332
335
|
await showRequests(ctx, {
|
|
333
336
|
...argv,
|
|
334
337
|
txHash: request.tx.hash,
|
package/src/commands/show.ts
CHANGED
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
type ChainTransaction,
|
|
4
4
|
CCIPExecTxRevertedError,
|
|
5
5
|
CCIPNotImplementedError,
|
|
6
|
+
ExecutionState,
|
|
7
|
+
MessageStatus,
|
|
6
8
|
bigIntReplacer,
|
|
7
9
|
discoverOffRamp,
|
|
8
10
|
isSupportedTxHash,
|
|
@@ -82,18 +84,18 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
|
|
|
82
84
|
getChain = fetchChainsFromRpcs(ctx, argv)
|
|
83
85
|
let idFromSource, onRamp
|
|
84
86
|
if (argv.idFromSource.includes('@')) {
|
|
85
|
-
;[onRamp, idFromSource] = argv.idFromSource.split('@')
|
|
87
|
+
;[onRamp, idFromSource] = argv.idFromSource.split('@') as [string, string]
|
|
86
88
|
} else idFromSource = argv.idFromSource
|
|
87
89
|
const sourceNetwork = networkInfo(idFromSource)
|
|
88
90
|
source = await getChain(sourceNetwork.chainId)
|
|
89
|
-
if (!source.
|
|
90
|
-
throw new CCIPNotImplementedError(`
|
|
91
|
-
request = await source.
|
|
91
|
+
if (!source.getMessageById)
|
|
92
|
+
throw new CCIPNotImplementedError(`getMessageById for ${source.constructor.name}`)
|
|
93
|
+
request = await source.getMessageById(argv.txHash, onRamp, argv)
|
|
92
94
|
} else {
|
|
93
95
|
const [getChain_, tx$] = fetchChainsFromRpcs(ctx, argv, argv.txHash)
|
|
94
96
|
getChain = getChain_
|
|
95
97
|
;[source, tx] = await tx$
|
|
96
|
-
request = await selectRequest(await source.
|
|
98
|
+
request = await selectRequest(await source.getMessagesInTx(tx), 'to know more', argv)
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
switch (argv.format) {
|
|
@@ -118,17 +120,16 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
|
|
|
118
120
|
let cancelWaitFinalized: (() => void) | undefined
|
|
119
121
|
const finalized$ = (async () => {
|
|
120
122
|
if (argv.wait) {
|
|
121
|
-
logger.info(
|
|
122
|
-
await source.waitFinalized(
|
|
123
|
+
logger.info(`[${MessageStatus.Sent}] Waiting for source chain finalization...`)
|
|
124
|
+
await source.waitFinalized({
|
|
123
125
|
request,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
)
|
|
127
|
-
logger.info(`Transaction "${request.log.transactionHash}" finalized ✅`)
|
|
126
|
+
cancel$: new Promise<void>((resolve) => (cancelWaitFinalized = resolve)),
|
|
127
|
+
})
|
|
128
|
+
logger.info(`[${MessageStatus.SourceFinalized}] Source chain finalized`)
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
const offchainTokenData = await source.
|
|
131
|
-
if (offchainTokenData
|
|
131
|
+
const offchainTokenData = await source.getOffchainTokenData(request)
|
|
132
|
+
if (offchainTokenData.length && offchainTokenData.some((d) => !!d)) {
|
|
132
133
|
switch (argv.format) {
|
|
133
134
|
case Format.log: {
|
|
134
135
|
logger.log('attestations =', offchainTokenData)
|
|
@@ -147,7 +148,8 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
|
|
|
147
148
|
}
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
if (argv.wait)
|
|
151
|
+
if (argv.wait)
|
|
152
|
+
logger.info(`[${MessageStatus.SourceFinalized}] Waiting for commit on destination chain...`)
|
|
151
153
|
else logger.info('Commit (dest):')
|
|
152
154
|
})()
|
|
153
155
|
|
|
@@ -157,13 +159,16 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
|
|
|
157
159
|
|
|
158
160
|
let cancelWaitCommit: (() => void) | undefined
|
|
159
161
|
const commit$ = (async () => {
|
|
160
|
-
const commit = await dest.
|
|
162
|
+
const commit = await dest.getCommitReport({
|
|
163
|
+
commitStore,
|
|
164
|
+
request,
|
|
161
165
|
...argv,
|
|
162
166
|
watch: argv.wait && new Promise<void>((resolve) => (cancelWaitCommit = resolve)),
|
|
163
167
|
})
|
|
164
168
|
cancelWaitFinalized?.()
|
|
165
|
-
if (!commit) return
|
|
166
169
|
await finalized$
|
|
170
|
+
if (argv.wait)
|
|
171
|
+
logger.info(`[${MessageStatus.Committed}] Commit report accepted on destination chain`)
|
|
167
172
|
switch (argv.format) {
|
|
168
173
|
case Format.log:
|
|
169
174
|
logger.log('commit =', commit)
|
|
@@ -175,20 +180,33 @@ export async function showRequests(ctx: Ctx, argv: Parameters<typeof handler>[0]
|
|
|
175
180
|
logger.info(JSON.stringify(commit, bigIntReplacer, 2))
|
|
176
181
|
break
|
|
177
182
|
}
|
|
178
|
-
if (argv.wait)
|
|
183
|
+
if (argv.wait)
|
|
184
|
+
logger.info(`[${MessageStatus.Blessed}] Waiting for execution on destination chain...`)
|
|
179
185
|
else logger.info('Receipts (dest):')
|
|
180
186
|
return commit
|
|
181
187
|
})()
|
|
182
188
|
|
|
183
189
|
let found = false
|
|
184
|
-
for await (const receipt of dest.
|
|
190
|
+
for await (const receipt of dest.getExecutionReceipts({
|
|
191
|
+
...argv,
|
|
185
192
|
offRamp,
|
|
186
|
-
request,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
messageId: request.message.messageId,
|
|
194
|
+
sourceChainSelector: request.message.sourceChainSelector,
|
|
195
|
+
startTime: request.tx.timestamp,
|
|
196
|
+
commit: !argv.wait ? await commit$ : undefined,
|
|
197
|
+
watch: argv.wait && ctx.destroy$,
|
|
198
|
+
})) {
|
|
190
199
|
cancelWaitCommit?.()
|
|
191
200
|
await commit$
|
|
201
|
+
const status =
|
|
202
|
+
receipt.receipt.state === ExecutionState.Success
|
|
203
|
+
? MessageStatus.Success
|
|
204
|
+
: MessageStatus.Failed
|
|
205
|
+
const statusMessage =
|
|
206
|
+
receipt.receipt.state === ExecutionState.Success
|
|
207
|
+
? 'Message executed on destination chain'
|
|
208
|
+
: 'Message execution failed on destination chain'
|
|
209
|
+
logger.info(`[${status}] ${statusMessage}`)
|
|
192
210
|
switch (argv.format) {
|
|
193
211
|
case Format.log:
|
|
194
212
|
logger.log('receipt =', withDateTimestamp(receipt))
|
package/src/commands/types.ts
CHANGED
package/src/commands/utils.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
CCIPError,
|
|
12
12
|
CCIPErrorCode,
|
|
13
13
|
ExecutionState,
|
|
14
|
+
getCCIPExplorerUrl,
|
|
14
15
|
networkInfo,
|
|
15
16
|
supportedChains,
|
|
16
17
|
} from '@chainlink/ccip-sdk/src/index.ts'
|
|
@@ -42,7 +43,7 @@ export async function selectRequest(
|
|
|
42
43
|
hints?: { logIndex?: number },
|
|
43
44
|
): Promise<CCIPRequest> {
|
|
44
45
|
if (hints?.logIndex != null) requests = requests.filter((req) => req.log.index === hints.logIndex)
|
|
45
|
-
if (requests.length === 1) return requests[0]
|
|
46
|
+
if (requests.length === 1) return requests[0]!
|
|
46
47
|
const answer = await select({
|
|
47
48
|
message: `${requests.length} messageIds found; select one${promptSuffix ? ' ' + promptSuffix : ''}`,
|
|
48
49
|
choices: [
|
|
@@ -66,7 +67,7 @@ tokenTransfers =\t[${req.message.tokenAmounts.map((ta) => ('token' in ta ? ta.to
|
|
|
66
67
|
],
|
|
67
68
|
})
|
|
68
69
|
if (answer < 0) throw new CCIPError(CCIPErrorCode.UNKNOWN, 'User requested exit')
|
|
69
|
-
return requests[answer]
|
|
70
|
+
return requests[answer]!
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
@@ -142,7 +143,7 @@ async function formatToken(
|
|
|
142
143
|
* @returns Record with indexed keys.
|
|
143
144
|
*/
|
|
144
145
|
export function formatArray<T>(name: string, values: readonly T[]): Record<string, T> {
|
|
145
|
-
if (values.length <= 1) return { [name]: values[0] }
|
|
146
|
+
if (values.length <= 1) return { [name]: values[0]! }
|
|
146
147
|
return Object.fromEntries(values.map((v, i) => [`${name}[${i}]`, v] as const))
|
|
147
148
|
}
|
|
148
149
|
|
|
@@ -296,6 +297,7 @@ export async function prettyRequest(this: Ctx, source: Chain, request: CCIPReque
|
|
|
296
297
|
...('accounts' in request.message ? formatArray('accounts', request.message.accounts) : {}),
|
|
297
298
|
...rest,
|
|
298
299
|
})
|
|
300
|
+
this.logger.info('CCIP Explorer:', getCCIPExplorerUrl('msg', request.message.messageId))
|
|
299
301
|
}
|
|
300
302
|
|
|
301
303
|
/**
|
|
@@ -521,7 +523,7 @@ export function logParsedError(this: Ctx, err: unknown): boolean {
|
|
|
521
523
|
export async function parseTokenAmounts(source: Chain, transferTokens: readonly string[]) {
|
|
522
524
|
return Promise.all(
|
|
523
525
|
transferTokens.map(async (tokenAmount) => {
|
|
524
|
-
const [token, amount_] = tokenAmount.split('=')
|
|
526
|
+
const [token, amount_] = tokenAmount.split('=') as [string, string]
|
|
525
527
|
const { decimals } = await source.getTokenInfo(token)
|
|
526
528
|
const amount = parseUnits(amount_, decimals)
|
|
527
529
|
return { token, amount }
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { Format } from './commands/index.ts'
|
|
|
11
11
|
util.inspect.defaultOptions.depth = 6 // print down to tokenAmounts in requests
|
|
12
12
|
// generate:nofail
|
|
13
13
|
// `const VERSION = '${require('./package.json').version}-${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}'`
|
|
14
|
-
const VERSION = '0.
|
|
14
|
+
const VERSION = '0.93.0-e6b317b'
|
|
15
15
|
// generate:end
|
|
16
16
|
|
|
17
17
|
const globalOpts = {
|
|
@@ -41,7 +41,11 @@ const globalOpts = {
|
|
|
41
41
|
page: {
|
|
42
42
|
type: 'number',
|
|
43
43
|
describe: 'getLogs page/range size',
|
|
44
|
-
|
|
44
|
+
},
|
|
45
|
+
'no-api': {
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
describe: 'Disable CCIP API integration (full decentralization mode)',
|
|
48
|
+
default: false,
|
|
45
49
|
},
|
|
46
50
|
} as const
|
|
47
51
|
|
|
@@ -66,12 +70,12 @@ async function main() {
|
|
|
66
70
|
}
|
|
67
71
|
|
|
68
72
|
function wasCalledAsScript() {
|
|
69
|
-
const realPath = realpathSync(process.argv[1])
|
|
73
|
+
const realPath = realpathSync(process.argv[1]!)
|
|
70
74
|
const realPathAsUrl = pathToFileURL(realPath).href
|
|
71
75
|
return import.meta.url === realPathAsUrl
|
|
72
76
|
}
|
|
73
77
|
|
|
74
|
-
if (import.meta
|
|
78
|
+
if (import.meta.main || wasCalledAsScript()) {
|
|
75
79
|
const later = setTimeout(() => {}, 2 ** 31 - 1) // keep event-loop alive
|
|
76
80
|
await main()
|
|
77
81
|
.catch((err) => {
|
package/src/providers/aptos.ts
CHANGED
|
@@ -99,7 +99,7 @@ export class AptosLedgerSigner /*implements AptosAsyncAccount*/ {
|
|
|
99
99
|
*/
|
|
100
100
|
export async function loadAptosWallet({ wallet: walletOpt }: { wallet?: unknown }) {
|
|
101
101
|
if (typeof walletOpt !== 'string') throw new CCIPArgumentInvalidError('wallet', String(walletOpt))
|
|
102
|
-
if (
|
|
102
|
+
if (walletOpt.startsWith('ledger')) {
|
|
103
103
|
let derivationPath = walletOpt.split(':')[1]
|
|
104
104
|
if (!derivationPath) derivationPath = "m/44'/637'/0'/0'/0'"
|
|
105
105
|
else if (!isNaN(Number(derivationPath))) derivationPath = `m/44'/637'/${derivationPath}'/0'/0'`
|
package/src/providers/evm.ts
CHANGED
|
@@ -43,7 +43,7 @@ export async function loadEvmWallet(
|
|
|
43
43
|
)
|
|
44
44
|
}
|
|
45
45
|
if (typeof walletOpt !== 'string') throw new CCIPArgumentInvalidError('wallet', String(walletOpt))
|
|
46
|
-
if (
|
|
46
|
+
if (walletOpt.startsWith('ledger')) {
|
|
47
47
|
let derivationPath = walletOpt.split(':')[1]
|
|
48
48
|
if (derivationPath && !isNaN(Number(derivationPath)))
|
|
49
49
|
derivationPath = `m/44'/60'/${derivationPath}'/0/0`
|
package/src/providers/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type ChainGetter,
|
|
7
7
|
type ChainTransaction,
|
|
8
8
|
type EVMChain,
|
|
9
|
+
type TONChain,
|
|
9
10
|
CCIPChainFamilyUnsupportedError,
|
|
10
11
|
CCIPNetworkFamilyUnsupportedError,
|
|
11
12
|
CCIPRpcNotFoundError,
|
|
@@ -53,11 +54,11 @@ async function collectEndpoints(
|
|
|
53
54
|
|
|
54
55
|
export function fetchChainsFromRpcs(
|
|
55
56
|
ctx: Ctx,
|
|
56
|
-
argv: { rpcs?: string[]; rpcsFile?: string },
|
|
57
|
+
argv: { rpcs?: string[]; rpcsFile?: string; noApi?: boolean },
|
|
57
58
|
): ChainGetter
|
|
58
59
|
export function fetchChainsFromRpcs(
|
|
59
60
|
ctx: Ctx,
|
|
60
|
-
argv: { rpcs?: string[]; rpcsFile?: string },
|
|
61
|
+
argv: { rpcs?: string[]; rpcsFile?: string; noApi?: boolean },
|
|
61
62
|
txHash: string,
|
|
62
63
|
): [ChainGetter, Promise<[Chain, ChainTransaction]>]
|
|
63
64
|
|
|
@@ -66,13 +67,13 @@ export function fetchChainsFromRpcs(
|
|
|
66
67
|
* If txHash is provided, fetches matching families first and returns [chainGetter, txPromise];
|
|
67
68
|
* Otherwise, spawns racing URLs for each family asked by `getChain` getter
|
|
68
69
|
* @param ctx - Context object containing destroy$ promise and logger properties
|
|
69
|
-
* @param argv - Options containing rpcs (list) and
|
|
70
|
+
* @param argv - Options containing rpcs (list), rpcs file and noApi flag
|
|
70
71
|
* @param txHash - Optional txHash to fetch concurrently; causes the function to return a [ChainGetter, Promise<ChainTransaction>]
|
|
71
72
|
* @returns a ChainGetter (if txHash was provided), or a tuple of [ChainGetter, Promise<ChainTransaction>]
|
|
72
73
|
*/
|
|
73
74
|
export function fetchChainsFromRpcs(
|
|
74
75
|
ctx: Ctx,
|
|
75
|
-
argv: { rpcs?: string[]; rpcsFile?: string },
|
|
76
|
+
argv: { rpcs?: string[]; rpcsFile?: string; noApi?: boolean },
|
|
76
77
|
txHash?: string,
|
|
77
78
|
) {
|
|
78
79
|
const chains: Record<string, Promise<Chain>> = {}
|
|
@@ -82,7 +83,7 @@ export function fetchChainsFromRpcs(
|
|
|
82
83
|
> = {}
|
|
83
84
|
const finished: Partial<Record<ChainFamily, boolean>> = {}
|
|
84
85
|
const initFamily$: Partial<Record<ChainFamily, Promise<unknown>>> = {}
|
|
85
|
-
let endpoints$: Promise<Set<string>>
|
|
86
|
+
let endpoints$: Promise<Set<string>> | undefined
|
|
86
87
|
|
|
87
88
|
let txResolve: (value: [Chain, ChainTransaction]) => void, txReject: (reason?: unknown) => void
|
|
88
89
|
const txResult = new Promise<[Chain, ChainTransaction]>((resolve, reject) => {
|
|
@@ -100,7 +101,10 @@ export function fetchChainsFromRpcs(
|
|
|
100
101
|
const txs$: Promise<unknown>[] = []
|
|
101
102
|
let txFound = false
|
|
102
103
|
for (const url of endpoints) {
|
|
103
|
-
const chain$ = C.fromUrl(url,
|
|
104
|
+
const chain$ = C.fromUrl(url, {
|
|
105
|
+
...ctx,
|
|
106
|
+
apiClient: argv.noApi ? null : undefined,
|
|
107
|
+
})
|
|
104
108
|
chains$.push(chain$)
|
|
105
109
|
|
|
106
110
|
void chain$.then(
|
|
@@ -116,7 +120,7 @@ export function fetchChainsFromRpcs(
|
|
|
116
120
|
chains[chain.network.name] = Promise.resolve(chain)
|
|
117
121
|
} else if (chain.network.name in chainsCbs) {
|
|
118
122
|
// chain detected, and there's a "pending request" by getChain: resolve
|
|
119
|
-
const [resolve] = chainsCbs[chain.network.name]
|
|
123
|
+
const [resolve] = chainsCbs[chain.network.name]!
|
|
120
124
|
resolve(chain)
|
|
121
125
|
}
|
|
122
126
|
return chain
|
|
@@ -152,16 +156,16 @@ export function fetchChainsFromRpcs(
|
|
|
152
156
|
|
|
153
157
|
const chainGetter = async (idOrSelectorOrName: number | string | bigint): Promise<Chain> => {
|
|
154
158
|
const network = networkInfo(idOrSelectorOrName)
|
|
155
|
-
if (network.name in chains) return chains[network.name]
|
|
159
|
+
if (network.name in chains) return chains[network.name]!
|
|
156
160
|
if (finished[network.family]) throw new CCIPRpcNotFoundError(network.name)
|
|
157
|
-
chains[network.name] = new Promise((resolve, reject) => {
|
|
161
|
+
const c = (chains[network.name] = new Promise((resolve, reject) => {
|
|
158
162
|
chainsCbs[network.name] = [resolve, reject]
|
|
159
|
-
})
|
|
160
|
-
void
|
|
163
|
+
}))
|
|
164
|
+
void c.finally(() => {
|
|
161
165
|
delete chainsCbs[network.name] // when chain is settled, delete the callbacks
|
|
162
166
|
})
|
|
163
167
|
void loadChainFamily(network.family)
|
|
164
|
-
return
|
|
168
|
+
return c
|
|
165
169
|
}
|
|
166
170
|
|
|
167
171
|
if (!txHash) return chainGetter
|
|
@@ -210,8 +214,8 @@ export async function loadChainWallet(chain: Chain, argv: { wallet?: unknown; rp
|
|
|
210
214
|
wallet = loadSuiWallet(argv)
|
|
211
215
|
return [wallet.toSuiAddress(), wallet] as const
|
|
212
216
|
case ChainFamily.TON:
|
|
213
|
-
wallet = await loadTonWallet(argv)
|
|
214
|
-
return [wallet.
|
|
217
|
+
wallet = await loadTonWallet((chain as TONChain).provider, argv, chain.network.isTestnet)
|
|
218
|
+
return [wallet.getAddress(), wallet] as const
|
|
215
219
|
default:
|
|
216
220
|
// TypeScript exhaustiveness check - this should never be reached
|
|
217
221
|
throw new CCIPChainFamilyUnsupportedError((chain.network as { family: string }).family)
|
package/src/providers/sui.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { CCIPArgumentInvalidError } from '@chainlink/ccip-sdk/src/index.ts'
|
|
2
|
-
import { bytesToBuffer } from '@chainlink/ccip-sdk/src/utils.ts'
|
|
1
|
+
import { CCIPArgumentInvalidError, bytesToBuffer } from '@chainlink/ccip-sdk/src/index.ts'
|
|
3
2
|
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
|
|
4
3
|
|
|
5
4
|
/**
|
package/src/providers/ton.ts
CHANGED
|
@@ -1,36 +1,106 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
type UnsignedTONTx,
|
|
4
5
|
CCIPArgumentInvalidError,
|
|
5
6
|
CCIPWalletInvalidError,
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
bytesToBuffer,
|
|
8
|
+
} from '@chainlink/ccip-sdk/src/index.ts'
|
|
9
|
+
import HIDTransport from '@ledgerhq/hw-transport-node-hid'
|
|
8
10
|
import { keyPairFromSecretKey, mnemonicToPrivateKey } from '@ton/crypto'
|
|
9
|
-
import { WalletContractV4 } from '@ton/ton'
|
|
11
|
+
import { type TonClient, Address, SendMode, WalletContractV4, internal, toNano } from '@ton/ton'
|
|
12
|
+
import { TonTransport } from '@ton-community/ton-ledger'
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Loads a TON wallet from the provided options.
|
|
16
|
+
* @param client - TON client instance
|
|
13
17
|
* @param wallet - wallet options (as passed from yargs argv)
|
|
18
|
+
* @param isTestnet - whether the wallet is on the testnet
|
|
14
19
|
* @returns Promise to TONWallet instance
|
|
15
20
|
*/
|
|
16
|
-
export async function loadTonWallet(
|
|
17
|
-
|
|
18
|
-
}: { wallet?: unknown } = {}
|
|
21
|
+
export async function loadTonWallet(
|
|
22
|
+
client: TonClient,
|
|
23
|
+
{ wallet: walletOpt }: { wallet?: unknown } = {},
|
|
24
|
+
isTestnet?: boolean,
|
|
25
|
+
) {
|
|
19
26
|
if (typeof walletOpt !== 'string') throw new CCIPWalletInvalidError(walletOpt)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
if (walletOpt === 'ledger' || walletOpt.startsWith('ledger:')) {
|
|
28
|
+
const transport = await HIDTransport.default.create()
|
|
29
|
+
const ton = new TonTransport(transport)
|
|
30
|
+
let derivationPath = walletOpt.split(':')[1]
|
|
31
|
+
if (!derivationPath) derivationPath = `44'/607'/${isTestnet ? '1' : '0'}'/0/0/0`
|
|
32
|
+
else if (!isNaN(Number(derivationPath)))
|
|
33
|
+
derivationPath = `44'/607'/${isTestnet ? '1' : '0'}'/0/${derivationPath}/0`
|
|
34
|
+
const match = derivationPath.match(
|
|
35
|
+
/^(?:m\/)?(\d+)'?\/(\d+)'?\/(\d+)'?\/(\d+)'?\/(\d+)'?\/(\d+)'?$/,
|
|
36
|
+
)
|
|
37
|
+
if (!match) throw new CCIPWalletInvalidError(walletOpt)
|
|
38
|
+
const path = match.slice(1).map((x) => parseInt(x))
|
|
39
|
+
const { address, publicKey } = await ton.getAddress(path, {
|
|
40
|
+
chain: 0,
|
|
41
|
+
bounceable: false,
|
|
42
|
+
testOnly: isTestnet,
|
|
43
|
+
})
|
|
44
|
+
console.info('Ledger TON:', address, ', derivationPath:', derivationPath)
|
|
25
45
|
const contract = WalletContractV4.create({
|
|
26
46
|
workchain: 0,
|
|
27
|
-
publicKey
|
|
47
|
+
publicKey,
|
|
28
48
|
})
|
|
29
|
-
|
|
49
|
+
const openedWallet = client.open(contract)
|
|
50
|
+
return {
|
|
51
|
+
getAddress: () => address,
|
|
52
|
+
sendTransaction: async ({ value, body, ...args }: UnsignedTONTx) => {
|
|
53
|
+
const seqno = await openedWallet.getSeqno()
|
|
54
|
+
const to = Address.parse(args.to)
|
|
55
|
+
if (!value) {
|
|
56
|
+
const { source_fees } = await client.estimateExternalMessageFee(to, {
|
|
57
|
+
ignoreSignature: true,
|
|
58
|
+
body,
|
|
59
|
+
initCode: null,
|
|
60
|
+
initData: null,
|
|
61
|
+
})
|
|
62
|
+
value =
|
|
63
|
+
BigInt(
|
|
64
|
+
source_fees.storage_fee +
|
|
65
|
+
source_fees.gas_fee +
|
|
66
|
+
source_fees.fwd_fee +
|
|
67
|
+
source_fees.in_fwd_fee,
|
|
68
|
+
) + toNano('0.0001') // buffer
|
|
69
|
+
}
|
|
70
|
+
const signed = await ton.signTransaction(path, {
|
|
71
|
+
seqno,
|
|
72
|
+
amount: value,
|
|
73
|
+
sendMode: SendMode.IGNORE_ERRORS | SendMode.PAY_GAS_SEPARATELY,
|
|
74
|
+
timeout: Math.floor(Date.now() / 1000 + 60),
|
|
75
|
+
bounce: false,
|
|
76
|
+
...args,
|
|
77
|
+
to,
|
|
78
|
+
payload: {
|
|
79
|
+
type: 'unsafe',
|
|
80
|
+
message: body,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
await openedWallet.send(signed)
|
|
84
|
+
return seqno
|
|
85
|
+
},
|
|
86
|
+
}
|
|
30
87
|
}
|
|
31
88
|
|
|
32
|
-
|
|
33
|
-
if (walletOpt
|
|
89
|
+
let keyPair
|
|
90
|
+
if (existsSync(walletOpt)) {
|
|
91
|
+
// Handle file path
|
|
92
|
+
const content = readFileSync(walletOpt, 'utf8').trim()
|
|
93
|
+
const secretKey = bytesToBuffer(content)
|
|
94
|
+
if (secretKey.length !== 64) {
|
|
95
|
+
throw new CCIPArgumentInvalidError('wallet', 'Invalid private key in file: must be 64 bytes')
|
|
96
|
+
}
|
|
97
|
+
keyPair = keyPairFromSecretKey(secretKey)
|
|
98
|
+
} else if (walletOpt.includes(' ')) {
|
|
99
|
+
// Handle mnemonic phrase
|
|
100
|
+
const mnemonic = walletOpt.trim().split(' ')
|
|
101
|
+
keyPair = await mnemonicToPrivateKey(mnemonic)
|
|
102
|
+
} else if (walletOpt.startsWith('0x')) {
|
|
103
|
+
// Handle hex private key
|
|
34
104
|
const secretKey = Buffer.from(walletOpt.slice(2), 'hex')
|
|
35
105
|
if (secretKey.length === 32) {
|
|
36
106
|
throw new CCIPArgumentInvalidError(
|
|
@@ -41,27 +111,33 @@ export async function loadTonWallet({
|
|
|
41
111
|
if (secretKey.length !== 64) {
|
|
42
112
|
throw new CCIPArgumentInvalidError('wallet', 'must be 64 bytes (or use mnemonic)')
|
|
43
113
|
}
|
|
44
|
-
|
|
45
|
-
const contract = WalletContractV4.create({
|
|
46
|
-
workchain: 0,
|
|
47
|
-
publicKey: keyPair.publicKey,
|
|
48
|
-
})
|
|
49
|
-
return { contract, keyPair }
|
|
114
|
+
keyPair = keyPairFromSecretKey(secretKey)
|
|
50
115
|
}
|
|
51
116
|
|
|
52
|
-
|
|
53
|
-
if (existsSync(walletOpt)) {
|
|
54
|
-
const content = readFileSync(walletOpt, 'utf8').trim()
|
|
55
|
-
const secretKey = Buffer.from(content.startsWith('0x') ? content.slice(2) : content, 'hex')
|
|
56
|
-
if (secretKey.length !== 64) {
|
|
57
|
-
throw new CCIPArgumentInvalidError('wallet', 'Invalid private key in file: must be 64 bytes')
|
|
58
|
-
}
|
|
59
|
-
const keyPair = keyPairFromSecretKey(secretKey)
|
|
117
|
+
if (keyPair) {
|
|
60
118
|
const contract = WalletContractV4.create({
|
|
61
119
|
workchain: 0,
|
|
62
120
|
publicKey: keyPair.publicKey,
|
|
63
121
|
})
|
|
64
|
-
|
|
122
|
+
const openedWallet = client.open(contract)
|
|
123
|
+
return {
|
|
124
|
+
getAddress: () => contract.address.toString(),
|
|
125
|
+
sendTransaction: async (args: UnsignedTONTx) => {
|
|
126
|
+
const seqno = await openedWallet.getSeqno()
|
|
127
|
+
const signed = await openedWallet.createTransfer({
|
|
128
|
+
...keyPair,
|
|
129
|
+
seqno,
|
|
130
|
+
messages: [
|
|
131
|
+
internal({
|
|
132
|
+
value: toNano('0.3'), // TODO: FIXME: estimate proper value for execution costs instead of hardcoding.
|
|
133
|
+
...args,
|
|
134
|
+
}),
|
|
135
|
+
],
|
|
136
|
+
})
|
|
137
|
+
await openedWallet.send(signed)
|
|
138
|
+
return seqno
|
|
139
|
+
},
|
|
140
|
+
}
|
|
65
141
|
}
|
|
66
142
|
|
|
67
143
|
throw new CCIPArgumentInvalidError('wallet', 'Wallet not specified')
|