From 1d9d0a681458addaaf556c988e1b11975b9d0371 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Thu, 18 Jul 2024 12:22:02 +0200 Subject: [PATCH] fix: `/v2/addresses/{addr}/transactions` incorrect when address only involved with token events (#2033) * fix: `/v2/addresses/{addr}/transactions` incorrect when address only involved with token events * docs: specify recommended alternative endpoints in deprecated `/extended/v1/address/*` endpoints --- docs/openapi.yaml | 11 +++++-- src/datastore/pg-store-v2.ts | 9 ++++-- src/tests/address-tests.ts | 57 ++++++++++++++++++++++++++++++++++-- 3 files changed, 68 insertions(+), 9 deletions(-) diff --git a/docs/openapi.yaml b/docs/openapi.yaml index f68756d35..f5bde8be2 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -1938,9 +1938,11 @@ paths: get: summary: Get account transactions description: | - Retrieves a list of all Transactions for a given Address or Contract Identifier. More information on Transaction types can be found [here](https://docs.stacks.co/understand-stacks/transactions#types). + **NOTE:** This endpoint is deprecated in favor of [Get address transactions](/api/get-address-transactions). - If you need to actively monitor new transactions for an address or contract id, we highly recommend subscribing to [WebSockets or Socket.io](https://github.com/hirosystems/stacks-blockchain-api/tree/master/client) for real-time updates. + Retrieves a list of all Transactions for a given Address or Contract Identifier. More information on Transaction types can be found [here](https://docs.stacks.co/understand-stacks/transactions#types). + + If you need to actively monitor new transactions for an address or contract id, we highly recommend subscribing to [WebSockets or Socket.io](https://github.com/hirosystems/stacks-blockchain-api/tree/master/client) for real-time updates. deprecated: true tags: - Accounts @@ -2002,7 +2004,10 @@ paths: /extended/v1/address/{principal}/{tx_id}/with_transfers: get: summary: Get account transaction information for specific transaction - description: Retrieves transaction details for a given Transaction Id `tx_id`, for a given account or contract Identifier. + description: | + **NOTE:** This endpoint is deprecated in favor of [Get events for an address transaction](/api/get-address-transaction-events). + + Retrieves transaction details for a given Transaction Id `tx_id`, for a given account or contract Identifier. deprecated: true tags: - Accounts diff --git a/src/datastore/pg-store-v2.ts b/src/datastore/pg-store-v2.ts index 018f18646..36cc48035 100644 --- a/src/datastore/pg-store-v2.ts +++ b/src/datastore/pg-store-v2.ts @@ -417,7 +417,6 @@ export class PgStoreV2 extends BasePgStoreModule { args: AddressParams & TransactionPaginationQueryParams ): Promise> { return await this.sqlTransaction(async sql => { - await assertAddressExists(sql, args.address); const limit = args.limit ?? TransactionLimitParamSchema.default; const offset = args.offset ?? 0; @@ -461,7 +460,12 @@ export class PgStoreV2 extends BasePgStoreModule { SELECT COALESCE(SUM(amount), 0) FROM stx_events WHERE ${eventCond} AND sender = ${args.address} - ) + txs.fee_rate AS stx_sent, + ) + + CASE + WHEN (txs.sponsored = false AND txs.sender_address = ${args.address}) + OR (txs.sponsored = true AND txs.sponsor_address = ${args.address}) + THEN txs.fee_rate ELSE 0 + END AS stx_sent, ( SELECT COALESCE(SUM(amount), 0) FROM stx_events @@ -526,7 +530,6 @@ export class PgStoreV2 extends BasePgStoreModule { args: AddressTransactionParams & TransactionPaginationQueryParams ): Promise> { return await this.sqlTransaction(async sql => { - await assertAddressExists(sql, args.address); await assertTxIdExists(sql, args.tx_id); const limit = args.limit ?? TransactionLimitParamSchema.default; const offset = args.offset ?? 0; diff --git a/src/tests/address-tests.ts b/src/tests/address-tests.ts index 3eb699602..b9d2a6a04 100644 --- a/src/tests/address-tests.ts +++ b/src/tests/address-tests.ts @@ -69,6 +69,7 @@ describe('address tests', () => { const testAddr2 = 'ST1HB64MAJ1MBV4CQ80GF01DZS4T1DSMX20ADCRA4'; const testContractAddr = 'ST27W5M8BRKA7C5MZE2R1S1F4XTPHFWFRNHA9M04Y.hello-world'; const testAddr4 = 'ST3DWSXBPYDB484QXFTR81K4AWG4ZB5XZNFF3H70C'; + const testAddr5 = 'ST29H5FH57AZVJSBWHCTJB5ATS2ZAH9SXN1XJDNK'; const testTxId = '0x03807fdb726b3cb843e0330c564a4974037be8f9ea58ec7f8ebe03c34b890009'; const block: DbBlock = { @@ -208,6 +209,23 @@ describe('address tests', () => { createStxTx(testContractAddr, testAddr4, 15, true, 1, 1, 0), createStxTx(testAddr2, testAddr4, 35, true, 3, 1, 2), ]; + + const tx1 = txs[0]; + const addr3FtEvent: DbFtEvent = { + canonical: true, + event_type: DbEventTypeId.FungibleTokenAsset, + asset_event_type_id: DbAssetEventTypeId.Transfer, + asset_identifier: 'usdc', + event_index: 1234, + tx_id: tx1[0].tx_id, + tx_index: tx1[0].tx_index, + block_height: tx1[0].block_height, + amount: BigInt(12345), + recipient: testAddr5, + sender: 'STEH21DTN67ECXDRXG858XHBZMEBEBFE2FV79DEF', + }; + tx1[2].push(addr3FtEvent); + await db.update({ block: block, microblocks: [], @@ -536,7 +554,7 @@ describe('address tests', () => { mint: 0, burn: 0, }); - expect(v2Fetch1Json.results[4].stx_sent).toBe('1234'); + expect(v2Fetch1Json.results[4].stx_sent).toBe('0'); expect(v2Fetch1Json.results[4].stx_received).toBe('0'); expect(v2Fetch1Json.results[4].events.stx).toStrictEqual({ transfer: 0, @@ -553,7 +571,7 @@ describe('address tests', () => { mint: 0, burn: 0, }); - expect(v2Fetch1Json.results[5].stx_sent).toBe('1234'); + expect(v2Fetch1Json.results[5].stx_sent).toBe('0'); expect(v2Fetch1Json.results[5].stx_received).toBe('0'); expect(v2Fetch1Json.results[5].events.stx).toStrictEqual({ transfer: 0, @@ -570,7 +588,7 @@ describe('address tests', () => { mint: 0, burn: 0, }); - expect(v2Fetch1Json.results[6].stx_sent).toBe('1234'); + expect(v2Fetch1Json.results[6].stx_sent).toBe('0'); expect(v2Fetch1Json.results[6].stx_received).toBe('0'); expect(v2Fetch1Json.results[6].events.stx).toStrictEqual({ transfer: 1, @@ -682,6 +700,39 @@ describe('address tests', () => { total: 6, }); + // test address that only received ft balance from an ft event + const v2Fetch4 = await supertest(api.server).get( + `/extended/v2/addresses/${testAddr5}/transactions` + ); + expect(v2Fetch4.status).toBe(200); + expect(v2Fetch4.type).toBe('application/json'); + expect(v2Fetch4.body.total).toBe(1); + expect(v2Fetch4.body.results[0].events.ft).toStrictEqual({ + transfer: 1, + mint: 0, + burn: 0, + }); + expect(v2Fetch4.body.results[0].stx_sent).toBe('0'); + expect(v2Fetch4.body.results[0].stx_received).toBe('0'); + + const v2Fetch4Events = await supertest(api.server).get( + `/extended/v2/addresses/${testAddr5}/transactions/${addr3FtEvent.tx_id}/events` + ); + expect(v2Fetch4Events.status).toBe(200); + expect(v2Fetch4Events.type).toBe('application/json'); + expect(v2Fetch4Events.body.total).toBe(1); + expect(v2Fetch4Events.body.results[0]).toStrictEqual({ + type: 'ft', + event_index: addr3FtEvent.event_index, + data: { + type: 'transfer', + amount: addr3FtEvent.amount.toString(), + asset_identifier: addr3FtEvent.asset_identifier, + sender: addr3FtEvent.sender, + recipient: addr3FtEvent.recipient, + }, + }); + // testing single txs information based on given tx_id const fetchSingleTxInformation = await supertest(api.server).get( `/extended/v1/address/${testAddr4}/${testTxId}/with_transfers`