@availproject/cli
Version:
A simple CLI for Avail utilities
234 lines (233 loc) • 9.14 kB
JavaScript
import { spawn } from "node:child_process";
import { formatNumberToBalance, getKeyringFromSeed, initialize, isValidAddress, } from "avail-js-sdk";
import { Argument, Command, Option } from "commander";
var NetworkNames;
(function (NetworkNames) {
NetworkNames["Mainnet"] = "mainnet";
NetworkNames["Turing"] = "turing";
NetworkNames["Local"] = "local";
})(NetworkNames || (NetworkNames = {}));
var Wait;
(function (Wait) {
Wait["Yes"] = "yes";
Wait["No"] = "no";
Wait["Final"] = "final";
})(Wait || (Wait = {}));
const RPC_URLS = {
mainnet: "wss://mainnet-rpc.avail.so/ws",
turing: "wss://turing-rpc.avail.so/ws",
local: "wss://127.0.0.1:9944/ws",
};
const EXPLORER_URLS = {
mainnet: "https://explorer.availproject.org/?rpc=wss://mainnet-rpc.avail.so/ws&light=https://api.lightclient.mainnet.avail.so/v1",
turing: "https://explorer.availproject.org/?rpc=wss://turing-rpc.avail.so/ws&light=https://api.lightclient.turing.avail.so/v1",
local: "https://explorer.availproject.org/?rpc=wss://127.0.0.1:9944/ws",
};
const program = new Command();
program
.name("avail")
.description("A simple CLI for Avail network utilities")
.version("0.1.15");
const sendTransferTx = async (api, to, amount, keyring, opt, network, wait) => {
return await new Promise((resolve, reject) => {
api.tx.balances
.transfer(to, amount)
.signAndSend(keyring, opt, (result) => {
if (wait === Wait.Yes && result.status.isInBlock) {
console.log(`✅ Transfer included at block hash: ${String(result.status.asInBlock)}`);
if (typeof network !== "undefined") {
console.log(`🧭 Link to explorer: ${EXPLORER_URLS[network]}/#/explorer/query/${String(result.status.asInBlock)}`);
}
resolve();
}
else if (wait === Wait.Final && result.status.isFinalized) {
console.log(`✅ Transfer finalized at block hash: ${String(result.status.asFinalized)}`);
if (typeof network !== "undefined") {
console.log(`🧭 Link to explorer: https://${EXPLORER_URLS[network]}/#/explorer/query/${String(result.status.asFinalized)}`);
}
resolve();
}
})
.catch((error) => {
reject(new Error(`❌ Transaction failed: ${error}`));
});
});
};
const transfer = async (to, value, options) => {
try {
if (!isValidAddress(to))
throw new Error(`${to} recipient address is invalid`);
const seed = options.seed;
let rpcUrl;
if (typeof RPC_URLS[options.network] === "undefined") {
rpcUrl = options.rpc;
}
else {
rpcUrl = RPC_URLS[options.network];
}
const tempConsoleWarn = console.warn;
console.warn = () => { };
const api = await initialize(rpcUrl, { noInitWarn: true });
console.warn = tempConsoleWarn;
const keyring = getKeyringFromSeed(seed);
const amount = formatNumberToBalance(value);
const opt = { nonce: -1 };
if (options.wait !== Wait.No) {
await sendTransferTx(api, to, amount, keyring, opt, options.network, options.wait);
}
else {
await api.tx.balances.transfer(to, amount).signAndSend(keyring, opt);
}
console.log(`✅ ${value} AVL successfully sent to ${to}`);
process.exit(0);
}
catch (err) {
console.error(err);
process.exit(1);
}
};
const sendBlobTx = async (api, blob, keyring, opt, network, wait) => {
return await new Promise((resolve, reject) => {
api.tx.dataAvailability
.submitData(blob)
.signAndSend(keyring, opt, (result) => {
if (wait === Wait.Yes && result.status.isInBlock) {
console.log(`✅ Blob included at block hash: ${String(result.status.asInBlock)}`);
if (typeof network !== "undefined") {
console.log(`🧭 Link to explorer: ${EXPLORER_URLS[network]}/#/explorer/query/${String(result.status.asInBlock)}`);
}
resolve();
}
else if (wait === Wait.Final && result.status.isFinalized) {
console.log(`✅ Blob finalized at block hash: ${String(result.status.asFinalized)}`);
if (typeof network !== "undefined") {
console.log(`🧭 Link to explorer: ${EXPLORER_URLS[network]}/#/explorer/query/${String(result.status.asFinalized)}`);
}
resolve();
}
})
.catch((error) => {
reject(new Error(`❌ Transaction failed: ${error}`));
});
});
};
async function data(blob, options) {
try {
const seed = options.seed;
let rpcUrl;
if (typeof RPC_URLS[options.network] === "undefined") {
rpcUrl = options.rpc;
}
else {
rpcUrl = RPC_URLS[options.network];
}
const tempConsoleWarn = console.warn;
console.warn = () => { };
const api = await initialize(rpcUrl, { noInitWarn: true });
console.warn = tempConsoleWarn;
const keyring = getKeyringFromSeed(seed);
const opt = {
app_id: options.appId,
nonce: -1,
};
if (options.wait !== Wait.No) {
await sendBlobTx(api, blob, keyring, opt, options.network, options.wait);
}
else {
await api.tx.dataAvailability.submitData(blob).signAndSend(keyring, opt);
}
console.log("✅ Data blob sent to Avail");
process.exit(0);
}
catch (err) {
console.error(err);
process.exit(1);
}
}
const lc = async (options) => {
try {
let cmd = "curl -sL1 avail.sh | bash -s --";
if (typeof options.config !== "undefined") {
cmd = cmd.concat(` --config ${options.config}`);
}
else {
cmd = cmd.concat(` --network ${options.network}`);
}
if (typeof options.identity !== "undefined") {
cmd = cmd.concat(` --identity ${options.identity}`);
}
if (!options.noUpgrade) {
cmd = cmd.concat(" --upgrade y");
}
const child = spawn(cmd, {
cwd: process.cwd(),
shell: true,
stdio: "inherit",
});
child.on("close", (code) => {
process.exit(code);
});
child.on("exit", (code) => {
process.exit(code);
});
}
catch (err) {
console.error(err);
process.exit(1);
}
};
program
.command("transfer")
.description("Transfer AVL token to another account")
.addOption(new Option("-n, --network <network name>", "network name")
.choices(["mainnet", "turing", "local"])
.default("turing")
.conflicts("rpc"))
.addOption(new Option("-r, --rpc <RPC url>", "the RPC url to connect to")
.env("AVAIL_RPC_URL")
.default(RPC_URLS.turing))
.addOption(new Option("-s, --seed <seed phrase>", "the seed phrase for the Avail account")
.env("AVAIL_SEED")
.makeOptionMandatory())
.addOption(new Option("-w, --wait <status>", "wait for extrinsic inclusion")
.choices(["yes", "no", "final"])
.default("yes"))
.argument("<to>", "the recipient address")
.argument("<value>", "the amount of AVL (10e18 units) to transfer")
.action(transfer);
program
.command("data")
.description("Utilities to operate with data on Avail network")
.command("submit")
.description("Submit a data blob to an Avail network")
.addOption(new Option("-n, --network <network name>", "network name")
.choices(["mainnet", "turing", "local"])
.default("turing")
.conflicts("rpc"))
.addOption(new Option("-r, --rpc <RPC url>", "the RPC url to connect to")
.env("AVAIL_RPC_URL")
.default(RPC_URLS.turing))
.addOption(new Option("-s, --seed <seed phrase>", "the seed phrase for the Avail account")
.env("AVAIL_SEED")
.makeOptionMandatory())
.addOption(new Option("-a, --app-id <app ID>", "the blob will be submitted with this app ID").default(0))
.addOption(new Option("-w, --wait <status>", "wait for extrinsic inclusion")
.choices(["yes", "no", "final"])
.default("yes"))
.addArgument(new Argument("<blob>", "the data blob to submit"))
.action(data);
program
.command("lc")
.description("Utilities to operate an Avail light client")
.command("up")
.description("Spawns a new Avail light client or runs an existing one")
.addOption(new Option("-n, --network <network name>", "network name")
.choices(["mainnet", "turing", "local"])
.default("turing")
.makeOptionMandatory())
.option("-i, --identity <identity>", "the identity to use")
.option("-c, --config <path to config file>", "the config file to use")
.option("-nu, --no-upgrade", "do not upgrade the version of the light client")
.action(lc);
program.parse();