Skip to content

Commit

Permalink
feat: API for total burn reward amount for address
Browse files Browse the repository at this point in the history
  • Loading branch information
zone117x committed Nov 17, 2020
1 parent a63be91 commit 72a560f
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 0 deletions.
55 changes: 55 additions & 0 deletions client/src/generated/apis/BurnchainApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {
BurnchainRewardListResponse,
BurnchainRewardListResponseFromJSON,
BurnchainRewardListResponseToJSON,
BurnchainRewardsTotal,
BurnchainRewardsTotalFromJSON,
BurnchainRewardsTotalToJSON,
} from '../models';

export interface GetBurnchainRewardListRequest {
Expand All @@ -31,6 +34,10 @@ export interface GetBurnchainRewardListByAddressRequest {
offset?: number;
}

export interface GetBurnchainRewardsTotalByAddressRequest {
address: string;
}

/**
* BurnchainApi - interface
*
Expand Down Expand Up @@ -73,6 +80,22 @@ export interface BurnchainApiInterface {
*/
getBurnchainRewardListByAddress(requestParameters: GetBurnchainRewardListByAddressRequest): Promise<BurnchainRewardListResponse>;

/**
* Get the total burnchain (e.g. Bitcoin) rewards for the given recipient
* @summary Get total burnchain rewards for the given recipient
* @param {string} address Reward recipient address. Should either be in the native burnchain\&#39;s format (e.g. B58 for Bitcoin), or if a STX principal address is provided it will be encoded as into the equivalent burnchain format
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof BurnchainApiInterface
*/
getBurnchainRewardsTotalByAddressRaw(requestParameters: GetBurnchainRewardsTotalByAddressRequest): Promise<runtime.ApiResponse<BurnchainRewardsTotal>>;

/**
* Get the total burnchain (e.g. Bitcoin) rewards for the given recipient
* Get total burnchain rewards for the given recipient
*/
getBurnchainRewardsTotalByAddress(requestParameters: GetBurnchainRewardsTotalByAddressRequest): Promise<BurnchainRewardsTotal>;

}

/**
Expand Down Expand Up @@ -156,4 +179,36 @@ export class BurnchainApi extends runtime.BaseAPI implements BurnchainApiInterfa
return await response.value();
}

/**
* Get the total burnchain (e.g. Bitcoin) rewards for the given recipient
* Get total burnchain rewards for the given recipient
*/
async getBurnchainRewardsTotalByAddressRaw(requestParameters: GetBurnchainRewardsTotalByAddressRequest): Promise<runtime.ApiResponse<BurnchainRewardsTotal>> {
if (requestParameters.address === null || requestParameters.address === undefined) {
throw new runtime.RequiredError('address','Required parameter requestParameters.address was null or undefined when calling getBurnchainRewardsTotalByAddress.');
}

const queryParameters: runtime.HTTPQuery = {};

const headerParameters: runtime.HTTPHeaders = {};

const response = await this.request({
path: `/extended/v1/burnchain/rewards/{address}/total`.replace(`{${"address"}}`, encodeURIComponent(String(requestParameters.address))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
});

return new runtime.JSONApiResponse(response, (jsonValue) => BurnchainRewardsTotalFromJSON(jsonValue));
}

/**
* Get the total burnchain (e.g. Bitcoin) rewards for the given recipient
* Get total burnchain rewards for the given recipient
*/
async getBurnchainRewardsTotalByAddress(requestParameters: GetBurnchainRewardsTotalByAddressRequest): Promise<BurnchainRewardsTotal> {
const response = await this.getBurnchainRewardsTotalByAddressRaw(requestParameters);
return await response.value();
}

}
65 changes: 65 additions & 0 deletions client/src/generated/models/BurnchainRewardsTotal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
/**
* Stacks 2.0 Blockchain API
* This is the documentation for the Stacks 2.0 Blockchain API. It is comprised of two parts; the Stacks Blockchain API and the Stacks Core API. [![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/614feab5c108d292bffa#?env%5BStacks%20Blockchain%20API%5D=W3sia2V5Ijoic3R4X2FkZHJlc3MiLCJ2YWx1ZSI6IlNUMlRKUkhESE1ZQlE0MTdIRkIwQkRYNDMwVFFBNVBYUlg2NDk1RzFWIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJibG9ja19pZCIsInZhbHVlIjoiMHgiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6Im9mZnNldCIsInZhbHVlIjoiMCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoibGltaXRfdHgiLCJ2YWx1ZSI6IjIwMCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoibGltaXRfYmxvY2siLCJ2YWx1ZSI6IjMwIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJ0eF9pZCIsInZhbHVlIjoiMHg1NDA5MGMxNmE3MDJiNzUzYjQzMTE0ZTg4NGJjMTlhODBhNzk2MzhmZDQ0OWE0MGY4MDY4Y2RmMDAzY2RlNmUwIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJjb250cmFjdF9pZCIsInZhbHVlIjoiU1RKVFhFSlBKUFBWRE5BOUIwNTJOU1JSQkdRQ0ZOS1ZTMTc4VkdIMS5oZWxsb193b3JsZFxuIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJidGNfYWRkcmVzcyIsInZhbHVlIjoiYWJjIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJjb250cmFjdF9hZGRyZXNzIiwidmFsdWUiOiJTVEpUWEVKUEpQUFZETkE5QjA1Mk5TUlJCR1FDRk5LVlMxNzhWR0gxIiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJjb250cmFjdF9uYW1lIiwidmFsdWUiOiJoZWxsb193b3JsZCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiY29udHJhY3RfbWFwIiwidmFsdWUiOiJzdG9yZSIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiY29udHJhY3RfbWV0aG9kIiwidmFsdWUiOiJnZXQtdmFsdWUiLCJlbmFibGVkIjp0cnVlfV0=)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

import { exists, mapValues } from '../runtime';
/**
* Total burnchain rewards made to a recipient
* @export
* @interface BurnchainRewardsTotal
*/
export interface BurnchainRewardsTotal {
/**
* The recipient address that received the burnchain rewards, in the format native to the burnchain (e.g. B58 encoded for Bitcoin)
* @type {string}
* @memberof BurnchainRewardsTotal
*/
reward_recipient: string;
/**
* The total amount of burnchain tokens rewarded to the recipient, in the smallest unit (e.g. satoshis for Bitcoin)
* @type {string}
* @memberof BurnchainRewardsTotal
*/
reward_amount: string;
}

export function BurnchainRewardsTotalFromJSON(json: any): BurnchainRewardsTotal {
return BurnchainRewardsTotalFromJSONTyped(json, false);
}

export function BurnchainRewardsTotalFromJSONTyped(json: any, ignoreDiscriminator: boolean): BurnchainRewardsTotal {
if ((json === undefined) || (json === null)) {
return json;
}
return {

'reward_recipient': json['reward_recipient'],
'reward_amount': json['reward_amount'],
};
}

export function BurnchainRewardsTotalToJSON(value?: BurnchainRewardsTotal | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {

'reward_recipient': value.reward_recipient,
'reward_amount': value.reward_amount,
};
}


1 change: 1 addition & 0 deletions client/src/generated/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './Block';
export * from './BlockListResponse';
export * from './BurnchainReward';
export * from './BurnchainRewardListResponse';
export * from './BurnchainRewardsTotal';
export * from './ContractInterfaceResponse';
export * from './ContractSourceResponse';
export * from './CoreNodeInfoResponse';
Expand Down
4 changes: 4 additions & 0 deletions docs/entities/burnchain/rewards-total.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"reward_recipient": "1C56LYirKa3PFXFsvhSESgDy2acEHVAEt6",
"reward_amount": "18000"
}
17 changes: 17 additions & 0 deletions docs/entities/burnchain/rewards-total.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "BurnchainRewardsTotal",
"description": "Total burnchain rewards made to a recipient",
"type": "object",
"required": ["reward_recipient", "reward_amount"],
"properties": {
"reward_recipient": {
"type": "string",
"description": "The recipient address that received the burnchain rewards, in the format native to the burnchain (e.g. B58 encoded for Bitcoin)"
},
"reward_amount": {
"type": "string",
"description": "The total amount of burnchain tokens rewarded to the recipient, in the smallest unit (e.g. satoshis for Bitcoin)"
}
}
}
14 changes: 14 additions & 0 deletions docs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,20 @@ export interface BurnchainReward {
reward_index: number;
}

/**
* Total burnchain rewards made to a recipient
*/
export interface BurnchainRewardsTotal {
/**
* The recipient address that received the burnchain rewards, in the format native to the burnchain (e.g. B58 encoded for Bitcoin)
*/
reward_recipient: string;
/**
* The total amount of burnchain tokens rewarded to the recipient, in the smallest unit (e.g. satoshis for Bitcoin)
*/
reward_amount: string;
}

/**
* Describes representation of a Type-0 Stacks 2.0 transaction. https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-005-blocks-and-transactions.md#type-0-transferring-an-asset
*/
Expand Down
23 changes: 23 additions & 0 deletions docs/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,29 @@ paths:
$ref: ./api/burnchain/get-rewards.schema.json
example:
$ref: ./api/burnchain/get-rewards.example.json
/extended/v1/burnchain/rewards/{address}/total:
get:
summary: Get total burnchain rewards for the given recipient
description: Get the total burnchain (e.g. Bitcoin) rewards for the given recipient
tags:
- Burnchain
operationId: get_burnchain_rewards_total_by_address
parameters:
- name: address
in: path
description: Reward recipient address. Should either be in the native burnchain's format (e.g. B58 for Bitcoin), or if a STX principal address is provided it will be encoded as into the equivalent burnchain format
required: true
schema:
type: string
responses:
200:
description: List of burnchain reward recipients and amounts
content:
application/json:
schema:
$ref: ./entities/burnchain/rewards-total.schema.json
example:
$ref: ./entities/burnchain/rewards-total.example.json

/extended/v1/contract/{contract_id}:
get:
Expand Down
30 changes: 30 additions & 0 deletions src/api/routes/burnchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { addAsync, RouterWithAsync } from '@awaitjs/express';
import {
BurnchainReward,
BurnchainRewardListResponse,
BurnchainRewardsTotal,
} from '@blockstack/stacks-blockchain-api-types';

import { DataStore } from '../../datastore/common';
Expand Down Expand Up @@ -85,5 +86,34 @@ export function createBurnchainRouter(db: DataStore): RouterWithAsync {
res.json(response);
});

router.getAsync('/rewards/:address/total', async (req, res) => {
const { address } = req.params;

let burnchainAddress: string | undefined = undefined;
const queryAddr = address.trim();
if (isValidBitcoinAddress(queryAddr)) {
burnchainAddress = queryAddr;
} else {
const convertedAddr = tryConvertC32ToBtc(queryAddr);
if (convertedAddr) {
burnchainAddress = convertedAddr;
}
}
if (!burnchainAddress) {
res
.status(400)
.json({ error: `Address ${queryAddr} is not a valid Bitcoin or STX address.` });
return;
}

const queryResults = await db.getBurnchainRewardsTotal(burnchainAddress);
const response: BurnchainRewardsTotal = {
reward_recipient: queryResults.reward_recipient,
reward_amount: queryResults.reward_amount.toString(),
};
// TODO: schema validation
res.json(response);
});

return router;
}
3 changes: 3 additions & 0 deletions src/datastore/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ export interface DataStore extends DataStoreEventEmitter {
limit: number;
offset: number;
}): Promise<DbBurnchainReward[]>;
getBurnchainRewardsTotal(
burnchainRecipient: string
): Promise<{ reward_recipient: string; reward_amount: bigint }>;

getStxBalance(stxAddress: string): Promise<DbStxBalance>;
getStxBalanceAtBlock(stxAddress: string, blockHeight: number): Promise<DbStxBalance>;
Expand Down
6 changes: 6 additions & 0 deletions src/datastore/memory-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ export class MemoryDataStore extends (EventEmitter as { new (): DataStoreEventEm
throw new Error('Method not implemented.');
}

getBurnchainRewardsTotal(
burnchainRecipient: string
): Promise<{ reward_recipient: string; reward_amount: bigint }> {
throw new Error('Method not implemented.');
}

updateTx(tx: DbTx) {
const txStored = { ...tx };
this.txs.set(tx.tx_id, { entry: txStored });
Expand Down
26 changes: 26 additions & 0 deletions src/datastore/postgres-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,32 @@ export class PgDataStore extends (EventEmitter as { new (): DataStoreEventEmitte
}
}

async getBurnchainRewardsTotal(
burnchainRecipient: string
): Promise<{ reward_recipient: string; reward_amount: bigint }> {
const client = await this.pool.connect();
try {
await client.query('BEGIN');
const queryResults = await client.query<{
amount: string;
}>(
`
SELECT sum(reward_amount) amount
FROM burnchain_rewards
WHERE canonical = true AND reward_recipient = $1
`,
[burnchainRecipient]
);
const resultAmount = BigInt(queryResults.rows[0]?.amount ?? 0);
return { reward_recipient: burnchainRecipient, reward_amount: resultAmount };
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
}

async updateTx(client: ClientBase, tx: DbTx): Promise<number> {
const result = await client.query(
`
Expand Down
56 changes: 56 additions & 0 deletions src/tests/api-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,62 @@ describe('api tests', () => {
expect(JSON.parse(rewardResult.text)).toEqual(expectedResp1);
});

test('fetch burnchain total rewards for BTC address', async () => {
const addr = '1G4ayBXJvxZMoZpaNdZG6VyWwWq2mHpMjQ';
const reward1: DbBurnchainReward = {
canonical: true,
burn_block_hash: '0x1234',
burn_block_height: 200,
burn_amount: 2000n,
reward_recipient: addr,
reward_amount: 1000n,
reward_index: 0,
};
const reward2: DbBurnchainReward = {
canonical: true,
burn_block_hash: '0x2234',
burn_block_height: 201,
burn_amount: 2000n,
reward_recipient: addr,
reward_amount: 1001n,
reward_index: 0,
};
const reward3: DbBurnchainReward = {
canonical: true,
burn_block_hash: '0x3234',
burn_block_height: 202,
burn_amount: 2000n,
reward_recipient: addr,
reward_amount: 1002n,
reward_index: 0,
};
await db.updateBurnchainRewards({
burnchainBlockHash: reward1.burn_block_hash,
burnchainBlockHeight: reward1.burn_block_height,
rewards: [reward1],
});
await db.updateBurnchainRewards({
burnchainBlockHash: reward2.burn_block_hash,
burnchainBlockHeight: reward2.burn_block_height,
rewards: [reward2],
});
await db.updateBurnchainRewards({
burnchainBlockHash: reward3.burn_block_hash,
burnchainBlockHeight: reward3.burn_block_height,
rewards: [reward3],
});
const rewardResult = await supertest(api.server).get(
`/extended/v1/burnchain/rewards/${addr}/total`
);
expect(rewardResult.status).toBe(200);
expect(rewardResult.type).toBe('application/json');
const expectedResp1 = {
reward_recipient: '1G4ayBXJvxZMoZpaNdZG6VyWwWq2mHpMjQ',
reward_amount: '3003',
};
expect(JSON.parse(rewardResult.text)).toEqual(expectedResp1);
});

test('fetch burnchain rewards for BTC address', async () => {
const addr1 = '1G4ayBXJvxZMoZpaNdZG6VyWwWq2mHpMjQ';
const reward1: DbBurnchainReward = {
Expand Down

0 comments on commit 72a560f

Please sign in to comment.