@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
417 lines • 20.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.bitcoinFamilyAccountGetAddressesLogic = exports.bitcoinFamilyAccountGetXPubLogic = exports.bitcoinFamilyAccountGetPublicKeyLogic = exports.bitcoinFamilyAccountGetAddressLogic = void 0;
exports.translateContent = translateContent;
exports.receiveOnAccountLogic = receiveOnAccountLogic;
exports.signTransactionLogic = signTransactionLogic;
exports.signRawTransactionLogic = signRawTransactionLogic;
exports.broadcastTransactionLogic = broadcastTransactionLogic;
exports.signMessageLogic = signMessageLogic;
exports.startExchangeLogic = startExchangeLogic;
exports.completeExchangeLogic = completeExchangeLogic;
exports.protectStorageLogic = protectStorageLogic;
const types_live_1 = require("@ledgerhq/types-live");
const converters_1 = require("./converters");
const index_1 = require("../account/index");
const index_2 = require("../hw/signMessage/index");
const bridge_1 = require("../bridge");
const state_1 = require("@ledgerhq/cryptoassets/state");
const index_3 = require("@ledgerhq/coin-bitcoin/wallet-btc/index");
function translateContent(content, locale = "en") {
if (!content || typeof content === "string")
return content;
return content[locale] || content.en;
}
async function receiveOnAccountLogic(walletState, { manifest, accounts, tracking }, walletAccountId, uiNavigation, tokenCurrency) {
tracking.receiveRequested(manifest);
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.receiveFail(manifest);
throw new Error(`accountId ${walletAccountId} unknown`);
}
const account = accounts.find(account => account.id === accountId);
if (!account) {
tracking.receiveFail(manifest);
throw new Error("Account required");
}
const parentAccount = (0, index_1.getParentAccount)(account, accounts);
const mainAccount = (0, index_1.getMainAccount)(account, parentAccount);
const currency = tokenCurrency ? await (0, state_1.getCryptoAssetsStore)().findTokenById(tokenCurrency) : null;
const receivingAccount = currency ? (0, index_1.makeEmptyTokenAccount)(mainAccount, currency) : account;
const accountAddress = (0, converters_1.accountToWalletAPIAccount)(walletState, account, parentAccount).address;
return uiNavigation(receivingAccount, parentAccount, accountAddress);
}
async function signTransactionLogic({ manifest, accounts, tracking }, walletAccountId, transaction, uiNavigation, tokenCurrency, isEmbeddedSwap, partner) {
tracking.signTransactionRequested(manifest, isEmbeddedSwap, partner);
if (!transaction) {
tracking.signTransactionFail(manifest, isEmbeddedSwap, partner);
throw new Error("Transaction required");
}
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.signTransactionFail(manifest, isEmbeddedSwap, partner);
throw new Error(`accountId ${walletAccountId} unknown`);
}
const account = accounts.find(account => account.id === accountId);
if (!account) {
tracking.signTransactionFail(manifest, isEmbeddedSwap, partner);
throw new Error("Account required");
}
const parentAccount = (0, index_1.getParentAccount)(account, accounts);
const accountFamily = (0, index_1.isTokenAccount)(account)
? parentAccount?.currency.family
: account.currency.family;
const mainAccount = (0, index_1.getMainAccount)(account, parentAccount);
const currency = tokenCurrency ? await (0, state_1.getCryptoAssetsStore)().findTokenById(tokenCurrency) : null;
const signerAccount = currency ? (0, index_1.makeEmptyTokenAccount)(mainAccount, currency) : account;
const { canEditFees, liveTx, hasFeesProvided } = (0, converters_1.getWalletAPITransactionSignFlowInfos)({
walletApiTransaction: transaction,
account: mainAccount,
});
if (accountFamily !== liveTx.family) {
throw new Error(`Account and transaction must be from the same family. Account family: ${accountFamily}, Transaction family: ${liveTx.family}`);
}
return uiNavigation(signerAccount, parentAccount, {
canEditFees,
liveTx,
hasFeesProvided,
});
}
function signRawTransactionLogic({ manifest, accounts, tracking }, walletAccountId, transaction, uiNavigation) {
tracking.signRawTransactionRequested(manifest);
if (!transaction) {
tracking.signRawTransactionFail(manifest);
throw new Error("Transaction required");
}
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.signRawTransactionFail(manifest);
throw new Error(`accountId ${walletAccountId} unknown`);
}
const account = accounts.find(account => account.id === accountId);
if (!account) {
tracking.signRawTransactionFail(manifest);
throw new Error("Account required");
}
const parentAccount = (0, index_1.getParentAccount)(account, accounts);
return uiNavigation(account, parentAccount, transaction);
}
async function broadcastTransactionLogic({ manifest, accounts, tracking }, walletAccountId, signedOperation, uiNavigation, tokenCurrency) {
if (!signedOperation) {
tracking.broadcastFail(manifest);
throw new Error("Transaction required");
}
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.broadcastFail(manifest);
throw new Error(`accountId ${walletAccountId} unknown`);
}
const account = accounts.find(account => account.id === accountId);
if (!account) {
tracking.broadcastFail(manifest);
throw new Error("Account required");
}
const currency = tokenCurrency ? await (0, state_1.getCryptoAssetsStore)().findTokenById(tokenCurrency) : null;
const parentAccount = (0, index_1.getParentAccount)(account, accounts);
const mainAccount = (0, index_1.getMainAccount)(account, parentAccount);
const signerAccount = currency ? (0, index_1.makeEmptyTokenAccount)(mainAccount, currency) : account;
return uiNavigation(signerAccount, parentAccount, signedOperation);
}
function signMessageLogic({ manifest, accounts, tracking }, walletAccountId, message, uiNavigation) {
tracking.signMessageRequested(manifest);
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.signMessageFail(manifest);
return Promise.reject(new Error(`accountId ${walletAccountId} unknown`));
}
const account = accounts.find(account => account.id === accountId);
if (account === undefined) {
tracking.signMessageFail(manifest);
return Promise.reject(new Error("account not found"));
}
let formattedMessage;
try {
if ((0, index_1.isAccount)(account)) {
formattedMessage = (0, index_2.prepareMessageToSign)(account, message);
}
else {
throw new Error("account provided should be the main one");
}
}
catch (error) {
tracking.signMessageFail(manifest);
return Promise.reject(error);
}
return uiNavigation(account, formattedMessage);
}
const bitcoinFamilyAccountGetAddressLogic = ({ manifest, accounts, tracking }, walletAccountId, derivationPath) => {
tracking.bitcoinFamilyAccountAddressRequested(manifest);
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.bitcoinFamilyAccountAddressFail(manifest);
return Promise.reject(new Error(`accountId ${walletAccountId} unknown`));
}
const account = accounts.find(account => account.id === accountId);
if (account === undefined) {
tracking.bitcoinFamilyAccountAddressFail(manifest);
return Promise.reject(new Error("account not found"));
}
if (!(0, index_1.isAccount)(account) || account.currency.family !== "bitcoin") {
tracking.bitcoinFamilyAccountAddressFail(manifest);
return Promise.reject(new Error("account requested is not a bitcoin family account"));
}
if (derivationPath) {
const path = derivationPath.split("/");
const accountNumber = Number(path[0]);
const index = Number(path[1]);
if (Number.isNaN(accountNumber) || Number.isNaN(index)) {
tracking.bitcoinFamilyAccountAddressFail(manifest);
return Promise.reject(new Error("Invalid derivationPath"));
}
const walletAccount = (0, index_3.getWalletAccount)(account);
const address = walletAccount.xpub.crypto.getAddress(walletAccount.xpub.derivationMode, walletAccount.xpub.xpub, accountNumber, index);
tracking.bitcoinFamilyAccountAddressSuccess(manifest);
return Promise.resolve(address);
}
tracking.bitcoinFamilyAccountAddressSuccess(manifest);
return Promise.resolve(account.freshAddress);
};
exports.bitcoinFamilyAccountGetAddressLogic = bitcoinFamilyAccountGetAddressLogic;
function getRelativePath(path) {
const splitPath = path.split("'/");
return splitPath[splitPath.length - 1];
}
const bitcoinFamilyAccountGetPublicKeyLogic = async ({ manifest, accounts, tracking }, walletAccountId, derivationPath) => {
tracking.bitcoinFamilyAccountPublicKeyRequested(manifest);
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.bitcoinFamilyAccountPublicKeyFail(manifest);
return Promise.reject(new Error(`accountId ${walletAccountId} unknown`));
}
const account = accounts.find(account => account.id === accountId);
if (account === undefined) {
tracking.bitcoinFamilyAccountPublicKeyFail(manifest);
return Promise.reject(new Error("account not found"));
}
if (!(0, index_1.isAccount)(account) || account.currency.family !== "bitcoin") {
tracking.bitcoinFamilyAccountPublicKeyFail(manifest);
return Promise.reject(new Error("account requested is not a bitcoin family account"));
}
const path = derivationPath?.split("/") ?? getRelativePath(account.freshAddressPath).split("/");
const accountNumber = Number(path[0]);
const index = Number(path[1]);
if (Number.isNaN(accountNumber) || Number.isNaN(index)) {
tracking.bitcoinFamilyAccountPublicKeyFail(manifest);
return Promise.reject(new Error("Invalid derivationPath"));
}
const walletAccount = (0, index_3.getWalletAccount)(account);
const publicKey = await walletAccount.xpub.crypto.getPubkeyAt(walletAccount.xpub.xpub, accountNumber, index);
tracking.bitcoinFamilyAccountPublicKeySuccess(manifest);
return publicKey.toString("hex");
};
exports.bitcoinFamilyAccountGetPublicKeyLogic = bitcoinFamilyAccountGetPublicKeyLogic;
const bitcoinFamilyAccountGetXPubLogic = ({ manifest, accounts, tracking }, walletAccountId) => {
tracking.bitcoinFamilyAccountXpubRequested(manifest);
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.bitcoinFamilyAccountXpubFail(manifest);
return Promise.reject(new Error(`accountId ${walletAccountId} unknown`));
}
const account = accounts.find(account => account.id === accountId);
if (account === undefined) {
tracking.bitcoinFamilyAccountXpubFail(manifest);
return Promise.reject(new Error("account not found"));
}
if (!(0, index_1.isAccount)(account) || account.currency.family !== "bitcoin") {
tracking.bitcoinFamilyAccountXpubFail(manifest);
return Promise.reject(new Error("account requested is not a bitcoin family account"));
}
if (!account.xpub) {
tracking.bitcoinFamilyAccountXpubFail(manifest);
return Promise.reject(new Error("account xpub not available"));
}
tracking.bitcoinFamilyAccountXpubSuccess(manifest);
return Promise.resolve(account.xpub);
};
exports.bitcoinFamilyAccountGetXPubLogic = bitcoinFamilyAccountGetXPubLogic;
const PAYMENT_INTENTION = "payment";
const bitcoinFamilyAccountGetAddressesLogic = async ({ manifest, accounts, tracking }, walletAccountId, intentions) => {
tracking.bitcoinFamilyAccountAddressesRequested(manifest);
const accountId = (0, converters_1.getAccountIdFromWalletAccountId)(walletAccountId);
if (!accountId) {
tracking.bitcoinFamilyAccountAddressesFail(manifest);
throw new Error(`accountId ${walletAccountId} unknown`);
}
const account = accounts.find(account => account.id === accountId);
if (account === undefined) {
tracking.bitcoinFamilyAccountAddressesFail(manifest);
throw new Error("account not found");
}
if (!(0, index_1.isAccount)(account) || account.currency.family !== "bitcoin") {
tracking.bitcoinFamilyAccountAddressesFail(manifest);
throw new Error("account requested is not a bitcoin family account");
}
if (intentions !== undefined &&
intentions.length > 0 &&
!intentions.includes(PAYMENT_INTENTION)) {
tracking.bitcoinFamilyAccountAddressesSuccess(manifest);
return [];
}
try {
const walletAccount = (0, index_3.getWalletAccount)(account);
const { xpub } = walletAccount;
const { path: rootPath, index: accountIndex } = walletAccount.params;
const allAddresses = await xpub.getXpubAddresses();
let maxReceiveIndex = 0;
let maxChangeIndex = 0;
const receiveIndicesToInclude = new Set([0]);
const changeIndicesToInclude = new Set();
for (const a of allAddresses) {
const hasUtxo = xpub.storage.getAddressUnspentUtxos(a).length > 0;
if (a.account === 0) {
if (a.index > maxReceiveIndex)
maxReceiveIndex = a.index;
if (hasUtxo)
receiveIndicesToInclude.add(a.index);
}
else if (a.account === 1) {
if (a.index > maxChangeIndex)
maxChangeIndex = a.index;
if (hasUtxo)
changeIndicesToInclude.add(a.index);
}
}
receiveIndicesToInclude.add(maxReceiveIndex + 1);
receiveIndicesToInclude.add(maxReceiveIndex + 2);
changeIndicesToInclude.add(maxChangeIndex + 1);
changeIndicesToInclude.add(maxChangeIndex + 2);
const buildPath = (addressAccount, addressIndex) => `m/${rootPath}/${accountIndex}'/${addressAccount}/${addressIndex}`;
const toInclude = [];
receiveIndicesToInclude.forEach(index => toInclude.push({ account: 0, index }));
changeIndicesToInclude.forEach(index => toInclude.push({ account: 1, index }));
const storedAddressByAccountIndex = new Map();
for (const a of allAddresses) {
storedAddressByAccountIndex.set(`${a.account}:${a.index}`, a.address);
}
const addressPromises = toInclude.map(({ account: addrAccount, index: addrIndex }) => {
const key = `${addrAccount}:${addrIndex}`;
const cached = storedAddressByAccountIndex.get(key);
return cached !== undefined
? Promise.resolve(cached)
: xpub.crypto.getAddress(xpub.derivationMode, xpub.xpub, addrAccount, addrIndex);
});
const publicKeyPromises = toInclude.map(({ account: addrAccount, index: addrIndex }) => xpub.crypto.getPubkeyAt(xpub.xpub, addrAccount, addrIndex));
const [addressStrs, publicKeys] = await Promise.all([
Promise.all(addressPromises),
Promise.all(publicKeyPromises),
]);
const result = toInclude.map(({ account: addrAccount, index: addrIndex }, i) => ({
address: addressStrs[i],
publicKey: publicKeys[i].toString("hex"),
path: buildPath(addrAccount, addrIndex),
intention: PAYMENT_INTENTION,
}));
tracking.bitcoinFamilyAccountAddressesSuccess(manifest);
return result;
}
catch (error) {
tracking.bitcoinFamilyAccountAddressesFail(manifest);
throw error;
}
};
exports.bitcoinFamilyAccountGetAddressesLogic = bitcoinFamilyAccountGetAddressesLogic;
function startExchangeLogic({ manifest, tracking }, exchangeType, uiNavigation) {
tracking.startExchangeRequested(manifest);
return uiNavigation(exchangeType);
}
async function completeExchangeLogic({ manifest, accounts, tracking }, { provider, fromAccountId, toAccountId, transaction, binaryPayload, signature, feesStrategy, exchangeType, swapId, rate, tokenCurrency, }, uiNavigation) {
tracking.completeExchangeRequested(manifest);
const realFromAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(fromAccountId);
if (!realFromAccountId) {
throw new Error(`accountId ${fromAccountId} unknown`);
}
// Nb get a hold of the actual accounts, and parent accounts
const fromAccount = accounts.find(a => a.id === realFromAccountId);
let toAccount;
if (toAccountId) {
const realToAccountId = (0, converters_1.getAccountIdFromWalletAccountId)(toAccountId);
if (!realToAccountId) {
throw new Error(`accountId ${toAccountId} unknown`);
}
toAccount = accounts.find(a => a.id === realToAccountId);
}
if (!fromAccount) {
throw new Error("From account not found");
}
if (exchangeType === 0x00 && !toAccount) {
// if we do a swap, a destination account must be provided
throw new Error("To account required for swap");
}
const fromParentAccount = (0, index_1.getParentAccount)(fromAccount, accounts);
const currency = tokenCurrency ? await (0, state_1.getCryptoAssetsStore)().findTokenById(tokenCurrency) : null;
const newTokenAccount = currency ? (0, index_1.makeEmptyTokenAccount)(toAccount, currency) : undefined;
const toParentAccount = toAccount ? (0, index_1.getParentAccount)(toAccount, accounts) : undefined;
const exchange = {
fromAccount,
fromParentAccount: fromAccount !== fromParentAccount ? fromParentAccount : undefined,
fromCurrency: (0, types_live_1.getCurrencyForAccount)(fromAccount),
toAccount: newTokenAccount ? newTokenAccount : toAccount,
toParentAccount: newTokenAccount ? toAccount : toParentAccount,
toCurrency: toAccount ? getToCurrency(toAccount, newTokenAccount) : undefined,
};
const accountBridge = (0, bridge_1.getAccountBridge)(fromAccount, fromParentAccount);
const mainFromAccount = (0, index_1.getMainAccount)(fromAccount, fromParentAccount);
const mainFromAccountFamily = mainFromAccount.currency.family;
const { liveTx } = (0, converters_1.getWalletAPITransactionSignFlowInfos)({
walletApiTransaction: transaction,
account: fromAccount,
});
if (liveTx.family !== mainFromAccountFamily) {
throw new Error(`Account and transaction must be from the same family. Account family: ${mainFromAccountFamily}, Transaction family: ${liveTx.family}`);
}
/**
* 'subAccountId' is used for ETH and it's ERC-20 tokens.
* This field is ignored for BTC
*/
const subAccountId = exchange.fromParentAccount ? fromAccount.id : undefined;
const bridgeTx = accountBridge.createTransaction(fromAccount);
/**
* We append the `recipient` to the tx created from `createTransaction`
* to avoid having userGasLimit reset to null for ETH txs
* cf. libs/ledger-live-common/src/families/ethereum/updateTransaction.ts
*/
const tx = accountBridge.updateTransaction({
...bridgeTx,
recipient: liveTx.recipient,
}, {
...liveTx,
feesStrategy: feesStrategy.toLowerCase(),
subAccountId,
});
return uiNavigation({
provider,
exchange,
transaction: tx,
binaryPayload,
signature,
feesStrategy,
exchangeType,
swapId,
rate,
});
}
function getToCurrency(account, tokenAccount) {
return tokenAccount ? (0, types_live_1.getCurrencyForAccount)(tokenAccount) : (0, types_live_1.getCurrencyForAccount)(account);
}
function protectStorageLogic(manifest, handler) {
return (args) => {
const { storeId } = args;
// Either the live app can access storage created by itself OR storage explitly listed in the manifest's permissions
if (storeId !== manifest.id && (!manifest.storage || !manifest.storage.includes(storeId))) {
throw new Error(`Live App "${manifest.id}" is not permitted to access storage "${storeId}".`);
}
// Forward call to original handler
return handler(args);
};
}
//# sourceMappingURL=logic.js.map