Skip to content

Commit

Permalink
Merge pull request #6172 from LedgerHQ/support/remove-api-injection
Browse files Browse the repository at this point in the history
Remove api injection in CoinModule
  • Loading branch information
hedi-edelbloute authored Feb 23, 2024
2 parents 52d69c8 + e28d307 commit b4f6b33
Show file tree
Hide file tree
Showing 40 changed files with 1,300 additions and 1,489 deletions.
8 changes: 8 additions & 0 deletions .changeset/lovely-cheetahs-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@ledgerhq/live-common": minor
"@ledgerhq/coin-algorand": minor
"@ledgerhq/coin-polkadot": minor
"@ledgerhq/live-network": minor
---

Remove API injection in coin-module
1 change: 1 addition & 0 deletions libs/coin-algorand/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@ledgerhq/devices": "workspace:^",
"@ledgerhq/errors": "workspace:^",
"@ledgerhq/live-env": "workspace:^",
"@ledgerhq/live-network": "workspace:^",
"@ledgerhq/live-promise": "workspace:^",
"@ledgerhq/types-cryptoassets": "workspace:^",
"@ledgerhq/types-live": "workspace:^",
Expand Down
30 changes: 9 additions & 21 deletions libs/coin-algorand/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NetworkRequestCall } from "@ledgerhq/coin-framework/network";
import network from "@ledgerhq/live-network/network";
import { AlgoAccount, AlgoTransactionParams } from "./algodv2.types";

import { broadcastTransaction, getAccount, getTransactionParams } from "./algodv2";
Expand All @@ -9,26 +9,14 @@ import { AlgoTransaction } from "./indexer.types";
export * from "./algodv2.types";
export * from "./indexer.types";

export class AlgorandAPI {
network: NetworkRequestCall;
export default {
getAccount: async (address: string): Promise<AlgoAccount> => getAccount(network)(address),

constructor(network: NetworkRequestCall) {
this.network = network;
}
getTransactionParams: async (): Promise<AlgoTransactionParams> => getTransactionParams(network)(),

async getAccount(address: string): Promise<AlgoAccount> {
return getAccount(this.network)(address);
}
broadcastTransaction: async (payload: Buffer): Promise<string> =>
broadcastTransaction(network)(payload),

async getTransactionParams(): Promise<AlgoTransactionParams> {
return getTransactionParams(this.network)();
}

async broadcastTransaction(payload: Buffer): Promise<string> {
return broadcastTransaction(this.network)(payload);
}

async getAccountTransactions(address: string, startAt?: number): Promise<AlgoTransaction[]> {
return getAccountTransactions(this.network)(address, startAt);
}
}
getAccountTransactions: async (address: string, startAt?: number): Promise<AlgoTransaction[]> =>
getAccountTransactions(network)(address, startAt),
};
25 changes: 8 additions & 17 deletions libs/coin-algorand/src/bridge/js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import {
makeSync,
} from "@ledgerhq/coin-framework/bridge/jsHelpers";
import { SignerContext } from "@ledgerhq/coin-framework/signer";
import type { NetworkRequestCall } from "@ledgerhq/coin-framework/network";

import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live";
import { AlgorandAPI } from "../api";
import resolver from "../hw-getAddress";
import { initAccount } from "../initAccount";
import { broadcast } from "../js-broadcast";
Expand All @@ -18,7 +16,7 @@ import { estimateMaxSpendable } from "../js-estimateMaxSpendable";
import { getTransactionStatus } from "../js-getTransactionStatus";
import prepareTransaction from "../js-prepareTransaction";
import { buildSignOperation } from "../js-signOperation";
import { makeGetAccountShape } from "../js-synchronization";
import { getAccountShape } from "../js-synchronization";
import {
assignFromAccountRaw,
assignToAccountRaw,
Expand All @@ -30,12 +28,9 @@ import { AlgorandAddress, AlgorandSignature, AlgorandSigner } from "../signer";

export function buildCurrencyBridge(
signerContext: SignerContext<AlgorandSigner, AlgorandAddress | AlgorandSignature>,
network: NetworkRequestCall,
): CurrencyBridge {
const algorandAPI = new AlgorandAPI(network);
const getAddress = resolver(signerContext);

const getAccountShape = makeGetAccountShape(algorandAPI);
const scanAccounts = makeScanAccounts({
getAccountShape,
getAddressFn: getAddress,
Expand All @@ -50,40 +45,36 @@ export function buildCurrencyBridge(

export function buildAccountBridge(
signerContext: SignerContext<AlgorandSigner, AlgorandAddress | AlgorandSignature>,
network: NetworkRequestCall,
): AccountBridge<Transaction> {
const algorandAPI = new AlgorandAPI(network);
const getAddress = resolver(signerContext);

const receive = makeAccountBridgeReceive(getAddressWrapper(getAddress));
const signOperation = buildSignOperation(signerContext, algorandAPI);
const getAccountShape = makeGetAccountShape(algorandAPI);
const signOperation = buildSignOperation(signerContext);
const sync = makeSync({ getAccountShape });

return {
createTransaction,
updateTransaction: defaultUpdateTransaction,
prepareTransaction: prepareTransaction(algorandAPI),
getTransactionStatus: getTransactionStatus(algorandAPI),
prepareTransaction,
getTransactionStatus,
sync,
receive,
assignToAccountRaw,
assignFromAccountRaw,
initAccount,
signOperation,
broadcast: broadcast(algorandAPI),
estimateMaxSpendable: estimateMaxSpendable(algorandAPI),
broadcast,
estimateMaxSpendable,
fromOperationExtraRaw,
toOperationExtraRaw,
};
}

export function createBridges(
signerContext: SignerContext<AlgorandSigner, AlgorandAddress | AlgorandSignature>,
network: NetworkRequestCall,
) {
return {
currencyBridge: buildCurrencyBridge(signerContext, network),
accountBridge: buildAccountBridge(signerContext, network),
currencyBridge: buildCurrencyBridge(signerContext),
accountBridge: buildAccountBridge(signerContext),
};
}
103 changes: 52 additions & 51 deletions libs/coin-algorand/src/buildTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,68 +10,69 @@ import {
} from "algosdk";

import type { Account } from "@ledgerhq/types-live";
import { AlgorandAPI } from "./api";
import algorandAPI from "./api";
import { extractTokenId } from "./tokens";
import type { Transaction } from "./types";

export const buildTransactionPayload =
(algorandAPI: AlgorandAPI) =>
async (account: Account, transaction: Transaction): Promise<AlgoTransactionPayload> => {
const { amount, recipient, mode, memo, assetId, subAccountId } = transaction;
const subAccount = subAccountId
? account.subAccounts && account.subAccounts.find(t => t.id === subAccountId)
: null;
export const buildTransactionPayload = async (
account: Account,
transaction: Transaction,
): Promise<AlgoTransactionPayload> => {
const { amount, recipient, mode, memo, assetId, subAccountId } = transaction;
const subAccount = subAccountId
? account.subAccounts && account.subAccounts.find(t => t.id === subAccountId)
: null;

const note = memo ? new TextEncoder().encode(memo) : undefined;
const note = memo ? new TextEncoder().encode(memo) : undefined;

const params = await algorandAPI.getTransactionParams();
const params = await algorandAPI.getTransactionParams();

let algoTxn: AlgoTransaction;
if (subAccount || (assetId && mode === "optIn")) {
const targetAssetId =
subAccount && subAccount.type === "TokenAccount"
? extractTokenId(subAccount.token.id)
: assetId
? extractTokenId(assetId)
: "";
let algoTxn: AlgoTransaction;
if (subAccount || (assetId && mode === "optIn")) {
const targetAssetId =
subAccount && subAccount.type === "TokenAccount"
? extractTokenId(subAccount.token.id)
: assetId
? extractTokenId(assetId)
: "";

if (!targetAssetId) {
throw new Error("Token Asset Id not found");
}

algoTxn = makeAssetTransferTxnWithSuggestedParams(
account.freshAddress,
recipient,
undefined,
undefined,
amount.toNumber(),
note,
Number(targetAssetId),
params,
undefined,
);
} else {
algoTxn = makePaymentTxnWithSuggestedParams(
account.freshAddress,
recipient,
amount.toNumber(),
undefined,
note,
params,
);
if (!targetAssetId) {
throw new Error("Token Asset Id not found");
}

// Bit of safety: set tx validity to the next 1000 blocks
algoTxn.firstRound = params.lastRound;
algoTxn.lastRound = params.lastRound + 1000;
algoTxn = makeAssetTransferTxnWithSuggestedParams(
account.freshAddress,
recipient,
undefined,
undefined,
amount.toNumber(),
note,
Number(targetAssetId),
params,
undefined,
);
} else {
algoTxn = makePaymentTxnWithSuggestedParams(
account.freshAddress,
recipient,
amount.toNumber(),
undefined,
note,
params,
);
}

// Flaw in the SDK: payload isn't sorted, but it needs to be for msgPack encoding
const sorted = Object.fromEntries(
Object.entries(algoTxn.get_obj_for_encoding()).sort(),
) as AlgoTransactionPayload;
// Bit of safety: set tx validity to the next 1000 blocks
algoTxn.firstRound = params.lastRound;
algoTxn.lastRound = params.lastRound + 1000;

return sorted;
};
// Flaw in the SDK: payload isn't sorted, but it needs to be for msgPack encoding
const sorted = Object.fromEntries(
Object.entries(algoTxn.get_obj_for_encoding()).sort(),
) as AlgoTransactionPayload;

return sorted;
};

export const encodeToSign = (payload: AlgoTransactionPayload): string => {
const msgPackEncoded = msgpackEncode(payload);
Expand Down
18 changes: 10 additions & 8 deletions libs/coin-algorand/src/js-broadcast.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation";
import type { Operation, SignedOperation } from "@ledgerhq/types-live";
import { AlgorandAPI } from "./api";
import algorandAPI from "./api";

/**
* Broadcast a signed transaction
* @param {signature: string, operation: string} signedOperation
*/
export const broadcast =
(algorandAPI: AlgorandAPI) =>
async ({ signedOperation }: { signedOperation: SignedOperation }): Promise<Operation> => {
const { signature, operation } = signedOperation;
const hash = await algorandAPI.broadcastTransaction(Buffer.from(signature, "hex"));
return patchOperationWithHash(operation, hash);
};
export const broadcast = async ({
signedOperation,
}: {
signedOperation: SignedOperation;
}): Promise<Operation> => {
const { signature, operation } = signedOperation;
const hash = await algorandAPI.broadcastTransaction(Buffer.from(signature, "hex"));
return patchOperationWithHash(operation, hash);
};
79 changes: 38 additions & 41 deletions libs/coin-algorand/src/js-estimateMaxSpendable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,55 +2,52 @@ import { getMainAccount } from "@ledgerhq/coin-framework/account/index";
import { getAbandonSeedAddress } from "@ledgerhq/cryptoassets";
import type { Account, AccountLike } from "@ledgerhq/types-live";
import { BigNumber } from "bignumber.js";
import { AlgorandAPI } from "./api";
import createTransaction from "./js-createTransaction";
import { getEstimatedFees } from "./js-getFeesForTransaction";
import { computeAlgoMaxSpendable } from "./logic";
import type { AlgorandAccount, AlgorandTransaction } from "./types";

export const estimateMaxSpendable =
(algorandAPI: AlgorandAPI) =>
async ({
account,
parentAccount,
transaction,
}: {
account: AccountLike;
parentAccount?: Account | null | undefined;
transaction?: AlgorandTransaction | null | undefined;
}): Promise<BigNumber> => {
const mainAccount = getMainAccount(account, parentAccount);
const { algorandResources } = mainAccount as AlgorandAccount;
if (!algorandResources) {
throw new Error("Algorand account expected");
}
export const estimateMaxSpendable = async ({
account,
parentAccount,
transaction,
}: {
account: AccountLike;
parentAccount?: Account | null | undefined;
transaction?: AlgorandTransaction | null | undefined;
}): Promise<BigNumber> => {
const mainAccount = getMainAccount(account, parentAccount);
const { algorandResources } = mainAccount as AlgorandAccount;
if (!algorandResources) {
throw new Error("Algorand account expected");
}

const tx = {
...createTransaction(),
subAccountId: account.type === "Account" ? null : account.id,
...transaction,
recipient: transaction?.recipient || getAbandonSeedAddress(mainAccount.currency.id),
useAllAmount: true,
};
const tx = {
...createTransaction(),
subAccountId: account.type === "Account" ? null : account.id,
...transaction,
recipient: transaction?.recipient || getAbandonSeedAddress(mainAccount.currency.id),
useAllAmount: true,
};

const tokenAccount =
tx.subAccountId &&
mainAccount.subAccounts &&
mainAccount.subAccounts.find(ta => ta.id === tx.subAccountId);
const tokenAccount =
tx.subAccountId &&
mainAccount.subAccounts &&
mainAccount.subAccounts.find(ta => ta.id === tx.subAccountId);

if (tokenAccount) {
return tokenAccount.balance;
} else {
const fees = await getEstimatedFees(algorandAPI)(mainAccount, tx);
if (tokenAccount) {
return tokenAccount.balance;
} else {
const fees = await getEstimatedFees(mainAccount, tx);

let maxSpendable = computeAlgoMaxSpendable({
accountBalance: mainAccount.balance,
nbAccountAssets: algorandResources.nbAssets,
mode: tx.mode,
});
let maxSpendable = computeAlgoMaxSpendable({
accountBalance: mainAccount.balance,
nbAccountAssets: algorandResources.nbAssets,
mode: tx.mode,
});

maxSpendable = maxSpendable.minus(fees);
maxSpendable = maxSpendable.minus(fees);

return maxSpendable.gte(0) ? maxSpendable : new BigNumber(0);
}
};
return maxSpendable.gte(0) ? maxSpendable : new BigNumber(0);
}
};
Loading

0 comments on commit b4f6b33

Please sign in to comment.