@chorus-one/ton
Version:
All-in-one tooling for building staking dApps on TON
179 lines (178 loc) • 9.86 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.TonSingleNominatorPoolStaker = void 0;
const ton_1 = require("@ton/ton");
const TonBaseStaker_1 = require("./TonBaseStaker");
class TonSingleNominatorPoolStaker extends TonBaseStaker_1.TonBaseStaker {
/**
* Builds a staking (delegation) transaction for Single Nominator Pool contract.
* For more information see: https://github.com/orbs-network/single-nominator/tree/main
*
* @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');
}
// be sure the delegator is the owner of the contract otherwise we can't withdraw the funds back
const roles = await this.getContractRoles(validatorAddress);
if (!roles.ownerAddress.equals(ton_1.Address.parse(delegatorAddress))) {
throw new Error('delegator is not the owner of the single nominator pool contract');
}
// this serves purely as a sanity check
const data = await this.getNominatorContractPoolData(validatorAddress);
if (data.nominators_count !== 1) {
throw new Error('the single nominator pool contract is expected to have exactly one nominator');
}
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: ton_1.Cell.EMPTY
}
]
};
return { tx };
}
/**
* Builds a unstaking (withdraw nominator) transaction for Single Nominator Pool contract.
* For more information see: https://github.com/orbs-network/single-nominator/tree/main
*
* @param params - Parameters for building the transaction
* @param params.delegatorAddress - The delegator address
* @param params.validatorAddress - The validator address to unstake from
* @param params.amount - The amount to unstake, 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 unstaking transaction.
*/
async buildUnstakeTx(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');
}
// only onwer can withdraw the funds
const roles = await this.getContractRoles(validatorAddress);
if (!roles.ownerAddress.equals(ton_1.Address.parse(delegatorAddress))) {
throw new Error('delegator is not the owner of the single nominator pool contract');
}
// this serves purely as a sanity check
const data = await this.getNominatorContractPoolData(validatorAddress);
if (data.nominators_count !== 1) {
throw new Error('the single nominator pool contract is expected to have exactly one nominator');
}
// source: https://github.com/orbs-network/single-nominator/tree/main?tab=readme-ov-file#1-withdraw
// https://github.com/orbs-network/single-nominator/blob/main/scripts/ts/withdraw-deeplink.ts#L7C5-L7C137
const payload = (0, ton_1.beginCell)().storeUint(0x1000, 32).storeUint(1, 64).storeCoins((0, ton_1.toNano)(amount)).endCell();
// 1 TON should be enough to cover the transaction fees (similar to nominator pool contract)
const amountToCoverTxFees = '1';
// ensure we don't drain the validator wallet by accident
this.checkMinimumExistentialBalance(validatorAddress, amount);
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)(amountToCoverTxFees),
payload
}
]
};
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;
// otherise it is a single nominator pool contract
const roles = await this.getContractRoles(validatorAddress);
if (!roles.ownerAddress.equals(ton_1.Address.parse(delegatorAddress))) {
throw new Error('delegator is not the owner of the single nominator pool contract');
}
const balance = await this.getBalance({ address: validatorAddress });
return { balance: balance.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 };
}
async getContractRoles(contractAddress) {
const client = this.getClient();
const response = await client.runMethod(ton_1.Address.parse(contractAddress), 'get_roles', []);
// reference: https://github.com/orbs-network/single-nominator/blob/main/contracts/single-nominator.fc#L186
if (response.stack.remaining !== 2) {
throw new Error('invalid get_pool_data response, expected 17 fields got ' + response.stack.remaining);
}
const ownerAddress = response.stack.readAddress();
const validatorAddress = response.stack.readAddress();
return {
ownerAddress,
validatorAddress
};
}
}
exports.TonSingleNominatorPoolStaker = TonSingleNominatorPoolStaker;