@ledgerhq/coin-algorand
Version:
Ledger Algorand Coin integration
131 lines (115 loc) • 3.65 kB
text/typescript
import type {
TransactionIntent,
CraftedTransaction,
FeeEstimation,
} from "@ledgerhq/coin-module-framework/api/index";
import { isSendTransactionIntent } from "@ledgerhq/coin-module-framework/utils";
import type { Transaction as AlgoTx } from "algosdk";
import {
base64ToBytes,
encodeMsgpack,
makeAssetTransferTxnWithSuggestedParamsFromObject,
makePaymentTxnWithSuggestedParamsFromObject,
} from "algosdk";
import { getTransactionParams } from "../network";
import type { AlgorandMemo } from "../types";
import { estimateFees } from "./estimateFees";
export type CraftedAlgorandTransaction = {
serializedTransaction: string;
txPayload: Record<string, unknown>;
};
/**
* Craft an unsigned Algorand transaction
* @param input - Transaction parameters
* @returns Serialized unsigned transaction and payload
*/
export async function craftTransaction(input: {
sender: string;
recipient: string;
amount: bigint;
memo?: string | undefined;
assetId?: string | undefined;
fees?: bigint | undefined;
}): Promise<CraftedAlgorandTransaction> {
const { sender, recipient, amount, memo, assetId, fees } = input;
const note = memo ? new TextEncoder().encode(memo) : undefined;
const params = await getTransactionParams();
if (typeof fees === "bigint") {
params.fee = 0;
params.minFee = Number(fees);
}
const suggestedParams = {
...params,
firstValid: params.lastRound,
lastValid: params.lastRound + 1000,
genesisHash: base64ToBytes(params.genesisHash),
};
const algoTxn: AlgoTx = assetId
? makeAssetTransferTxnWithSuggestedParamsFromObject({
sender,
receiver: recipient,
amount: Number(amount),
assetIndex: Number(assetId),
suggestedParams,
...(note ? { note } : {}),
})
: makePaymentTxnWithSuggestedParamsFromObject({
sender,
receiver: recipient,
amount: Number(amount),
suggestedParams,
...(note ? { note } : {}),
});
const msgPackEncoded = encodeMsgpack(algoTxn);
const serializedTransaction = Buffer.from(msgPackEncoded).toString("hex");
return {
serializedTransaction,
txPayload: algoTxn as unknown as Record<string, unknown>,
};
}
/**
* Craft an opt-in transaction for an ASA token
* @param sender - The sender address (also recipient for opt-in)
* @param assetId - The ASA token ID to opt into
* @param fees - Optional fee override
* @returns Serialized unsigned transaction
*/
export async function craftOptInTransaction(
sender: string,
assetId: string,
fees?: bigint,
): Promise<CraftedAlgorandTransaction> {
return craftTransaction({
sender,
recipient: sender, // Opt-in sends to self
amount: 0n,
assetId,
fees,
});
}
export async function craftApiTransaction(
transactionIntent: TransactionIntent<AlgorandMemo>,
customFees?: FeeEstimation,
): Promise<CraftedTransaction> {
if (!isSendTransactionIntent(transactionIntent)) {
throw new Error("Only send transaction intent is supported");
}
const fees = customFees?.value ?? (await estimateFees()).value;
const memo = transactionIntent.memo?.type === "string" ? transactionIntent.memo.value : undefined;
const assetId =
transactionIntent.asset.type === "asa" ? transactionIntent.asset.assetReference : undefined;
const result = await craftTransaction({
sender: transactionIntent.sender,
recipient: transactionIntent.recipient,
amount: transactionIntent.amount,
memo,
assetId,
fees,
});
return {
transaction: result.serializedTransaction,
details: {
txPayload: result.txPayload,
},
};
}