Skip to content
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
8 changes: 8 additions & 0 deletions migrations/1779742831642_principal-bond-positions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ export function up(pgm: MigrationBuilder): void {
type: 'numeric',
notNull: true,
},
// Running sBTC reward sats accrued to this position, distributed from the
// bond's per-sat reward rate by this participant's staked weight. Maintained
// incrementally on write (and via signed deltas on reorg).
accrued_rewards: {
type: 'numeric',
notNull: true,
default: 0,
},
});

pgm.createIndex('principal_bond_positions', ['principal', 'bond_index'], {
Expand Down
24 changes: 11 additions & 13 deletions migrations/1779745340752_bond-reward-distributions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,34 +59,32 @@ export function up(pgm: MigrationBuilder): void {
type: 'boolean',
notNull: true,
},
remaining_rewards: {
// Per-bond reward distribution, from the pox-5 `bond-distribution` event
// (emitted once per bond during a `calculate-rewards` call).
target_yield: {
type: 'numeric',
notNull: true,
},
accrued_rewards: {
// sBTC rewards earned by this bond in this calculation.
bond_rewards: {
type: 'numeric',
notNull: true,
},
new_reserve: {
// Total sats staked in the bond at the time of this calculation.
bond_staked_sats: {
type: 'numeric',
notNull: true,
},
stx_staker_rewards: {
// Per-sat rewards accrued in this calculation.
accrued_rewards_per_sat: {
type: 'numeric',
notNull: true,
},
stx_cycle: {
type: 'integer',
notNull: true,
},
cycle_staked_ustx: {
// Running per-sat reward total for the bond after this calculation.
cumulative_rewards_per_sat: {
type: 'numeric',
notNull: true,
},
next_rewards_per_ustx: {
type: 'numeric',
notNull: true,
}
});
}

Expand Down
123 changes: 123 additions & 0 deletions migrations/1779745340753_bond-reward-calculations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate';

export const shorthands: ColumnDefinitions | undefined = undefined;

/**
* Cycle-level aggregate emitted once per pox-5 `calculate-rewards` call (the
* `calculate-rewards` event), after all per-bond `bond-distribution` events have
* been folded and the STX reward-cycle accounting committed. Per-bond reward
* data lives in `bond_reward_distributions`.
*/
export function up(pgm: MigrationBuilder): void {
pgm.createTable('bond_reward_calculations', {
tx_id: {
type: 'bytea',
notNull: true,
},
tx_index: {
type: 'smallint',
notNull: true,
},
block_height: {
type: 'integer',
notNull: true,
},
block_hash: {
type: 'bytea',
},
block_time: {
type: 'bigint',
},
index_block_hash: {
type: 'bytea',
notNull: true,
},
parent_block_hash: {
type: 'bytea',
},
parent_index_block_hash: {
type: 'bytea',
notNull: true,
},
burn_block_height: {
type: 'integer',
},
burn_block_time: {
type: 'bigint',
},
microblock_hash: {
type: 'bytea',
notNull: true,
},
microblock_sequence: {
type: 'integer',
notNull: true,
},
microblock_canonical: {
type: 'boolean',
notNull: true,
},
canonical: {
type: 'boolean',
notNull: true,
},
// Burn height at which rewards were calculated.
calculation_height: {
type: 'integer',
notNull: true,
},
// Total new rewards accrued since the last calculation.
gross_accrued_rewards: {
type: 'numeric',
notNull: true,
},
// Portion of gross_accrued_rewards paid out to bonds.
total_bond_rewards: {
type: 'numeric',
notNull: true,
},
// Amount added to the reserve this calculation.
reserve_deposit: {
type: 'numeric',
notNull: true,
},
// Reserve balance after reserve_deposit was applied.
reserve_balance: {
type: 'numeric',
notNull: true,
},
// STX reward cycle this calculation accounts for.
stx_cycle: {
type: 'integer',
notNull: true,
},
// Rewards allocated to STX stakers for the cycle.
total_stx_staker_rewards: {
type: 'numeric',
notNull: true,
},
// Total uSTX staked for the cycle.
cycle_staked_ustx: {
type: 'numeric',
notNull: true,
},
// Per-uSTX rewards accrued this calculation (zero when no STX is staked).
accrued_rewards_per_ustx: {
type: 'numeric',
notNull: true,
},
// Running per-uSTX reward total for the cycle after this calculation.
cumulative_rewards_per_ustx: {
type: 'numeric',
notNull: true,
},
});

pgm.createIndex('bond_reward_calculations', 'tx_id');
pgm.createIndex('bond_reward_calculations', ['index_block_hash', 'canonical']);
pgm.createIndex('bond_reward_calculations', 'stx_cycle');
}

export function down(pgm: MigrationBuilder): void {
pgm.dropTable('bond_reward_calculations');
}
92 changes: 92 additions & 0 deletions migrations/1779745340754_principal-bond-reward-distributions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { ColumnDefinitions, MigrationBuilder } from 'node-pg-migrate';

export const shorthands: ColumnDefinitions | undefined = undefined;

/**
* Per-participant reward distribution source rows. Each pox-5 `bond-distribution`
* event is split across the bond's participants by their staked weight; one row
* is written here per participant per distribution (the amount that participant
* accrued). These rows are the source of truth for reorg adjustments to the
* running `principal_bond_positions.accrued_rewards` total — analogous to how
* `ft_events` rows back the running `ft_balances` totals.
*/
export function up(pgm: MigrationBuilder): void {
pgm.createTable('principal_bond_reward_distributions', {
id: {
type: 'bigserial',
primaryKey: true,
},
principal: {
type: 'text',
notNull: true,
},
bond_index: {
type: 'integer',
notNull: true,
},
// sBTC reward sats this participant accrued from this distribution.
reward_amount: {
type: 'numeric',
notNull: true,
},
tx_id: {
type: 'bytea',
notNull: true,
},
tx_index: {
type: 'smallint',
notNull: true,
},
block_height: {
type: 'integer',
notNull: true,
},
block_hash: {
type: 'bytea',
},
block_time: {
type: 'bigint',
},
index_block_hash: {
type: 'bytea',
notNull: true,
},
parent_block_hash: {
type: 'bytea',
},
parent_index_block_hash: {
type: 'bytea',
notNull: true,
},
burn_block_height: {
type: 'integer',
},
burn_block_time: {
type: 'bigint',
},
microblock_hash: {
type: 'bytea',
notNull: true,
},
microblock_sequence: {
type: 'integer',
notNull: true,
},
microblock_canonical: {
type: 'boolean',
notNull: true,
},
canonical: {
type: 'boolean',
notNull: true,
},
});

pgm.createIndex('principal_bond_reward_distributions', 'tx_id');
pgm.createIndex('principal_bond_reward_distributions', ['index_block_hash', 'canonical']);
pgm.createIndex('principal_bond_reward_distributions', ['principal', 'bond_index']);
}

export function down(pgm: MigrationBuilder): void {
pgm.dropTable('principal_bond_reward_distributions');
}
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"@fastify/type-provider-typebox": "5.2.0",
"@sinclair/typebox": "0.34.48",
"@stacks/api-toolkit": "1.13.0",
"@stacks/codec": "2.0.0-pox5.1",
"@stacks/codec": "2.0.0-pox5.2",
"@stacks/common": "7.3.1",
"@stacks/encryption": "7.4.0",
"@stacks/network": "7.3.1",
Expand Down
2 changes: 0 additions & 2 deletions src/api/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ import { TransactionsRoutes } from './routes/v3/transactions.js';
import { MempoolRoutes } from './routes/v3/mempool.js';
import { BlocksRoutes } from './routes/v3/blocks.js';
import { StakingBondsRoutes } from './routes/v3/staking-bonds.js';
import { StakingPrincipalsRoutes } from './routes/v3/staking-principals.js';

export interface ApiServer {
fastifyApp: FastifyInstance;
Expand Down Expand Up @@ -111,7 +110,6 @@ export const StacksApiRoutes: FastifyPluginAsync<
await fastify.register(MempoolRoutes);
await fastify.register(PrincipalsRoutes);
await fastify.register(StakingBondsRoutes);
await fastify.register(StakingPrincipalsRoutes);
await fastify.register(TransactionsRoutes);
},
{ prefix: '/extended/v3' }
Expand Down
18 changes: 15 additions & 3 deletions src/api/routes/v3/principals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import {
} from '../../schemas/v3/cursors.js';
import { PrincipalTransactionSummarySchema } from '../../schemas/v3/entities/principal-transactions.js';
import { serializePrincipalTransactionSummary } from '../../serializers/v3/transactions.js';
import { PrincipalBondPositionSchema } from '../../schemas/v3/entities/principal-bond-positions.js';
import { serializeDbPrincipalBondPosition } from '../../serializers/v3/bonds.js';
import { handleChainTipCache } from '../../controllers/cache-controller.js';

export const PrincipalsRoutes: FastifyPluginAsync<
Record<never, never>,
Expand Down Expand Up @@ -59,15 +62,24 @@ export const PrincipalsRoutes: FastifyPluginAsync<
fastify.get(
'/principals/:principal/balances/staking',
{
preHandler: handleChainTipCache,
schema: {
operationId: 'get_principal_staking_balances',
summary: 'Get principal staking balances',
description: 'Get principal staking balances',
description:
"Get a principal's staking balances: its bond positions (staked amounts and accrued rewards) across all bonds it is enrolled in",
tags: ['Staking'],
params: Type.Object({ principal: PrincipalSchema }),
response: {
200: Type.Array(PrincipalBondPositionSchema),
},
},
},
async (_req, reply) => {
await reply.send();
async (req, reply) => {
const positions = await fastify.db.v3.getPrincipalStakingPositions({
principal: req.params.principal,
});
await reply.send(positions.map(serializeDbPrincipalBondPosition));
}
);

Expand Down
Loading
Loading