@ledgerhq/coin-multiversx
Version:
Ledger MultiversX Coin integration
286 lines • 14.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const invariant_1 = __importDefault(require("invariant"));
const currencies_1 = require("@ledgerhq/coin-framework/currencies");
const currencies_2 = require("@ledgerhq/cryptoassets/currencies");
const specs_1 = require("@ledgerhq/coin-framework/bot/specs");
const serialization_1 = require("@ledgerhq/coin-framework/serialization");
const devices_1 = require("@ledgerhq/devices");
const expect_1 = __importDefault(require("expect"));
const speculos_deviceActions_1 = require("./speculos-deviceActions");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const constants_1 = require("./constants");
const sample_1 = __importDefault(require("lodash/sample"));
const currency = (0, currencies_2.getCryptoCurrencyById)("elrond");
const minimalAmount = (0, currencies_1.parseCurrencyUnit)(currency.units[0], "0.001");
const maxAccounts = 6;
const MULTIVERSX_MIN_ACTIVATION_SAFE = new bignumber_js_1.default(10000);
const UNCAPPED_PROVIDER = "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqlhllllsr0pd0j";
function expectCorrectBalanceChange(input) {
const { account, operation, accountBeforeTransaction } = input;
(0, specs_1.botTest)("EGLD balance change is correct", () => (0, expect_1.default)(account.balance.toFixed()).toStrictEqual(accountBeforeTransaction.balance.minus(operation.value).toFixed()));
}
function expectCorrectEsdtBalanceChange(input) {
const { account, operation, accountBeforeTransaction, transaction } = input;
const { subAccountId } = transaction;
const subAccounts = account.subAccounts ?? [];
const subAccountsBefore = accountBeforeTransaction.subAccounts ?? [];
const tokenAccount = subAccounts.find(ta => ta.id === subAccountId);
const tokenAccountBefore = subAccountsBefore.find(ta => ta.id === subAccountId);
const subOperation = operation.subOperations?.find(sa => sa.id === operation.id);
if (tokenAccount && tokenAccountBefore && subOperation) {
(0, specs_1.botTest)("ESDT balance change is correct", () => (0, expect_1.default)(tokenAccount.balance.toFixed()).toStrictEqual(tokenAccountBefore.balance.minus(subOperation.value).toFixed()));
}
}
function expectCorrectOptimisticOperation(input) {
const { operation, optimisticOperation, transaction } = input;
const opExpected = (0, serialization_1.toOperationRaw)({
...optimisticOperation,
});
delete opExpected.value;
delete opExpected.fee;
delete opExpected.date;
delete opExpected.blockHash;
delete opExpected.blockHeight;
if (operation.type !== "OUT") {
delete opExpected.senders;
delete opExpected.recipients;
delete opExpected.contract;
}
(0, specs_1.botTest)("optimistic operation matches id", () => (0, expect_1.default)(operation.id).toStrictEqual(optimisticOperation.id));
(0, specs_1.botTest)("optimistic operation matches hash", () => (0, expect_1.default)(operation.hash).toStrictEqual(optimisticOperation.hash));
(0, specs_1.botTest)("optimistic operation matches accountId", () => (0, expect_1.default)(operation.accountId).toStrictEqual(optimisticOperation.accountId));
// On ESDT transactions the fee can decrease when the transaction is executed
if (!transaction.subAccountId) {
(0, specs_1.botTest)("optimistic operation matches fee", () => (0, expect_1.default)(operation.fee.toFixed()).toStrictEqual(optimisticOperation.fee.toFixed()));
}
(0, specs_1.botTest)("optimistic operation matches type", () => (0, expect_1.default)(operation.type).toStrictEqual(optimisticOperation.type));
if (operation.type === "OUT") {
(0, specs_1.botTest)("optimistic operation matches contract", () => (0, expect_1.default)(operation.contract).toStrictEqual(optimisticOperation.contract));
(0, specs_1.botTest)("optimistic operation matches senders", () => (0, expect_1.default)(operation.senders).toStrictEqual(optimisticOperation.senders));
(0, specs_1.botTest)("optimistic operation matches recipients", () => (0, expect_1.default)(operation.recipients).toStrictEqual(optimisticOperation.recipients));
if (!transaction.subAccountId) {
(0, specs_1.botTest)("optimistic operation matches value", () => (0, expect_1.default)(operation.value.toFixed()).toStrictEqual(optimisticOperation.value.toFixed()));
}
}
(0, specs_1.botTest)("optimistic operation matches transactionSequenceNumber", () => (0, expect_1.default)(operation.transactionSequenceNumber).toStrictEqual(optimisticOperation.transactionSequenceNumber));
(0, specs_1.botTest)("raw optimistic operation matches", () => (0, expect_1.default)((0, serialization_1.toOperationRaw)(operation)).toMatchObject(opExpected));
}
function expectCorrectSpendableBalanceChange(input) {
const { account, accountBeforeTransaction } = input;
const operation = input.operation;
let value = operation.value;
if (operation.extra.amount) {
if (operation.type === "DELEGATE") {
value = value.plus(operation.extra.amount);
}
else if (operation.type === "WITHDRAW_UNBONDED") {
value = value.minus(operation.extra.amount);
}
}
(0, specs_1.botTest)("EGLD spendable balance change is correct", () => (0, expect_1.default)(account.spendableBalance.toFixed()).toStrictEqual(accountBeforeTransaction.spendableBalance.minus(value).toFixed()));
}
function expectCorrectBalanceFeeChange(input) {
const { account, operation, accountBeforeTransaction } = input;
(0, specs_1.botTest)("Only change on balance is fees", () => (0, expect_1.default)(account.balance.toFixed()).toStrictEqual(accountBeforeTransaction.balance.minus(operation.fee).toFixed()));
}
const multiversx = {
name: "MultiversX",
currency: (0, currencies_2.getCryptoCurrencyById)("elrond"),
appQuery: {
model: devices_1.DeviceModelId.nanoS,
appName: "MultiversX",
},
genericDeviceAction: speculos_deviceActions_1.acceptMoveBalanceTransaction,
genericDeviceActionForSubAccountTransfers: speculos_deviceActions_1.acceptEsdtTransferTransaction,
testTimeout: 2 * 60 * 1000,
minViableAmount: minimalAmount,
transactionCheck: ({ maxSpendable }) => {
(0, invariant_1.default)(maxSpendable.gt(minimalAmount), "balance is too low");
},
test: input => {
expectCorrectOptimisticOperation(input);
},
mutations: [
{
name: "send 50%~",
feature: "send",
maxRun: 1,
deviceAction: speculos_deviceActions_1.acceptMoveBalanceTransaction,
transaction: ({ account, siblings, bridge }) => {
(0, invariant_1.default)(account.spendableBalance.gt(0), "balance is 0");
const sibling = (0, specs_1.pickSiblings)(siblings, maxAccounts);
let amount = account.spendableBalance.div(1.9 + 0.2 * Math.random()).integerValue();
if (!sibling.used && amount.lt(MULTIVERSX_MIN_ACTIVATION_SAFE)) {
(0, invariant_1.default)(account.spendableBalance.gt(MULTIVERSX_MIN_ACTIVATION_SAFE), "send is too low to activate account");
amount = MULTIVERSX_MIN_ACTIVATION_SAFE;
}
return {
transaction: bridge.createTransaction(account),
updates: [
{
recipient: sibling.freshAddress,
},
{
amount,
},
],
};
},
testDestination: specs_1.genericTestDestination,
test: input => {
expectCorrectBalanceChange(input);
},
},
{
name: "send max",
feature: "sendMax",
maxRun: 1,
deviceAction: speculos_deviceActions_1.acceptMoveBalanceTransaction,
transaction: ({ account, siblings, bridge }) => {
(0, invariant_1.default)(account.spendableBalance.gt(0), "balance is 0");
const sibling = (0, specs_1.pickSiblings)(siblings, maxAccounts);
if (!sibling.used) {
(0, invariant_1.default)(account.spendableBalance.gt(MULTIVERSX_MIN_ACTIVATION_SAFE), "send is too low to activate account");
}
return {
transaction: bridge.createTransaction(account),
updates: [
{
recipient: sibling.freshAddress,
},
{
useAllAmount: true,
},
],
};
},
testDestination: specs_1.genericTestDestination,
test: input => {
expectCorrectBalanceChange(input);
},
},
{
name: "move some ESDT",
feature: "tokens",
maxRun: 1,
deviceAction: speculos_deviceActions_1.acceptEsdtTransferTransaction,
transaction: ({ account, siblings, bridge }) => {
const esdtAccount = (0, sample_1.default)((account.subAccounts || []).filter(a => a.balance.gt(0)));
(0, invariant_1.default)(esdtAccount, "no esdt account");
(0, invariant_1.default)(esdtAccount?.balance.gt(0), "esdt balance is 0");
const sibling = (0, specs_1.pickSiblings)(siblings, 2);
const recipient = sibling.freshAddress;
const amount = esdtAccount?.balance.times(Math.random()).integerValue();
return {
transaction: bridge.createTransaction(account),
updates: [
{
subAccountId: esdtAccount?.id,
},
{
recipient,
},
{
amount,
},
],
};
},
test: input => {
expectCorrectEsdtBalanceChange(input);
expectCorrectBalanceFeeChange(input);
},
},
{
name: "delegate 1 EGLD",
feature: "staking",
maxRun: 1,
deviceAction: speculos_deviceActions_1.acceptDelegateTransaction,
transaction: ({ account, bridge }) => {
(0, invariant_1.default)(account.spendableBalance.gt(constants_1.MIN_DELEGATION_AMOUNT), `spendable balance is less than minimum delegation amount`);
const amount = constants_1.MIN_DELEGATION_AMOUNT;
return {
transaction: bridge.createTransaction(account),
updates: [
{
recipient: UNCAPPED_PROVIDER,
mode: "delegate",
amount,
},
],
};
},
test: input => {
expectCorrectSpendableBalanceChange(input);
expectCorrectBalanceFeeChange(input);
},
},
{
name: "unDelegate 1 EGLD",
feature: "staking",
maxRun: 1,
deviceAction: speculos_deviceActions_1.acceptUndelegateTransaction,
transaction: ({ account, bridge }) => {
const delegations = account?.multiversxResources?.delegations;
(0, invariant_1.default)(delegations?.length, "account doesn't have any delegations");
(0, invariant_1.default)(delegations.some(d => new bignumber_js_1.default(d.userActiveStake).gt(0)), "no active stake for account");
const amount = constants_1.MIN_DELEGATION_AMOUNT;
return {
transaction: bridge.createTransaction(account),
updates: [
{
recipient: UNCAPPED_PROVIDER,
mode: "unDelegate",
amount,
},
],
};
},
test: input => {
expectCorrectSpendableBalanceChange(input);
expectCorrectBalanceFeeChange(input);
},
},
{
name: "withdraw all EGLD",
feature: "staking",
maxRun: 1,
deviceAction: speculos_deviceActions_1.acceptWithdrawTransaction,
transaction: ({ account, bridge }) => {
const delegations = account?.multiversxResources?.delegations;
(0, invariant_1.default)(delegations?.length, "account doesn't have any delegations");
(0, invariant_1.default)(
// among all delegations
delegations.some(d =>
// among all undelegating amounts
d.userUndelegatedList?.some(u => new bignumber_js_1.default(u.amount).gt(0) && // the undelegation has a positive amount
new bignumber_js_1.default(u.seconds).eq(0))), "no withdrawable stake for account");
return {
transaction: bridge.createTransaction(account),
updates: [
{
recipient: UNCAPPED_PROVIDER,
mode: "withdraw",
amount: new bignumber_js_1.default(0),
},
],
};
},
test: input => {
expectCorrectSpendableBalanceChange(input);
expectCorrectBalanceFeeChange(input);
},
},
// TODO
// "reDelegateRewards"
// "claimRewards"
],
};
exports.default = {
multiversx,
};
//# sourceMappingURL=specs.js.map