From 5330c07d599fdff34f64dc9c158d8e322d385cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Wed, 29 Jan 2025 14:08:30 -0600 Subject: [PATCH] feat: show pending inbound and outbound stx balances (#2208) * feat: add estimated inbound and outbound stx balance * fix: add select * fix: query * test: pending balance * fix: address tests --- src/api/routes/address.ts | 20 ++++++++++++++++---- src/api/schemas/entities/balances.ts | 10 ++++++++++ src/datastore/pg-store.ts | 21 +++++++++++++-------- tests/api/address.test.ts | 8 ++++++++ tests/api/mempool.test.ts | 12 ++++++++++++ tests/api/tx.test.ts | 8 ++++++++ 6 files changed, 67 insertions(+), 12 deletions(-) diff --git a/src/api/routes/address.ts b/src/api/routes/address.ts index 7cf31bedb..77e709463 100644 --- a/src/api/routes/address.ts +++ b/src/api/routes/address.ts @@ -121,13 +121,19 @@ export const AddressRoutes: FastifyPluginAsync< blockHeight ); let mempoolBalance: bigint | undefined = undefined; + let mempoolInbound: bigint | undefined = undefined; + let mempoolOutbound: bigint | undefined = undefined; if (req.query.until_block === undefined) { - const delta = await fastify.db.getPrincipalMempoolStxBalanceDelta(sql, stxAddress); - mempoolBalance = stxBalanceResult.balance + delta; + const pending = await fastify.db.getPrincipalMempoolStxBalanceDelta(sql, stxAddress); + mempoolInbound = pending.inbound; + mempoolOutbound = pending.outbound; + mempoolBalance = stxBalanceResult.balance + pending.delta; } const result: AddressStxBalance = { balance: stxBalanceResult.balance.toString(), estimated_balance: mempoolBalance?.toString(), + pending_balance_inbound: mempoolInbound?.toString(), + pending_balance_outbound: mempoolOutbound?.toString(), total_sent: stxBalanceResult.totalSent.toString(), total_received: stxBalanceResult.totalReceived.toString(), total_fees_sent: stxBalanceResult.totalFeesSent.toString(), @@ -211,15 +217,21 @@ export const AddressRoutes: FastifyPluginAsync< }); let mempoolBalance: bigint | undefined = undefined; + let mempoolInbound: bigint | undefined = undefined; + let mempoolOutbound: bigint | undefined = undefined; if (req.query.until_block === undefined) { - const delta = await fastify.db.getPrincipalMempoolStxBalanceDelta(sql, stxAddress); - mempoolBalance = stxBalanceResult.balance + delta; + const pending = await fastify.db.getPrincipalMempoolStxBalanceDelta(sql, stxAddress); + mempoolInbound = pending.inbound; + mempoolOutbound = pending.outbound; + mempoolBalance = stxBalanceResult.balance + pending.delta; } const result: AddressBalance = { stx: { balance: stxBalanceResult.balance.toString(), estimated_balance: mempoolBalance?.toString(), + pending_balance_inbound: mempoolInbound?.toString(), + pending_balance_outbound: mempoolOutbound?.toString(), total_sent: stxBalanceResult.totalSent.toString(), total_received: stxBalanceResult.totalReceived.toString(), total_fees_sent: stxBalanceResult.totalFeesSent.toString(), diff --git a/src/api/schemas/entities/balances.ts b/src/api/schemas/entities/balances.ts index 4adb466fc..c23c58491 100644 --- a/src/api/schemas/entities/balances.ts +++ b/src/api/schemas/entities/balances.ts @@ -26,6 +26,16 @@ export const StxBalanceSchema = Type.Object( description: 'Total STX balance considering pending mempool transactions', }) ), + pending_balance_inbound: Type.Optional( + Type.String({ + description: 'Inbound STX balance from pending mempool transactions', + }) + ), + pending_balance_outbound: Type.Optional( + Type.String({ + description: 'Outbound STX balance from pending mempool transactions', + }) + ), total_sent: Type.String(), total_received: Type.String(), total_fees_sent: Type.String(), diff --git a/src/datastore/pg-store.ts b/src/datastore/pg-store.ts index 8213150f2..2f66ede05 100644 --- a/src/datastore/pg-store.ts +++ b/src/datastore/pg-store.ts @@ -2480,8 +2480,8 @@ export class PgStore extends BasePgStore { * Returns the total STX balance delta affecting a principal from transactions currently in the * mempool. */ - async getPrincipalMempoolStxBalanceDelta(sql: PgSqlClient, principal: string): Promise { - const results = await sql<{ delta: string }[]>` + async getPrincipalMempoolStxBalanceDelta(sql: PgSqlClient, principal: string) { + const results = await sql<{ inbound: string; outbound: string; delta: string }[]>` WITH sent AS ( SELECT SUM(COALESCE(token_transfer_amount, 0) + fee_rate) AS total FROM mempool_txs @@ -2496,14 +2496,19 @@ export class PgStore extends BasePgStore { SELECT SUM(COALESCE(token_transfer_amount, 0)) AS total FROM mempool_txs WHERE pruned = false AND token_transfer_recipient_address = ${principal} + ), + values AS ( + SELECT + COALESCE((SELECT total FROM received), 0) AS inbound, + COALESCE((SELECT total FROM sent), 0) + COALESCE((SELECT total FROM sponsored), 0) AS outbound ) - SELECT - COALESCE((SELECT total FROM received), 0) - - COALESCE((SELECT total FROM sent), 0) - - COALESCE((SELECT total FROM sponsored), 0) - AS delta + SELECT inbound, outbound, (inbound - outbound) AS delta FROM values `; - return BigInt(results[0]?.delta ?? '0'); + return { + inbound: BigInt(results[0]?.inbound ?? '0'), + outbound: BigInt(results[0]?.outbound ?? '0'), + delta: BigInt(results[0]?.delta ?? '0'), + }; } async getUnlockedStxSupply( diff --git a/tests/api/address.test.ts b/tests/api/address.test.ts index 3edd7ee76..43ab73cdb 100644 --- a/tests/api/address.test.ts +++ b/tests/api/address.test.ts @@ -1551,6 +1551,8 @@ describe('address tests', () => { stx: { balance: '88679', estimated_balance: '88679', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '6385', total_received: '100000', total_fees_sent: '4936', @@ -1601,6 +1603,8 @@ describe('address tests', () => { stx: { balance: '91', estimated_balance: '91', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '15', total_received: '1350', total_fees_sent: '1244', @@ -1637,6 +1641,8 @@ describe('address tests', () => { const expectedStxResp1 = { balance: '91', estimated_balance: '91', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '15', total_received: '1350', total_fees_sent: '1244', @@ -1668,6 +1674,8 @@ describe('address tests', () => { const expectedStxResp1Sponsored = { balance: '3766', estimated_balance: '3766', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '0', total_received: '5000', total_fees_sent: '1234', diff --git a/tests/api/mempool.test.ts b/tests/api/mempool.test.ts index 6a59e9a25..b29935250 100644 --- a/tests/api/mempool.test.ts +++ b/tests/api/mempool.test.ts @@ -2208,6 +2208,8 @@ describe('mempool tests', () => { const balance0 = await supertest(api.server).get(url); expect(balance0.body.balance).toEqual('2000'); expect(balance0.body.estimated_balance).toEqual('2000'); + expect(balance0.body.pending_balance_inbound).toEqual('0'); + expect(balance0.body.pending_balance_outbound).toEqual('0'); // STX transfer in mempool await db.updateMempoolTxs({ @@ -2223,6 +2225,8 @@ describe('mempool tests', () => { const balance1 = await supertest(api.server).get(url); expect(balance1.body.balance).toEqual('2000'); expect(balance1.body.estimated_balance).toEqual('1850'); // Minus amount and fee + expect(balance1.body.pending_balance_inbound).toEqual('0'); + expect(balance1.body.pending_balance_outbound).toEqual('150'); // Contract call in mempool await db.updateMempoolTxs({ @@ -2242,6 +2246,8 @@ describe('mempool tests', () => { const balance1b = await supertest(api.server).get(url); expect(balance1b.body.balance).toEqual('2000'); expect(balance1b.body.estimated_balance).toEqual('1800'); // Minus fee + expect(balance1b.body.pending_balance_inbound).toEqual('0'); + expect(balance1b.body.pending_balance_outbound).toEqual('200'); // Sponsored tx in mempool await db.updateMempoolTxs({ @@ -2258,6 +2264,8 @@ describe('mempool tests', () => { const balance2 = await supertest(api.server).get(url); expect(balance2.body.balance).toEqual('2000'); expect(balance2.body.estimated_balance).toEqual('1750'); // Minus fee + expect(balance2.body.pending_balance_inbound).toEqual('0'); + expect(balance2.body.pending_balance_outbound).toEqual('250'); // STX received in mempool await db.updateMempoolTxs({ @@ -2273,6 +2281,8 @@ describe('mempool tests', () => { const balance3 = await supertest(api.server).get(url); expect(balance3.body.balance).toEqual('2000'); expect(balance3.body.estimated_balance).toEqual('1850'); // Plus amount + expect(balance3.body.pending_balance_inbound).toEqual('100'); + expect(balance3.body.pending_balance_outbound).toEqual('250'); // Confirm all txs await db.update( @@ -2317,5 +2327,7 @@ describe('mempool tests', () => { const balance4 = await supertest(api.server).get(url); expect(balance4.body.balance).toEqual('1850'); expect(balance4.body.estimated_balance).toEqual('1850'); + expect(balance4.body.pending_balance_inbound).toEqual('0'); + expect(balance4.body.pending_balance_outbound).toEqual('0'); }); }); diff --git a/tests/api/tx.test.ts b/tests/api/tx.test.ts index 4e031b126..f7a95eab5 100644 --- a/tests/api/tx.test.ts +++ b/tests/api/tx.test.ts @@ -1031,6 +1031,8 @@ describe('tx tests', () => { const expectedSponsoredRespBefore = { balance: '0', estimated_balance: '0', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '0', total_received: '0', total_fees_sent: '0', @@ -1139,6 +1141,8 @@ describe('tx tests', () => { const expectedResp = { balance: '0', estimated_balance: '0', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '0', total_received: '0', total_fees_sent: '0', @@ -1158,6 +1162,8 @@ describe('tx tests', () => { stx: { balance: '0', estimated_balance: '0', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '0', total_received: '0', total_fees_sent: '0', @@ -1181,6 +1187,8 @@ describe('tx tests', () => { const expectedSponsoredRespAfter = { balance: '-300', estimated_balance: '-300', + pending_balance_inbound: '0', + pending_balance_outbound: '0', total_sent: '0', total_received: '0', total_fees_sent: '300',