Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(coin:tezos): handle missing recipients or senders cleanly #9126

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/coin-modules/coin-tezos/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache",
"lint:fix": "pnpm lint --fix",
"test": "jest",
"test-watch": "jest --watch",
"test-integ": "jest --config=jest.integ.config.js",
"unimported": "unimported"
},
Expand Down
101 changes: 101 additions & 0 deletions libs/coin-modules/coin-tezos/src/logic/listOperations.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { listOperations } from "./listOperations";
import { APIDelegationType, APITransactionType } from "../network/types";

const mockNetworkGetTransactions = jest.fn();
jest.mock("../network", () => ({
tzkt: {
getAccountOperations: async () => {
return mockNetworkGetTransactions();
},
},
}));

describe("listOperations", () => {
afterEach(() => {
mockNetworkGetTransactions.mockClear();
});

it("should return no operations", async () => {
// Given
mockNetworkGetTransactions.mockResolvedValue([]);
// When
const [results, token] = await listOperations("any address", {});
// Then
expect(results).toEqual([]);
expect(token).toEqual("");
});

const someDestinationAddress = "tz3Vq38qYD3GEbWcXHMLt5PaASZrkDtEiA8D";
const someSenderAddress = "tz2CVMDVA16dD9A7kpWym2ptGDhs5zUhwWXr";
const delegate: APIDelegationType = {
type: "delegation",
id: 111,
level: 2702551,
block: "BMJ1ZQ6",
timestamp: "2022-09-12T01:36:59Z",
amount: 724846,
sender: {
address: someSenderAddress,
},
counter: 65214462,
prevDelegate: {
address: someDestinationAddress,
},
newDelegate: null,
};

const undelegate: APIDelegationType = {
...delegate,
id: 222,
prevDelegate: null,
newDelegate: { address: someDestinationAddress },
};

const transfer: APITransactionType = {
...delegate,
id: 333,
initiator: null,
type: "transaction",
target: { address: someDestinationAddress },
};

it.each([undelegate, delegate, transfer])(
"should return operation with proper recipient list",
async operation => {
// Given
mockNetworkGetTransactions.mockResolvedValue([operation]);
// When
const [results, token] = await listOperations("any address", {});
// Then
expect(results.length).toEqual(1);
expect(results[0].recipients).toEqual([someDestinationAddress]);
expect(token).toEqual(JSON.stringify(operation.id));
},
);

it.each([
{ ...undelegate, newDelegate: null, prevDelegate: null },
{ ...transfer, target: null },
])("should return empty recipient list when no target can be found", async operation => {
// Given
mockNetworkGetTransactions.mockResolvedValue([operation]);
// When
const [results, token] = await listOperations("any address", {});
// Then
expect(results.length).toEqual(1);
expect(results[0].recipients).toEqual([]);
expect(token).toEqual(JSON.stringify(operation.id));
});

it("should return empty sender list when no sender can be found", async () => {
// Given
const operation = { ...undelegate, sender: null };
mockNetworkGetTransactions.mockResolvedValue([operation]);
// When
const [results, token] = await listOperations("any address", {});
// Then
expect(results.length).toEqual(1);
expect(results[0].senders).toEqual([]);
expect(token).toEqual(JSON.stringify(operation.id));
});
});
30 changes: 23 additions & 7 deletions libs/coin-modules/coin-tezos/src/logic/listOperations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { tzkt } from "../network";
import { log } from "@ledgerhq/logs";
import {
type APIDelegationType,
type APITransactionType,
Expand Down Expand Up @@ -33,25 +34,40 @@ export async function listOperations(
}
const operations = await tzkt.getAccountOperations(address, options);
const lastOperation = operations.slice(-1)[0];
const nextId = lastOperation ? JSON.stringify(lastOperation?.id) : "";
const nextToken = lastOperation ? JSON.stringify(lastOperation?.id) : "";
return [
operations
.filter(op => isAPITransactionType(op) || isAPIDelegationType(op))
.reduce((acc, op) => acc.concat(convertOperation(address, op)), [] as Operation[]),
nextId,
nextToken,
];
}

// note that "initiator" of APITransactionType is never used in the conversion
function convertOperation(
address: string,
operation: APITransactionType | APIDelegationType,
): Operation {
const { amount, hash, storageFee, sender, timestamp, type, counter } = operation;
let targetAddress = "";
if (isAPITransactionType(operation) && operation.target) {
targetAddress = operation.target.address;

let targetAddress = undefined;
if (isAPITransactionType(operation)) {
targetAddress = operation?.target?.address;
} else if (isAPIDelegationType(operation)) {
targetAddress = operation?.newDelegate?.address || operation?.prevDelegate?.address;
}

const recipients = [];
if (!targetAddress) {
log("coin:tezos", "(logic/operations): No target address found for operation", operation);
} else {
recipients.push(targetAddress);
}

const senders = sender?.address ? [sender.address] : [];

return {
// hash id defined nullable in the tzkt API, but I wonder when it would be null ?
hash: hash ?? "",
address,
type: type,
Expand All @@ -63,8 +79,8 @@ function convertOperation(
height: operation.level,
time: new Date(operation.timestamp),
},
senders: [sender?.address ?? ""],
recipients: [targetAddress],
senders: senders,
recipients: recipients,
date: new Date(timestamp),
transactionSequenceNumber: counter,
};
Expand Down
Loading