@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
241 lines (215 loc) • 7 kB
text/typescript
// TODO makeMockBridge need to be exploded into families (bridge/mock) with utility code shared.
import { genOperation } from "@ledgerhq/ledger-wallet-framework/mocks/account";
import { Account, AccountBridge, CurrencyBridge, Operation } from "@ledgerhq/types-live";
import { BigNumber } from "bignumber.js";
import Prando from "prando";
import { Observable, of } from "rxjs";
import { getEnv } from "@ledgerhq/live-env";
import perFamilyMock from "../generated/mock";
import { genAccount } from "../mock/account";
import { getOperationAmountNumber } from "../operation";
import { delay } from "../promise";
import { Result } from "@ledgerhq/ledger-wallet-framework/derivation";
const MOCK_DATA_SEED = getEnv("MOCK") || "MOCK";
const broadcasted: Record<string, Operation[]> = {};
const syncTimeouts = {};
export const sync: AccountBridge<any>["sync"] = initialAccount =>
new Observable(o => {
const accountId = initialAccount.id;
const sync = () => {
const ops = broadcasted[accountId] || [];
broadcasted[accountId] = [];
o.next(acc => {
const balance = ops.reduce(
(sum, op) => sum.plus(getOperationAmountNumber(op)),
acc.balance,
);
const nextAcc = {
...acc,
blockHeight: acc.blockHeight + 1000, // make a sync move a lot by blockHeight to avoid flawky tests issue on op confirm.
lastSyncDate: new Date(),
operations: ops.concat(acc.operations.slice(0)),
pendingOperations: [],
balance,
spendableBalance: balance,
};
const perFamilyOperation = perFamilyMock[acc.currency.id];
const postSyncAccount = perFamilyOperation && perFamilyOperation.postSyncAccount;
if (postSyncAccount) return postSyncAccount(nextAcc);
return nextAcc;
});
o.complete();
};
syncTimeouts[accountId] = setTimeout(sync, 500);
return () => {
clearTimeout(syncTimeouts[accountId]);
syncTimeouts[accountId] = null;
};
});
export const broadcast: AccountBridge<any>["broadcast"] = ({ signedOperation }) =>
Promise.resolve(signedOperation.operation);
export const signOperation: AccountBridge<any>["signOperation"] = ({ account, transaction }) =>
new Observable(o => {
let cancelled = false;
async function main() {
await delay(1000);
if (cancelled) return;
for (let i = 0; i <= 1; i += 0.1) {
o.next({
type: "device-streaming",
progress: i,
index: i,
total: 10,
});
await delay(300);
}
o.next({
type: "device-signature-requested",
});
await delay(2000);
if (cancelled) return;
o.next({
type: "device-signature-granted",
});
const rng = new Prando("");
const op = genOperation(account, account, account.operations, rng);
op.type = "OUT";
op.value = transaction.amount;
op.blockHash = null;
op.blockHeight = null;
op.senders = [account.freshAddress];
op.recipients = [transaction.recipient];
op.blockHeight = account.blockHeight;
op.date = new Date();
await delay(1000);
if (cancelled) return;
broadcasted[account.id] = (broadcasted[account.id] || []).concat(op);
o.next({
type: "signed",
signedOperation: {
operation: { ...op },
signature: "",
},
});
}
main().then(
() => o.complete(),
e => o.error(e),
);
return () => {
cancelled = true;
};
});
export { isInvalidRecipient } from "./validateAddress";
const subtractOneYear = date =>
new Date(new Date(date).setFullYear(new Date(date).getFullYear() - 1));
export const scanAccounts: CurrencyBridge["scanAccounts"] = ({ currency }) =>
new Observable(o => {
let unsubscribed = false;
async function job() {
// TODO offer a way to mock a failure
const nbAccountToGen = 3;
for (let i = 0; i < nbAccountToGen && !unsubscribed; i++) {
const isLast = i === 2;
await delay(500);
const account = genAccount(`${MOCK_DATA_SEED}_${currency.id}_${i}`, {
operationsSize: isLast ? 0 : 100,
currency,
subAccountsCount: isLast ? 0 : undefined,
bandwidth: !isLast,
});
account.index = i;
account.operations = isLast
? []
: account.operations.map(operation => ({
...operation,
date: subtractOneYear(operation.date),
}));
account.used = isLast ? false : account.used;
if (isLast) {
account.spendableBalance = account.balance = new BigNumber(0);
}
const perFamilyOperation = perFamilyMock[currency.id];
const postScanAccount = perFamilyOperation && perFamilyOperation.postScanAccount;
if (postScanAccount)
postScanAccount(account, {
isEmpty: isLast,
});
if (!unsubscribed)
o.next({
type: "discovered",
account,
});
}
if (!unsubscribed) o.complete();
}
job();
return () => {
unsubscribed = true;
};
});
export const makeAccountBridgeReceive: () => (
account: Account,
arg1: {
verify?: boolean;
deviceId: string;
subAccountId?: string;
},
) => Observable<Result> = () => account =>
of({
address: account.freshAddress,
path: account.freshAddressPath,
publicKey: "mockPublickKey", // We could probably keep the publicKey in `account.freshPublicKey`
});
export const signRawOperation: AccountBridge<any>["signRawOperation"] = ({ account }) =>
new Observable(o => {
let cancelled = false;
async function main() {
await delay(1000);
if (cancelled) return;
for (let i = 0; i <= 1; i += 0.1) {
o.next({
type: "device-streaming",
progress: i,
index: i,
total: 10,
});
await delay(300);
}
o.next({
type: "device-signature-requested",
});
await delay(2000);
if (cancelled) return;
o.next({
type: "device-signature-granted",
});
const rng = new Prando("");
const op = genOperation(account, account, account.operations, rng);
op.type = "OUT";
op.value = new BigNumber(0);
op.blockHash = null;
op.blockHeight = null;
op.senders = [account.freshAddress];
op.recipients = [];
op.blockHeight = account.blockHeight;
op.date = new Date();
await delay(1000);
if (cancelled) return;
broadcasted[account.id] = (broadcasted[account.id] || []).concat(op);
o.next({
type: "signed",
signedOperation: {
operation: { ...op },
signature: "",
},
});
}
main().then(
() => o.complete(),
e => o.error(e),
);
return () => {
cancelled = true;
};
});