@chainlink/ccip-cli 0.0.0 → 0.90.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/LICENSE +21 -0
- package/README.md +238 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/manual-exec.d.ts +56 -0
- package/dist/commands/manual-exec.d.ts.map +1 -0
- package/dist/commands/manual-exec.js +405 -0
- package/dist/commands/manual-exec.js.map +1 -0
- package/dist/commands/parse.d.ts +9 -0
- package/dist/commands/parse.d.ts.map +1 -0
- package/dist/commands/parse.js +47 -0
- package/dist/commands/parse.js.map +1 -0
- package/dist/commands/send.d.ts +80 -0
- package/dist/commands/send.d.ts.map +1 -0
- package/dist/commands/send.js +258 -0
- package/dist/commands/send.js.map +1 -0
- package/dist/commands/show.d.ts +18 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +112 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/commands/supported-tokens.d.ts +37 -0
- package/dist/commands/supported-tokens.d.ts.map +1 -0
- package/dist/commands/supported-tokens.js +214 -0
- package/dist/commands/supported-tokens.js.map +1 -0
- package/dist/commands/types.d.ts +7 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +6 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/utils.d.ts +40 -0
- package/dist/commands/utils.d.ts.map +1 -0
- package/dist/commands/utils.js +330 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/aptos.d.ts +15 -0
- package/dist/providers/aptos.d.ts.map +1 -0
- package/dist/providers/aptos.js +74 -0
- package/dist/providers/aptos.js.map +1 -0
- package/dist/providers/evm.d.ts +2 -0
- package/dist/providers/evm.d.ts.map +1 -0
- package/dist/providers/evm.js +42 -0
- package/dist/providers/evm.js.map +1 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +104 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/solana.d.ts +13 -0
- package/dist/providers/solana.d.ts.map +1 -0
- package/dist/providers/solana.js +79 -0
- package/dist/providers/solana.js.map +1 -0
- package/package.json +57 -8
- package/src/commands/index.ts +1 -0
- package/src/commands/manual-exec.ts +468 -0
- package/src/commands/parse.ts +52 -0
- package/src/commands/send.ts +316 -0
- package/src/commands/show.ts +151 -0
- package/src/commands/supported-tokens.ts +245 -0
- package/src/commands/types.ts +6 -0
- package/src/commands/utils.ts +404 -0
- package/src/index.ts +70 -0
- package/src/providers/aptos.ts +100 -0
- package/src/providers/evm.ts +48 -0
- package/src/providers/index.ts +141 -0
- package/src/providers/solana.ts +93 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type Chain,
|
|
5
|
+
type ChainGetter,
|
|
6
|
+
type ChainTransaction,
|
|
7
|
+
networkInfo,
|
|
8
|
+
supportedChains,
|
|
9
|
+
} from '@chainlink/ccip-sdk/src/index.ts'
|
|
10
|
+
|
|
11
|
+
import './aptos.ts'
|
|
12
|
+
import './evm.ts'
|
|
13
|
+
import './solana.ts'
|
|
14
|
+
|
|
15
|
+
const RPCS_RE = /\b(?:http|ws)s?:\/\/[\w/\\@&?%~#.,;:=+-]+/
|
|
16
|
+
|
|
17
|
+
async function collectEndpoints({
|
|
18
|
+
rpcs,
|
|
19
|
+
'rpcs-file': rpcsFile,
|
|
20
|
+
}: {
|
|
21
|
+
rpcs?: string[]
|
|
22
|
+
'rpcs-file'?: string
|
|
23
|
+
}): Promise<Set<string>> {
|
|
24
|
+
const endpoints = new Set<string>(rpcs || [])
|
|
25
|
+
for (const [env, val] of Object.entries(process.env)) {
|
|
26
|
+
if (env.startsWith('RPC_') && val && RPCS_RE.test(val)) endpoints.add(val)
|
|
27
|
+
}
|
|
28
|
+
if (rpcsFile) {
|
|
29
|
+
try {
|
|
30
|
+
const fileContent = await readFile(rpcsFile, 'utf8')
|
|
31
|
+
for (const line of fileContent.toString().split(/(?:\r\n|\r|\n)/g)) {
|
|
32
|
+
const match = line.match(RPCS_RE)
|
|
33
|
+
if (match) endpoints.add(match[0])
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.debug('Error reading RPCs file', error)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return endpoints
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function fetchChainsFromRpcs(
|
|
43
|
+
argv: { rpcs?: string[]; 'rpcs-file'?: string },
|
|
44
|
+
txHash?: undefined,
|
|
45
|
+
destroy?: Promise<unknown>,
|
|
46
|
+
): ChainGetter
|
|
47
|
+
export function fetchChainsFromRpcs(
|
|
48
|
+
argv: { rpcs?: string[]; 'rpcs-file'?: string },
|
|
49
|
+
txHash: string,
|
|
50
|
+
destroy?: Promise<unknown>,
|
|
51
|
+
): [ChainGetter, Promise<ChainTransaction>]
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Receives a list of rpcs and/or rpcs file, and loads them all concurrently
|
|
55
|
+
* Returns a ChainGetter function and optinoally a ChainTransaction promise
|
|
56
|
+
* @param argv - Options containing rpcs (list) and/or rpcs file
|
|
57
|
+
* @param txHash - Optional txHash to fetch concurrently; causes the function to return a [ChainGetter, Promise<ChainTransaction>]
|
|
58
|
+
* @param destroy - A promise to signal when to stop fetching chains
|
|
59
|
+
* @returns a ChainGetter (alone if no txHash was provided), or a tuple of [ChainGetter, Promise<ChainTransaction>]
|
|
60
|
+
*/
|
|
61
|
+
export function fetchChainsFromRpcs(
|
|
62
|
+
argv: { rpcs?: string[]; 'rpcs-file'?: string },
|
|
63
|
+
txHash?: string,
|
|
64
|
+
destroy?: Promise<unknown>,
|
|
65
|
+
) {
|
|
66
|
+
const chains: Record<string, Promise<Chain>> = {}
|
|
67
|
+
const chainsCbs: Record<
|
|
68
|
+
string,
|
|
69
|
+
readonly [resolve: (value: Chain) => void, reject: (reason?: unknown) => void]
|
|
70
|
+
> = {}
|
|
71
|
+
let finished = false
|
|
72
|
+
const txs: Promise<ChainTransaction>[] = []
|
|
73
|
+
|
|
74
|
+
const init$ = collectEndpoints(argv).then((endpoints) => {
|
|
75
|
+
const pendingPromises: Promise<unknown>[] = []
|
|
76
|
+
let txFound = false
|
|
77
|
+
for (const C of Object.values(supportedChains)) {
|
|
78
|
+
for (const url of endpoints) {
|
|
79
|
+
const chain$ = C.fromUrl(url)
|
|
80
|
+
if (txHash) {
|
|
81
|
+
const tx$ = chain$.then((chain) => chain.getTransaction(txHash))
|
|
82
|
+
void tx$.then(
|
|
83
|
+
({ chain }) => {
|
|
84
|
+
if (txFound) return
|
|
85
|
+
txFound = true
|
|
86
|
+
// in case tx is found, prefer it over any previously found chain
|
|
87
|
+
chains[chain.network.name] = chain$
|
|
88
|
+
delete chainsCbs[chain.network.name]
|
|
89
|
+
},
|
|
90
|
+
() => {},
|
|
91
|
+
)
|
|
92
|
+
txs.push(tx$)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pendingPromises.push(
|
|
96
|
+
chain$.then((chain) => {
|
|
97
|
+
if (chain.network.name in chains && !(chain.network.name in chainsCbs))
|
|
98
|
+
return chain.destroy?.() // lost race
|
|
99
|
+
void destroy?.finally(() => {
|
|
100
|
+
void chain.destroy?.() // cleanup
|
|
101
|
+
})
|
|
102
|
+
if (!(chain.network.name in chains)) {
|
|
103
|
+
chains[chain.network.name] = Promise.resolve(chain)
|
|
104
|
+
} else if (chain.network.name in chainsCbs) {
|
|
105
|
+
const [resolve] = chainsCbs[chain.network.name]
|
|
106
|
+
resolve(chain)
|
|
107
|
+
}
|
|
108
|
+
}),
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const res = Promise.allSettled(pendingPromises)
|
|
113
|
+
void (destroy ? Promise.race([res, destroy]) : res).finally(() => {
|
|
114
|
+
finished = true
|
|
115
|
+
Object.entries(chainsCbs).forEach(([name, [_, reject]]) =>
|
|
116
|
+
reject(new Error(`No provider/chain found for network=${name}`)),
|
|
117
|
+
)
|
|
118
|
+
})
|
|
119
|
+
return Promise.any(txs)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const chainGetter = async (idOrSelectorOrName: number | string | bigint): Promise<Chain> => {
|
|
123
|
+
const network = networkInfo(idOrSelectorOrName)
|
|
124
|
+
if (network.name in chains) return chains[network.name]
|
|
125
|
+
if (finished) throw new Error(`No provider/chain found for network=${network.name}`)
|
|
126
|
+
chains[network.name] = new Promise((resolve, reject) => {
|
|
127
|
+
chainsCbs[network.name] = [resolve, reject]
|
|
128
|
+
})
|
|
129
|
+
void chains[network.name].finally(() => {
|
|
130
|
+
delete chainsCbs[network.name]
|
|
131
|
+
})
|
|
132
|
+
return chains[network.name]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (txHash) {
|
|
136
|
+
return [chainGetter, init$]
|
|
137
|
+
} else {
|
|
138
|
+
void init$.catch(() => {})
|
|
139
|
+
return chainGetter
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
+
import util from 'node:util'
|
|
3
|
+
|
|
4
|
+
import { SolanaChain } from '@chainlink/ccip-sdk/src/index.ts'
|
|
5
|
+
import { Wallet as SolanaWallet } from '@coral-xyz/anchor'
|
|
6
|
+
import SolanaLedger from '@ledgerhq/hw-app-solana'
|
|
7
|
+
import HIDTransport from '@ledgerhq/hw-transport-node-hid'
|
|
8
|
+
import {
|
|
9
|
+
type Message,
|
|
10
|
+
type MessageV0,
|
|
11
|
+
type VersionedTransaction,
|
|
12
|
+
Keypair,
|
|
13
|
+
PublicKey,
|
|
14
|
+
Transaction,
|
|
15
|
+
} from '@solana/web3.js'
|
|
16
|
+
import bs58 from 'bs58'
|
|
17
|
+
import { getBytes, hexlify } from 'ethers'
|
|
18
|
+
|
|
19
|
+
export class LedgerSolanaWallet {
|
|
20
|
+
publicKey: PublicKey
|
|
21
|
+
wallet: SolanaLedger.default
|
|
22
|
+
path: string
|
|
23
|
+
|
|
24
|
+
private constructor(solanaLW: SolanaLedger.default, pubKey: PublicKey, path: string) {
|
|
25
|
+
this.wallet = solanaLW
|
|
26
|
+
this.publicKey = pubKey
|
|
27
|
+
this.path = path
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static async create(path: string) {
|
|
31
|
+
try {
|
|
32
|
+
const transport = await HIDTransport.default.create()
|
|
33
|
+
const solana = new SolanaLedger.default(transport)
|
|
34
|
+
const { address } = await solana.getAddress(path, false)
|
|
35
|
+
const pubkey = new PublicKey(address)
|
|
36
|
+
console.info('Ledger connected:', pubkey.toBase58(), `, derivationPath:`, path)
|
|
37
|
+
return new LedgerSolanaWallet(solana, pubkey, path)
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.error('Ledger: Could not access ledger. Is it unlocked and Solana app open?')
|
|
40
|
+
throw e
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async signTransaction<T extends Transaction | VersionedTransaction>(tx: T) {
|
|
45
|
+
console.debug('Ledger: Request to sign message from', this.publicKey.toBase58())
|
|
46
|
+
// serializeMessage on v0, serialize on v1
|
|
47
|
+
|
|
48
|
+
let msg: Message | MessageV0
|
|
49
|
+
if (tx instanceof Transaction) {
|
|
50
|
+
msg = tx.compileMessage()
|
|
51
|
+
} else {
|
|
52
|
+
msg = tx.message
|
|
53
|
+
}
|
|
54
|
+
const { signature } = await this.wallet.signTransaction(this.path, Buffer.from(msg.serialize()))
|
|
55
|
+
tx.addSignature(this.publicKey, signature)
|
|
56
|
+
return tx
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async signAllTransactions<T extends Transaction | VersionedTransaction>(txs: T[]) {
|
|
60
|
+
console.info('Signing multiple transactions with Ledger')
|
|
61
|
+
const signedTxs: T[] = []
|
|
62
|
+
for (const tx of txs) {
|
|
63
|
+
signedTxs.push(await this.signTransaction(tx))
|
|
64
|
+
}
|
|
65
|
+
return signedTxs
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get payer(): Keypair {
|
|
69
|
+
throw new Error('Payer method not available on Ledger')
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
SolanaChain.getWallet = async function loadSolanaWallet({
|
|
74
|
+
wallet: walletOpt,
|
|
75
|
+
}: { wallet?: unknown } = {}): Promise<SolanaWallet> {
|
|
76
|
+
if (!walletOpt)
|
|
77
|
+
walletOpt = process.env['USER_KEY'] || process.env['OWNER_KEY'] || '~/.config/solana/id.json'
|
|
78
|
+
let wallet: string
|
|
79
|
+
if (typeof walletOpt !== 'string')
|
|
80
|
+
throw new Error(`Invalid wallet option: ${util.inspect(walletOpt)}`)
|
|
81
|
+
wallet = walletOpt
|
|
82
|
+
if (walletOpt === 'ledger' || walletOpt.startsWith('ledger:')) {
|
|
83
|
+
let derivationPath = walletOpt.split(':')[1]
|
|
84
|
+
if (!derivationPath) derivationPath = "44'/501'/0'"
|
|
85
|
+
else if (!isNaN(Number(derivationPath))) derivationPath = `44'/501'/${derivationPath}'`
|
|
86
|
+
return (await LedgerSolanaWallet.create(derivationPath)) as SolanaWallet
|
|
87
|
+
} else if (existsSync(walletOpt)) {
|
|
88
|
+
wallet = hexlify(new Uint8Array(JSON.parse(readFileSync(walletOpt, 'utf8'))))
|
|
89
|
+
}
|
|
90
|
+
return new SolanaWallet(
|
|
91
|
+
Keypair.fromSecretKey(wallet.startsWith('0x') ? getBytes(wallet) : bs58.decode(wallet)),
|
|
92
|
+
)
|
|
93
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"rewriteRelativeImportExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"erasableSyntaxOnly": true,
|
|
16
|
+
"resolveJsonModule": true
|
|
17
|
+
}
|
|
18
|
+
}
|