UNPKG

@flarenetwork/flare-stake-tool

Version:
500 lines 22.6 kB
"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