UNPKG

@chorus-one/ton

Version:

All-in-one tooling for building staking dApps on TON

168 lines (167 loc) 9.43 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TonNominatorPoolStaker = void 0; const ton_1 = require("@ton/ton"); const TonBaseStaker_1 = require("./TonBaseStaker"); class TonNominatorPoolStaker extends TonBaseStaker_1.TonBaseStaker { /** * Builds a staking (delegation) transaction for Nominator Pool contract. * For more information see: https://github.com/ton-blockchain/nominator-pool * * @param params - Parameters for building the transaction * @param params.delegatorAddress - The delegator address to stake from * @param params.validatorAddress - The validator address to stake to * @param params.amount - The amount to stake, specified in `TON` * @param params.validUntil - (Optional) The Unix timestamp when the transaction expires * * @returns Returns a promise that resolves to a TON nominator pool staking transaction. */ async buildStakeTx(params) { const { delegatorAddress, validatorAddress, amount, validUntil } = params; // ensure the address is for the right network this.checkIfAddressTestnetFlagMatches(validatorAddress); // ensure the validator address is bounceable. // NOTE: TEP-002 specifies that the address bounceable flag should match both the internal message and the address. // This has no effect as we force the bounce flag anyway. However it is a good practice to be consistent if (!ton_1.Address.parseFriendly(validatorAddress).isBounceable) { throw new Error('validator address is not bounceable! It is required for nominator pool contract operations to use bounceable addresses'); } // this is also a somewhat okay way to check the contract is indeed a staking pool contract const data = await this.getNominatorContractPoolData(validatorAddress); if (data.nominators_count >= data.max_nominators_count) { throw new Error('validator has reached the maximum number of nominators'); } // ensure we stake at least the minimum required amount const nominators = (await this.getPoolContractNominators({ validatorAddress })).nominators; const ourNominator = nominators.find((n) => ton_1.Address.parseRaw(n.address).equals(ton_1.Address.parse(delegatorAddress))); const amountStaked = ourNominator ? (0, ton_1.toNano)(ourNominator.amount) : BigInt(0); const amountToStake = (0, ton_1.toNano)(amount); if (amountToStake + amountStaked < data.min_nominator_stake) { throw new Error(`amount to stake (${(0, ton_1.fromNano)(amountToStake)}) is less than the minimum stake required (${(0, ton_1.fromNano)(data.min_nominator_stake)})`); } const tx = { validUntil: (0, TonBaseStaker_1.defaultValidUntil)(validUntil), messages: [ { address: validatorAddress, // to stake tokens we need to send a large amount of tokens // it is critical that the transaction is bounceable // otherwise in the case of contract failure we may loose tokens! bounceable: true, amount: (0, ton_1.toNano)(amount), payload: 'd' // 'd' for deposit / delegation } ] }; return { tx }; } /** * Builds an unstaking (withdraw nominator) transaction for Nominator Pool contract. * For more information see: https://github.com/ton-blockchain/nominator-pool * * @param params - Parameters for building the transaction * @param params.delegatorAddress - The delegator address * @param params.validatorAddress - The validator address to unstake from * @param params.validUntil - (Optional) The Unix timestamp when the transaction expires * * @returns Returns a promise that resolves to a TON nominator pool unstaking transaction. */ async buildUnstakeTx(params) { const { delegatorAddress, validatorAddress, validUntil } = params; // "In order for the nominator to make a withdrawal, he needs to send message to nominator-pool smart contract with text comment "w" // and some Toncoins for network fee (1 TON is enough). Unspent TONs attached to message will be returned except in very rare cases." // // source: https://github.com/ton-blockchain/nominator-pool?tab=readme-ov-file#nominators-withdrawal const amount = '1'; // 1 TON // ensure the address is for the right network this.checkIfAddressTestnetFlagMatches(validatorAddress); // ensure the validator address is bounceable. // NOTE: TEP-002 specifies that the address bounceable flag should match both the internal message and the address. // This has no effect as we force the bounce flag anyway. However it is a good practice to be consistent if (!ton_1.Address.parseFriendly(validatorAddress).isBounceable) { throw new Error('validator address is not bounceable! It is required for nominator pool contract operations to use bounceable addresses'); } // this is also a somewhat okay way to check the contract is indeed a staking pool contract const data = await this.getNominatorContractPoolData(validatorAddress); if (data.nominators_count === 0) { throw new Error('there is no nominators currently staking to the nominator pool contract'); } // ensure that the delegator has staked to the validator const nominators = (await this.getPoolContractNominators({ validatorAddress })).nominators; const ourNominator = nominators.find((n) => ton_1.Address.parseRaw(n.address).equals(ton_1.Address.parse(delegatorAddress))); if (!ourNominator) { throw new Error('delegator is not staking to the nominator pool contract'); } const tx = { validUntil: (0, TonBaseStaker_1.defaultValidUntil)(validUntil), messages: [ { address: validatorAddress, // to unstake tokens we need to send a some tokens that should // be returned to us in case of error bounceable: true, amount: (0, ton_1.toNano)(amount), payload: 'w' // 'w' for withdraw } ] }; return { tx }; } /** * Retrieves the staking information for a specified delegator. * * @param params - Parameters for the request * @param params.delegatorAddress - The delegator (wallet) address * @param params.validatorAddress - The validator address to gather rewards data from * @param params.contractType - The validator contract type (single-nominator-pool or nominator-pool) * * @returns Returns a promise that resolves to the staking information for the specified delegator. */ async getStake(params) { const { delegatorAddress, validatorAddress } = params; const { nominators } = await this.getPoolContractNominators({ validatorAddress }); if (nominators.length === 0) { return { balance: '0' }; } const nominator = nominators.find((n) => ton_1.Address.parse(n.address).equals(ton_1.Address.parse(delegatorAddress))); if (nominator === undefined) { return { balance: '0' }; } return { balance: nominator.amount }; } /** * Retrieves the active nominators for a Nominator Pool contract. * For more information see: https://github.com/ton-blockchain/nominator-pool * * @param params - Parameters for the request * @param params.validatorAddress - The validator address to gather rewards data from * * @returns Returns a promise that resolves to the nominator data for the validator address. */ async getPoolContractNominators(params) { const client = this.getClient(); const { validatorAddress } = params; // ensure the address is for the right network this.checkIfAddressTestnetFlagMatches(validatorAddress); const response = await client.runMethod(ton_1.Address.parse(validatorAddress), 'list_nominators', []); // @ts-expect-error the library does not handle 'list' type well. This is a workaround to get the data out of the 'list' type const reader = new ton_1.TupleReader(response.stack.pop().items); // extract nominators from contract response const nominators = []; if (reader.remaining > 0) { do { const x = reader.readTuple(); nominators.push({ // The nominator pool contract allows only the basechain addresses (`0:`) // https://github.com/ton-blockchain/nominator-pool/blob/main/func/pool.fc#L618 address: `0:${BigInt(x.readBigNumber()).toString(16)}`, amount: (0, ton_1.fromNano)(x.readBigNumber()), pending_deposit_amount: (0, ton_1.fromNano)(x.readBigNumber()), withdraw_requested: (0, ton_1.fromNano)(x.readBigNumber()) }); } while (reader.remaining); } return { nominators }; } } exports.TonNominatorPoolStaker = TonNominatorPoolStaker;