@flarenetwork/flare-stake-tool
Version:
Utilities for staking on the Flare network
988 lines • 46.1 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.networkTokenSymbol = void 0;
exports.cli = cli;
exports.contextFromOptions = contextFromOptions;
exports.networkFromOptions = networkFromOptions;
exports.getOptions = getOptions;
exports.initCtxJsonFromOptions = initCtxJsonFromOptions;
exports.logAddressInfo = logAddressInfo;
exports.logBalanceInfo = logBalanceInfo;
exports.logNetworkInfo = logNetworkInfo;
exports.logValidatorInfo = logValidatorInfo;
exports.logMirrorFundInfo = logMirrorFundInfo;
const flarejs_1 = require("@flarenetwork/flarejs");
const context_1 = require("./context");
const utils_1 = require("./utils");
const evmTx_1 = require("./forDefi/evmTx");
const output_1 = require("./output");
const ledger = __importStar(require("./ledger"));
const flare = __importStar(require("./flare"));
const settings = __importStar(require("./settings"));
const chain_1 = require("./flare/chain");
const ethers_1 = require("ethers");
const bn_js_1 = require("bn.js");
const transaction_1 = require("./transaction");
const transaction_2 = require("./forDefi/transaction");
const contracts_1 = require("./contracts");
const BASE_DERIVATION_PATH = "m/44'/60'/0'/0/0"; // base derivation path for ledger
// mapping from network to symbol
exports.networkTokenSymbol = {
flare: "FLR",
songbird: "SGB",
costwo: "C2FLR",
coston: "CFLR",
localflare: "PHT",
};
function cli(program) {
// global configurations
program
.option("--network <network>", "Network name (flare|songbird|costwo|coston|localflare)")
.option("--ledger", "Use ledger to sign transactions")
.option("--blind", "Blind signing (used for ledger)", true)
.option("--derivation-path <derivation-path>", "Ledger address derivation path", BASE_DERIVATION_PATH)
.option("--ctx-file <file>", "Context file as returned by init-ctx", "ctx.json")
.option("--env-path <path>", "Path to the .env file")
.option("--get-hacked", "Use the .env file with the exposed private key");
// interactive mode
program
.command("interactive")
.description("Interactive mode")
.action(async () => {
// this will never run, here just for --help display
});
// context setup
program
.command("init-ctx")
.description("Initialize context file")
.option("-p, --public-key <public-key>", "Public key of the account")
.action(async (options) => {
options = getOptions(program, options);
await initCtxJsonFromOptions(options);
});
// information about the network
program
.command("info")
.description("Relevant information")
.argument("<addresses|balance|network|validators>", "Type of information")
.action(async (type) => {
const options = getOptions(program, program.opts());
const ctx = await contextFromOptions(options);
if (type === "addresses") {
logAddressInfo(ctx);
}
else if (type === "balance") {
await logBalanceInfo(ctx);
}
else if (type === "network") {
logNetworkInfo(ctx);
}
else if (type === "validators") {
await logValidatorInfo(ctx);
}
else if (type === "mirror") {
await logMirrorFundInfo(ctx);
}
else {
(0, output_1.logError)(`Unknown information type ${type}`);
}
});
// transaction construction and sending
program
.command("transaction")
.description("Move funds from one chain to another or to another P-chain address, stake, and delegate")
.argument("<importCP|exportCP|importPC|exportPC|delegate|stake>", "Type of a cross chain transaction")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("-a, --amount <amount>", "Amount to transfer")
.option("-f, --fee <fee>", "Transaction fee (in FLR)")
.option("-n, --node-id <nodeId>", "The id of the node to stake/delegate to")
.option("-s, --start-time <start-time>", "Start time of the staking/delegating process")
.option("-e, --end-time <end-time>", "End time of the staking/delegating process")
.option("--nonce <nonce>", "Nonce of the constructed transaction")
.option("--delegation-fee <delegation-fee>", "Delegation fee defined by the deployed validator", "10")
.option("--pop-bls-public-key <popBlsPublicKey>", "BLS Public Key")
.option("--pop-bls-signature <popBlsSignature>", "BLS Signature")
.option("--transfer-address <transfer-address>", "P-chain address to transfer funds to")
.option("--threshold <threshold>", "Threshold of the constructed transaction", "1")
.action(async (type, options) => {
options = getOptions(program, options);
const ctx = await contextFromOptions(options);
if (options.getHacked) {
// for future development: users should get notified before the program gets access to their private keys
await cliBuildAndSendTxUsingPrivateKey(type, ctx, options);
}
else if (options.ledger) {
await cliBuildAndSendTxUsingLedger(type, ctx, options, options.blind, options.derivationPath);
}
else {
await cliBuildAndSaveUnsignedTxJson(type, ctx, options.transactionId, options);
}
});
// signed transaction sending
program
.command("send")
.description("Send signed transaction json to the node")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to send to the network")
.action(async (options) => {
options = getOptions(program, options);
const ctx = await contextFromOptions(options);
await cliSendSignedTxJson(ctx, options.transactionId);
});
// ForDefi signing
program
.command("forDefi")
.description("Sign with ForDefi")
.argument("<sign|fetch>", "Type of a forDefi transaction")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("--evm-tx", "Regular EVM transaction")
.action(async (type, options) => {
options = getOptions(program, options);
if (typeof options.transactionId !== "string") {
throw new Error("Option --blind must be a string");
}
if (type === "sign") {
if (typeof options.ctxFile !== "string") {
throw new Error("Option --ctx-file must be a string");
}
if (options.evmTx) {
await signForDefi(options.transactionId, options.ctxFile, true);
}
else {
await signForDefi(options.transactionId, options.ctxFile);
}
}
else if (type === "fetch") {
if (options.evmTx) {
await fetchForDefiTx(options.transactionId, true);
}
else {
await fetchForDefiTx(options.transactionId);
}
}
});
// withdrawal (transfer) from C-chain (ForDefi)
program
.command("withdrawal")
.description("Withdraw funds from C-chain")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("-a, --amount <amount>", "Amount to transfer")
.option("-t, --to <to>", "Address to send funds to")
.option("--nonce <nonce>", "Nonce of the constructed transaction")
.action(async (options) => {
options = getOptions(program, options);
const ctx = await contextFromOptions(options);
await buildUnsignedWithdrawalTxJson(ctx, options.to, options.amount, options.transactionId, options.nonce);
});
// opt out (ForDefi)
program
.command("optOut")
.description("Opt out of airdrop on the c-chain")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("--nonce <nonce>", "Nonce of the constructed transaction")
.action(async (options) => {
options = getOptions(program, options);
const ctx = await contextFromOptions(options);
await buildUnsignedOptOutTxJson(ctx, options.transactionId, options.nonce);
});
// claim staking (ValidatorRewardManager) rewards
program
.command("claim")
.description("claim staking rewards")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("-n,--nonce <nonce>", "Nonce of the constructed transaction")
.option("-a, --amount <amount>", "Amount to claim")
.option("-r, --recipient <recipient>", "Address to send the rewards to")
.option("-w, --wrap", "Wrap the rewards", false)
.action(async (options) => {
options = getOptions(program, options);
await processClaimTx(options, options.transactionId, options.amount, options.recipient, options.wrap, options.nonce);
});
// set claim executor (ForDefi)
program
.command("setClaimExecutors")
.description("Set claim executors for claiming FSP rewards")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("--nonce <nonce>", "Nonce of the constructed transaction")
.option("--executors <executors...>", "Addresses of the executors to set (space separated); empty string to remove all executors")
.action(async (options) => {
options = getOptions(program, options);
const ctx = await contextFromOptions(options);
await buildUnsignedSetClaimExecutorsTxJson(ctx, options.transactionId, options.executors, options.nonce);
});
// set allowed claim recipients (ForDefi)
program
.command("setAllowedClaimRecipients")
.description("Set allowed claim recipients for claiming FSP rewards")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("--nonce <nonce>", "Nonce of the constructed transaction")
.option("--recipients <recipients...>", "Addresses of the allowed claim recipients (space separated)")
.action(async (options) => {
options = getOptions(program, options);
const ctx = await contextFromOptions(options);
await buildUnsignedSetAllowedClaimRecipientsTxJson(ctx, options.transactionId, options.recipients, options.nonce);
});
// custom c-chain transaction (ForDefi)
program
.command("customCChainTx")
.description("Custom C-chain transaction")
.option("-i, --transaction-id <transaction-id>", "Id of the transaction to finalize")
.option("--nonce <nonce>", "Nonce of the constructed transaction")
.option("--data <data>", "Data to send in the transaction", "")
.option("--to <to>", "Address to send the transaction to")
.option("--value <value>", "Value to send in the transaction", "0")
.action(async (options) => {
options = getOptions(program, options);
const ctx = await contextFromOptions(options);
await buildUnsignedCustomCChainTxJson(ctx, options.transactionId, options.data, options.to, options.value, options.nonce);
});
}
/**
* @description - returns context from the options that are passed
* @param options - option to define whether its from ledger/env/ctx.file
* @returns Returns the context based the source passed in the options
*/
async function contextFromOptions(options) {
if (options.ledger) {
(0, output_1.logInfo)("Fetching account from ledger...");
const publicKey = await ledger.getPublicKey(options.derivationPath, options.network);
const ctx = (0, context_1.getContext)(options.network, publicKey);
return ctx;
}
else if (options.envPath) {
return (0, context_1.contextEnv)(options.envPath, options.network);
}
else {
return (0, context_1.contextFile)(options.ctxFile);
}
}
// Network is obtained from context file, if it exists, else from --network flag.
// This is because ledger does not need a context file
/**
* @description Returns the network config from the options that were passed
* @param options - contains the options to derive the network
* @returns - network
*/
function networkFromOptions(options) {
let network = options.network;
if (network === undefined) {
try {
network = (0, context_1.networkFromContextFile)(options.ctxFile);
}
catch (e) {
network = "flare";
(0, output_1.logError)(`Error ${String(e)}. No network was passed and no context file was found. Defaulting to flare network`);
}
}
(0, output_1.logInfo)(`Using network: ${network}`);
return network;
}
/**
* @description - Returns the options for a command
* @param program - the command
* @param options - option available for the command
*/
function getOptions(program, options) {
const allOptions = { ...program.opts(), ...options };
const network = networkFromOptions(allOptions);
// amount and fee are given in FLR, transform into nanoFLR (FLR = 1e9 nanoFLR)
if (allOptions.amount) {
if (typeof allOptions.amount !== "string") {
throw new Error("Option --amount must be a string");
}
const cleanedAmount = allOptions.amount.replace(/,/g, "");
allOptions.amount = (0, utils_1.decimalToInteger)(cleanedAmount, 9).toString();
}
if (allOptions.fee) {
allOptions.fee = (0, utils_1.decimalToInteger)(allOptions.fee, 9);
}
return { ...allOptions, network };
}
//////////////////////////////////////////////////////////////////////////////////////////
// transaction-type translators
async function buildUnsignedTx(transactionType, ctx, params) {
if (!ctx.cAddressHex) {
throw new Error("C-chain address is not set in the context file");
}
if (!ctx.pAddressBech32) {
throw new Error("P-chain bech32 address is not set in the context file");
}
if (!ctx.cAddressBech32) {
throw new Error("C-chain bech32 address is not set in the context file");
}
const provider = new ethers_1.JsonRpcProvider(settings.URL[ctx.config.hrp] + "/ext/bc/C/rpc");
const evmapi = new flarejs_1.evm.EVMApi(settings.URL[ctx.config.hrp]);
const pvmapi = new flarejs_1.pvm.PVMApi(settings.URL[ctx.config.hrp]);
const infoapi = new flarejs_1.info.InfoApi(settings.URL[ctx.config.hrp]);
const context = await flarejs_1.Context.getContextFromURI(settings.URL[ctx.config.hrp]);
const txCount = await provider.getTransactionCount(ctx.cAddressHex);
const feeState = await pvmapi.getFeeState();
feeState.price = 1000n;
const { etnaTime } = await infoapi.getUpgradesInfo();
const isEtnaForkActive = new Date() > new Date(etnaTime);
function getChainIdFromContext(sourceChain, context) {
return sourceChain === "C"
? context.cBlockchainID
: sourceChain === "P"
? context.pBlockchainID
: context.xBlockchainID;
}
switch (transactionType) {
case "exportCP": {
if (!params.fee) {
throw new Error(`fee is required for exportCP transaction. Use --fee <fee> to specify the fee`);
}
if (!params.amount) {
throw new Error(`amount is required for exportCP transaction. Use --amount <amount> to specify the amount`);
}
const nonce = params.nonce ?? txCount;
const exportTx = flarejs_1.evm.newExportTx(context, BigInt(params.amount), context.pBlockchainID, flarejs_1.utils.hexToBuffer(ctx.cAddressHex), [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], BigInt(params.fee), BigInt(nonce));
return exportTx;
}
case "importCP": {
const { utxos } = await pvmapi.getUTXOs({
sourceChain: "C",
addresses: [ctx.pAddressBech32],
});
let importTx;
if (isEtnaForkActive) {
importTx = flarejs_1.pvm.e.newImportTx({
feeState,
fromAddressesBytes: [flarejs_1.utils.bech32ToBytes(ctx.cAddressBech32)],
sourceChainId: getChainIdFromContext("C", context),
toAddressesBytes: [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)],
utxos,
}, context);
}
else {
importTx = flarejs_1.pvm.newImportTx(context, getChainIdFromContext("C", context), utxos, [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], [flarejs_1.utils.bech32ToBytes(ctx.cAddressBech32)]);
}
return importTx;
}
case "exportPC": {
const { utxos } = await pvmapi.getUTXOs({
addresses: [ctx.pAddressBech32],
});
if (!params.amount) {
throw new Error(`amount is required for exportPC transaction. Use --amount <amount> to specify the amount`);
}
let exportTx;
if (isEtnaForkActive) {
exportTx = flarejs_1.pvm.e.newExportTx({
destinationChainId: getChainIdFromContext("C", context),
feeState,
fromAddressesBytes: [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)],
outputs: [
flarejs_1.TransferableOutput.fromNative(context.avaxAssetID, BigInt(params.amount), [
flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32),
]),
],
utxos,
}, context);
}
else {
exportTx = flarejs_1.pvm.newExportTx(context, getChainIdFromContext("C", context), [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], utxos, [
flarejs_1.TransferableOutput.fromNative(context.avaxAssetID, BigInt(params.amount), [
flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32),
]),
]);
}
return exportTx;
}
case "importPC": {
if (!params.fee) {
throw new Error(`fee is required for importPC transaction. Use --fee <fee> to specify the fee`);
}
const { utxos } = await evmapi.getUTXOs({
sourceChain: "P",
addresses: [ctx.cAddressBech32],
});
const exportTx = flarejs_1.evm.newImportTx(context, flarejs_1.utils.hexToBuffer(ctx.cAddressHex), [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], utxos, getChainIdFromContext("P", context), BigInt(params.fee));
return exportTx;
}
case "stake": {
if (!params.amount) {
throw new Error(`amount is required for stake transaction. Use --amount <amount> to specify the amount`);
}
if (!params.nodeId) {
throw new Error(`nodeId is required for stake transaction. Use --node-id <nodeId> to specify the node id`);
}
if (!params.endTime) {
throw new Error(`endTime is required for stake transaction. Use --end-time <endTime> to specify the end time`);
}
if (!params.popBlsPublicKey) {
throw new Error(`popBlsPublicKey is required for stake transaction. Use --pop-bls-public-key <popBlsPublicKey> to specify the BLS public key`);
}
if (!params.popBlsSignature) {
throw new Error(`popBlsSignature is required for stake transaction. Use --pop-bls-signature <popBlsSignature> to specify the BLS signature`);
}
const { utxos } = await pvmapi.getUTXOs({ addresses: [ctx.pAddressBech32] });
const start = BigInt((0, utils_1.adjustStartTimeForDefi)(params.startTime));
const end = BigInt(params.endTime);
const nodeId = params.nodeId;
const publicKey = flarejs_1.utils.hexToBuffer(params.popBlsPublicKey);
const signature = flarejs_1.utils.hexToBuffer(params.popBlsSignature);
let stakeTx;
if (isEtnaForkActive) {
stakeTx = flarejs_1.pvm.e.newAddPermissionlessValidatorTx({
end,
delegatorRewardsOwner: [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)],
feeState,
fromAddressesBytes: [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)],
nodeId,
publicKey,
rewardAddresses: [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)],
shares: Number(params.delegationFee) * 1e4, // default fee is 10%
signature,
start,
subnetId: flarejs_1.networkIDs.PrimaryNetworkID.toString(),
utxos,
weight: BigInt(params.amount),
}, context);
}
else {
stakeTx = flarejs_1.pvm.newAddPermissionlessValidatorTx(context, utxos, [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], nodeId, flarejs_1.networkIDs.PrimaryNetworkID.toString(), start, end, BigInt(params.amount), [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], Number(params.delegationFee) * 1e4, // default fee is 10%
undefined, 1, 0n, publicKey, signature);
}
return stakeTx;
}
case "delegate": {
if (!params.amount) {
throw new Error(`amount is required for stake transaction. Use --amount <amount> to specify the amount`);
}
if (!params.nodeId) {
throw new Error(`nodeId is required for stake transaction. Use --node-id <nodeId> to specify the node id`);
}
if (!params.endTime) {
throw new Error(`endTime is required for stake transaction. Use --end-time <endTime> to specify the end time`);
}
const { utxos } = await pvmapi.getUTXOs({ addresses: [ctx.pAddressBech32] });
const start = BigInt((0, utils_1.adjustStartTimeForDefi)(params.startTime));
const end = BigInt(params.endTime);
const nodeId = params.nodeId;
let delegateTx;
if (isEtnaForkActive) {
delegateTx = flarejs_1.pvm.e.newAddPermissionlessDelegatorTx({
end,
feeState,
fromAddressesBytes: [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)],
nodeId,
rewardAddresses: [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)],
start,
subnetId: flarejs_1.networkIDs.PrimaryNetworkID.toString(),
utxos,
weight: BigInt(params.amount),
}, context);
}
else {
delegateTx = flarejs_1.pvm.newAddPermissionlessDelegatorTx(context, utxos, [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)], nodeId, flarejs_1.networkIDs.PrimaryNetworkID.toString(), start, end, BigInt(params.amount), [flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32)]);
}
return delegateTx;
}
case "transfer": {
if (!params.amount) {
throw new Error(`amount is required for transfer transaction. Use --amount <amount> to specify the amount`);
}
if (!params.transferAddress) {
throw new Error(`transferAddress is required for transfer transaction. Use --transfer-address <address> to specify the address`);
}
const { utxos } = await pvmapi.getUTXOs({ addresses: [ctx.pAddressBech32] });
const senderPAddressBytes = flarejs_1.utils.bech32ToBytes(ctx.pAddressBech32);
const recipientPAddressBytes = flarejs_1.utils.bech32ToBytes(params.transferAddress);
let transferTx;
if (isEtnaForkActive) {
transferTx = flarejs_1.pvm.e.newBaseTx({
feeState,
fromAddressesBytes: [senderPAddressBytes],
outputs: [
flarejs_1.TransferableOutput.fromNative(context.avaxAssetID, BigInt(params.amount), [recipientPAddressBytes]),
],
utxos,
}, context);
}
else {
transferTx = flarejs_1.pvm.newBaseTx(context, [senderPAddressBytes], utxos, [
flarejs_1.TransferableOutput.fromNative(context.avaxAssetID, BigInt(params.amount), [recipientPAddressBytes]),
]);
}
return transferTx;
}
default:
throw new Error(`Unknown transaction type: ${transactionType}`);
}
}
async function sendSignedTxJson(ctx, signedTxJson) {
const unsignedTx = flarejs_1.UnsignedTx.fromJSON(signedTxJson.serialization);
const signature = Buffer.from(signedTxJson.signature, "hex");
unsignedTx.addSignature(signature);
const signedTx = unsignedTx.getSignedTx();
switch (signedTxJson.transactionType) {
case "exportCP":
case "importPC": {
const evmapi = new flarejs_1.evm.EVMApi(settings.URL[ctx.config.hrp]);
const resp = await evmapi.issueSignedTx(signedTx);
return resp.txID;
}
case "exportPC":
case "importCP":
case "stake":
case "delegate":
case "transfer": {
const pvmapi = new flarejs_1.pvm.PVMApi(settings.URL[ctx.config.hrp]);
const resp = await pvmapi.issueSignedTx(signedTx);
return resp.txID;
}
default:
throw new Error(`Unknown transaction type: ${signedTxJson.transactionType}`);
}
}
function getPublicKeyFromPair(keypair) {
const pk = Buffer.concat(keypair).toString("hex");
return pk;
}
async function buildAndSendTxUsingPrivateKey(transactionType, ctx, params) {
if (transactionType === "exportCP") {
return await (0, transaction_1.exportCP)(ctx, params);
}
else if (transactionType === "exportPC") {
return await (0, transaction_1.exportPC)(ctx, params);
}
else if (transactionType === "importCP") {
return await (0, transaction_1.importCP)(ctx, params);
}
else if (transactionType === "importPC") {
return await (0, transaction_1.importPC)(ctx, params);
}
else if (transactionType === "stake") {
return await (0, transaction_1.addValidator)(ctx, params);
}
else if (transactionType === "delegate") {
return await (0, transaction_1.addDelegator)(ctx, params);
}
else if (transactionType === "transfer") {
return await (0, transaction_1.internalTransfer)(ctx, params);
}
else {
throw new Error(`Unknown transaction type ${transactionType}`);
}
}
//////////////////////////////////////////////////////////////////////////////////////////
// initializing ctx.json
async function initCtxJsonFromOptions(options, derivationPath = BASE_DERIVATION_PATH) {
let ctxFile;
if (options.ledger) {
const publicKey = await ledger.getPublicKey(derivationPath, options.network);
const address = await ledger.verifyCAddress(derivationPath);
const ethAddress = (0, utils_1.publicKeyToEthereumAddressString)(publicKey);
ctxFile = {
wallet: "ledger",
publicKey,
ethAddress,
flareAddress: address,
network: options.network,
derivationPath,
};
}
else if (options.publicKey) {
if (!(0, utils_1.validatePublicKey)(options.publicKey))
return (0, output_1.logError)("Invalid public key");
ctxFile = {
wallet: "publicKey",
publicKey: options.publicKey,
network: options.network,
};
if (options.vaultId) {
ctxFile = {
...ctxFile,
vaultId: options.vaultId,
};
}
}
else {
throw new Error("Either --ledger or --public-key must be specified");
}
(0, utils_1.initCtxJson)(ctxFile);
(0, output_1.logSuccess)("Context file created");
}
//////////////////////////////////////////////////////////////////////////////////////////
// Network info
/**
* @description Logs the address info
* @param ctx - the context file aka ctx.json
* @returns Returns the address info
*/
function logAddressInfo(ctx) {
if (!ctx.publicKey) {
(0, output_1.logError)("Public key is not set in the context file");
return;
}
const [pubX, pubY] = ctx.publicKey;
const compressedPubKey = (0, utils_1.compressPublicKey)(pubX, pubY).toString("hex");
(0, output_1.logInfo)(`Addresses on the network "${ctx.config.hrp}"`);
(0, output_1.log)(`P-chain address: ${ctx.pAddressBech32}`);
(0, output_1.log)(`C-chain address hex: ${ctx.cAddressHex}`);
(0, output_1.log)(`secp256k1 public key: 0x${compressedPubKey}`);
}
/**
* @description Logs the balance info of the account
* @param ctx - the context file aka ctx.json
*/
async function logBalanceInfo(ctx) {
if (!ctx.cAddressHex) {
throw new Error("C-chain address is not set in the context file");
}
if (!ctx.pAddressBech32) {
throw new Error("P-chain bech32 address is not set in the context file");
}
let cbalance = (await ctx.web3.eth.getBalance(ctx.cAddressHex)).toString();
let pbalance = // NOTE: HRP seems the right string for this function
(await (0, chain_1.getPBalance)(ctx.config.hrp, ctx.pAddressBech32)).toString();
cbalance = (0, utils_1.integerToDecimal)(cbalance, 18);
pbalance = (0, utils_1.integerToDecimal)(pbalance, 9);
const symbol = exports.networkTokenSymbol[ctx.config.hrp];
(0, output_1.logInfo)(`Balances on the network "${ctx.config.hrp}"`);
(0, output_1.log)(`C-chain ${ctx.cAddressHex}: ${cbalance} ${symbol}`);
(0, output_1.log)(`P-chain ${ctx.pAddressBech32}: ${pbalance} ${symbol}`);
}
/**
* @description Logs info aboout P,C and asset id
* @param ctx - the context file
*/
function logNetworkInfo(ctx) {
// TODO: necessary?
//const pchainId = ctx.pchain.getBlockchainID();
//const cchainId = ctx.cchain.getBlockchainID();
(0, output_1.logInfo)(`Information about the network "${ctx.config.hrp}"`);
//log(`blockchainId for P-chain: ${ctx.config.chainID}`);
//log(`blockchainId for C-chain: ${ctx.config.chainID}`);
//log(`assetId: ${ctx.avaxAssetID}`);
}
/**
* @description Logs the validator information regrading current validators
* @param ctx - the context file
*/
async function logValidatorInfo(ctx) {
const pvmapi = new flarejs_1.pvm.PVMApi(settings.URL[ctx.config.hrp]);
const current = await pvmapi.getCurrentValidators();
//const current = await ctx.pchain.getCurrentValidators();
const fcurrent = JSON.stringify(current.validators, null, 2);
(0, output_1.logInfo)(`Validators on the network "${ctx.config.hrp}"`);
(0, output_1.log)(fcurrent);
}
/**
* @description Logs mirror fund details
* @param ctx - context
*/
async function logMirrorFundInfo(ctx) {
const mirroFundDetails = await (0, contracts_1.fetchMirrorFunds)(ctx);
(0, output_1.logInfo)(`Mirror fund details on the network "${ctx.config.hrp}"`);
(0, output_1.log)(JSON.stringify(mirroFundDetails, null, 2));
}
//////////////////////////////////////////////////////////////////////////////////////////
// Transaction building and execution
async function cliBuildAndSendTxUsingLedger(transactionType, ctx, params, _blind, _derivationPath) {
if (transactionType === "exportCP" || transactionType === "exportPC") {
(0, output_1.logInfo)("Creating export transaction...");
}
if (transactionType === "importCP" || transactionType === "importPC") {
(0, output_1.logInfo)("Creating import transaction...");
}
const sign = async (request) => {
if (request.isEvmTx) {
return ledger.signEvmTransaction(_derivationPath, request.unsignedTxHex);
}
else if (await ledger.onlyHashSign()) {
// ethereum or avalanche app
if (!request.unsignedTxHash) {
throw new Error("unsignedTxHash is required for blind signing with ethereum or avalanche app");
}
return ledger.signHash(_derivationPath, request.unsignedTxHash);
}
else {
try {
return await ledger.sign(_derivationPath, request.unsignedTxHex);
}
catch (e) {
if (typeof e === "object" &&
e &&
"errorMessage" in e &&
typeof e.errorMessage === "string" &&
e.errorMessage.includes("Data is invalid : Unrecognized error code")) {
(0, output_1.logInfo)(`Non-blind signing for transaction type ${transactionType} on network ${ctx.network} is not supported by the Flare app. Blind signing with signing only the hash will be used instead.`);
if (!request.unsignedTxHash) {
throw new Error("unsignedTxHash is required for ledger signing");
}
return ledger.signHash(_derivationPath, request.unsignedTxHash);
}
else {
throw new Error(`Ledger signing error: ${e instanceof Error ? e.message : String(e)}`);
}
}
}
};
if (!ctx.publicKey) {
throw new Error("Public key is not set in the context file");
}
if (transactionType === "exportCP") {
if (!params.amount) {
throw new Error(`amount is required for exportCP transaction. Use --amount <amount> to specify the amount`);
}
const amount = (0, utils_1.toBN)(params.amount);
if (!amount)
throw new Error(`Amount is invalid: ${params.amount}`);
const tp = {
amount: amount,
exportFee: (0, utils_1.toBN)(params.fee),
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey),
};
await flare.exportCP(tp, sign);
return;
}
else if (transactionType === "importCP") {
const tp = {
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey),
};
await flare.importCP(tp, sign);
return;
}
else if (transactionType === "exportPC") {
const tp = {
amount: (0, utils_1.toBN)(params.amount),
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey),
};
await flare.exportPC(tp, sign);
return;
}
else if (transactionType === "importPC") {
const tp = {
importFee: (0, utils_1.toBN)(params.fee),
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey),
};
await flare.importPC(tp, sign);
return;
}
else if (transactionType === "stake") {
(0, output_1.logInfo)("Creating stake transaction...");
if (!params.amount) {
throw new Error(`amount is required for stake transaction. Use --amount <amount> to specify the amount`);
}
if (!params.nodeId) {
throw new Error(`nodeId is required for stake transaction. Use --node-id <nodeId> to specify the node id`);
}
if (!params.endTime) {
throw new Error(`endTime is required for stake transaction. Use --end-time <endTime> to specify the end time`);
}
if (!params.popBlsPublicKey) {
throw new Error(`popBlsPublicKey is required for stake transaction. Use --pop-bls-public-key <popBlsPublicKey> to specify the BLS public key`);
}
if (!params.popBlsSignature) {
throw new Error(`popBlsSignature is required for stake transaction. Use --pop-bls-signature <popBlsSignature> to specify the BLS signature`);
}
const tp = {
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey),
delegationFee: Number(params.delegationFee) * 1e4, // default fee is 10%
nodeId: params.nodeId,
popBLSPublicKey: flarejs_1.utils.hexToBuffer(params.popBlsPublicKey),
popBLSSignature: flarejs_1.utils.hexToBuffer(params.popBlsSignature),
amount: new bn_js_1.BN(params.amount),
startTime: new bn_js_1.BN((0, utils_1.adjustStartTime)(params.startTime)),
endTime: new bn_js_1.BN(params.endTime),
// unnecessary?
useConsumableUTXOs: false,
customUTXOs: [],
};
await flare.addValidator(tp, sign, true);
return;
}
else if (transactionType === "delegate") {
(0, output_1.logInfo)("Creating delegate transaction...");
if (!params.amount) {
throw new Error(`amount is required for delegate transaction. Use --amount <amount> to specify the amount`);
}
if (!params.nodeId) {
throw new Error(`nodeId is required for delegate transaction. Use --node-id <nodeId> to specify the node id`);
}
if (!params.endTime) {
throw new Error(`endTime is required for delegate transaction. Use --end-time <endTime> to specify the end time`);
}
const tp = {
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey),
nodeId: params.nodeId,
amount: new bn_js_1.BN(params.amount),
startTime: new bn_js_1.BN((0, utils_1.adjustStartTime)(params.startTime)),
endTime: new bn_js_1.BN(params.endTime),
// unnecessary?
useConsumableUTXOs: false,
customUTXOs: [],
};
// let presubmit = null (): Promise<boolean> => new Promise(() => false)
await flare.addDelegator(tp, sign, true);
return;
}
else if (transactionType === "transfer") {
(0, output_1.logInfo)("Creating transfer transaction...");
if (!params.amount) {
throw new Error(`amount is required for transfer transaction. Use --amount <amount> to specify the amount`);
}
if (!params.transferAddress) {
throw new Error(`transferAddress is required for transfer transaction. Use --transfer-address <address> to specify the address`);
}
const tp = {
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey),
amount: params.amount,
recipientAddress: params.transferAddress,
};
await flare.internalTransfer(tp, sign);
return;
}
else {
throw new Error(`Unknown transaction type ${transactionType}`);
}
}
async function cliBuildAndSaveUnsignedTxJson(transactionType, ctx, id, params) {
const unsignedTx = await buildUnsignedTx(transactionType, ctx, params);
const txBuffer = Buffer.from(unsignedTx.toBytes()).toString("hex");
const txMessage = Buffer.from((0, flarejs_1.messageHashFromUnsignedTx)(unsignedTx)).toString("hex");
const unsignedTxJson = {
transactionType,
signatureRequests: [
{
message: txMessage,
signer: "",
},
],
unsignedTransactionBuffer: txBuffer,
serialization: JSON.stringify(unsignedTx.toJSON()),
};
const forDefiHash = (0, utils_1.saveUnsignedTxJson)(unsignedTxJson, id);
(0, output_1.logSuccess)(`Unsigned transaction ${id} constructed`);
(0, output_1.logSuccess)(`ForDefi hash: ${forDefiHash}`);
}
async function cliSendSignedTxJson(ctx, id) {
if ((0, utils_1.isAlreadySentToChain)(id)) {
throw new Error("Tx already sent to chain");
}
const signedTxnJson = (0, utils_1.readSignedTxJson)(id);
let chainTxId;
if (signedTxnJson.transactionType === "EVM") {
chainTxId = await (0, evmTx_1.sendSignedEvmTransaction)(ctx, id);
}
else {
chainTxId = await sendSignedTxJson(ctx, signedTxnJson);
}
(0, utils_1.addFlagForSentSignedTx)(id);
(0, output_1.logSuccess)(`Signed transaction ${id} with hash ${chainTxId} sent to the node`);
}
async function cliBuildAndSendTxUsingPrivateKey(transactionType, ctx, params) {
const { txid, usedFee } = await buildAndSendTxUsingPrivateKey(transactionType, ctx, params);
const symbol = exports.networkTokenSymbol[ctx.config.hrp];
if (usedFee)
(0, output_1.logInfo)(`Used fee of ${(0, utils_1.integerToDecimal)(usedFee, 9)} ${symbol}`);
(0, output_1.logSuccess)(`Transaction with hash ${txid} built and sent to the network`);
}
//////////////////////////////////////////////////////////////////////////////////////////
// ForDefi
async function signForDefi(transaction, ctx, evmTx = false) {
const txid = await (0, transaction_2.sendToForDefi)(transaction, ctx, evmTx);
(0, output_1.logSuccess)(`Transaction with hash ${txid} sent to the ForDefi`);
}
async function fetchForDefiTx(transaction, evmTx = false) {
if ((0, utils_1.isAlreadySentToChain)(transaction)) {
throw new Error("Tx already sent to chain");
}
const signature = await (0, transaction_2.getSignature)(transaction, evmTx);
(0, output_1.logSuccess)(`Success! Signature: ${signature}`);
}
async function buildUnsignedWithdrawalTxJson(ctx, to, amount, id, nonce) {
const forDefiHash = await (0, evmTx_1.createWithdrawalTransaction)(ctx, to, amount, id, nonce);
(0, output_1.logSuccess)(`Transaction ${id} constructed`);
(0, output_1.logSuccess)(`ForDefi hash: ${forDefiHash}`);
}
async function buildUnsignedOptOutTxJson(ctx, id, nonce) {
const forDefiHash = await (0, evmTx_1.createOptOutTransaction)(ctx, id, nonce);
(0, output_1.logSuccess)(`Transaction ${id} constructed`);
(0, output_1.logSuccess)(`ForDefi hash: ${forDefiHash}`);
}
async function processClaimTx(options, id, amount, recipient, wrap, nonce) {
const ctx = await contextFromOptions(options);
const rawTx = await (0, evmTx_1.createClaimTransaction)(ctx, amount, recipient, wrap, nonce);
if (options.getHacked) {
if (!ctx.cAddressHex || !ctx.privkHex) {
throw new Error("cAddressHex or private key is undefined or null");
}
const txId = await (0, evmTx_1.signEvmTransaction)("privateKey", ctx, rawTx);
(0, output_1.logSuccess)(`Transaction with hash ${txId} built and sent to the network`);
}
else if (options.ledger) {
const txId = await (0, evmTx_1.signEvmTransaction)("ledger", ctx, rawTx, options.derivationPath);
(0, output_1.logSuccess)(`Transaction with hash ${txId} built and sent to the network`);
}
else {
// ForDefi
const forDefiHash = (0, evmTx_1.saveUnsignedClaimTx)(rawTx, id);
(0, output_1.logSuccess)(`Transaction ${id} constructed`);
(0, output_1.logSuccess)(`ForDefi hash: ${forDefiHash}`);
}
}
async function buildUnsignedSetClaimExecutorsTxJson(ctx, id, executors, nonce) {
const forDefiHash = await (0, evmTx_1.createSetClaimExecutorsTransaction)(ctx, id, executors, nonce);
(0, output_1.logSuccess)(`Transaction ${id} constructed`);
(0, output_1.logSuccess)(`ForDefi hash: ${forDefiHash}`);
}
async function buildUnsignedSetAllowedClaimRecipientsTxJson(ctx, id, recipients, nonce) {
const forDefiHash = await (0, evmTx_1.createSetAllowedClaimRecipientsTransaction)(ctx, id, recipients, nonce);
(0, output_1.logSuccess)(`Transaction ${id} constructed`);
(0, output_1.logSuccess)(`ForDefi hash: ${forDefiHash}`);
}
async function buildUnsignedCustomCChainTxJson(ctx, id, data, to, value, nonce) {
const forDefiHash = await (0, evmTx_1.createCustomCChainTransaction)(ctx, id, to, data, value, nonce);
(0, output_1.logSuccess)(`Transaction ${id} constructed`);
(0, output_1.logSuccess)(`ForDefi hash: ${forDefiHash}`);
}
//# sourceMappingURL=cli.js.map