@chainlink/ccip-cli 0.91.0 → 0.92.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -55
- package/dist/commands/manual-exec.d.ts +7 -1
- package/dist/commands/manual-exec.d.ts.map +1 -1
- package/dist/commands/manual-exec.js +16 -7
- package/dist/commands/manual-exec.js.map +1 -1
- package/dist/commands/parse.d.ts.map +1 -1
- package/dist/commands/parse.js +6 -5
- package/dist/commands/parse.js.map +1 -1
- package/dist/commands/send.d.ts +6 -1
- package/dist/commands/send.d.ts.map +1 -1
- package/dist/commands/send.js +27 -22
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/show.d.ts +10 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +85 -27
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/supported-tokens.d.ts.map +1 -1
- package/dist/commands/supported-tokens.js +6 -6
- package/dist/commands/supported-tokens.js.map +1 -1
- package/dist/commands/types.d.ts +3 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/utils.d.ts +27 -7
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +97 -35
- package/dist/commands/utils.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/providers/aptos.d.ts.map +1 -1
- package/dist/providers/aptos.js +3 -5
- package/dist/providers/aptos.js.map +1 -1
- package/dist/providers/evm.d.ts.map +1 -1
- package/dist/providers/evm.js +2 -4
- package/dist/providers/evm.js.map +1 -1
- package/dist/providers/index.d.ts +6 -5
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +104 -71
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/solana.d.ts.map +1 -1
- package/dist/providers/solana.js +5 -4
- package/dist/providers/solana.js.map +1 -1
- package/dist/providers/sui.d.ts +10 -0
- package/dist/providers/sui.d.ts.map +1 -0
- package/dist/providers/sui.js +15 -0
- package/dist/providers/sui.js.map +1 -0
- package/dist/providers/ton.d.ts +10 -0
- package/dist/providers/ton.d.ts.map +1 -0
- package/dist/providers/ton.js +55 -0
- package/dist/providers/ton.js.map +1 -0
- package/package.json +13 -13
- package/src/commands/manual-exec.ts +18 -6
- package/src/commands/parse.ts +9 -5
- package/src/commands/send.ts +36 -28
- package/src/commands/show.ts +94 -31
- package/src/commands/supported-tokens.ts +6 -9
- package/src/commands/types.ts +3 -2
- package/src/commands/utils.ts +118 -43
- package/src/index.ts +13 -5
- package/src/providers/aptos.ts +3 -5
- package/src/providers/evm.ts +2 -4
- package/src/providers/index.ts +120 -94
- package/src/providers/solana.ts +5 -6
- package/src/providers/sui.ts +15 -0
- package/src/providers/ton.ts +68 -0
package/src/providers/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
1
2
|
import { readFile } from 'node:fs/promises'
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -5,6 +6,10 @@ import {
|
|
|
5
6
|
type ChainGetter,
|
|
6
7
|
type ChainTransaction,
|
|
7
8
|
type EVMChain,
|
|
9
|
+
CCIPChainFamilyUnsupportedError,
|
|
10
|
+
CCIPNetworkFamilyUnsupportedError,
|
|
11
|
+
CCIPRpcNotFoundError,
|
|
12
|
+
CCIPTransactionNotFoundError,
|
|
8
13
|
ChainFamily,
|
|
9
14
|
networkInfo,
|
|
10
15
|
supportedChains,
|
|
@@ -13,46 +18,26 @@ import {
|
|
|
13
18
|
import { loadAptosWallet } from './aptos.ts'
|
|
14
19
|
import { loadEvmWallet } from './evm.ts'
|
|
15
20
|
import { loadSolanaWallet } from './solana.ts'
|
|
21
|
+
import { loadSuiWallet } from './sui.ts'
|
|
22
|
+
import { loadTonWallet } from './ton.ts'
|
|
16
23
|
import type { Ctx } from '../commands/index.ts'
|
|
17
24
|
|
|
18
25
|
const RPCS_RE = /\b(?:http|ws)s?:\/\/[\w/\\@&?%~#.,;:=+-]+/
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
() =>
|
|
31
|
-
reject(
|
|
32
|
-
signal.reason instanceof Error
|
|
33
|
-
? signal.reason
|
|
34
|
-
: new Error(`Aborted: ${signal.reason as string}`),
|
|
35
|
-
),
|
|
36
|
-
{ once: true },
|
|
37
|
-
)
|
|
38
|
-
})),
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
return promise
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function collectEndpoints({
|
|
45
|
-
rpcs,
|
|
46
|
-
'rpcs-file': rpcsFile,
|
|
47
|
-
}: {
|
|
48
|
-
rpcs?: string[]
|
|
49
|
-
'rpcs-file'?: string
|
|
50
|
-
}): Promise<Set<string>> {
|
|
51
|
-
const endpoints = new Set<string>(rpcs || [])
|
|
27
|
+
async function collectEndpoints(
|
|
28
|
+
this: Ctx,
|
|
29
|
+
{ rpcs, rpcsFile }: { rpcs?: string[]; rpcsFile?: string },
|
|
30
|
+
): Promise<Set<string>> {
|
|
31
|
+
const endpoints = new Set<string>(
|
|
32
|
+
rpcs
|
|
33
|
+
?.map((s) => s.split(','))
|
|
34
|
+
.flat()
|
|
35
|
+
.map((s) => s.trim()) || [],
|
|
36
|
+
)
|
|
52
37
|
for (const [env, val] of Object.entries(process.env)) {
|
|
53
38
|
if (env.startsWith('RPC_') && val && RPCS_RE.test(val)) endpoints.add(val)
|
|
54
39
|
}
|
|
55
|
-
if (rpcsFile) {
|
|
40
|
+
if (rpcsFile && existsSync(rpcsFile)) {
|
|
56
41
|
try {
|
|
57
42
|
const fileContent = await readFile(rpcsFile, 'utf8')
|
|
58
43
|
for (const line of fileContent.toString().split(/(?:\r\n|\r|\n)/g)) {
|
|
@@ -60,7 +45,7 @@ async function collectEndpoints({
|
|
|
60
45
|
if (match) endpoints.add(match[0])
|
|
61
46
|
}
|
|
62
47
|
} catch (error) {
|
|
63
|
-
|
|
48
|
+
this.logger.debug('Error reading RPCs file', error)
|
|
64
49
|
}
|
|
65
50
|
}
|
|
66
51
|
return endpoints
|
|
@@ -68,126 +53,167 @@ async function collectEndpoints({
|
|
|
68
53
|
|
|
69
54
|
export function fetchChainsFromRpcs(
|
|
70
55
|
ctx: Ctx,
|
|
71
|
-
argv: { rpcs?: string[];
|
|
56
|
+
argv: { rpcs?: string[]; rpcsFile?: string },
|
|
72
57
|
): ChainGetter
|
|
73
58
|
export function fetchChainsFromRpcs(
|
|
74
59
|
ctx: Ctx,
|
|
75
|
-
argv: { rpcs?: string[];
|
|
60
|
+
argv: { rpcs?: string[]; rpcsFile?: string },
|
|
76
61
|
txHash: string,
|
|
77
62
|
): [ChainGetter, Promise<[Chain, ChainTransaction]>]
|
|
78
63
|
|
|
79
64
|
/**
|
|
80
|
-
* Receives a list of rpcs and/or rpcs file, and loads them
|
|
81
|
-
*
|
|
65
|
+
* Receives a list of rpcs and/or rpcs file, and loads them concurrently for each chain family
|
|
66
|
+
* If txHash is provided, fetches matching families first and returns [chainGetter, txPromise];
|
|
67
|
+
* Otherwise, spawns racing URLs for each family asked by `getChain` getter
|
|
82
68
|
* @param ctx - Context object containing destroy$ promise and logger properties
|
|
83
69
|
* @param argv - Options containing rpcs (list) and/or rpcs file
|
|
84
70
|
* @param txHash - Optional txHash to fetch concurrently; causes the function to return a [ChainGetter, Promise<ChainTransaction>]
|
|
85
|
-
* @returns a ChainGetter (
|
|
71
|
+
* @returns a ChainGetter (if txHash was provided), or a tuple of [ChainGetter, Promise<ChainTransaction>]
|
|
86
72
|
*/
|
|
87
73
|
export function fetchChainsFromRpcs(
|
|
88
74
|
ctx: Ctx,
|
|
89
|
-
argv: { rpcs?: string[];
|
|
75
|
+
argv: { rpcs?: string[]; rpcsFile?: string },
|
|
90
76
|
txHash?: string,
|
|
91
77
|
) {
|
|
92
|
-
const { destroy$ } = ctx
|
|
93
78
|
const chains: Record<string, Promise<Chain>> = {}
|
|
94
79
|
const chainsCbs: Record<
|
|
95
80
|
string,
|
|
96
81
|
readonly [resolve: (value: Chain) => void, reject: (reason?: unknown) => void]
|
|
97
82
|
> = {}
|
|
98
|
-
|
|
99
|
-
const
|
|
83
|
+
const finished: Partial<Record<ChainFamily, boolean>> = {}
|
|
84
|
+
const initFamily$: Partial<Record<ChainFamily, Promise<unknown>>> = {}
|
|
85
|
+
let endpoints$: Promise<Set<string>>
|
|
86
|
+
|
|
87
|
+
let txResolve: (value: [Chain, ChainTransaction]) => void, txReject: (reason?: unknown) => void
|
|
88
|
+
const txResult = new Promise<[Chain, ChainTransaction]>((resolve, reject) => {
|
|
89
|
+
txResolve = resolve
|
|
90
|
+
txReject = reject
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const loadChainFamily = (F: ChainFamily, txHash?: string) =>
|
|
94
|
+
(initFamily$[F] ??= (endpoints$ ??= collectEndpoints.call(ctx, argv)).then((endpoints) => {
|
|
95
|
+
const C = supportedChains[F]
|
|
96
|
+
if (!C) throw new CCIPNetworkFamilyUnsupportedError(F)
|
|
97
|
+
ctx.logger.debug('Racing', endpoints.size, 'RPC endpoints for', F)
|
|
100
98
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
for (const C of Object.values(supportedChains)) {
|
|
99
|
+
const chains$: Promise<Chain>[] = []
|
|
100
|
+
const txs$: Promise<unknown>[] = []
|
|
101
|
+
let txFound = false
|
|
105
102
|
for (const url of endpoints) {
|
|
106
103
|
const chain$ = C.fromUrl(url, ctx)
|
|
107
|
-
|
|
108
|
-
const tx$ = chain$.then((chain) =>
|
|
109
|
-
chain.getTransaction(txHash).then<[Chain, ChainTransaction]>((tx) => [chain, tx]),
|
|
110
|
-
)
|
|
111
|
-
void tx$.then(
|
|
112
|
-
([chain]) => {
|
|
113
|
-
if (txFound) return
|
|
114
|
-
txFound = true
|
|
115
|
-
// in case tx is found, prefer it over any previously found chain
|
|
116
|
-
chains[chain.network.name] = chain$
|
|
117
|
-
delete chainsCbs[chain.network.name]
|
|
118
|
-
},
|
|
119
|
-
() => {},
|
|
120
|
-
)
|
|
121
|
-
txs.push(tx$)
|
|
122
|
-
}
|
|
104
|
+
chains$.push(chain$)
|
|
123
105
|
|
|
124
|
-
|
|
125
|
-
|
|
106
|
+
void chain$.then(
|
|
107
|
+
(chain) => {
|
|
108
|
+
endpoints.delete(url) // when resolved, remove from set so it isn't tried for future families
|
|
109
|
+
// on chain detected for url
|
|
126
110
|
if (chain.network.name in chains && !(chain.network.name in chainsCbs))
|
|
127
|
-
return chain.destroy?.() // lost race
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
})
|
|
111
|
+
return chain.destroy?.() // but lost race, cleanup right away
|
|
112
|
+
// keep and schedule cleanup on shutdown
|
|
113
|
+
if (chain.destroy) void ctx.destroy$.finally(chain.destroy.bind(chain))
|
|
131
114
|
if (!(chain.network.name in chains)) {
|
|
115
|
+
// chain won for this network, but was not "asked" by getChain (yet?): save
|
|
132
116
|
chains[chain.network.name] = Promise.resolve(chain)
|
|
133
117
|
} else if (chain.network.name in chainsCbs) {
|
|
118
|
+
// chain detected, and there's a "pending request" by getChain: resolve
|
|
134
119
|
const [resolve] = chainsCbs[chain.network.name]
|
|
135
120
|
resolve(chain)
|
|
136
121
|
}
|
|
137
|
-
|
|
122
|
+
return chain
|
|
123
|
+
},
|
|
124
|
+
() => {},
|
|
138
125
|
)
|
|
126
|
+
|
|
127
|
+
if (txHash) {
|
|
128
|
+
txs$.push(
|
|
129
|
+
chain$.then(async (chain) => {
|
|
130
|
+
const tx = await chain.getTransaction(txHash)
|
|
131
|
+
if (!txFound) {
|
|
132
|
+
txFound = true
|
|
133
|
+
// in case tx is first found, prefer it over any previously found chain for this network
|
|
134
|
+
chains[chain.network.name] = chain$
|
|
135
|
+
delete chainsCbs[chain.network.name]
|
|
136
|
+
}
|
|
137
|
+
txResolve([chain, tx])
|
|
138
|
+
}),
|
|
139
|
+
)
|
|
140
|
+
}
|
|
139
141
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
142
|
+
|
|
143
|
+
void Promise.race([Promise.allSettled(chains$), ctx.destroy$]).finally(() => {
|
|
144
|
+
if (finished[F]) return
|
|
145
|
+
finished[F] = true
|
|
146
|
+
Object.entries(chainsCbs)
|
|
147
|
+
.filter(([name]) => networkInfo(name).family === F)
|
|
148
|
+
.forEach(([name, [_, reject]]) => reject(new CCIPRpcNotFoundError(name)))
|
|
149
|
+
})
|
|
150
|
+
return Promise.any(txHash ? txs$ : chains$)
|
|
151
|
+
}))
|
|
150
152
|
|
|
151
153
|
const chainGetter = async (idOrSelectorOrName: number | string | bigint): Promise<Chain> => {
|
|
152
154
|
const network = networkInfo(idOrSelectorOrName)
|
|
153
155
|
if (network.name in chains) return chains[network.name]
|
|
154
|
-
if (finished) throw new
|
|
156
|
+
if (finished[network.family]) throw new CCIPRpcNotFoundError(network.name)
|
|
155
157
|
chains[network.name] = new Promise((resolve, reject) => {
|
|
156
158
|
chainsCbs[network.name] = [resolve, reject]
|
|
157
159
|
})
|
|
158
160
|
void chains[network.name].finally(() => {
|
|
159
|
-
delete chainsCbs[network.name]
|
|
161
|
+
delete chainsCbs[network.name] // when chain is settled, delete the callbacks
|
|
160
162
|
})
|
|
163
|
+
void loadChainFamily(network.family)
|
|
161
164
|
return chains[network.name]
|
|
162
165
|
}
|
|
163
166
|
|
|
164
|
-
if (txHash)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
167
|
+
if (!txHash) return chainGetter
|
|
168
|
+
|
|
169
|
+
void Promise.allSettled(
|
|
170
|
+
Object.values(supportedChains)
|
|
171
|
+
.filter((C) => C.isTxHash(txHash))
|
|
172
|
+
.map((C) => loadChainFamily(C.family, txHash)),
|
|
173
|
+
).finally(() => txReject(new CCIPTransactionNotFoundError(txHash))) // noop if txResolved
|
|
174
|
+
return [chainGetter, txResult]
|
|
170
175
|
}
|
|
171
176
|
|
|
172
177
|
/**
|
|
173
178
|
* Load chain-specific wallet for given chain
|
|
174
179
|
* @param chain - Chain instance to load wallet for
|
|
175
|
-
* @param
|
|
180
|
+
* @param argv - Wallet options (as passed from yargs argv)
|
|
176
181
|
* @returns Promise to chain-specific wallet instance
|
|
177
182
|
*/
|
|
178
|
-
export async function loadChainWallet(chain: Chain,
|
|
183
|
+
export async function loadChainWallet(chain: Chain, argv: { wallet?: unknown; rpcsFile?: string }) {
|
|
184
|
+
// Centralized wallet resolution: check env vars first, then rpcsFile
|
|
185
|
+
if (!argv.wallet) {
|
|
186
|
+
argv.wallet = process.env['PRIVATE_KEY'] || process.env['USER_KEY'] || process.env['OWNER_KEY']
|
|
187
|
+
}
|
|
188
|
+
if (!argv.wallet && argv.rpcsFile && existsSync(argv.rpcsFile)) {
|
|
189
|
+
try {
|
|
190
|
+
const file = readFileSync(argv.rpcsFile, 'utf8')
|
|
191
|
+
const match = file.match(/^\s*(PRIVATE_KEY|USER_KEY|OWNER_KEY)=(\S+)/m)
|
|
192
|
+
if (match) argv.wallet = match[2]
|
|
193
|
+
} catch (_) {
|
|
194
|
+
// pass
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
179
198
|
let wallet
|
|
180
199
|
switch (chain.network.family) {
|
|
181
200
|
case ChainFamily.EVM:
|
|
182
|
-
wallet = await loadEvmWallet((chain as EVMChain).provider,
|
|
201
|
+
wallet = await loadEvmWallet((chain as EVMChain).provider, argv)
|
|
183
202
|
return [await wallet.getAddress(), wallet] as const
|
|
184
203
|
case ChainFamily.Solana:
|
|
185
|
-
wallet = await loadSolanaWallet(
|
|
204
|
+
wallet = await loadSolanaWallet(argv)
|
|
186
205
|
return [wallet.publicKey.toBase58(), wallet] as const
|
|
187
206
|
case ChainFamily.Aptos:
|
|
188
|
-
wallet = await loadAptosWallet(
|
|
207
|
+
wallet = await loadAptosWallet(argv)
|
|
189
208
|
return [wallet.accountAddress.toString(), wallet] as const
|
|
209
|
+
case ChainFamily.Sui:
|
|
210
|
+
wallet = loadSuiWallet(argv)
|
|
211
|
+
return [wallet.toSuiAddress(), wallet] as const
|
|
212
|
+
case ChainFamily.TON:
|
|
213
|
+
wallet = await loadTonWallet(argv)
|
|
214
|
+
return [wallet.contract.address.toString(), wallet] as const
|
|
190
215
|
default:
|
|
191
|
-
|
|
216
|
+
// TypeScript exhaustiveness check - this should never be reached
|
|
217
|
+
throw new CCIPChainFamilyUnsupportedError((chain.network as { family: string }).family)
|
|
192
218
|
}
|
|
193
219
|
}
|
package/src/providers/solana.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
-
import util from 'node:util'
|
|
3
2
|
|
|
3
|
+
import { CCIPArgumentInvalidError, CCIPNotImplementedError } from '@chainlink/ccip-sdk/src/index.ts'
|
|
4
4
|
import { Wallet as AnchorWallet } from '@coral-xyz/anchor'
|
|
5
5
|
import SolanaLedger from '@ledgerhq/hw-app-solana'
|
|
6
6
|
import HIDTransport from '@ledgerhq/hw-transport-node-hid'
|
|
@@ -86,7 +86,7 @@ export class LedgerSolanaWallet {
|
|
|
86
86
|
|
|
87
87
|
/** Payer property - not available on Ledger. */
|
|
88
88
|
get payer(): Keypair {
|
|
89
|
-
throw new
|
|
89
|
+
throw new CCIPNotImplementedError('payer for Ledger')
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
@@ -98,11 +98,10 @@ export class LedgerSolanaWallet {
|
|
|
98
98
|
export async function loadSolanaWallet({
|
|
99
99
|
wallet: walletOpt,
|
|
100
100
|
}: { wallet?: unknown } = {}): Promise<AnchorWallet> {
|
|
101
|
-
if
|
|
102
|
-
|
|
101
|
+
// Default to Solana's standard keypair location if no wallet provided
|
|
102
|
+
if (!walletOpt) walletOpt = '~/.config/solana/id.json'
|
|
103
103
|
let wallet: string
|
|
104
|
-
if (typeof walletOpt !== 'string')
|
|
105
|
-
throw new Error(`Invalid wallet option: ${util.inspect(walletOpt)}`)
|
|
104
|
+
if (typeof walletOpt !== 'string') throw new CCIPArgumentInvalidError('wallet', String(walletOpt))
|
|
106
105
|
wallet = walletOpt
|
|
107
106
|
if (walletOpt === 'ledger' || walletOpt.startsWith('ledger:')) {
|
|
108
107
|
let derivationPath = walletOpt.split(':')[1]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CCIPArgumentInvalidError } from '@chainlink/ccip-sdk/src/index.ts'
|
|
2
|
+
import { bytesToBuffer } from '@chainlink/ccip-sdk/src/utils.ts'
|
|
3
|
+
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Loads a Sui wallet from the provided options.
|
|
7
|
+
* @param wallet - wallet options (as passed from yargs argv)
|
|
8
|
+
* @returns Sui Keypair instance
|
|
9
|
+
*/
|
|
10
|
+
export function loadSuiWallet({ wallet: walletOpt }: { wallet?: unknown }) {
|
|
11
|
+
if (typeof walletOpt !== 'string') throw new CCIPArgumentInvalidError('wallet', String(walletOpt))
|
|
12
|
+
|
|
13
|
+
const keyBytes = bytesToBuffer(walletOpt)
|
|
14
|
+
return Ed25519Keypair.fromSecretKey(keyBytes)
|
|
15
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CCIPArgumentInvalidError,
|
|
5
|
+
CCIPWalletInvalidError,
|
|
6
|
+
} from '@chainlink/ccip-sdk/src/errors/specialized.ts'
|
|
7
|
+
import type { TONWallet } from '@chainlink/ccip-sdk/src/ton/types.ts'
|
|
8
|
+
import { keyPairFromSecretKey, mnemonicToPrivateKey } from '@ton/crypto'
|
|
9
|
+
import { WalletContractV4 } from '@ton/ton'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Loads a TON wallet from the provided options.
|
|
13
|
+
* @param wallet - wallet options (as passed from yargs argv)
|
|
14
|
+
* @returns Promise to TONWallet instance
|
|
15
|
+
*/
|
|
16
|
+
export async function loadTonWallet({
|
|
17
|
+
wallet: walletOpt,
|
|
18
|
+
}: { wallet?: unknown } = {}): Promise<TONWallet> {
|
|
19
|
+
if (typeof walletOpt !== 'string') throw new CCIPWalletInvalidError(walletOpt)
|
|
20
|
+
|
|
21
|
+
// Handle mnemonic phrase
|
|
22
|
+
if (walletOpt.includes(' ')) {
|
|
23
|
+
const mnemonic = walletOpt.trim().split(' ')
|
|
24
|
+
const keyPair = await mnemonicToPrivateKey(mnemonic)
|
|
25
|
+
const contract = WalletContractV4.create({
|
|
26
|
+
workchain: 0,
|
|
27
|
+
publicKey: keyPair.publicKey,
|
|
28
|
+
})
|
|
29
|
+
return { contract, keyPair }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Handle hex private key
|
|
33
|
+
if (walletOpt.startsWith('0x')) {
|
|
34
|
+
const secretKey = Buffer.from(walletOpt.slice(2), 'hex')
|
|
35
|
+
if (secretKey.length === 32) {
|
|
36
|
+
throw new CCIPArgumentInvalidError(
|
|
37
|
+
'wallet',
|
|
38
|
+
'32-byte seeds not supported. Use 64-byte secret key or mnemonic.',
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
if (secretKey.length !== 64) {
|
|
42
|
+
throw new CCIPArgumentInvalidError('wallet', 'must be 64 bytes (or use mnemonic)')
|
|
43
|
+
}
|
|
44
|
+
const keyPair = keyPairFromSecretKey(secretKey)
|
|
45
|
+
const contract = WalletContractV4.create({
|
|
46
|
+
workchain: 0,
|
|
47
|
+
publicKey: keyPair.publicKey,
|
|
48
|
+
})
|
|
49
|
+
return { contract, keyPair }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle file path
|
|
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)
|
|
60
|
+
const contract = WalletContractV4.create({
|
|
61
|
+
workchain: 0,
|
|
62
|
+
publicKey: keyPair.publicKey,
|
|
63
|
+
})
|
|
64
|
+
return { contract, keyPair }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new CCIPArgumentInvalidError('wallet', 'Wallet not specified')
|
|
68
|
+
}
|