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

[LIVE-10494] Feature - Add internal transactions to EVM chains #5744

Merged
merged 8 commits into from
Dec 22, 2023
Merged
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
5 changes: 5 additions & 0 deletions .changeset/red-sloths-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ledgerhq/coin-evm": minor
---

Add support for internal transactions in transactions' history
1 change: 1 addition & 0 deletions libs/coin-evm/src/__tests__/fixtures/common.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const makeOperation = (partialOp?: Partial<Operation>): Operation => {
date: new Date(),
nftOperations: [],
subOperations: [],
internalOperations: [],
extra: {},
...partialOp,
});
Expand Down
51 changes: 51 additions & 0 deletions libs/coin-evm/src/__tests__/fixtures/etherscan.fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,54 @@ export const etherscanERC1155Operations = [
confirmations: "870255",
},
];

export const etherscanInternalOperations = [
{
blockNumber: "14878012",
timeStamp: "1653990239",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
from: "0xdef171fe48cf0115b1d80b88dc8eab59176fee57",
to: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
value: "66616263350003",
contractAddress: "",
input: "",
type: "call",
gas: "129878",
gasUsed: "0",
traceId: "0_1",
isError: "0",
errCode: "",
},
{
blockNumber: "14914090",
timeStamp: "1654506123",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
from: "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5",
to: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
value: "2631018327985208",
contractAddress: "",
input: "",
type: "call",
gas: "2300",
gasUsed: "0",
traceId: "10",
isError: "0",
errCode: "",
},
{
blockNumber: "15214745",
timeStamp: "1658792819",
hash: "0x3e85e486fbf92dd5dca1f7a29aed3324bdd78ce61e38fed7cec075de106987a1",
from: "0xdef171fe48cf0115b1d80b88dc8eab59176fee57",
to: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
value: "7431111373037",
contractAddress: "",
input: "",
type: "call",
gas: "152667",
gasUsed: "0",
traceId: "0_1_1",
isError: "0",
errCode: "",
},
];
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BigNumber from "bignumber.js";
import { getTokenById } from "@ledgerhq/cryptoassets/tokens";
import { CryptoCurrency } from "@ledgerhq/types-cryptoassets";
import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
import { encodeSubOperationId } from "@ledgerhq/coin-framework/operation";
import * as logic from "../../logic";
import {
makeAccount,
Expand Down Expand Up @@ -200,6 +201,48 @@ export const erc1155Operations = [
),
];

export const internalOperations = [
makeOperation({
hash: coinOperations[0].hash, // on purpose to make this internal op a subOp of coinOp 1
accountId: coinOperations[0].accountId,
blockHash: coinOperations[0].blockHash,
recipients: ["0xB0B"],
senders: ["0x9b744C0451D73C0958d8aA566dAd33022E4Ee797"], // sbf.eth
value: new BigNumber(12),
fee: new BigNumber(0),
type: "NONE",
date: new Date(),
blockHeight: 10,
id: encodeSubOperationId(coinOperations[0].accountId, coinOperations[0].hash, "NONE", 0),
}),
makeOperation({
hash: coinOperations[1].hash, // on purpose to make this internal op a subOp of coinOp 1
accountId: coinOperations[1].accountId,
blockHash: coinOperations[1].blockHash,
recipients: ["0xB0B"],
senders: [coinOperations[1].recipients[0]],
value: new BigNumber(34),
fee: new BigNumber(0),
type: "OUT",
date: new Date(),
blockHeight: 11,
id: encodeSubOperationId(coinOperations[1].accountId, coinOperations[1].hash, "OUT", 0),
}),
makeOperation({
hash: coinOperations[2].hash, // on purpose to make this internal op a subOp of coinOp 1
accountId: coinOperations[2].accountId,
blockHash: coinOperations[2].blockHash,
recipients: [coinOperations[2].senders[0]],
senders: ["0x9b744C0451D73C0958d8aA566dAd33022E4Ee797"], // sbf.eth
value: new BigNumber(45),
fee: new BigNumber(0),
type: "IN",
date: new Date(),
blockHeight: 12,
id: encodeSubOperationId(coinOperations[2].accountId, coinOperations[2].hash, "IN", 0),
}),
];

export const ignoredTokenOperation = makeOperation({
hash: "0xigN0r3Me",
accountId: "js:2:ethereum:0xkvn:+ethereum%2Ferc20%2Fusd_tether__erc20_",
Expand Down
198 changes: 198 additions & 0 deletions libs/coin-evm/src/__tests__/unit/adapters/etherscan.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {
etherscanERC1155EventToOperations,
etherscanERC20EventToOperations,
etherscanERC721EventToOperations,
etherscanInternalTransactionToOperations,
etherscanOperationToOperations,
} from "../../../adapters";
import {
EtherscanERC1155Event,
EtherscanERC20Event,
EtherscanERC721Event,
EtherscanInternalTransaction,
EtherscanOperation,
} from "../../../types";

Expand Down Expand Up @@ -67,6 +69,7 @@ describe("EVM Family", () => {
hasFailed: false,
nftOperations: [],
subOperations: [],
internalOperations: [],
type: "FEES",
extra: {},
};
Expand Down Expand Up @@ -124,6 +127,7 @@ describe("EVM Family", () => {
hasFailed: false,
nftOperations: [],
subOperations: [],
internalOperations: [],
type: "FEES",
extra: {},
};
Expand Down Expand Up @@ -180,6 +184,7 @@ describe("EVM Family", () => {
hasFailed: false,
nftOperations: [],
subOperations: [],
internalOperations: [],
type: "OUT",
extra: {},
};
Expand Down Expand Up @@ -236,6 +241,7 @@ describe("EVM Family", () => {
hasFailed: false,
nftOperations: [],
subOperations: [],
internalOperations: [],
type: "IN",
extra: {},
};
Expand Down Expand Up @@ -292,6 +298,7 @@ describe("EVM Family", () => {
hasFailed: false,
nftOperations: [],
subOperations: [],
internalOperations: [],
type: "NONE",
extra: {},
};
Expand Down Expand Up @@ -349,6 +356,7 @@ describe("EVM Family", () => {
hasFailed: false,
nftOperations: [],
subOperations: [],
internalOperations: [],
type: "IN",
extra: {},
},
Expand All @@ -367,6 +375,7 @@ describe("EVM Family", () => {
hasFailed: false,
nftOperations: [],
subOperations: [],
internalOperations: [],
type: "OUT",
extra: {},
},
Expand Down Expand Up @@ -1072,6 +1081,195 @@ describe("EVM Family", () => {
);
});
});

describe("etherscanInternalTransactionToOperations", () => {
it("should convert a etherscan-like out internal transaction (from their API) to a Ledger Live Operation", () => {
const etherscanOp: EtherscanInternalTransaction = {
blockNumber: "14878012",
timeStamp: "1653990239",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
from: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
to: "0xdef171fe48cf0115b1d80b88dc8eab59176fee57",
value: "66616263350003",
contractAddress: "",
input: "",
type: "call",
gas: "129878",
gasUsed: "0",
traceId: "0_1",
isError: "0",
errCode: "",
};

const accountId = encodeAccountId({
type: "js",
version: "2",
currencyId: "ethereum",
xpubOrAddress: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
derivationMode: "",
});

const expectedOperation: Operation = {
id: "js:2:ethereum:0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d:-0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885-OUT-i0",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
accountId,
blockHeight: 14878012,
blockHash: undefined,
senders: ["0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d"],
recipients: ["0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57"],
value: new BigNumber("66616263350003"),
fee: new BigNumber("0"),
date: new Date("2022-05-31T09:43:59.000Z"),
type: "OUT",
hasFailed: false,
extra: {},
};

expect(etherscanInternalTransactionToOperations(accountId, etherscanOp)).toEqual([
expectedOperation,
]);
});

it("should convert a etherscan-like in internal transaction (from their API) to a Ledger Live Operation", () => {
const etherscanOp: EtherscanInternalTransaction = {
blockNumber: "14878012",
timeStamp: "1653990239",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
from: "0xdef171fe48cf0115b1d80b88dc8eab59176fee57",
to: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
value: "66616263350003",
contractAddress: "",
input: "",
type: "call",
gas: "129878",
gasUsed: "0",
traceId: "0_1",
isError: "0",
errCode: "",
};

const accountId = encodeAccountId({
type: "js",
version: "2",
currencyId: "ethereum",
xpubOrAddress: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
derivationMode: "",
});

const expectedOperation: Operation = {
id: "js:2:ethereum:0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d:-0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885-IN-i0",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
accountId,
blockHeight: 14878012,
blockHash: undefined,
senders: ["0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57"],
recipients: ["0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d"],
value: new BigNumber("66616263350003"),
fee: new BigNumber("0"),
date: new Date("2022-05-31T09:43:59.000Z"),
type: "IN",
hasFailed: false,
extra: {},
};

expect(etherscanInternalTransactionToOperations(accountId, etherscanOp)).toEqual([
expectedOperation,
]);
});

it("should convert a etherscan-like none internal transaction (from their API) to a Ledger Live Operation", () => {
const etherscanOp: EtherscanInternalTransaction = {
blockNumber: "14878012",
timeStamp: "1653990239",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
from: "0xdef171fe48cf0115b1d80b88dc8eab59176fee57",
to: "0x3244100A07c7fEE9bDE409e877ed2e8Ff1EdeEda", // pdv.eth
value: "66616263350003",
contractAddress: "",
input: "",
type: "call",
gas: "129878",
gasUsed: "0",
traceId: "0_1",
isError: "0",
errCode: "",
};

const accountId = encodeAccountId({
type: "js",
version: "2",
currencyId: "ethereum",
xpubOrAddress: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
derivationMode: "",
});

expect(etherscanInternalTransactionToOperations(accountId, etherscanOp)).toEqual([]);
});

it("should convert a etherscan-like self internal transaction (from their API) to 2 Ledger Live Operations", () => {
const etherscanOp: EtherscanInternalTransaction = {
blockNumber: "14878012",
timeStamp: "1653990239",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
from: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
to: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
value: "66616263350003",
contractAddress: "",
input: "",
type: "call",
gas: "129878",
gasUsed: "0",
traceId: "0_1",
isError: "0",
errCode: "",
};

const accountId = encodeAccountId({
type: "js",
version: "2",
currencyId: "ethereum",
xpubOrAddress: "0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d",
derivationMode: "",
});

const expectedOperations: Operation[] = [
{
id: "js:2:ethereum:0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d:-0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885-IN-i0",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
accountId,
blockHeight: 14878012,
blockHash: undefined,
senders: ["0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d"],
recipients: ["0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d"],
value: new BigNumber("66616263350003"),
fee: new BigNumber("0"),
date: new Date("2022-05-31T09:43:59.000Z"),
type: "IN",
hasFailed: false,
extra: {},
},
{
id: "js:2:ethereum:0x6cbcd73cd8e8a42844662f0a0e76d7f79afd933d:-0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885-OUT-i0",
hash: "0xb3effb3b6c52c719507f8219fe0dd2147a9f7ba366261ab43532efb0b9b01885",
accountId,
blockHeight: 14878012,
blockHash: undefined,
senders: ["0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d"],
recipients: ["0x6cBCD73CD8e8a42844662f0A0e76D7F79Afd933d"],
value: new BigNumber("66616263350003"),
fee: new BigNumber("0"),
date: new Date("2022-05-31T09:43:59.000Z"),
type: "OUT",
hasFailed: false,
extra: {},
},
];

expect(etherscanInternalTransactionToOperations(accountId, etherscanOp)).toEqual(
expectedOperations,
);
});
});
});
});
});
Loading
Loading