import * as dotenv from 'dotenv' ; import * as getopts from 'getopts' ; import * as bigint from 'extra-bigint' ; import { table } from 'table' ; import { StacksCoreRpcClient } from '../../src/core-rpc/client' ; import { PgDataStore } from '../../src/datastore/postgres-store' ; type AddressBalanceResult = { count : number ; address : string ; apiBalance ? : bigint ; nodeBalance ? : bigint ; } ; type BlockInfo = { block_height : number ; block_hash : Buffer ; index_block_hash : Buffer ; } ; type TableCellValue = string | number | bigint | undefined ; param count param blockHeight async function printTopAccountBalances ( count , blockHeight ) { const db = await PgDataStore . connect ( true ) ; const heightText = blockHeight == 0 ? 'chain tip' : `block height ${ blockHeight }` ; console . log ( `Calculating balances for top ${ count } accounts at ${ heightText }...` ) ; const blockInfo = await db . query ( async ( client ) => { const result = await client . query < BlockInfo > ( `
      SELECT block_height, block_hash, index_block_hash
      FROM blocks
      WHERE canonical = true AND block_height = (
        CASE
          WHEN $1=0 THEN (SELECT MAX(block_height) FROM blocks)
          ELSE $1
        END
      )
      ` , [ blockHeight ] ) ; return result . rows [ 0 ] ; } ) ; const addressBalances = await db . query ( async ( client ) => { const result = await client . query < AddressBalanceResult > ( `
      WITH addresses AS ((
        SELECT
          sender AS address
        FROM
          stx_events
        WHERE
          sender IS NOT NULL
          AND block_height <= $1)
      UNION ALL (
        SELECT
          recipient AS address
        FROM
          stx_events
        WHERE
          recipient IS NOT NULL
          AND block_height <= $1)
      )
      SELECT
        COUNT(*) AS count, address
      FROM
        addresses
      GROUP BY
        address
      ORDER BY
        count DESC
      LIMIT $2;
      ` , [ blockInfo . block_height , count ] ) ; return result . rows ; } ) ; const dbBalances = addressBalances . map ( async ( item ) => { const balance = await db . getStxBalanceAtBlock ( item . address , blockInfo . block_height ) ; item . apiBalance = balance . balance ; } ) ; const nodeBalances = addressBalances . map ( async ( item ) => { const account = await new StacksCoreRpcClient ( ) . getAccount ( item . address , false , blockInfo . index_block_hash . toString ( 'hex' ) ) ; item . nodeBalance = BigInt ( account . balance ) + BigInt ( account . locked ) ; } ) ; await Promise . all ( dbBalances . concat ( nodeBalances ) ) ; const tabularData = [ [ 'event count' , 'address' , 'api balance' , 'node balance' , 'delta' ] , ] ; addressBalances . forEach ( item => { tabularData . push ( [ item . count , item . address , item . apiBalance , item . nodeBalance , bigint . abs ( ( item . apiBalance ?? BigInt ( 0 ) ) - ( item . nodeBalance ?? BigInt ( 0 ) ) ) , ] ) ; } ) ; console . log ( table ( tabularData ) ) ; await db . close ( ) ; } function printUsage ( ) { console . log ( `Usage:` ) ; console . log ( `  node ./index.js stx-balances [--count=<count>] [--block-height=<height>]` ) ; } async function handleProgramArgs ( ) { const parsedOpts = getopts ( process . argv . slice ( 2 ) ) ; const args = { operand : parsedOpts . _ [ 0 ] , options : parsedOpts , } as { operand : 'stx-balances' ; options : { [ 'count' ] ? : number ; [ 'block-height' ] ? : number ; } ; } ; if ( args . operand === 'stx-balances' ) { await printTopAccountBalances ( args . options . count ?? 10 , args . options [ 'block-height' ] ?? 0 ) ; } else if ( parsedOpts . _ [ 0 ] ) { printUsage ( ) ; throw new Error ( `Unexpected program argument: ${ parsedOpts . _ [ 0 ] }` ) ; } else { printUsage ( ) ; } } dotenv . config ( { path : '../.env' } ) ; void handleProgramArgs ( ) . catch ( error => { console . error ( error ) ; process . exit ( 1 ) ; } ) ;