@ledgerhq/coin-canton
Version:
242 lines (215 loc) • 7.6 kB
text/typescript
import { getCryptoCurrencyById } from "@ledgerhq/ledger-wallet-framework/currencies";
import { AccountShapeInfo } from "@ledgerhq/ledger-wallet-framework/bridge/jsHelpers";
import {
getDerivationModesForCurrency,
getDerivationScheme,
runDerivationScheme,
} from "@ledgerhq/ledger-wallet-framework/derivation";
import { Operation } from "@ledgerhq/types-live";
import BigNumber from "bignumber.js";
import coinConfig from "../config";
import * as gateway from "../network/gateway";
import { generateMockKeyPair, createMockSigner } from "../test/cantonTestUtils";
import { CantonAccount } from "../types";
import { makeGetAccountShape } from "./sync";
const TEST_ADDRESS =
"b6400f93ea1c74aea86be39b0ccc846fc5de01f12b2ad0d7c31848d6fb6eb6d9::1220c81315e2bf2524a9141bcc6cbf19b61c151e0dcaa95343c0ccf53aed7415c4ec";
const currency = getCryptoCurrencyById("canton_network");
const derivationMode = getDerivationModesForCurrency(currency)[0];
const derivationPath = runDerivationScheme(
getDerivationScheme({ derivationMode, currency }),
currency,
{
account: 0,
},
);
const xpub = TEST_ADDRESS;
const ACCOUNT_SHAPE_INFO: AccountShapeInfo<CantonAccount> = {
address: TEST_ADDRESS,
currency,
derivationMode,
derivationPath,
index: 0,
initialAccount: {
xpub,
} as CantonAccount,
};
const TIMEOUT = 30000;
// Mock signer context for testing
const keyPair = generateMockKeyPair();
const mockSigner = createMockSigner(keyPair);
const mockSignerContext = jest.fn().mockImplementation((_deviceId, callback) => {
return callback(mockSigner);
});
describe.skip("sync (devnet)", () => {
beforeAll(async () => {
coinConfig.setCoinConfig(() => ({
gatewayUrl: "https://canton-gateway-devnet.api.live.ledger-test.com",
useGateway: true,
networkType: "devnet",
nativeInstrumentId: "Amulet",
status: {
type: "active",
},
}));
});
describe("makeGetAccountShape", () => {
it(
"should fetch account shape for a valid address",
async () => {
const getAccountShape = makeGetAccountShape(mockSignerContext);
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
expect(result.id).toEqual(expect.any(String));
expect(result.xpub).toBe(TEST_ADDRESS);
expect(result.blockHeight).toBeGreaterThan(0);
expect(result.balance?.toNumber()).toBeGreaterThanOrEqual(0);
expect(result.spendableBalance?.toNumber()).toBeGreaterThanOrEqual(0);
expect(result.operations).toBeInstanceOf(Array);
expect(result.operationsCount).toBeGreaterThanOrEqual(0);
expect(result.spendableBalance?.toNumber()).toBeLessThanOrEqual(
result.balance?.toNumber() || 0,
);
},
TIMEOUT,
);
it(
"should handle address with colons correctly",
async () => {
const getAccountShape = makeGetAccountShape(mockSignerContext);
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
expect(result.xpub).toContain("::");
},
TIMEOUT,
);
it(
"should merge operations correctly with initial account",
async () => {
const getAccountShape = makeGetAccountShape(mockSignerContext);
const operations: Operation[] = [
{
id: "test-op-1",
hash: "test-hash-1",
accountId: "test-account",
type: "OUT" as const,
value: new BigNumber(1000000),
fee: new BigNumber(100000),
blockHash: "block-hash-1",
blockHeight: 100,
senders: [TEST_ADDRESS],
recipients: ["recipient-1"],
date: new Date("2023-01-01"),
transactionSequenceNumber: BigNumber(100),
extra: { uid: "uid-1" },
},
];
const result = await getAccountShape(
{
...ACCOUNT_SHAPE_INFO,
initialAccount: {
xpub,
operations,
cantonResources: {
isOnboarded: true,
instrumentUtxoCounts: {},
pendingTransferProposals: [],
},
} as unknown as CantonAccount,
},
{ paginationConfig: {} },
);
expect(result.operationsCount).toBeGreaterThanOrEqual(1);
expect(result.operations?.find(op => op.id === "test-op-1")).toMatchObject({
id: "test-op-1",
});
},
TIMEOUT,
);
it(
"should take locked balance into account when calculating spendable balance",
async () => {
const mockGetBalance = jest.spyOn(gateway, "getBalance");
mockGetBalance.mockResolvedValue([
{
instrument_id: "Amulet",
admin_id: "native-admin",
amount: "500",
locked: false,
utxo_count: 1,
},
{
instrument_id: "LockedAmulet",
admin_id: "native-admin",
amount: "1000000",
locked: true,
utxo_count: 1,
},
]);
const getAccountShape = makeGetAccountShape(mockSignerContext);
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
expect(result.balance?.toNumber()).toBe(1000500);
expect(result.spendableBalance?.toNumber()).toBe(500);
mockGetBalance.mockRestore();
},
TIMEOUT,
);
it(
"should call getOperations with correct cursor based on initial account",
async () => {
const mockGetOperations = jest.spyOn(gateway, "getOperations");
const operation: Operation = {
id: "test-op-1",
hash: "test-hash-1",
accountId: "test-account",
type: "OUT" as const,
value: new BigNumber(1000000),
fee: new BigNumber(100000),
blockHash: "block-hash-1",
blockHeight: 100,
senders: [TEST_ADDRESS],
recipients: ["recipient-1"],
date: new Date("2023-01-01"),
transactionSequenceNumber: BigNumber(100),
extra: { uid: "uid-1" },
};
const initialAccount = {
xpub,
operations: [operation],
cantonResources: {
isOnboarded: true,
instrumentUtxoCounts: {},
pendingTransferProposals: [],
},
} as unknown as CantonAccount;
const getAccountShape = makeGetAccountShape(mockSignerContext);
const result = await getAccountShape(
{
...ACCOUNT_SHAPE_INFO,
initialAccount,
},
{ paginationConfig: {} },
);
expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
cursor: (operation.blockHeight || 0) + 1,
limit: 100,
});
expect(result.operationsCount).toBeGreaterThan(1);
},
TIMEOUT,
);
it(
"should call getOperations with cursor 0 when no initial account",
async () => {
const mockGetOperations = jest.spyOn(gateway, "getOperations");
const getAccountShape = makeGetAccountShape(mockSignerContext);
const result = await getAccountShape(ACCOUNT_SHAPE_INFO, { paginationConfig: {} });
expect(result.operationsCount).toBeGreaterThanOrEqual(1);
expect(mockGetOperations).toHaveBeenCalledWith(currency, TEST_ADDRESS, {
cursor: 0,
limit: 100,
});
mockGetOperations.mockRestore();
},
TIMEOUT,
);
});
});