From 3d9ea5cf2d09cb9be09d6615fd3c5f7693323d2e Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Tue, 17 Dec 2024 11:32:41 +0100 Subject: [PATCH 1/8] [Alpaca] Add block hash and timestamp --- libs/coin-framework/src/api/types.ts | 1 + libs/coin-modules/coin-xrp/src/api/index.ts | 11 +++++++++-- .../coin-xrp/src/logic/listOperations.ts | 16 ++++++++++------ libs/coin-modules/coin-xrp/src/network/index.ts | 3 ++- libs/coin-modules/coin-xrp/src/network/types.ts | 7 ++++--- libs/coin-modules/coin-xrp/src/types/model.ts | 2 ++ 6 files changed, 28 insertions(+), 12 deletions(-) diff --git a/libs/coin-framework/src/api/types.ts b/libs/coin-framework/src/api/types.ts index 8185bcd380f2..2edc32ac2c4a 100644 --- a/libs/coin-framework/src/api/types.ts +++ b/libs/coin-framework/src/api/types.ts @@ -11,6 +11,7 @@ export type Operation = { value: bigint; fee: bigint; blockHeight: number; + block?: BlockInfo; senders: string[]; recipients: string[]; date: Date; diff --git a/libs/coin-modules/coin-xrp/src/api/index.ts b/libs/coin-modules/coin-xrp/src/api/index.ts index 6fddc72a292a..a00568ce2fcc 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.ts @@ -48,8 +48,15 @@ async function operations( const [ops, index] = await listOperations(address, { limit, mostRecentIndex: start }); return [ ops.map(op => { - const { simpleType, ...rest } = op; - return { ...rest } satisfies Operation; + const { simpleType, blockHash, blockTime, ...rest } = op; + return { + ...rest, + block: { + height: rest.blockHeight, + hash: blockHash, + time: blockTime, + }, + } satisfies Operation; }), index, ]; diff --git a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts index da4e2d9ac770..2ff666b6bd96 100644 --- a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts @@ -47,9 +47,9 @@ export async function listOperations( return [ transactions - .filter(op => op.tx.TransactionType === "Payment") + .filter(op => op.tx_json.TransactionType === "Payment") .map(convertToCoreOperation(address)), - transactions.slice(-1)[0].tx.ledger_index - 1, // Returns the next index to start from for pagination + transactions.slice(-1)[0].tx_json.ledger_index - 1, // Returns the next index to start from for pagination ]; } @@ -57,18 +57,20 @@ const convertToCoreOperation = (address: string) => (operation: XrplOperation): XrpOperation => { const { + ledger_hash, + hash, + close_time_iso, meta: { delivered_amount }, - tx: { + tx_json: { TransactionType, Fee, - hash, - inLedger, date, Account, Destination, DestinationTag, Sequence, Memos, + ledger_index, }, } = operation; @@ -112,13 +114,15 @@ const convertToCoreOperation = } let op: XrpOperation = { + blockTime: new Date(close_time_iso), + blockHash: ledger_hash, hash, address, type: TransactionType, simpleType: type, value, fee, - blockHeight: inLedger, + blockHeight: ledger_index, senders: [Account], recipients: [Destination], date: new Date(toEpochDate), diff --git a/libs/coin-modules/coin-xrp/src/network/index.ts b/libs/coin-modules/coin-xrp/src/network/index.ts index 29e4250d23f8..a00fe481bb2e 100644 --- a/libs/coin-modules/coin-xrp/src/network/index.ts +++ b/libs/coin-modules/coin-xrp/src/network/index.ts @@ -68,12 +68,13 @@ export const getServerInfos = async (): Promise => { export const getTransactions = async ( address: string, - options: { ledger_index_min?: number; ledger_index_max?: number; limit?: number } | undefined, + options: { ledger_index_min?: number; ledger_index_max?: number; limit?: number} | undefined, ): Promise => { const result = await rpcCall("account_tx", { account: address, ledger_index: "validated", ...options, + api_version: 2, }); return result.transactions; diff --git a/libs/coin-modules/coin-xrp/src/network/types.ts b/libs/coin-modules/coin-xrp/src/network/types.ts index fb6d428caa21..047e5c3897cc 100644 --- a/libs/coin-modules/coin-xrp/src/network/types.ts +++ b/libs/coin-modules/coin-xrp/src/network/types.ts @@ -1,4 +1,7 @@ export type XrplOperation = { + ledger_hash: string; + hash: string; + close_time_iso: string; meta: { AffectedNodes: { ModifiedNode: { @@ -22,7 +25,7 @@ export type XrplOperation = { TransactionResult: string; delivered_amount: string; }; - tx: { + tx_json: { Account: string; Amount: string; DeliverMax: string; @@ -44,8 +47,6 @@ export type XrplOperation = { TransactionType: string; TxnSignature: string; date: number; - hash: string; - inLedger: number; ledger_index: number; }; validated: boolean; diff --git a/libs/coin-modules/coin-xrp/src/types/model.ts b/libs/coin-modules/coin-xrp/src/types/model.ts index 66a9042c5eb0..e18063ac24e8 100644 --- a/libs/coin-modules/coin-xrp/src/types/model.ts +++ b/libs/coin-modules/coin-xrp/src/types/model.ts @@ -11,6 +11,8 @@ export type XrpMemo = { type?: string; }; export type XrpOperation = { + blockTime: Date; + blockHash: string; hash: string; address: string; type: string; From 00cf86df39ecfed9878710adb70fc296357ea88d Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Wed, 18 Dec 2024 14:36:20 +0100 Subject: [PATCH 2/8] work on xrp coin module --- libs/coin-modules/coin-xrp/src/api/index.ts | 2 +- libs/coin-modules/coin-xrp/src/logic/listOperations.ts | 3 +-- libs/coin-modules/coin-xrp/src/network/index.ts | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/libs/coin-modules/coin-xrp/src/api/index.ts b/libs/coin-modules/coin-xrp/src/api/index.ts index a00568ce2fcc..4664d56c0b79 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.ts @@ -45,7 +45,7 @@ async function operations( address: string, { limit, start }: Pagination, ): Promise<[Operation[], number]> { - const [ops, index] = await listOperations(address, { limit, mostRecentIndex: start }); + const [ops, index] = await listOperations(address, { limit, startAt: start ?? 0 }); return [ ops.map(op => { const { simpleType, blockHash, blockTime, ...rest } = op; diff --git a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts index 2ff666b6bd96..79b8537ca73b 100644 --- a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts @@ -39,7 +39,7 @@ export async function listOperations( options = { ...options, // if there is no ops, it might be after a clear and we prefer to pull from the oldest possible history - ledger_index_min: Math.max(startAt ?? 0, minLedgerVersion), + ledger_index_min: Math.max(startAt, minLedgerVersion), }; } @@ -47,7 +47,6 @@ export async function listOperations( return [ transactions - .filter(op => op.tx_json.TransactionType === "Payment") .map(convertToCoreOperation(address)), transactions.slice(-1)[0].tx_json.ledger_index - 1, // Returns the next index to start from for pagination ]; diff --git a/libs/coin-modules/coin-xrp/src/network/index.ts b/libs/coin-modules/coin-xrp/src/network/index.ts index a00fe481bb2e..3184d1205c1c 100644 --- a/libs/coin-modules/coin-xrp/src/network/index.ts +++ b/libs/coin-modules/coin-xrp/src/network/index.ts @@ -68,13 +68,13 @@ export const getServerInfos = async (): Promise => { export const getTransactions = async ( address: string, - options: { ledger_index_min?: number; ledger_index_max?: number; limit?: number} | undefined, + options: { ledger_index_min?: number; ledger_index_max?: number; limit?: number } | undefined, ): Promise => { const result = await rpcCall("account_tx", { account: address, - ledger_index: "validated", ...options, api_version: 2, + tx_type: "Payment", }); return result.transactions; @@ -110,7 +110,7 @@ async function rpcCall( }); if (isResponseStatus(result) && result.status !== "success") { - throw new Error(`couldn't fetch ${method} with params ${params}`); + throw new Error(`couldn't fetch ${method} with params ${JSON.stringify(params)}`); } return result; From 96e407c7de4cb21a6f6f65b7386c3f37a1c49eb6 Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Wed, 18 Dec 2024 18:07:44 +0100 Subject: [PATCH 3/8] Add imp for stellar --- .../src/bridge/buildOptimisticOperation.ts | 1 + .../coin-stellar/src/logic/listOperations.ts | 10 ++++++++++ .../coin-stellar/src/network/horizon.ts | 16 +++++++++++++--- .../coin-stellar/src/network/serialization.ts | 4 +++- .../coin-stellar/src/types/bridge.fixture.ts | 1 + .../coin-stellar/src/types/bridge.ts | 1 + 6 files changed, 29 insertions(+), 4 deletions(-) diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildOptimisticOperation.ts b/libs/coin-modules/coin-stellar/src/bridge/buildOptimisticOperation.ts index 18bb126b4435..fda3fefca825 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildOptimisticOperation.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildOptimisticOperation.ts @@ -28,6 +28,7 @@ export async function buildOptimisticOperation( transactionSequenceNumber: transactionSequenceNumber?.plus(1).toNumber(), extra: { ledgerOpType: type, + blockTime: new Date(), }, }; diff --git a/libs/coin-modules/coin-stellar/src/logic/listOperations.ts b/libs/coin-modules/coin-stellar/src/logic/listOperations.ts index e69218b27a5d..bec89a699f35 100644 --- a/libs/coin-modules/coin-stellar/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-stellar/src/logic/listOperations.ts @@ -8,6 +8,11 @@ export type Operation = { value: bigint; fee: bigint; blockHeight: number; + block?: { + hash: string; + time: Date; + height: number; + }; senders: string[]; recipients: string[]; date: Date; @@ -42,6 +47,11 @@ const convertToCoreOperation = (address: string) => (operation: StellarOperation value: BigInt(operation.value.toString()), fee: BigInt(operation.fee.toString()), blockHeight: operation.blockHeight!, + block: { + hash: operation.blockHash!, + time: operation.extra.blockTime, + height: operation.blockHeight!, + }, senders: operation.senders, recipients: operation.recipients, date: operation.date, diff --git a/libs/coin-modules/coin-stellar/src/network/horizon.ts b/libs/coin-modules/coin-stellar/src/network/horizon.ts index f11ac47546bd..af2fb5ac7805 100644 --- a/libs/coin-modules/coin-stellar/src/network/horizon.ts +++ b/libs/coin-modules/coin-stellar/src/network/horizon.ts @@ -73,14 +73,24 @@ Horizon.AxiosClient.interceptors.response.use(response => { // FIXME: workaround for the Stellar SDK not using the correct URL: the "next" URL // included in server responses points to the node itself instead of our reverse proxy... // (https://github.com/stellar/js-stellar-sdk/issues/637) + + function fixURL(url: string): string { + const u = new URL(url); + u.host = new URL(coinConfig.getCoinConfig().explorer.url).host; + return u.toString(); + } + const next_href = response?.data?._links?.next?.href; if (next_href) { - const next = new URL(next_href); - next.host = new URL(coinConfig.getCoinConfig().explorer.url).host; - response.data._links.next.href = next.toString(); + response.data._links.next.href = fixURL(next_href); } + response?.data?._embedded?.records?.forEach((r: any) => { + const href = r.transaction?._links?.ledger?.href; + if (href) r.transaction._links.ledger.href = fixURL(href); + }); + return response; }); diff --git a/libs/coin-modules/coin-stellar/src/network/serialization.ts b/libs/coin-modules/coin-stellar/src/network/serialization.ts index 29b67e904473..ba1677916e8e 100644 --- a/libs/coin-modules/coin-stellar/src/network/serialization.ts +++ b/libs/coin-modules/coin-stellar/src/network/serialization.ts @@ -79,6 +79,7 @@ async function formatOperation( addr: string, ): Promise { const transaction = await rawOperation.transaction(); + const { hash: blockHash, closed_at: blockTime } = await transaction.ledger(); const type = getOperationType(rawOperation, addr); const value = getValue(rawOperation, transaction, type); const recipients = getRecipients(rawOperation); @@ -103,9 +104,10 @@ async function formatOperation( recipients, transactionSequenceNumber: Number(transaction.source_account_sequence), hasFailed: !rawOperation.transaction_successful, - blockHash: null, + blockHash: blockHash, extra: { ledgerOpType: type, + blockTime: new Date(blockTime), }, }; diff --git a/libs/coin-modules/coin-stellar/src/types/bridge.fixture.ts b/libs/coin-modules/coin-stellar/src/types/bridge.fixture.ts index 9583a2e3a148..5f9f06aa429f 100644 --- a/libs/coin-modules/coin-stellar/src/types/bridge.fixture.ts +++ b/libs/coin-modules/coin-stellar/src/types/bridge.fixture.ts @@ -71,6 +71,7 @@ export function createFixtureOperation(operation?: Partial): S let extra: StellarOperationExtra = { assetAmount: operation?.extra?.assetAmount || undefined, ledgerOpType: operation?.extra?.ledgerOpType || "IN", + blockTime: operation?.extra?.blockTime || faker.date.past(), }; if (operation?.extra?.pagingToken) { extra = { diff --git a/libs/coin-modules/coin-stellar/src/types/bridge.ts b/libs/coin-modules/coin-stellar/src/types/bridge.ts index 93b5487ba362..8803a275a5e7 100644 --- a/libs/coin-modules/coin-stellar/src/types/bridge.ts +++ b/libs/coin-modules/coin-stellar/src/types/bridge.ts @@ -111,6 +111,7 @@ export type StellarOperationExtra = { assetAmount?: string | undefined; ledgerOpType: OperationType; memo?: string; + blockTime: Date; }; export type StellarAccount = Account; From 64dde66e159d1f27c8d97f247b25005d9c08f9d4 Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Thu, 19 Dec 2024 09:53:27 +0100 Subject: [PATCH 4/8] Some corrections of tests for xrp and get of ops --- .../coin-xrp/src/api/index.test.ts | 39 ++++++++++++++----- .../src/bridge/synchronization.test.ts | 9 +++-- .../coin-xrp/src/bridge/synchronization.ts | 2 +- .../coin-xrp/src/logic/listOperations.test.ts | 30 +++++++++----- .../coin-xrp/src/logic/listOperations.ts | 32 +++++++++++++-- .../coin-xrp/src/network/index.ts | 1 - 6 files changed, 85 insertions(+), 28 deletions(-) diff --git a/libs/coin-modules/coin-xrp/src/api/index.test.ts b/libs/coin-modules/coin-xrp/src/api/index.test.ts index d87aefdf17c9..8a268ff32894 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.test.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.test.ts @@ -41,12 +41,14 @@ describe("listOperations", () => { const fee = 10; mockGetTransactions.mockResolvedValue([ { + ledger_hash: "HASH_VALUE_BLOCK", + hash: "HASH_VALUE", + close_time_iso: "2000-01-01T00:00:01Z", meta: { delivered_amount: deliveredAmount.toString() }, - tx: { + tx_json: { TransactionType: "Payment", Fee: fee.toString(), - hash: "HASH_VALUE", - inLedger: 1, + ledger_index: 1, date: 1000, Account: opSender, Destination: opDestination, @@ -54,12 +56,14 @@ describe("listOperations", () => { }, }, { + ledger_hash: "HASH_VALUE_BLOCK", + hash: "HASH_VALUE", + close_time_iso: "2000-01-01T00:00:01Z", meta: { delivered_amount: deliveredAmount.toString() }, - tx: { + tx_json: { TransactionType: "Payment", Fee: fee.toString(), - hash: "HASH_VALUE", - inLedger: 1, + ledger_index: 1, date: 1000, Account: opSender, Destination: opDestination, @@ -68,12 +72,14 @@ describe("listOperations", () => { }, }, { + ledger_hash: "HASH_VALUE_BLOCK", + hash: "HASH_VALUE", + close_time_iso: "2000-01-01T00:00:01Z", meta: { delivered_amount: deliveredAmount.toString() }, - tx: { + tx_json: { TransactionType: "Payment", Fee: fee.toString(), - hash: "HASH_VALUE", - inLedger: 1, + ledger_index: 1, date: 1000, Account: opSender, Destination: opDestination, @@ -107,6 +113,11 @@ describe("listOperations", () => { value: expectedValue, fee: BigInt(10), blockHeight: 1, + block: { + hash: "HASH_VALUE_BLOCK", + height: 1, + time: new Date("2000-01-01T00:00:01Z"), + }, senders: [opSender], recipients: [opDestination], date: new Date(1000000 + RIPPLE_EPOCH * 1000), @@ -119,6 +130,11 @@ describe("listOperations", () => { value: expectedValue, fee: BigInt(10), blockHeight: 1, + block: { + hash: "HASH_VALUE_BLOCK", + height: 1, + time: new Date("2000-01-01T00:00:01Z"), + }, senders: [opSender], recipients: [opDestination], date: new Date(1000000 + RIPPLE_EPOCH * 1000), @@ -134,6 +150,11 @@ describe("listOperations", () => { value: expectedValue, fee: BigInt(10), blockHeight: 1, + block: { + hash: "HASH_VALUE_BLOCK", + height: 1, + time: new Date("2000-01-01T00:00:01Z"), + }, senders: [opSender], recipients: [opDestination], date: new Date(1000000 + RIPPLE_EPOCH * 1000), diff --git a/libs/coin-modules/coin-xrp/src/bridge/synchronization.test.ts b/libs/coin-modules/coin-xrp/src/bridge/synchronization.test.ts index 1bdda1587282..1b06b81d385d 100644 --- a/libs/coin-modules/coin-xrp/src/bridge/synchronization.test.ts +++ b/libs/coin-modules/coin-xrp/src/bridge/synchronization.test.ts @@ -78,12 +78,13 @@ describe("getAccountShape", () => { }); mockGetTransactions.mockResolvedValue([ { + ledger_hash: "HASH_VALUE_BLOCK", + hash: "HASH_VALUE", meta: { delivered_amount: "100" }, - tx: { + tx_json: { TransactionType: "Payment", Fee: "10", - hash: "HASH_VALUE", - inLedger: 1, + ledger_index: 1, date: 1000, Account: "account_addr", Destination: "destination_addr", @@ -117,7 +118,7 @@ describe("getAccountShape", () => { operations: [ { accountId: "js:2:ripple:address:", - blockHash: null, + blockHash: "HASH_VALUE_BLOCK", blockHeight: 1, date: new Date("2000-01-01T00:16:40.000Z"), hash: "HASH_VALUE", diff --git a/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts b/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts index 04021f10d150..12388d793500 100644 --- a/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts +++ b/libs/coin-modules/coin-xrp/src/bridge/synchronization.ts @@ -74,7 +74,7 @@ async function filterOperations( type: op.simpleType, value: new BigNumber(op.value.toString()), fee: new BigNumber(op.fee.toString()), - blockHash: null, + blockHash: op.blockHash, blockHeight: op.blockHeight, senders: op.senders, recipients: op.recipients, diff --git a/libs/coin-modules/coin-xrp/src/logic/listOperations.test.ts b/libs/coin-modules/coin-xrp/src/logic/listOperations.test.ts index 4d796784c548..b7696581435c 100644 --- a/libs/coin-modules/coin-xrp/src/logic/listOperations.test.ts +++ b/libs/coin-modules/coin-xrp/src/logic/listOperations.test.ts @@ -39,12 +39,14 @@ describe("listOperations", () => { const fee = 10; mockGetTransactions.mockResolvedValue([ { + ledger_hash: "HASH_VALUE_BLOCK", + hash: "HASH_VALUE", + close_time_iso: "2000-01-01T00:00:01Z", meta: { delivered_amount: deliveredAmount.toString() }, - tx: { + tx_json: { TransactionType: "Payment", Fee: fee.toString(), - hash: "HASH_VALUE", - inLedger: 1, + ledger_index: 1, date: 1000, Account: opSender, Destination: opDestination, @@ -52,12 +54,14 @@ describe("listOperations", () => { }, }, { + ledger_hash: "HASH_VALUE_BLOCK", + hash: "HASH_VALUE", + close_time_iso: "2000-01-01T00:00:01Z", meta: { delivered_amount: deliveredAmount.toString() }, - tx: { + tx_json: { TransactionType: "Payment", Fee: fee.toString(), - hash: "HASH_VALUE", - inLedger: 1, + ledger_index: 1, date: 1000, Account: opSender, Destination: opDestination, @@ -66,12 +70,14 @@ describe("listOperations", () => { }, }, { + ledger_hash: "HASH_VALUE_BLOCK", + hash: "HASH_VALUE", + close_time_iso: "2000-01-01T00:00:01Z", meta: { delivered_amount: deliveredAmount.toString() }, - tx: { + tx_json: { TransactionType: "Payment", Fee: fee.toString(), - hash: "HASH_VALUE", - inLedger: 1, + ledger_index: 1, date: 1000, Account: opSender, Destination: opDestination, @@ -106,6 +112,8 @@ describe("listOperations", () => { value: expectedValue, fee: BigInt(10), blockHeight: 1, + blockHash: "HASH_VALUE_BLOCK", + blockTime: new Date("2000-01-01T00:00:01Z"), senders: [opSender], recipients: [opDestination], date: new Date(1000000 + RIPPLE_EPOCH * 1000), @@ -119,6 +127,8 @@ describe("listOperations", () => { value: expectedValue, fee: BigInt(10), blockHeight: 1, + blockHash: "HASH_VALUE_BLOCK", + blockTime: new Date("2000-01-01T00:00:01Z"), senders: [opSender], recipients: [opDestination], date: new Date(1000000 + RIPPLE_EPOCH * 1000), @@ -135,6 +145,8 @@ describe("listOperations", () => { value: expectedValue, fee: BigInt(10), blockHeight: 1, + blockHash: "HASH_VALUE_BLOCK", + blockTime: new Date("2000-01-01T00:00:01Z"), senders: [opSender], recipients: [opDestination], date: new Date(1000000 + RIPPLE_EPOCH * 1000), diff --git a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts index 79b8537ca73b..258e4049714b 100644 --- a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts @@ -26,9 +26,16 @@ export async function listOperations( const minLedgerVersion = Number(ledgers[0]); const maxLedgerVersion = Number(ledgers[1]); - let options: { ledger_index_min?: number; ledger_index_max?: number; limit?: number } = { + let options: { + ledger_index_min?: number; + ledger_index_max?: number; + limit?: number; + tx_type?: string; + } = { ledger_index_max: mostRecentIndex ?? maxLedgerVersion, + tx_type: "Payment", }; + if (limit) { options = { ...options, @@ -43,11 +50,28 @@ export async function listOperations( }; } - const transactions = await getTransactions(address, options); + // We need to filter out the transactions that are not "Payment" type because the filter on "tx_type" of the node RPC + // is not working as expected. It returns all the transactions. We need to call the node RPC multiple times to get the + // desired number of transactions by the limiter. + let transactions: XrplOperation[] = []; + let needToStop = true; + do { + const newTransactions = await getTransactions(address, options); + const newPaymentsTxs = newTransactions.filter(tx => tx.tx_json.TransactionType === "Payment"); + + needToStop = options.limit ? newTransactions.length < options.limit : true; + + transactions = transactions.concat(newPaymentsTxs); + } while ( + options.limit && + !needToStop && + transactions.length < options.limit && + (options.limit -= transactions.length) && + (options.ledger_index_max = transactions.slice(-1)[0].tx_json.ledger_index - 1) + ); return [ - transactions - .map(convertToCoreOperation(address)), + transactions.map(convertToCoreOperation(address)), transactions.slice(-1)[0].tx_json.ledger_index - 1, // Returns the next index to start from for pagination ]; } diff --git a/libs/coin-modules/coin-xrp/src/network/index.ts b/libs/coin-modules/coin-xrp/src/network/index.ts index 3184d1205c1c..d35233abb33d 100644 --- a/libs/coin-modules/coin-xrp/src/network/index.ts +++ b/libs/coin-modules/coin-xrp/src/network/index.ts @@ -74,7 +74,6 @@ export const getTransactions = async ( account: address, ...options, api_version: 2, - tx_type: "Payment", }); return result.transactions; From 3956f1cd215919544e34412f35f3bd48a95a9952 Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Thu, 19 Dec 2024 15:08:17 +0100 Subject: [PATCH 5/8] handle of tezos coin --- .../coin-tezos/src/api/index.integ.test.ts | 1 + .../coin-tezos/src/logic/listOperations.ts | 25 +++++++++++++++---- .../coin-tezos/src/network/tzkt.ts | 6 +++++ .../coin-xrp/src/logic/listOperations.ts | 10 ++++---- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts b/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts index 945aec677710..14416b4cfb97 100644 --- a/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts @@ -55,6 +55,7 @@ describe("Tezos Api", () => { const isSenderOrReceipt = operation.senders.includes(address) || operation.recipients.includes(address); expect(isSenderOrReceipt).toBeTruthy(); + expect(operation.block).toBeDefined(); }); }); diff --git a/libs/coin-modules/coin-tezos/src/logic/listOperations.ts b/libs/coin-modules/coin-tezos/src/logic/listOperations.ts index f2d201773213..98553a413c0d 100644 --- a/libs/coin-modules/coin-tezos/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-tezos/src/logic/listOperations.ts @@ -1,5 +1,6 @@ import { tzkt } from "../network"; import { + APIBlock, type APIDelegationType, type APITransactionType, isAPIDelegationType, @@ -13,6 +14,11 @@ export type Operation = { value: bigint; fee: bigint; blockHeight: number; + block?: { + hash: string; + height: number; + time: Date; + }; senders: string[]; recipients: string[]; date: Date; @@ -24,17 +30,21 @@ export async function listOperations( { lastId, limit }: { lastId?: number; limit?: number }, ): Promise<[Operation[], number]> { const operations = await tzkt.getAccountOperations(address, { lastId, limit }); - return [ + const operationswithBlock = await Promise.all( operations .filter(op => isAPITransactionType(op) || isAPIDelegationType(op)) - .reduce((acc, op) => acc.concat(convertOperation(address, op)), [] as Operation[]), - operations.slice(-1)[0].id, - ]; + .map(async op => { + const block = await tzkt.getBlockByHash(op.block); + return convertOperation(address, op, block); + }), + ); + return [operationswithBlock, operations.slice(-1)[0].id]; } function convertOperation( address: string, operation: APITransactionType | APIDelegationType, + block: APIBlock, ): Operation { const { amount, hash, storageFee, sender, timestamp, type, counter } = operation; let targetAddress = ""; @@ -48,7 +58,12 @@ function convertOperation( value: BigInt(amount), // storageFee for transaction is always present fee: BigInt(storageFee ?? 0), - blockHeight: 0, // operation.block is a string + blockHeight: block.level, + block: { + hash: block.hash, + height: block.level, + time: new Date(block.timestamp), + }, senders: [sender?.address ?? ""], recipients: [targetAddress], date: new Date(timestamp), diff --git a/libs/coin-modules/coin-tezos/src/network/tzkt.ts b/libs/coin-modules/coin-tezos/src/network/tzkt.ts index 919e80621198..0ae704fd02ad 100644 --- a/libs/coin-modules/coin-tezos/src/network/tzkt.ts +++ b/libs/coin-modules/coin-tezos/src/network/tzkt.ts @@ -27,6 +27,12 @@ const api = { date: new Date(data[0].timestamp), }; }, + async getBlockByHash(hash: string): Promise { + const { data } = await network({ + url: `${getExplorerUrl()}/v1/blocks/${hash}`, + }); + return data; + }, async getAccountByAddress(address: string): Promise { const { data } = await network({ url: `${getExplorerUrl()}/v1/accounts/${address}`, diff --git a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts index 258e4049714b..b96fbed6b4a3 100644 --- a/libs/coin-modules/coin-xrp/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-xrp/src/logic/listOperations.ts @@ -58,16 +58,16 @@ export async function listOperations( do { const newTransactions = await getTransactions(address, options); const newPaymentsTxs = newTransactions.filter(tx => tx.tx_json.TransactionType === "Payment"); - - needToStop = options.limit ? newTransactions.length < options.limit : true; - + if (options.limit) { + needToStop = newTransactions.length < options.limit; + options.ledger_index_max = newTransactions.slice(-1)[0].tx_json.ledger_index - 1; + } transactions = transactions.concat(newPaymentsTxs); } while ( options.limit && !needToStop && transactions.length < options.limit && - (options.limit -= transactions.length) && - (options.ledger_index_max = transactions.slice(-1)[0].tx_json.ledger_index - 1) + (options.limit -= transactions.length) ); return [ From 12e9475fabb55f65c4823e63f1c6d0a7b3ac4be0 Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Thu, 19 Dec 2024 15:54:07 +0100 Subject: [PATCH 6/8] add of changeset --- .changeset/quick-frogs-develop.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/quick-frogs-develop.md diff --git a/.changeset/quick-frogs-develop.md b/.changeset/quick-frogs-develop.md new file mode 100644 index 000000000000..6f0a355aa0e2 --- /dev/null +++ b/.changeset/quick-frogs-develop.md @@ -0,0 +1,8 @@ +--- +"@ledgerhq/coin-stellar": minor +"@ledgerhq/coin-tezos": minor +"@ledgerhq/coin-xrp": minor +"@ledgerhq/coin-framework": minor +--- + +Add block informations to an operation (hash, time and height of a block) From 7d18f34e60e4007851c198d1a1db94e74235d6f7 Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Mon, 23 Dec 2024 11:29:24 +0100 Subject: [PATCH 7/8] Corrections of reviews --- libs/coin-framework/src/api/types.ts | 7 +++---- .../coin-polkadot/src/logic/listOperations.ts | 10 ++++++++-- .../coin-stellar/src/logic/listOperations.ts | 8 +++----- .../coin-stellar/src/network/horizon.ts | 18 ++++++++++-------- .../coin-tezos/src/logic/listOperations.ts | 8 +++----- .../coin-xrp/src/api/index.test.ts | 3 --- libs/coin-modules/coin-xrp/src/api/index.ts | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/libs/coin-framework/src/api/types.ts b/libs/coin-framework/src/api/types.ts index 2edc32ac2c4a..90d677ab6bab 100644 --- a/libs/coin-framework/src/api/types.ts +++ b/libs/coin-framework/src/api/types.ts @@ -1,7 +1,7 @@ export type BlockInfo = { height: number; - hash: string; - time: Date; + hash?: string; + time?: Date; }; export type Operation = { @@ -10,8 +10,7 @@ export type Operation = { type: string; value: bigint; fee: bigint; - blockHeight: number; - block?: BlockInfo; + block: BlockInfo; senders: string[]; recipients: string[]; date: Date; diff --git a/libs/coin-modules/coin-polkadot/src/logic/listOperations.ts b/libs/coin-modules/coin-polkadot/src/logic/listOperations.ts index 1b6721583270..3575fd0dd4bb 100644 --- a/libs/coin-modules/coin-polkadot/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-polkadot/src/logic/listOperations.ts @@ -7,7 +7,11 @@ export type Operation = { type: string; value: bigint; fee: bigint; - blockHeight: number; + block: { + height: number; + hash?: string; + time?: Date; + }; senders: string[]; recipients: string[]; date: Date; @@ -43,7 +47,9 @@ const convertToCoreOperation = (address: string) => (operation: PolkadotOperatio type, value: BigInt(value.toString()), fee: BigInt(fee.toString()), - blockHeight: blockHeight ?? 0, + block: { + height: blockHeight ?? 0, + }, senders, recipients, date, diff --git a/libs/coin-modules/coin-stellar/src/logic/listOperations.ts b/libs/coin-modules/coin-stellar/src/logic/listOperations.ts index bec89a699f35..d54a2812aba7 100644 --- a/libs/coin-modules/coin-stellar/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-stellar/src/logic/listOperations.ts @@ -7,10 +7,9 @@ export type Operation = { type: string; value: bigint; fee: bigint; - blockHeight: number; - block?: { - hash: string; - time: Date; + block: { + hash?: string; + time?: Date; height: number; }; senders: string[]; @@ -46,7 +45,6 @@ const convertToCoreOperation = (address: string) => (operation: StellarOperation type: operation.type, value: BigInt(operation.value.toString()), fee: BigInt(operation.fee.toString()), - blockHeight: operation.blockHeight!, block: { hash: operation.blockHash!, time: operation.extra.blockTime, diff --git a/libs/coin-modules/coin-stellar/src/network/horizon.ts b/libs/coin-modules/coin-stellar/src/network/horizon.ts index af2fb5ac7805..27977183a1ad 100644 --- a/libs/coin-modules/coin-stellar/src/network/horizon.ts +++ b/libs/coin-modules/coin-stellar/src/network/horizon.ts @@ -64,6 +64,14 @@ Horizon.AxiosClient.interceptors.request.use(config => { return config; }); +// This function allows to fix the URL, because the url returned by the Stellar SDK is not the correct one. +// It replaces the host of the URL returned with the host of the explorer. +function useConfigHost(url: string): string { + const u = new URL(url); + u.host = new URL(coinConfig.getCoinConfig().explorer.url).host; + return u.toString(); +} + Horizon.AxiosClient.interceptors.response.use(response => { if (coinConfig.getCoinConfig().enableNetworkLogs) { const { url, method } = response.config; @@ -74,21 +82,15 @@ Horizon.AxiosClient.interceptors.response.use(response => { // included in server responses points to the node itself instead of our reverse proxy... // (https://github.com/stellar/js-stellar-sdk/issues/637) - function fixURL(url: string): string { - const u = new URL(url); - u.host = new URL(coinConfig.getCoinConfig().explorer.url).host; - return u.toString(); - } - const next_href = response?.data?._links?.next?.href; if (next_href) { - response.data._links.next.href = fixURL(next_href); + response.data._links.next.href = useConfigHost(next_href); } response?.data?._embedded?.records?.forEach((r: any) => { const href = r.transaction?._links?.ledger?.href; - if (href) r.transaction._links.ledger.href = fixURL(href); + if (href) r.transaction._links.ledger.href = useConfigHost(href); }); return response; diff --git a/libs/coin-modules/coin-tezos/src/logic/listOperations.ts b/libs/coin-modules/coin-tezos/src/logic/listOperations.ts index 98553a413c0d..714557524c24 100644 --- a/libs/coin-modules/coin-tezos/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-tezos/src/logic/listOperations.ts @@ -13,11 +13,10 @@ export type Operation = { type: string; value: bigint; fee: bigint; - blockHeight: number; - block?: { - hash: string; + block: { + hash?: string; height: number; - time: Date; + time?: Date; }; senders: string[]; recipients: string[]; @@ -58,7 +57,6 @@ function convertOperation( value: BigInt(amount), // storageFee for transaction is always present fee: BigInt(storageFee ?? 0), - blockHeight: block.level, block: { hash: block.hash, height: block.level, diff --git a/libs/coin-modules/coin-xrp/src/api/index.test.ts b/libs/coin-modules/coin-xrp/src/api/index.test.ts index 8a268ff32894..6c5c2cf9f611 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.test.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.test.ts @@ -112,7 +112,6 @@ describe("listOperations", () => { type: "Payment", value: expectedValue, fee: BigInt(10), - blockHeight: 1, block: { hash: "HASH_VALUE_BLOCK", height: 1, @@ -129,7 +128,6 @@ describe("listOperations", () => { type: "Payment", value: expectedValue, fee: BigInt(10), - blockHeight: 1, block: { hash: "HASH_VALUE_BLOCK", height: 1, @@ -149,7 +147,6 @@ describe("listOperations", () => { type: "Payment", value: expectedValue, fee: BigInt(10), - blockHeight: 1, block: { hash: "HASH_VALUE_BLOCK", height: 1, diff --git a/libs/coin-modules/coin-xrp/src/api/index.ts b/libs/coin-modules/coin-xrp/src/api/index.ts index 4664d56c0b79..552e58a4336e 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.ts @@ -48,11 +48,11 @@ async function operations( const [ops, index] = await listOperations(address, { limit, startAt: start ?? 0 }); return [ ops.map(op => { - const { simpleType, blockHash, blockTime, ...rest } = op; + const { simpleType, blockHash, blockTime, blockHeight, ...rest } = op; return { ...rest, block: { - height: rest.blockHeight, + height: blockHeight, hash: blockHash, time: blockTime, }, From da3ef953ec137a87b9385a769cbaede903cc16bf Mon Sep 17 00:00:00 2001 From: sbelkhir-ledger Date: Mon, 23 Dec 2024 17:03:40 +0100 Subject: [PATCH 8/8] correction with Samy comments --- .../coin-tezos/src/logic/listOperations.ts | 19 +++++++------------ .../coin-tezos/src/network/tzkt.ts | 6 ------ 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/libs/coin-modules/coin-tezos/src/logic/listOperations.ts b/libs/coin-modules/coin-tezos/src/logic/listOperations.ts index 714557524c24..833303b21662 100644 --- a/libs/coin-modules/coin-tezos/src/logic/listOperations.ts +++ b/libs/coin-modules/coin-tezos/src/logic/listOperations.ts @@ -1,6 +1,5 @@ import { tzkt } from "../network"; import { - APIBlock, type APIDelegationType, type APITransactionType, isAPIDelegationType, @@ -29,21 +28,17 @@ export async function listOperations( { lastId, limit }: { lastId?: number; limit?: number }, ): Promise<[Operation[], number]> { const operations = await tzkt.getAccountOperations(address, { lastId, limit }); - const operationswithBlock = await Promise.all( + return [ operations .filter(op => isAPITransactionType(op) || isAPIDelegationType(op)) - .map(async op => { - const block = await tzkt.getBlockByHash(op.block); - return convertOperation(address, op, block); - }), - ); - return [operationswithBlock, operations.slice(-1)[0].id]; + .reduce((acc, op) => acc.concat(convertOperation(address, op)), [] as Operation[]), + operations.slice(-1)[0].id, + ]; } function convertOperation( address: string, operation: APITransactionType | APIDelegationType, - block: APIBlock, ): Operation { const { amount, hash, storageFee, sender, timestamp, type, counter } = operation; let targetAddress = ""; @@ -58,9 +53,9 @@ function convertOperation( // storageFee for transaction is always present fee: BigInt(storageFee ?? 0), block: { - hash: block.hash, - height: block.level, - time: new Date(block.timestamp), + hash: operation.block, + height: operation.level, + time: new Date(operation.timestamp), }, senders: [sender?.address ?? ""], recipients: [targetAddress], diff --git a/libs/coin-modules/coin-tezos/src/network/tzkt.ts b/libs/coin-modules/coin-tezos/src/network/tzkt.ts index 0ae704fd02ad..919e80621198 100644 --- a/libs/coin-modules/coin-tezos/src/network/tzkt.ts +++ b/libs/coin-modules/coin-tezos/src/network/tzkt.ts @@ -27,12 +27,6 @@ const api = { date: new Date(data[0].timestamp), }; }, - async getBlockByHash(hash: string): Promise { - const { data } = await network({ - url: `${getExplorerUrl()}/v1/blocks/${hash}`, - }); - return data; - }, async getAccountByAddress(address: string): Promise { const { data } = await network({ url: `${getExplorerUrl()}/v1/accounts/${address}`,