@flarenetwork/flare-stake-tool
Version:
Utilities for staking on the Flare network
500 lines • 22.6 kB
JavaScript
"use strict";
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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWithdrawalTransaction = createWithdrawalTransaction;
exports.createOptOutTransaction = createOptOutTransaction;
exports.createClaimTransaction = createClaimTransaction;
exports.saveUnsignedClaimTx = saveUnsignedClaimTx;
exports.sendSignedEvmTransaction = sendSignedEvmTransaction;
exports.signEvmTransaction = signEvmTransaction;
exports.getStateOfRewards = getStateOfRewards;
exports.createSetClaimExecutorsTransaction = createSetClaimExecutorsTransaction;
exports.createSetAllowedClaimRecipientsTransaction = createSetAllowedClaimRecipientsTransaction;
exports.createCustomCChainTransaction = createCustomCChainTransaction;
const ethers_1 = require("ethers");
const utils_1 = require("../utils");
const utils_2 = require("./utils");
const contracts_1 = require("../constants/contracts");
const output_1 = require("../output");
const ledger = __importStar(require("../ledger"));
const cli_1 = require("../cli");
const chalk_1 = __importDefault(require("chalk"));
/**
* @description Creates the withdrawal transaction and stores unsigned trx object in the file id
* @param ctx - context
* @param toAddress - the to address
* @param amount - amount to be withdrawn
* @param fileId - file id
* @param nonce - nonce
* @returns returns the ForDefi hash of the transaction
*/
async function createWithdrawalTransaction(ctx, toAddress, amount, fileId, nonce) {
const web3 = ctx.web3;
if (!ctx.cAddressHex) {
throw new Error("cAddressHex not found in context");
}
const txNonce = nonce ?? Number(await web3.eth.getTransactionCount(ctx.cAddressHex));
const amountWei = BigInt(amount) * BigInt(10 ** 9); // amount is already in nanoFLR
// check if address is valid
web3.utils.toChecksumAddress(toAddress);
const rawTx = {
nonce: txNonce,
gasPrice: 200_000_000_000,
gasLimit: 4_000_000,
to: toAddress,
value: amountWei.toString(),
chainId: ctx.config.chainID,
};
// serialized unsigned transaction
const ethersTx = ethers_1.Transaction.from(rawTx);
const hash = (0, utils_1.unPrefix0x)(ethersTx.unsignedHash);
const forDefiHash = Buffer.from(hash, "hex").toString("base64");
const unsignedTx = {
transactionType: "EVM",
rawTx: rawTx,
message: hash,
forDefiHash: forDefiHash,
};
// save tx data
(0, utils_2.saveUnsignedEvmTx)(unsignedTx, fileId);
return forDefiHash;
}
/**
* @description Creates the opt out transaction and stores unsigned transaction object in the file id
* @param ctx - context
* @param fileId - file id
* @param nonce - nonce
* @returns returns the ForDefi hash of the transaction
*/
async function createOptOutTransaction(ctx, fileId, nonce) {
const web3 = ctx.web3;
if (!ctx.cAddressHex) {
throw new Error("cAddressHex not found in context");
}
const flareContractRegistryWeb3Contract = (0, utils_2.getWeb3Contract)(web3, contracts_1.flareContractRegistryAddress, contracts_1.flareContractRegistryABI);
const distributionToDelegatorsAddress = await flareContractRegistryWeb3Contract.methods.getContractAddressByName("DistributionToDelegators").call();
if (distributionToDelegatorsAddress === ethers_1.ZeroAddress) {
throw new Error("Distribution contract address not found");
}
const txNonce = nonce ?? String(await ctx.web3.eth.getTransactionCount(ctx.cAddressHex));
const distributionWeb3Contract = (0, utils_2.getWeb3Contract)(ctx.web3, distributionToDelegatorsAddress, contracts_1.distributionToDelegatorsABI);
const fnToEncode = distributionWeb3Contract.methods.optOutOfAirdrop();
// check if address is already opt out candidate
const isOptOutCandidate = await distributionWeb3Contract.methods.optOutCandidate(ctx.cAddressHex).call();
if (isOptOutCandidate) {
throw new Error("Already an opt out candidate");
}
const rawTx = {
nonce: txNonce,
gasPrice: 200_000_000_000,
gasLimit: 4_000_000,
to: distributionWeb3Contract.options.address,
data: fnToEncode.encodeABI(),
chainId: ctx.config.networkID,
};
// serialized unsigned transaction
const ethersTx = ethers_1.Transaction.from(rawTx);
const hash = (0, utils_1.unPrefix0x)(ethersTx.unsignedHash);
const forDefiHash = Buffer.from(hash, "hex").toString("base64");
const unsignedTx = {
transactionType: "EVM",
rawTx: rawTx,
message: hash,
forDefiHash: forDefiHash,
};
// save tx data
(0, utils_2.saveUnsignedEvmTx)(unsignedTx, fileId);
return forDefiHash;
}
/**
* @description Creates the unsigned claim (staking rewards) transaction and stores unsigned transaction object in the file id
* @param ctx - context
* @param fileId - file id
* @param nonce - nonce
* @param amount - amount to be claimed (in nanoFLR)
* @param recipient - recipient address
* @param wrap - whether to wrap the claimed amount
* @returns returns the unsigned transaction object
*/
async function createClaimTransaction(ctx, amount, recipientAddress, wrap, nonce) {
(0, output_1.logInfo)("Creating claim transaction...");
const web3 = ctx.web3;
const owner = ctx.cAddressHex;
if (!owner) {
throw new Error("cAddressHex not found in context");
}
// check if address is valid
web3.utils.toChecksumAddress(recipientAddress);
const flareContractRegistryWeb3Contract = (0, utils_2.getWeb3Contract)(web3, contracts_1.flareContractRegistryAddress, contracts_1.flareContractRegistryABI);
const validatorRewardManagerAddress = await flareContractRegistryWeb3Contract.methods.getContractAddressByName("ValidatorRewardManager").call();
if (validatorRewardManagerAddress === ethers_1.ZeroAddress) {
throw new Error("ValidatorRewardManager contract address not found");
}
const txNonce = nonce ?? Number(await web3.eth.getTransactionCount(owner));
const validatorRewardManagerContract = (0, utils_2.getWeb3Contract)(web3, validatorRewardManagerAddress, contracts_1.validatorRewardManagerABI);
// check if unclaimed rewards are available
const rewardsState = await validatorRewardManagerContract.methods.getStateOfRewards(owner).call();
if (!rewardsState) {
throw new Error("Invalid rewards response");
}
const totalRewards = BigInt(rewardsState[0]);
const claimedRewards = BigInt(rewardsState[1]);
const unclaimedRewardsWei = totalRewards - claimedRewards;
const amountWei = amount ? BigInt(amount) * BigInt(10 ** 9) : unclaimedRewardsWei; // amount is already in nanoFLR
const unclaimedRewards = ethers_1.ethers.formatUnits(unclaimedRewardsWei, 18); // convert to FLR
if (amountWei > unclaimedRewardsWei || unclaimedRewardsWei === 0n || amountWei === 0n) {
const symbol = cli_1.networkTokenSymbol[ctx.config.hrp];
throw new Error(`Trying to claim: ${ethers_1.ethers.formatUnits(amount, 9)} ${symbol}. Unclaimed rewards: ${unclaimedRewards} ${symbol}. Amount should be greater than 0 and less than or equal to unclaimed rewards.`);
}
const fnToEncode = validatorRewardManagerContract.methods.claim(owner, recipientAddress, amountWei, wrap);
// const lastBlock = await web3.eth.getBlockNumber() - 3n;
// let gasPrice: bigint;
// try {
// const feeHistory = await web3.eth.getFeeHistory(50, lastBlock, [0]);
// const baseFee = feeHistory.baseFeePerGas as any as bigint[];
// // get max fee of the last 50 blocks
// let maxFee = 0n;
// for (const fee of baseFee) {
// if (fee > maxFee) {
// maxFee = fee;
// }
// }
// gasPrice = maxFee * 10n;
// } catch (e) {
// gasPrice = await web3.eth.getGasPrice() * 10n;
// }
const rawTx = {
nonce: txNonce,
gasPrice: 200_000_000_000,
gasLimit: 4_000_000,
to: validatorRewardManagerContract.options.address,
data: fnToEncode.encodeABI(),
chainId: ctx.config.networkID,
};
return rawTx;
}
/**
* @description Creates the unsigned claim (staking rewards) transaction and stores unsigned transaction object in the file id
* @param rawTx - unsigned transaction object
* @param fileId - file id
* @returns returns the ForDefi hash of the transaction
*/
function saveUnsignedClaimTx(rawTx, fileId) {
// serialized unsigned transaction
const ethersTx = ethers_1.Transaction.from(rawTx);
const hash = (0, utils_1.unPrefix0x)(ethersTx.unsignedHash);
const forDefiHash = Buffer.from(hash, "hex").toString("base64");
const unsignedTx = {
transactionType: "EVM",
rawTx: rawTx,
message: hash,
forDefiHash: forDefiHash,
};
// save tx data
(0, utils_2.saveUnsignedEvmTx)(unsignedTx, fileId);
return forDefiHash;
}
/**
* @description - sends the EVM transaction to the blockchain
* @param ctx - context
* @param fileId - id of the file containing the unsigned transaction
* @returns - the transaction hash
*/
async function sendSignedEvmTransaction(ctx, fileId) {
const waitFinalize3 = (0, utils_2.waitFinalize)(ctx.web3);
if (!ctx.cAddressHex) {
throw new Error("cAddressHex not found in context");
}
// read unsigned tx data
const unsignedTxJson = (0, utils_2.readUnsignedEvmTx)(fileId);
// read signed tx data
const signedTxJson = (0, utils_2.readSignedEvmTx)(fileId);
// read signature
const signature = signedTxJson.signature;
// create raw signed tx
const ethersTx = ethers_1.Transaction.from(unsignedTxJson.rawTx);
ethersTx.signature = (0, utils_1.prefix0x)(signature);
const serializedSigned = ethersTx.serialized;
// send signed tx to the network
const receipt = await waitFinalize3(ctx.cAddressHex, () => ctx.web3.eth.sendSignedTransaction(serializedSigned)).catch((error) => {
if (error &&
typeof error === "object" &&
"innerError" in error &&
error.innerError &&
typeof error.innerError === "object" &&
"message" in error.innerError) {
console.log(chalk_1.default.red(error.innerError.message));
}
else if (error && typeof error === "object" && "reason" in error) {
console.log(chalk_1.default.red(error.reason));
}
else {
console.log(chalk_1.default.red(error));
console.dir(error);
}
process.exit(1);
});
// Validate receipt
if (!receipt.transactionHash) {
throw new Error("Transaction receipt missing transactionHash");
}
return (0, utils_1.toHex)(receipt.transactionHash);
}
async function signEvmTransaction(type, ctx, unsignedTx, derivationPath) {
(0, output_1.logInfo)("Signing transaction...");
if (!ctx.web3)
throw new Error("Web3 instance missing in context");
if (!ctx.cAddressHex)
throw new Error("cAddressHex missing in context");
const web3 = ctx.web3;
const waitFinalize3 = (0, utils_2.waitFinalize)(web3);
// Default to legacy transaction (configurable via unsignedTx.type)
const txType = unsignedTx.type ?? 0;
const ethersTx = ethers_1.Transaction.from({ ...unsignedTx, type: txType });
let signedTxHex;
if (type === "ledger") {
if (!derivationPath)
throw new Error("Derivation path required for Ledger signing");
const serializedUnsignedTx = ethersTx.unsignedSerialized;
const signature = await ledger.signEvmTransaction(derivationPath, serializedUnsignedTx);
const sig = signature.startsWith("0x") ? signature.slice(2) : signature;
if (sig.length < 130 || sig.length > 132) {
throw new Error(`Invalid signature length: ${sig.length / 2} bytes`);
}
const parsedSignature = {
r: `0x${sig.slice(0, 64)}`,
s: `0x${sig.slice(64, 128)}`,
v: parseInt(sig.slice(128, 130), 16), // v is typically 27 or 28
};
ethersTx.signature = parsedSignature;
signedTxHex = ethersTx.serialized;
}
else if (type === "privateKey") {
if (!ctx.privkHex)
throw new Error("No private key found in context");
const wallet = new ethers_1.Wallet(ctx.privkHex);
const ethersTx = ethers_1.Transaction.from(unsignedTx);
signedTxHex = await wallet.signTransaction(ethersTx);
}
// send signed tx to the network
const receipt = await waitFinalize3(ctx.cAddressHex, () => web3.eth.sendSignedTransaction(signedTxHex)).catch((error) => {
if (error &&
typeof error === "object" &&
"innerError" in error &&
error.innerError &&
typeof error.innerError === "object" &&
"message" in error.innerError) {
console.log(chalk_1.default.red(error.innerError.message));
}
else if (error && typeof error === "object" && "reason" in error) {
console.log(chalk_1.default.red(error.reason));
}
else {
console.log(chalk_1.default.red(error));
console.dir(error);
}
process.exit(1);
});
if (!receipt.transactionHash) {
const error = new Error("Transaction receipt missing transactionHash");
throw error;
}
return (0, utils_1.toHex)(receipt.transactionHash);
}
async function getStateOfRewards(web3, owner) {
const flareContractRegistryWeb3Contract = (0, utils_2.getWeb3Contract)(web3, contracts_1.flareContractRegistryAddress, contracts_1.flareContractRegistryABI);
const validatorRewardManagerAddress = await flareContractRegistryWeb3Contract.methods.getContractAddressByName("ValidatorRewardManager").call();
const validatorRewardManagerContract = (0, utils_2.getWeb3Contract)(web3, validatorRewardManagerAddress, contracts_1.validatorRewardManagerABI);
if (validatorRewardManagerAddress === ethers_1.ZeroAddress) {
throw new Error("ValidatorRewardManager contract address not found");
}
// check if unclaimed rewards are available
const rewardsState = await validatorRewardManagerContract.methods.getStateOfRewards(owner).call();
if (!rewardsState) {
throw new Error("Invalid rewards response");
}
const totalRewardsWei = BigInt(rewardsState[0]);
const claimedRewardsWei = BigInt(rewardsState[1]);
const unclaimedRewardsWei = totalRewardsWei - claimedRewardsWei;
const unclaimedRewards = ethers_1.ethers.formatUnits(unclaimedRewardsWei, 18); // convert to FLR
const totalRewards = ethers_1.ethers.formatUnits(totalRewardsWei, 18);
const claimedRewards = ethers_1.ethers.formatUnits(claimedRewardsWei, 18);
return { unclaimedRewards, totalRewards, claimedRewards };
}
/**
* @description Creates the set executors transaction and stores unsigned transaction object in the file id
* @param ctx - context
* @param fileId - file id
* @param nonce - nonce
* @param executors - array of executors addresses
* @returns returns the ForDefi hash of the transaction
*/
async function createSetClaimExecutorsTransaction(ctx, fileId, executors, nonce) {
const web3 = ctx.web3;
if (!ctx.cAddressHex) {
throw new Error("cAddressHex not found in context");
}
const flareContractRegistryWeb3Contract = (0, utils_2.getWeb3Contract)(web3, contracts_1.flareContractRegistryAddress, contracts_1.flareContractRegistryABI);
const claimSetupManagerAddress = await flareContractRegistryWeb3Contract.methods.getContractAddressByName("ClaimSetupManager").call();
if (claimSetupManagerAddress === ethers_1.ZeroAddress) {
throw new Error("ClaimSetupManager contract address not found");
}
const txNonce = nonce ?? Number(await web3.eth.getTransactionCount(ctx.cAddressHex));
const claimSetupManagerWeb3Contract = (0, utils_2.getWeb3Contract)(web3, claimSetupManagerAddress, contracts_1.claimSetupManagerABI);
// filter out empty strings (if removing executors)
executors = executors.filter((executor) => executor.trim() !== "");
// check if executors are registered (and addresses are valid) and sum their fees
let totalFee = 0n;
for (const executor of executors) {
const executorInfo = await claimSetupManagerWeb3Contract.methods.getExecutorInfo(web3.utils.toChecksumAddress(executor)).call();
if (!executorInfo[0]) {
throw new Error(`Executor ${executor} is not registered`);
}
totalFee += executorInfo[1];
}
const fnToEncode = claimSetupManagerWeb3Contract.methods.setClaimExecutors(executors);
const rawTx = {
nonce: txNonce,
gasPrice: 200_000_000_000,
gasLimit: 4_000_000,
to: claimSetupManagerWeb3Contract.options.address,
data: fnToEncode.encodeABI(),
chainId: ctx.config.networkID,
value: totalFee.toString(),
};
// serialized unsigned transaction
const ethersTx = ethers_1.Transaction.from(rawTx);
const hash = (0, utils_1.unPrefix0x)(ethersTx.unsignedHash);
const forDefiHash = Buffer.from(hash, "hex").toString("base64");
const unsignedTx = {
transactionType: "EVM",
rawTx: rawTx,
message: hash,
forDefiHash: forDefiHash,
};
// save tx data
(0, utils_2.saveUnsignedEvmTx)(unsignedTx, fileId);
return forDefiHash;
}
/**
* @description Creates the set allowed claim recipients transaction and stores unsigned transaction object in the file id
* @param ctx - context
* @param fileId - file id
* @param nonce - nonce
* @param recipients - array of allowed claim recipients addresses
* @returns returns the ForDefi hash of the transaction
*/
async function createSetAllowedClaimRecipientsTransaction(ctx, fileId, recipients, nonce) {
const web3 = ctx.web3;
if (!ctx.cAddressHex) {
throw new Error("cAddressHex not found in context");
}
const flareContractRegistryWeb3Contract = (0, utils_2.getWeb3Contract)(web3, contracts_1.flareContractRegistryAddress, contracts_1.flareContractRegistryABI);
const claimSetupManagerAddress = await flareContractRegistryWeb3Contract.methods.getContractAddressByName("ClaimSetupManager").call();
if (claimSetupManagerAddress === ethers_1.ZeroAddress) {
throw new Error("ClaimSetupManager contract address not found");
}
const txNonce = nonce ?? Number(await web3.eth.getTransactionCount(ctx.cAddressHex));
const claimSetupManagerWeb3Contract = (0, utils_2.getWeb3Contract)(web3, claimSetupManagerAddress, contracts_1.claimSetupManagerABI);
// filter out empty strings (if removing recipients)
recipients = recipients.filter((recipient) => recipient.trim() !== "");
const fnToEncode = claimSetupManagerWeb3Contract.methods.setAllowedClaimRecipients(recipients);
const rawTx = {
nonce: txNonce,
gasPrice: 200_000_000_000,
gasLimit: 4_000_000,
to: claimSetupManagerWeb3Contract.options.address,
data: fnToEncode.encodeABI(),
chainId: ctx.config.networkID,
};
// serialized unsigned transaction
const ethersTx = ethers_1.Transaction.from(rawTx);
const hash = (0, utils_1.unPrefix0x)(ethersTx.unsignedHash);
const forDefiHash = Buffer.from(hash, "hex").toString("base64");
const unsignedTx = {
transactionType: "EVM",
rawTx: rawTx,
message: hash,
forDefiHash: forDefiHash,
};
// save tx data
(0, utils_2.saveUnsignedEvmTx)(unsignedTx, fileId);
return forDefiHash;
}
/**
* @description Creates a custom C-Chain transaction and stores unsigned transaction object in the file id
* @param ctx - context
* @param fileId - file id
* @param data - data field for the transaction
* @param toAddress - the recipient address
* @param value - value to be sent (in wei)
* @param nonce - nonce for the transaction
* @returns returns the ForDefi hash of the transaction
*/
async function createCustomCChainTransaction(ctx, fileId, toAddress, data, value, nonce) {
const web3 = ctx.web3;
if (!ctx.cAddressHex) {
throw new Error("cAddressHex not found in context");
}
const txNonce = nonce ?? Number(await web3.eth.getTransactionCount(ctx.cAddressHex));
// check if address is valid
web3.utils.toChecksumAddress(toAddress);
const rawTx = {
nonce: txNonce,
gasPrice: 200_000_000_000,
gasLimit: 4_000_000,
to: toAddress,
value: value,
data: data,
chainId: ctx.config.chainID,
};
// serialized unsigned transaction
const ethersTx = ethers_1.Transaction.from(rawTx);
const hash = (0, utils_1.unPrefix0x)(ethersTx.unsignedHash);
const forDefiHash = Buffer.from(hash, "hex").toString("base64");
const unsignedTx = {
transactionType: "EVM",
rawTx: rawTx,
message: hash,
forDefiHash: forDefiHash,
};
// save tx data
(0, utils_2.saveUnsignedEvmTx)(unsignedTx, fileId);
return forDefiHash;
}
//# sourceMappingURL=evmTx.js.map