@flarenetwork/flare-stake-tool
Version:
Utilities for staking on the Flare network
908 lines • 42.8 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 ${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 context = await flarejs_1.Context.getContextFromURI(settings.URL[ctx.config.hrp]);
const txCount = await provider.getTransactionCount(ctx.cAddressHex);
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]
});
const 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`);
}
const 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 blsPublicKey = flarejs_1.utils.hexToBuffer(params.popBlsPublicKey);
const blsSignature = flarejs_1.utils.hexToBuffer(params.popBlsSignature);
const 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, blsPublicKey, blsSignature);
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;
const 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);
const 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 and pending validators
* @param ctx - the context file
*/
async function logValidatorInfo(ctx) {
const pvmapi = new flarejs_1.pvm.PVMApi(settings.URL[ctx.config.hrp]);
const pending = await pvmapi.getPendingValidators();
const current = await pvmapi.getCurrentValidators();
//const pending = await ctx.pchain.getPendingValidators();
//const current = await ctx.pchain.getCurrentValidators();
const fpending = JSON.stringify(pending.validators, null, 2);
const fpendingDel = JSON.stringify(pending.delegators, null, 2);
const fcurrent = JSON.stringify(current.validators, null, 2);
(0, output_1.logInfo)(`Validators on the network "${ctx.config.hrp}"`);
(0, output_1.log)(`pending validators: ${fpending}`);
(0, output_1.log)(`pending delegations: ${fpendingDel}`);
(0, output_1.log)(`current: ${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}`);
let 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') {
let tp = {
network: ctx.config.hrp,
type: transactionType,
publicKey: getPublicKeyFromPair(ctx.publicKey)
};
await flare.importCP(tp, sign);
return;
}
else if (transactionType === 'exportPC') {
let 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') {
let 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`);
}
let 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`);
}
let 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