@ledgerhq/coin-stacks
Version:
Ledger Stacks Coin integration
264 lines (233 loc) • 7.71 kB
text/typescript
import { TransactionResponse } from "../types/api";
import {
extractTokenTransferTransactions,
extractSendManyTransactions,
extractContractTransactions,
} from "./transformers";
import * as api from "./api";
import * as cryptoAssets from "@ledgerhq/cryptoassets/state";
// Mock the CryptoAssets module
jest.mock("@ledgerhq/cryptoassets/state");
// Mock the API module to prevent actual network calls
jest.mock("./api", () => {
const originalModule = jest.requireActual("./api");
return {
...originalModule,
fetchFungibleTokenMetadataCached: jest.fn(),
};
});
describe("Stacks API Transformers", () => {
const mockTransactions: TransactionResponse[] = [
{
tx: {
tx_id: "0xabc123",
tx_type: "token_transfer",
// Other token transfer specific fields
},
},
{
tx: {
tx_id: "0xdef456",
tx_type: "contract_call",
contract_call: {
contract_id: "SP123.TOKEN-A",
function_name: "send-many",
},
},
},
{
tx: {
tx_id: "0xghi789",
tx_type: "contract_call",
contract_call: {
contract_id: "SP456.TOKEN-B",
function_name: "transfer",
},
post_conditions: [
{
type: "fungible",
asset: {
asset_name: "TOKEN-B",
},
},
],
},
},
{
tx: {
tx_id: "0xjkl012",
tx_type: "contract_call",
contract_call: {
contract_id: "SP789.TOKEN-C",
function_name: "transfer",
},
post_conditions: [
{
type: "fungible",
asset: {
asset_name: "TOKEN-C",
},
},
],
},
},
{
tx: {
tx_id: "0xmno345",
tx_type: "smart_contract",
// Other smart contract specific fields
},
},
{
tx: {
tx_id: "0xpqr678",
tx_type: "contract_call",
contract_call: {
contract_id: "SP456.TOKEN-B",
function_name: "other-function",
},
},
},
{
tx: {
tx_id: "0xstu901",
tx_type: "contract_call",
contract_call: {
contract_id: "SP456.TOKEN-B",
function_name: "transfer",
},
// Missing post_conditions
},
},
{
tx: {
tx_id: "0xvwx234",
tx_type: "contract_call",
contract_call: {
// Missing contract_id
function_name: "transfer",
},
post_conditions: [
{
type: "fungible",
asset: {
asset_name: "TOKEN-D",
},
},
],
},
},
] as TransactionResponse[];
describe("extractTokenTransferTransactions", () => {
it("should extract only token transfer transactions", () => {
const result = extractTokenTransferTransactions(mockTransactions);
expect(result).toHaveLength(1);
expect(result[0].tx?.tx_id).toEqual("0xabc123");
expect(result[0].tx?.tx_type).toEqual("token_transfer");
});
it("should return empty array when no token transfers exist", () => {
const noTokenTransfers = mockTransactions.filter(tx => tx.tx?.tx_type !== "token_transfer");
const result = extractTokenTransferTransactions(noTokenTransfers);
expect(result).toHaveLength(0);
});
});
describe("extractSendManyTransactions", () => {
it("should extract only send-many transactions", () => {
const result = extractSendManyTransactions(mockTransactions);
expect(result).toHaveLength(1);
expect(result[0].tx?.tx_id).toEqual("0xdef456");
expect(result[0].tx?.tx_type).toEqual("contract_call");
expect(result[0].tx?.contract_call?.function_name).toEqual("send-many");
});
it("should return empty array when no send-many transactions exist", () => {
const noSendManyTxs = mockTransactions.filter(
tx =>
!(
tx.tx?.tx_type === "contract_call" &&
tx.tx?.contract_call?.function_name === "send-many"
),
);
const result = extractSendManyTransactions(noSendManyTxs);
expect(result).toHaveLength(0);
});
});
describe("extractContractTransactions", () => {
beforeEach(() => {
// Reset all mocks before each test
jest.clearAllMocks();
// Mock CryptoAssetsStore
(cryptoAssets.getCryptoAssetsStore as jest.Mock).mockReturnValue({
findTokenById: jest.fn().mockResolvedValue(null),
findTokenByAddressInCurrency: jest.fn().mockResolvedValue(null),
});
// Mock fetchFungibleTokenMetadataCached to return empty results by default
// This prevents actual network calls and simulates tokens not found in metadata
(api.fetchFungibleTokenMetadataCached as unknown as jest.Mock).mockResolvedValue({
results: [],
});
});
it("should extract and group transfer transactions by token ID", async () => {
const result = await extractContractTransactions(mockTransactions);
// We expect two tokens with transfer function
expect(Object.keys(result)).toHaveLength(2);
// Check sp456.token-b::token-b
expect(result["sp456.token-b::token-b"]).toEqual([
expect.objectContaining({
tx: expect.objectContaining({
tx_id: "0xghi789",
}),
}),
]);
// Check sp789.token-c::token-c
expect(result["sp789.token-c::token-c"]).toEqual([
expect.objectContaining({
tx: expect.objectContaining({
tx_id: "0xjkl012",
}),
}),
]);
});
it("should exclude non-contract calls", async () => {
const result = await extractContractTransactions(mockTransactions);
// Make sure token_transfer and smart_contract are excluded
const allTxIds = Object.values(result)
.flat()
.map(tx => tx.tx?.tx_id);
expect(allTxIds).not.toContain("0xabc123"); // token_transfer
expect(allTxIds).not.toContain("0xmno345"); // smart_contract
});
it("should exclude send-many transactions", async () => {
const result = await extractContractTransactions(mockTransactions);
const allTxIds = Object.values(result)
.flat()
.map(tx => tx.tx?.tx_id);
expect(allTxIds).not.toContain("0xdef456"); // send-many
});
it("should exclude non-transfer function calls", async () => {
const result = await extractContractTransactions(mockTransactions);
const allTxIds = Object.values(result)
.flat()
.map(tx => tx.tx?.tx_id);
expect(allTxIds).not.toContain("0xpqr678"); // other-function
});
it("should handle missing post_conditions", async () => {
const result = await extractContractTransactions(mockTransactions);
const allTxIds = Object.values(result)
.flat()
.map(tx => tx.tx?.tx_id);
expect(allTxIds).not.toContain("0xstu901"); // Missing post_conditions
});
it("should handle missing contract_id", async () => {
const result = await extractContractTransactions(mockTransactions);
const allTxIds = Object.values(result)
.flat()
.map(tx => tx.tx?.tx_id);
expect(allTxIds).not.toContain("0xvwx234"); // Missing contract_id
});
it("should return empty object when no valid contract transactions exist", async () => {
const noContractTxs = mockTransactions.filter(tx => tx.tx?.tx_type !== "contract_call");
const result = await extractContractTransactions(noContractTxs);
expect(result).toEqual({});
});
});
});