@ledgerhq/coin-multiversx
Version:
Ledger MultiversX Coin integration
266 lines • 10.8 kB
JavaScript
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
import { inferSubOperations } from "@ledgerhq/coin-framework/serialization/index";
import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets";
import { getEnv } from "@ledgerhq/live-env";
import { Address, ApiNetworkProvider, Transaction as MultiversXSdkTransaction, TransactionPayload, } from "@multiversx/sdk-core";
import { BigNumber } from "bignumber.js";
import { CHAIN_ID, GAS_PER_DATA_BYTE, GAS_PRICE, GAS_PRICE_MODIFIER, MIN_GAS_LIMIT, MULTIVERSX_STAKING_POOL, } from "../constants";
import { MultiversXTransferOptions, } from "../types";
import { BinaryUtils } from "../utils/binary.utils";
import MultiversXApi from "./apiCalls";
import { MultiversXAccount } from "./dtos/multiversx-account";
const api = new MultiversXApi(getEnv("MULTIVERSX_API_ENDPOINT"), getEnv("MULTIVERSX_DELEGATION_API_ENDPOINT"));
const networkConfig = { clientName: "ledger-live" };
const proxy = new ApiNetworkProvider(getEnv("MULTIVERSX_API_ENDPOINT"), networkConfig);
/**
* Get account balances and nonce
*/
export const getAccount = async (addr) => {
const { balance, nonce, isGuarded } = await api.getAccountDetails(addr);
const blockHeight = await api.getBlockchainBlockHeight();
const account = new MultiversXAccount(new BigNumber(balance), nonce, isGuarded, blockHeight);
return account;
};
export const getProviders = async () => {
const providers = await api.getProviders();
return providers;
};
export const getNetworkConfig = async () => {
return await proxy.getNetworkConfig();
};
export const getAccountNonce = async (addr) => {
const address = new Address(addr);
const account = await proxy.getAccount(address);
return account.nonce;
};
/**
* Returns true if account is the signer
*/
function isSender(transaction, addr) {
return transaction.sender === addr;
}
function isSelfSend(transaction) {
return (!!transaction.sender && !!transaction.receiver && transaction.sender === transaction.receiver);
}
/**
* Map transaction to an Operation Type
*/
function getEGLDOperationType(transaction, addr) {
if (transaction.action && transaction.action.category === "stake") {
const stakeAction = transaction.action.name;
switch (stakeAction) {
case "delegate":
return "DELEGATE";
case "unDelegate":
return "UNDELEGATE";
case "withdraw":
return "WITHDRAW_UNBONDED";
case "claimRewards":
return "REWARD";
case "reDelegateRewards":
return "DELEGATE";
}
}
return isSender(transaction, addr)
? transaction.transfer === MultiversXTransferOptions.esdt
? "FEES"
: "OUT"
: "IN";
}
function getESDTOperationValue(transaction, tokenIdentifier) {
const hasFailed = !transaction.status || transaction.status === "fail" || transaction.status === "invalid";
if (!transaction.action || hasFailed) {
return new BigNumber(0);
}
let token1, token2;
switch (transaction.action.name) {
case "transfer":
return new BigNumber(transaction.action.arguments.transfers[0].value ?? 0);
case "swap":
token1 = transaction.action.arguments.transfers[0];
token2 = transaction.action.arguments.transfers[1];
if (token1.token === tokenIdentifier) {
return new BigNumber(token1.value);
}
else {
return new BigNumber(token2.value);
}
default:
return new BigNumber(transaction.tokenValue ?? 0);
}
}
function getStakingAmount(transaction, address) {
const operation = transaction.operations?.find(({ sender, receiver, action, type }) => action === "transfer" &&
type === "egld" &&
sender === transaction.receiver &&
(receiver === address || receiver === MULTIVERSX_STAKING_POOL));
let dataDecoded;
switch (transaction.mode) {
case "send":
return new BigNumber(0);
case "delegate":
return new BigNumber(transaction.value ?? 0);
case "unDelegate":
dataDecoded = BinaryUtils.base64Decode(transaction.data ?? "");
return new BigNumber(`0x${dataDecoded.split("@")[1]}`);
case "reDelegateRewards":
case "claimRewards":
case "withdraw":
default:
return new BigNumber(operation?.value ?? new BigNumber(0));
}
}
/**
* Map transaction to a correct Operation Value (affecting account balance)
*/
function getEGLDOperationValue(transaction, address) {
if (transaction.mode === "send") {
if (transaction.transfer === MultiversXTransferOptions.esdt) {
// Only fees paid in EGLD for token transactions
return isSender(transaction, address) && transaction.fee
? new BigNumber(transaction.fee)
: new BigNumber(0);
}
else {
return isSender(transaction, address)
? isSelfSend(transaction)
? new BigNumber(transaction.fee ?? 0) // Self-send, only fees are paid
: new BigNumber(transaction.value ?? 0).plus(transaction.fee ?? 0) // The sender pays the amount and the fees
: new BigNumber(transaction.value ?? 0); // The recipient gets the amount
}
}
else {
// Operation value for staking transactions are just the fees, plus possible rewards
// Other amounts are put in extra.amount
return new BigNumber(transaction.fee ?? 0);
}
}
/**
* Map the MultiversX history transaction to a Ledger Live Operation
*/
function transactionToEGLDOperation(accountId, addr, transaction, subAccounts) {
const type = getEGLDOperationType(transaction, addr);
const fee = new BigNumber(transaction.fee ?? 0);
const hasFailed = !transaction.status || transaction.status === "fail" || transaction.status === "invalid";
const delegationAmount = getStakingAmount(transaction, addr);
const value = hasFailed
? isSender(transaction, addr)
? fee
: new BigNumber(0)
: transaction.mode === "claimRewards"
? delegationAmount.minus(fee)
: getEGLDOperationValue(transaction, addr);
const operation = {
id: encodeOperationId(accountId, transaction.txHash ?? "", type),
accountId,
fee,
value,
type,
hash: transaction.txHash ?? "",
blockHash: transaction.miniBlockHash,
blockHeight: transaction.round,
date: new Date(transaction.timestamp ? transaction.timestamp * 1000 : 0),
extra: {
amount: delegationAmount,
},
senders: (type === "OUT" || type === "IN") && transaction.sender ? [transaction.sender] : [],
recipients: (type === "OUT" || type === "IN") && transaction.receiver ? [transaction.receiver] : [],
transactionSequenceNumber: isSender(transaction, addr) && transaction.nonce !== undefined
? new BigNumber(transaction.nonce.toString())
: undefined,
hasFailed,
};
const subOperations = subAccounts
? inferSubOperations(transaction.txHash ?? "", subAccounts)
: undefined;
if (subOperations) {
operation.subOperations = subOperations;
}
let contract = undefined;
if (transaction.receiver) {
const isReceiverSmartContract = Address.newFromBech32(transaction.receiver).isSmartContract();
contract = isReceiverSmartContract ? transaction.receiver : undefined;
}
if (contract) {
operation.contract = contract;
}
return operation;
}
const getESDTOperationType = (transaction, address) => {
return isSender(transaction, address) ? "OUT" : "IN";
};
const transactionToESDTOperation = (tokenAccountId, addr, transaction, tokenIdentifier) => {
const type = getESDTOperationType(transaction, addr);
const value = getESDTOperationValue(transaction, tokenIdentifier);
const fee = new BigNumber(transaction.fee ?? 0);
const senders = transaction.sender ? [transaction.sender] : [];
const recipients = transaction.receiver ? [transaction.receiver] : [];
const hash = transaction.txHash ?? "";
const blockHeight = transaction.round;
const date = new Date(transaction.timestamp ? transaction.timestamp * 1000 : 0);
return {
id: encodeOperationId(tokenAccountId, hash, type),
accountId: tokenAccountId,
hash,
date,
type,
value,
fee,
senders,
recipients,
blockHeight,
blockHash: transaction.miniBlockHash,
extra: {},
};
};
/**
* Fetch operation list
*/
export const getEGLDOperations = async (accountId, addr, startAt, subAccounts) => {
const rawTransactions = await api.getHistory(addr, startAt);
if (!rawTransactions)
return rawTransactions;
return rawTransactions.map(transaction => transactionToEGLDOperation(accountId, addr, transaction, subAccounts));
};
export const getAccountESDTTokens = async (address) => {
return await api.getESDTTokensForAddress(address);
};
export const getAccountDelegations = async (address) => {
return await api.getAccountDelegations(address);
};
export const hasESDTTokens = async (address) => {
const tokensCount = await api.getESDTTokensCountForAddress(address);
return tokensCount > 0;
};
export const getESDTOperations = async (tokenAccountId, address, tokenIdentifier, startAt) => {
const accountESDTTransactions = await api.getESDTTransactionsForAddress(address, tokenIdentifier, startAt);
return accountESDTTransactions.map(transaction => transactionToESDTOperation(tokenAccountId, address, transaction, tokenIdentifier));
};
/**
* Obtain fees from blockchain
*/
export const getFees = async (t) => {
const networkConfig = {
MinGasLimit: MIN_GAS_LIMIT,
GasPerDataByte: GAS_PER_DATA_BYTE,
GasPriceModifier: GAS_PRICE_MODIFIER,
ChainID: CHAIN_ID,
};
const transaction = new MultiversXSdkTransaction({
data: TransactionPayload.fromEncoded(t.data?.trim()),
receiver: new Address(getAbandonSeedAddress("elrond")),
chainID: CHAIN_ID,
gasPrice: GAS_PRICE,
gasLimit: t.gasLimit ?? networkConfig.MinGasLimit,
sender: Address.empty(),
});
const feesStr = transaction.computeFee(networkConfig).toFixed();
return new BigNumber(feesStr);
};
/**
* Broadcast blob to blockchain
*/
export const broadcastTransaction = async (signedOperation) => {
return await api.submit(signedOperation);
};
//# sourceMappingURL=sdk.js.map