diff --git a/genesis-local.json b/genesis-local.json deleted file mode 100644 index a2cef0c9..00000000 --- a/genesis-local.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "balances": [ - { - "address": "agoric1estsewt6jqsx77pwcxkn5ah0jqgu8rhgflwfdl", - "coins": [ - { - "denom": "ubld", - "amount": "999999995000000000" - } - ] - } - ] -} diff --git a/genesis-main.json b/genesis-main.json deleted file mode 100644 index 55851a92..00000000 --- a/genesis-main.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "balances": [ - { - "address": "agoric1qc3r5tlh9ldvcg54lj8ukrwhytpffkmjxav87c", - "coins": [ - { - "denom": "ubld", - "amount": "1000000" - } - ] - }, - { - "address": "agoric18du3gnu9qqgrcfln804g8gcmruv2gjwgs7mj3l", - "coins": [ - { - "denom": "ubld", - "amount": "497000000" - } - ] - }, - { - "address": "agoric18c72hdqrcc4hkpewvpf7grrx8rawk0045g3tm0", - "coins": [ - { - "amount": "200000000000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric18m9lqhqas8qrwafj92kta0ek2q96kwvyt84793", - "coins": [ - { - "amount": "49999400000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric1grgelyng2v6v3t8z87wu3sxgt9m5s03x7dax7m", - "coins": [ - { - "amount": "1000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric1gmru5vv2zrz9m0d3vwjz6fcchw9ddmq9w3s2qy", - "coins": [ - { - "amount": "1000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric1fl48vsnmsdzcv85q5d2q4z5ajdha8yu38xtsfp", - "coins": [ - { - "amount": "100000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric1w89nsqmcjfw9la2x8pk5k8pwwsgh8mhqs53tsr", - "coins": [ - { - "amount": "300000000000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric10fz6uxxlg7pdkscgpzcny4z6lzhqcluyqhr8au", - "coins": [ - { - "amount": "30000000000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric10mmdskqh6nv8tzd4lhzpevwlky35m43s45ssg5", - "coins": [ - { - "amount": "125000000000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric1kxquj9rnprhyaq2laulgqznz7t6yqmw6wm5ucx", - "coins": [ - { - "amount": "45000000000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric1euw2t0lxgeerlpj0tcy77f9syrmgx26ehdx3sq", - "coins": [ - { - "amount": "100000000000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric1a0fktnlglcpj3pkzwftnr63uqr42mmcskuhakj", - "coins": [ - { - "amount": "25000000000000", - "denom": "ubld" - } - ] - }, - { - "address": "agoric179gfr279yh3zhdw7aw85t7kj4vcsazdckzd42e", - "coins": [ - { - "amount": "125000000000000", - "denom": "ubld" - } - ] - } - ] -} diff --git a/package.json b/package.json index 441854ef..b1d11d10 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "pub": "subql publish", "codegen": "subql codegen", "start:docker": "docker-compose pull && docker-compose up --remove-orphans", - "dev": "network=local && subql codegen && subql build && docker-compose pull && docker-compose up --remove-orphans", + "dev": "subql codegen && subql build && docker-compose pull && docker-compose up --remove-orphans", "prepack": "rm -rf dist && npm run build", "test": "jest", "subql": "subql codegen --help", @@ -45,6 +45,7 @@ }, "dependencies": { "@subql/types-cosmos": "^3.2.3", + "cross-fetch": "^4.0.0", "@types/node": "^17.0.21", "bech32": "^2.0.0", "js-sha256": "^0.11.0", diff --git a/schema.graphql b/schema.graphql index bf1eb036..af258ce0 100644 --- a/schema.graphql +++ b/schema.graphql @@ -266,6 +266,10 @@ type ReserveMetrics @entity { allocations: [ReserveAllocationMetrics] @derivedFrom(field: "reserveMetrics") } +type Account @entity { + id: ID! +} + type IBCChannel @entity { id: ID! channelName: String! @@ -289,7 +293,6 @@ type IBCTransfer @entity { transferType: TransferType! } -<<<<<<< HEAD type VaultStatesDaily @entity { id: ID! blockHeightLast: BigInt! @@ -299,17 +302,11 @@ type VaultStatesDaily @entity { liquidating: BigInt! liquidated: BigInt! liquidatedClosed: BigInt! -======= -type Balance @entity { - id: ID! - address: String @index - balance: BigInt - denom: String @index - account: Account! } -type Account @entity { +type Balances @entity { id: ID! - balances: [Balance] @derivedFrom(field: "account") ->>>>>>> 31796b18 (feat: index transfer events) -} + address: String @index + balance: BigInt + denom: String @index +} \ No newline at end of file diff --git a/src/mappings/constants.ts b/src/mappings/constants.ts index 89ed69d8..8252b42f 100644 --- a/src/mappings/constants.ts +++ b/src/mappings/constants.ts @@ -31,6 +31,27 @@ export const VAULT_STATES = { LIQUIDATED_CLOSED: 'liquidatedClosed', }; +export const VALUE_KEY = b64encode('value'); +export const STORE_KEY = b64encode('store'); +export const VSTORAGE_VALUE = b64encode('vstorage'); +export const KEY_KEY = b64encode('key'); +export const STORE_NAME_KEY = b64encode('store_name'); +export const SUBKEY_KEY = b64encode('store_subkey'); +export const UNPROVED_VALUE_KEY = b64encode('unproved_value'); +export const PACKET_DATA_KEY = 'packet_data'; +export const PACKET_SRC_CHANNEL_KEY = 'packet_src_channel'; +export const PACKET_DST_CHANNEL_KEY = 'packet_dst_channel'; +export const PACKET_SRC_PORT_KEY = 'packet_src_port'; +export const PACKET_DST_PORT_KEY = 'packet_dst_port'; +export const ACTION_KEY = b64encode('action'); +export const IBC_MESSAGE_TRANSFER_VALUE = b64encode('/ibc.applications.transfer.v1.MsgTransfer'); +export const IBC_MESSAGE_RECEIVE_PACKET_VALUE = b64encode('/ibc.core.channel.v1.MsgRecvPacket'); +export const RECEPIENT_KEY = b64encode('recipient'); +export const SENDER_KEY = b64encode('sender'); +export const RECEIVER_KEY = b64encode('receiver'); +export const AMOUNT_KEY = b64encode('amount'); +export const TRANSFER_PORT_VALUE = 'transfer'; + export const BALANCE_FIELDS = { amount: 'amount', // Bank Events @@ -39,33 +60,16 @@ export const BALANCE_FIELDS = { coin_spent: 'spender', transfer_recipient: 'recipient', transfer_sender: 'sender', - burn: "burner", + burn: 'burner', // Distribution Events rewards: 'validator', commission: 'validator', proposer_reward: 'validator', withdraw_rewards: 'validator', withdraw_commission: 'validator', - }; -export const VALUE_KEY = b64encode("value"); -export const STORE_KEY = b64encode("store"); -export const VSTORAGE_VALUE = b64encode("vstorage"); -export const KEY_KEY = b64encode("key"); -export const STORE_NAME_KEY = b64encode("store_name"); -export const SUBKEY_KEY = b64encode("store_subkey"); -export const UNPROVED_VALUE_KEY = b64encode("unproved_value"); -export const PACKET_DATA_KEY = "packet_data"; -export const PACKET_SRC_CHANNEL_KEY = "packet_src_channel"; -export const PACKET_DST_CHANNEL_KEY = "packet_dst_channel"; -export const PACKET_SRC_PORT_KEY = "packet_src_port"; -export const PACKET_DST_PORT_KEY = "packet_dst_port"; -export const ACTION_KEY = b64encode("action"); -export const IBC_MESSAGE_TRANSFER_VALUE = b64encode("/ibc.applications.transfer.v1.MsgTransfer"); -export const IBC_MESSAGE_RECEIVE_PACKET_VALUE = b64encode("/ibc.core.channel.v1.MsgRecvPacket"); -export const RECEPIENT_KEY = b64encode("recipient"); -export const SENDER_KEY = b64encode("sender"); -export const RECEIVER_KEY = b64encode("receiver"); -export const AMOUNT_KEY = b64encode("amount"); -export const TRANSFER_PORT_VALUE = "transfer"; +export const FETCH_ACCOUNTS_URL = + 'http://localhost:1317/cosmos/auth/v1beta1/accounts'; +export const GET_FETCH_BALANCE_URL = (address: string) => + `http://localhost:1317/cosmos/bank/v1beta1/balances/${address}`; \ No newline at end of file diff --git a/src/mappings/custom-types.ts b/src/mappings/custom-types.ts new file mode 100644 index 00000000..8400039d --- /dev/null +++ b/src/mappings/custom-types.ts @@ -0,0 +1,44 @@ +export type Balance = { + denom: string; + amount: string; +}; + +interface PubKey { + '@type': string; + key: string; +} + +export type BaseAccount = { + '@type': string; + address: string; + pub_key: PubKey | null; + account_number: string; + sequence: string; +}; + +export type ModuleAccount = { + '@type': string; + base_account: { + address: string; + pub_key: PubKey | null; + account_number: string; + sequence: string; + }; + name: string; + permissions: string[]; +}; + +type Pagination = { + next_key: string | null; + total: string; +}; + +export type BalancesResponse = { + balances: Balance[]; + pagination: Pagination; +}; + +export type AccountsResponse = { + accounts: (BaseAccount | ModuleAccount)[]; + pagination: Pagination; +}; diff --git a/src/mappings/events/balances.ts b/src/mappings/events/balances.ts index 63ee67de..6f797de8 100644 --- a/src/mappings/events/balances.ts +++ b/src/mappings/events/balances.ts @@ -1,4 +1,4 @@ -import { Account, Balance } from '../../types'; +import { Account, Balances } from '../../types'; import { BALANCE_FIELDS } from '../constants'; import { b64decode } from '../utils'; import { CosmosEvent } from '@subql/types-cosmos'; @@ -72,7 +72,7 @@ export const balancesEventKit = () => { address: string, denom: string ): Promise { - const balance = await Balance.getByFields([ + const balance = await Balances.getByFields([ ['address', '=', address], ['denom', '=', denom], ]); @@ -88,7 +88,7 @@ export const balancesEventKit = () => { denom: string, primaryKey: string ) { - const newBalance = new Balance(primaryKey, address); + const newBalance = new Balances(primaryKey); newBalance.address = address; newBalance.balance = BigInt(0); newBalance.denom = denom; @@ -136,7 +136,7 @@ export const balancesEventKit = () => { amount: bigint, operation: Operation ): Promise { - const balances = await Balance.getByFields([ + const balances = await Balances.getByFields([ ['address', '=', address], ['denom', '=', denom], ]); diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index bbf20fa2..8496d51b 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -1,11 +1,13 @@ -import { StateChangeEvent, IBCChannel, IBCTransfer, TransferType } from '../types'; -import { CosmosEvent } from '@subql/types-cosmos'; +import { StateChangeEvent, IBCChannel, IBCTransfer, TransferType, Balances } from '../types'; +import { CosmosBlock, CosmosEvent } from '@subql/types-cosmos'; import { b64decode, extractStoragePath, getStateChangeModule, resolveBrandNamesAndValues, getEscrowAddress, + isBaseAccount, + isModuleAccount, } from './utils'; import { @@ -23,12 +25,18 @@ import { PACKET_DST_PORT_KEY, PACKET_SRC_PORT_KEY, TRANSFER_PORT_VALUE, + BALANCE_FIELDS, + FETCH_ACCOUNTS_URL, + GET_FETCH_BALANCE_URL, } from './constants'; import { psmEventKit } from './events/psm'; import { boardAuxEventKit } from './events/boardAux'; import { priceFeedEventKit } from './events/priceFeed'; import { vaultsEventKit } from './events/vaults'; import { reservesEventKit } from './events/reserves'; +import { AccountsResponse, BaseAccount, ModuleAccount, BalancesResponse, Balance } from './custom-types'; +import { Operation, balancesEventKit } from './events/balances'; +import crossFetch from 'cross-fetch'; // @ts-ignore BigInt.prototype.toJSON = function () { @@ -242,20 +250,12 @@ export async function handleStateChangeEvent(cosmosEvent: CosmosEvent): Promise< await Promise.allSettled(recordSaves); } -export async function handleBalanceEvent( - cosmosEvent: CosmosEvent -): Promise { - const { event } = cosmosEvent; - const incrementEventTypes = [ - EVENT_TYPES.COMMISSION, - EVENT_TYPES.REWARDS, - EVENT_TYPES.PROPOSER_REWARD, - EVENT_TYPES.COIN_RECEIVED, - EVENT_TYPES.COINBASE, - ]; +export const handleBalanceEvent = async (cosmosEvent: CosmosEvent): Promise => { + const { event } = cosmosEvent; - const decrementEventTypes = [EVENT_TYPES.COIN_SPENT, EVENT_TYPES.BURN]; + const incrementEventTypes = [EVENT_TYPES.COIN_RECEIVED]; + const decrementEventTypes = [EVENT_TYPES.COIN_SPENT]; let operation: Operation | null = null; @@ -275,60 +275,90 @@ export async function handleBalanceEvent( const data = balancesKit.getData(cosmosEvent); logger.info(`Decoded Data:${JSON.stringify(data)}`); - const address = balancesKit.getAttributeValue( - data, - BALANCE_FIELDS[event.type as keyof typeof BALANCE_FIELDS] - ); + const address = balancesKit.getAttributeValue(data, BALANCE_FIELDS[event.type as keyof typeof BALANCE_FIELDS]); - const transactionAmount = balancesKit.getAttributeValue( - data, - BALANCE_FIELDS.amount - ); + const transactionAmount = balancesKit.getAttributeValue(data, BALANCE_FIELDS.amount); if (!address) { - logger.error(`Address ${address} is missing or invalid.`); + logger.error('Address is missing or invalid.'); return; } - const { isValidTransaction, coins } = - balancesKit.validateTransaction(transactionAmount); + const { isValidTransaction, coins } = balancesKit.validateTransaction(transactionAmount); if (!transactionAmount || !isValidTransaction) { logger.error(`Amount ${transactionAmount} invalid.`); return; } - for (let coin of coins) { - const { amount, denom } = coin; + for (let { denom, amount } of coins) { const entryExists = await balancesKit.addressExists(address, denom); if (!entryExists) { - const primaryKey = `${address}-${denom}`; + const primaryKey = address + denom; await balancesKit.createBalancesEntry(address, denom, primaryKey); } - const formattedAmount = BigInt(Math.round(Number(amount))); + const formattedAmount = BigInt(Math.round(Number(amount.slice(0, -4)))); await balancesKit.updateBalance(address, denom, formattedAmount, operation); } -} +}; -export async function initiateBalancesTable(block: CosmosBlock): Promise { +const fetchAccounts = async (): Promise => { try { - const data = mainnetGenesisData; - for (let element of data.balances) { - let newBalance; - for (const coin of element.coins) { - newBalance = new Balance(`${element.address}-${coin.denom}`, element.address); - newBalance.address = element.address; - newBalance.balance = BigInt(coin.amount); - newBalance.denom = coin.denom; - - await newBalance.save(); + const response = await crossFetch(FETCH_ACCOUNTS_URL); + const accounts: AccountsResponse = await response.json(); + return accounts; + } catch (error) { + logger.error(`Error fetching accounts: ${error}`); + } +}; + +const extractAddresses = (accounts: (BaseAccount | ModuleAccount)[]): Array => { + return accounts + .map((account) => { + if (isBaseAccount(account)) { + return account.address; + } else if (isModuleAccount(account)) { + return account.base_account.address; } - } + return ''; + }) + .filter((address) => address !== null); +}; - logger.info(`Balances Table Initiated`); +const fetchBalance = async (address: string): Promise => { + try { + const response = await crossFetch(GET_FETCH_BALANCE_URL(address)); + const balance: BalancesResponse = await response.json(); + return balance; } catch (error) { - logger.error(`Error initiating balances table: ${error}`); + logger.error(`Error fetching balance for address ${address}: ${error}`); } -} \ No newline at end of file +}; + +const saveAccountBalances = async (address: string, balances: Balance[]) => { + for (let { denom, amount } of balances) { + const newBalance = new Balances(address); + newBalance.address = address; + newBalance.balance = BigInt(amount); + newBalance.denom = denom; + + await newBalance.save(); + } +}; + +export const initiateBalancesTable = async (block: CosmosBlock): Promise => { + const response = await fetchAccounts(); + + if (response) { + const accountAddresses = extractAddresses(response.accounts); + for (let address of accountAddresses) { + const response = await fetchBalance(address); + + if (response) { + await saveAccountBalances(address, response.balances); + } + } + } +}; diff --git a/src/mappings/utils.ts b/src/mappings/utils.ts index e0a05639..acbdde94 100644 --- a/src/mappings/utils.ts +++ b/src/mappings/utils.ts @@ -1,5 +1,6 @@ import { bech32 } from 'bech32'; import sha256 from 'js-sha256'; +import { BaseAccount, ModuleAccount } from './custom-types'; export function extractBrand(str: string): string { return str.replace('Alleged: ', '').replace(' brand', ''); @@ -108,3 +109,11 @@ export const getEscrowAddress = (port: string, channel: string) => { const address = bech32.encode(chainPrefix, bechWords); return address; }; + +export const isBaseAccount = (account: any): account is BaseAccount => { + return account['@type'] === '/cosmos.auth.v1beta1.BaseAccount'; +}; + +export const isModuleAccount = (account: any): account is ModuleAccount => { + return account['@type'] === '/cosmos.auth.v1beta1.ModuleAccount'; +}; \ No newline at end of file