@ledgerhq/coin-stellar
Version:
Ledger Stellar Coin integration
237 lines • 11.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const expect_1 = __importDefault(require("expect"));
const invariant_1 = __importDefault(require("invariant"));
const bignumber_js_1 = __importDefault(require("bignumber.js"));
const devices_1 = require("@ledgerhq/devices");
const currencies_1 = require("@ledgerhq/cryptoassets/currencies");
const currencies_2 = require("@ledgerhq/coin-framework/currencies");
const specs_1 = require("@ledgerhq/coin-framework/bot/specs");
const tokens_1 = require("@ledgerhq/cryptoassets/tokens");
const bot_deviceActions_1 = require("./bot-deviceActions");
const currency = (0, currencies_1.getCryptoCurrencyById)("stellar");
const minAmountCutoff = (0, currencies_2.parseCurrencyUnit)(currency.units[0], "0.1");
const reserve = (0, currencies_2.parseCurrencyUnit)(currency.units[0], "1.5");
const MAX_FEE = 5000;
const USDC_CODE = "USDC";
const USDC_ISSUER = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN";
const USDC_ASSET_ID = `${USDC_CODE}:${USDC_ISSUER}`;
const MIN_ASSET_BALANCE = (0, currencies_2.parseCurrencyUnit)(currency.units[0], "0.01");
const findAssetUSDC = (subAccounts) => (subAccounts || []).find(s => s.id.endsWith(USDC_ASSET_ID));
const stellar = {
name: "Stellar",
currency,
appQuery: {
model: devices_1.DeviceModelId.nanoSP,
appName: "Stellar",
},
genericDeviceAction: bot_deviceActions_1.acceptTransaction,
testTimeout: 2 * 60 * 1000,
minViableAmount: minAmountCutoff,
mutations: [
{
name: "move ~50% XLM",
feature: "send",
maxRun: 1,
transaction: ({ account, siblings, bridge, maxSpendable }) => {
(0, invariant_1.default)(maxSpendable.gt(minAmountCutoff), "XLM balance is too low");
const transaction = bridge.createTransaction(account);
const sibling = (0, specs_1.pickSiblings)(siblings, 4);
const recipient = sibling.freshAddress;
let amount = maxSpendable.div(1.9 + 0.2 * Math.random()).integerValue();
if (!sibling.used && amount.lt(reserve)) {
(0, invariant_1.default)(maxSpendable.gt(reserve.plus(minAmountCutoff)), "not enough XLM funds to send to new account");
amount = reserve;
}
const updates = [
{
recipient,
// Setting higher max fee here to make sure transaction doesn't
// time out.
fees: new bignumber_js_1.default(MAX_FEE),
},
{
amount,
},
];
if (Math.random() < 0.5) {
updates.push({
memoType: "MEMO_TEXT",
memoValue: "Ledger Live",
});
}
return {
transaction,
updates,
};
},
test: ({ account, accountBeforeTransaction, operation, transaction }) => {
// We don't know what the final fee will be until after the tx is
// submitted. Using higher max fee to make sure tx doesn't time out.
(0, specs_1.botTest)("account balance decreased with operation", () => (0, expect_1.default)(account.balance.toNumber()).toBeLessThanOrEqual(accountBeforeTransaction.balance.minus(operation.value).toNumber()));
if (transaction.memoValue) {
(0, specs_1.botTest)("operation memo", () => (0, expect_1.default)(operation.extra).toMatchObject({
memo: transaction.memoValue,
}));
}
const getType = () => {
switch (transaction.mode) {
case "send":
return "send";
case "changeTrust":
return /change_trust/;
default:
return "";
}
};
(0, specs_1.botTest)("transaction mode", () => (0, expect_1.default)(transaction.mode).toMatch(getType()));
},
},
{
name: "Send max XLM",
feature: "sendMax",
maxRun: 1,
transaction: ({ account, siblings, bridge, maxSpendable }) => {
(0, invariant_1.default)(maxSpendable.gt(minAmountCutoff), "XLM balance is too low");
const transaction = bridge.createTransaction(account);
const sibling = (0, specs_1.pickSiblings)(siblings, 4);
const recipient = sibling.freshAddress;
const updates = [
{
recipient,
// Setting higher max fee here to make sure transaction doesn't
// time out.
fees: new bignumber_js_1.default(MAX_FEE),
},
{
useAllAmount: true,
},
];
if (Math.random() < 0.5) {
updates.push({
memoType: "MEMO_TEXT",
memoValue: "Ledger Live",
});
}
return {
transaction,
updates,
};
},
test: ({ account, accountBeforeTransaction, operation, transaction }) => {
// We don't know what the final fee will be until after the tx is
// submitted. Using higher max fee to make sure tx doesn't time out.
(0, specs_1.botTest)("balance decreased with operation", () => (0, expect_1.default)(account.balance.toNumber()).toBeLessThanOrEqual(accountBeforeTransaction.balance.minus(operation.value).toNumber()));
if (transaction.memoValue) {
(0, specs_1.botTest)("operation memo", () => (0, expect_1.default)(operation.extra).toMatchObject({
memo: transaction.memoValue,
}));
}
const getType = () => {
switch (transaction.mode) {
case "send":
return "send";
case "changeTrust":
return /change_trust/;
default:
return "";
}
};
(0, specs_1.botTest)("transaction mode", () => (0, expect_1.default)(transaction.mode).toMatch(getType()));
},
},
{
name: "add USDC asset",
feature: "tokens",
maxRun: 1,
transaction: ({ account, bridge, maxSpendable }) => {
(0, invariant_1.default)(maxSpendable.gt(reserve), "XLM balance is too low 1");
(0, invariant_1.default)(account.subAccounts && !findAssetUSDC(account.subAccounts), "already have subaccounts");
const assetUSDC = findAssetUSDC((0, tokens_1.listTokensForCryptoCurrency)(account.currency));
(0, invariant_1.default)(assetUSDC, "USDC asset not found");
const transaction = bridge.createTransaction(account);
const updates = [
{
mode: "changeTrust",
// Setting higher max fee here to make sure transaction doesn't
// time out.
fees: new bignumber_js_1.default(MAX_FEE),
},
{
assetCode: USDC_CODE,
assetIssuer: USDC_ISSUER,
},
];
return {
transaction,
updates,
};
},
test: ({ account }) => {
const assetId = `${USDC_CODE}:${USDC_ISSUER}`;
const hasAsset = account.subAccounts?.find(a => a.id.endsWith(assetId));
(0, specs_1.botTest)("has asset", () => (0, expect_1.default)(hasAsset).toBeTruthy());
},
},
{
name: "move ~50% USDC asset",
feature: "tokens",
maxRun: 1,
transaction: ({ account, siblings, bridge, maxSpendable }) => {
(0, invariant_1.default)(maxSpendable.gt(minAmountCutoff), "XLM balance is too low");
const usdcSubAccount = findAssetUSDC(account?.subAccounts);
(0, invariant_1.default)(usdcSubAccount, "USDC asset not found");
(0, invariant_1.default)(usdcSubAccount?.balance.gt(MIN_ASSET_BALANCE), "USDC balance is too low");
const siblingWithAssetUSDC = siblings.find(s => findAssetUSDC(s.subAccounts));
(0, invariant_1.default)(siblingWithAssetUSDC, "No siblings with USDC asset");
if (!usdcSubAccount || !siblingWithAssetUSDC) {
throw new Error("No USDC asset or sibling with USDC asset");
}
const transaction = bridge.createTransaction(account);
const recipient = siblingWithAssetUSDC.freshAddress;
const amount = usdcSubAccount.balance.div(1.9 + 0.2 * Math.random()).integerValue();
const updates = [
{
recipient,
},
{
subAccountId: usdcSubAccount.id,
},
{
amount,
// Setting higher max fee here to make sure transaction doesn't
// time out.
fees: new bignumber_js_1.default(MAX_FEE),
},
];
if (Math.random() < 0.5) {
updates.push({
memoType: "MEMO_TEXT",
memoValue: "Ledger Live",
});
}
return {
transaction,
updates,
};
},
test: ({ account, accountBeforeTransaction, operation, transaction, status }) => {
const asset = findAssetUSDC(account?.subAccounts);
const assetBeforeTx = findAssetUSDC(accountBeforeTransaction?.subAccounts);
(0, specs_1.botTest)("asset balance decreased with operation", () => (0, expect_1.default)(asset?.balance.toString()).toBe(assetBeforeTx?.balance.minus(status.amount).toString()));
if (transaction.memoValue) {
(0, specs_1.botTest)("operation memo", () => (0, expect_1.default)(operation.extra).toMatchObject({
memo: transaction.memoValue,
}));
}
},
},
],
};
exports.default = {
stellar,
};
//# sourceMappingURL=bot-specs.js.map