@ledgerhq/coin-canton
Version:
117 lines (104 loc) • 3.95 kB
text/typescript
import { FeeNotLoaded } from "@ledgerhq/ledger-wallet-framework/errors";
import { decodeAccountId } from "@ledgerhq/ledger-wallet-framework/account";
import { encodeOperationId } from "@ledgerhq/ledger-wallet-framework/operation";
import { SignerContext } from "@ledgerhq/ledger-wallet-framework/signer";
import { AccountBridge, Operation } from "@ledgerhq/types-live";
import BigNumber from "bignumber.js";
import { Observable } from "rxjs";
import { combine, craftTransaction } from "../common-logic";
import { signTransaction } from "../common-logic/transaction/sign";
import { CantonSigner, Transaction } from "../types";
export const buildSignOperation =
(signerContext: SignerContext<CantonSigner>): AccountBridge<Transaction>["signOperation"] =>
({ account, deviceId, transaction }) =>
new Observable(o => {
async function main() {
const { fee } = transaction;
if (!fee) throw new FeeNotLoaded();
try {
// o observables allows to define steps of the signing process with the device
o.next({
type: "device-signature-requested",
});
let transactionHash = "";
const signature = await signerContext(deviceId, async signer => {
const { id, freshAddressPath: derivationPath, xpub } = account;
const address = xpub ?? decodeAccountId(id).xpubOrAddress;
const params: {
recipient?: string;
amount: BigNumber;
tokenId: string;
expireInSeconds: number;
memo?: string;
instrumentAdmin?: string;
} = {
recipient: transaction.recipient,
amount: transaction.amount,
expireInSeconds: transaction.expireInSeconds ?? 24 * 60 * 60, // Default to 1 day
tokenId: transaction.tokenId,
};
if (transaction.instrumentAdmin) {
params.instrumentAdmin = transaction.instrumentAdmin;
}
if (transaction.memo) {
params.memo = transaction.memo;
}
const { nativeTransaction, serializedTransaction, hash } = await craftTransaction(
account.currency,
{
address,
},
params,
);
transactionHash = hash || "";
const signatureResult = await signTransaction(
signer,
derivationPath,
nativeTransaction,
);
return combine(
serializedTransaction,
`${signatureResult.signature}__PARTY__${address}`,
);
});
o.next({
type: "device-signature-granted",
});
// We create an optimistic operation here, the framework will then replace this transaction with the one returned by the indexer
const hash = transactionHash;
const operation: Operation = {
id: encodeOperationId(account.id, hash, "OUT"),
hash,
accountId: account.id,
type: "OUT",
value: transaction.amount,
fee,
blockHash: null,
blockHeight: null,
senders: [account.freshAddress],
recipients: [transaction.recipient],
date: new Date(),
extra: {},
};
o.next({
type: "signed",
signedOperation: {
operation,
signature,
},
});
} catch (e) {
if (e instanceof Error) {
const errorMessage =
(e as Error & { data?: { resultMessage?: string } })?.data?.resultMessage ||
e.message;
throw new Error(errorMessage);
}
throw e;
}
}
main().then(
() => o.complete(),
e => o.error(e),
);
});