From 63c692c5a26c6613bd3ffbd5feb46f3497f2353d Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Mon, 27 Jan 2025 13:35:28 -0600 Subject: [PATCH 1/5] feat: add estimated inbound and outbound stx balance --- src/api/routes/address.ts | 20 ++++++++++++++++---- src/api/schemas/entities/balances.ts | 10 ++++++++++ src/datastore/pg-store.ts | 20 ++++++++++++-------- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/api/routes/address.ts b/src/api/routes/address.ts index 7cf31bedb..2737754b0 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(), + estimated_balance_inbound: mempoolInbound?.toString(), + estimated_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(), + estimated_balance_inbound: mempoolInbound?.toString(), + estimated_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..cec755499 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', }) ), + estimated_balance_inbound: Type.Optional( + Type.String({ + description: 'Inbound STX balance from pending mempool transactions', + }) + ), + estimated_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..256642601 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,18 @@ 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 ( + 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 `; - 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( From 54e0b70faaaa87966769378fdebbf926f3309747 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Mon, 27 Jan 2025 13:53:14 -0600 Subject: [PATCH 2/5] fix: add select --- src/datastore/pg-store.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/datastore/pg-store.ts b/src/datastore/pg-store.ts index 256642601..7a5062690 100644 --- a/src/datastore/pg-store.ts +++ b/src/datastore/pg-store.ts @@ -2498,8 +2498,9 @@ export class PgStore extends BasePgStore { WHERE pruned = false AND token_transfer_recipient_address = ${principal} ), values AS ( - 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) AS inbound, + COALESCE((SELECT total FROM sent), 0) + COALESCE((SELECT total FROM sponsored), 0) AS outbound ) SELECT inbound, outbound, (inbound - outbound) AS delta `; From 850ca2136ba5c3c15fdf467c5a98a2ddf5114eaa Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Mon, 27 Jan 2025 14:01:52 -0600 Subject: [PATCH 3/5] fix: query --- src/datastore/pg-store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/datastore/pg-store.ts b/src/datastore/pg-store.ts index 7a5062690..2f66ede05 100644 --- a/src/datastore/pg-store.ts +++ b/src/datastore/pg-store.ts @@ -2502,7 +2502,7 @@ export class PgStore extends BasePgStore { COALESCE((SELECT total FROM received), 0) AS inbound, COALESCE((SELECT total FROM sent), 0) + COALESCE((SELECT total FROM sponsored), 0) AS outbound ) - SELECT inbound, outbound, (inbound - outbound) AS delta + SELECT inbound, outbound, (inbound - outbound) AS delta FROM values `; return { inbound: BigInt(results[0]?.inbound ?? '0'), From 458027f9a910cbc43e04b234cfcaf66f45e626fd Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Mon, 27 Jan 2025 15:48:08 -0600 Subject: [PATCH 4/5] test: pending balance --- src/api/routes/address.ts | 8 ++++---- src/api/schemas/entities/balances.ts | 4 ++-- tests/api/mempool.test.ts | 12 ++++++++++++ tests/api/tx.test.ts | 8 ++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/api/routes/address.ts b/src/api/routes/address.ts index 2737754b0..77e709463 100644 --- a/src/api/routes/address.ts +++ b/src/api/routes/address.ts @@ -132,8 +132,8 @@ export const AddressRoutes: FastifyPluginAsync< const result: AddressStxBalance = { balance: stxBalanceResult.balance.toString(), estimated_balance: mempoolBalance?.toString(), - estimated_balance_inbound: mempoolInbound?.toString(), - estimated_balance_outbound: mempoolOutbound?.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(), @@ -230,8 +230,8 @@ export const AddressRoutes: FastifyPluginAsync< stx: { balance: stxBalanceResult.balance.toString(), estimated_balance: mempoolBalance?.toString(), - estimated_balance_inbound: mempoolInbound?.toString(), - estimated_balance_outbound: mempoolOutbound?.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 cec755499..c23c58491 100644 --- a/src/api/schemas/entities/balances.ts +++ b/src/api/schemas/entities/balances.ts @@ -26,12 +26,12 @@ export const StxBalanceSchema = Type.Object( description: 'Total STX balance considering pending mempool transactions', }) ), - estimated_balance_inbound: Type.Optional( + pending_balance_inbound: Type.Optional( Type.String({ description: 'Inbound STX balance from pending mempool transactions', }) ), - estimated_balance_outbound: Type.Optional( + pending_balance_outbound: Type.Optional( Type.String({ description: 'Outbound STX balance from pending mempool transactions', }) 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', From 55ca2b2aa9d0e92da0aafcf08fc8069eb31bfc22 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Tue, 28 Jan 2025 10:04:32 -0600 Subject: [PATCH 5/5] fix: address tests --- tests/api/address.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) 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',