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
77 changes: 77 additions & 0 deletions indexer/src/kadena-server/config/graphql-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ export type Scalars = {
Decimal: { input: any; output: any };
};

export type BalanceNode = {
__typename?: 'BalanceNode';
balance: Scalars['String']['output'];
chainId: Scalars['String']['output'];
module: Scalars['String']['output'];
};

/** A unit of information that stores a set of verified transactions. */
export type Block = Node & {
__typename?: 'Block';
Expand Down Expand Up @@ -704,6 +711,8 @@ export type PoolTransactionsConnection = {

export type Query = {
__typename?: 'Query';
/** Retrieve live balances for a given account with optional filtering by chains and module. Default page size is 20. */
balance: QueryBalanceConnection;
/** Retrieve a block by hash. */
block?: Maybe<Block>;
/** Retrieve blocks by chain and minimal depth. Default page size is 20. */
Expand Down Expand Up @@ -786,6 +795,16 @@ export type Query = {
transfers: QueryTransfersConnection;
};

export type QueryBalanceArgs = {
accountName: Scalars['String']['input'];
after?: InputMaybe<Scalars['String']['input']>;
before?: InputMaybe<Scalars['String']['input']>;
chainIds?: InputMaybe<Array<Scalars['String']['input']>>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
module?: InputMaybe<Scalars['String']['input']>;
};

export type QueryBlockArgs = {
hash: Scalars['String']['input'];
};
Expand Down Expand Up @@ -991,6 +1010,19 @@ export type QueryTransfersArgs = {
requestKey?: InputMaybe<Scalars['String']['input']>;
};

/** Connection type for balance query results. */
export type QueryBalanceConnection = {
__typename?: 'QueryBalanceConnection';
edges: Array<QueryBalanceConnectionEdge>;
pageInfo: PageInfo;
};

export type QueryBalanceConnectionEdge = {
__typename?: 'QueryBalanceConnectionEdge';
cursor: Scalars['String']['output'];
node: BalanceNode;
};

export type QueryBlocksFromDepthConnection = {
__typename?: 'QueryBlocksFromDepthConnection';
edges: Array<QueryBlocksFromDepthConnectionEdge>;
Expand Down Expand Up @@ -1521,6 +1553,7 @@ export type ResolversInterfaceTypes<_RefType extends Record<string, unknown>> =

/** Mapping between all available schema types and the resolvers types */
export type ResolversTypes = {
BalanceNode: ResolverTypeWrapper<BalanceNode>;
BigInt: ResolverTypeWrapper<Scalars['BigInt']['output']>;
Block: ResolverTypeWrapper<
Omit<Block, 'events' | 'minerAccount' | 'parent' | 'transactions'> & {
Expand Down Expand Up @@ -1697,6 +1730,8 @@ export type ResolversTypes = {
PoolTransactionType: PoolTransactionType;
PoolTransactionsConnection: ResolverTypeWrapper<PoolTransactionsConnection>;
Query: ResolverTypeWrapper<{}>;
QueryBalanceConnection: ResolverTypeWrapper<QueryBalanceConnection>;
QueryBalanceConnectionEdge: ResolverTypeWrapper<QueryBalanceConnectionEdge>;
QueryBlocksFromDepthConnection: ResolverTypeWrapper<
Omit<QueryBlocksFromDepthConnection, 'edges'> & {
edges: Array<ResolversTypes['QueryBlocksFromDepthConnectionEdge']>;
Expand Down Expand Up @@ -1822,6 +1857,7 @@ export type ResolversTypes = {

/** Mapping between all available schema types and the resolvers parents */
export type ResolversParentTypes = {
BalanceNode: BalanceNode;
BigInt: Scalars['BigInt']['output'];
Block: Omit<Block, 'events' | 'minerAccount' | 'parent' | 'transactions'> & {
events: ResolversParentTypes['BlockEventsConnection'];
Expand Down Expand Up @@ -1968,6 +2004,8 @@ export type ResolversParentTypes = {
PoolTransactionEdge: PoolTransactionEdge;
PoolTransactionsConnection: PoolTransactionsConnection;
Query: {};
QueryBalanceConnection: QueryBalanceConnection;
QueryBalanceConnectionEdge: QueryBalanceConnectionEdge;
QueryBlocksFromDepthConnection: Omit<QueryBlocksFromDepthConnection, 'edges'> & {
edges: Array<ResolversParentTypes['QueryBlocksFromDepthConnectionEdge']>;
};
Expand Down Expand Up @@ -2075,6 +2113,16 @@ export type ComplexityDirectiveResolver<
Args = ComplexityDirectiveArgs,
> = DirectiveResolverFn<Result, Parent, ContextType, Args>;

export type BalanceNodeResolvers<
ContextType = any,
ParentType extends ResolversParentTypes['BalanceNode'] = ResolversParentTypes['BalanceNode'],
> = {
balance?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
chainId?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
module?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export interface BigIntScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['BigInt'], any> {
name: 'BigInt';
}
Expand Down Expand Up @@ -2849,6 +2897,12 @@ export type QueryResolvers<
ContextType = any,
ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query'],
> = {
balance?: Resolver<
ResolversTypes['QueryBalanceConnection'],
ParentType,
ContextType,
RequireFields<QueryBalanceArgs, 'accountName'>
>;
block?: Resolver<
Maybe<ResolversTypes['Block']>,
ParentType,
Expand Down Expand Up @@ -3025,6 +3079,26 @@ export type QueryResolvers<
>;
};

export type QueryBalanceConnectionResolvers<
ContextType = any,
ParentType extends
ResolversParentTypes['QueryBalanceConnection'] = ResolversParentTypes['QueryBalanceConnection'],
> = {
edges?: Resolver<Array<ResolversTypes['QueryBalanceConnectionEdge']>, ParentType, ContextType>;
pageInfo?: Resolver<ResolversTypes['PageInfo'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type QueryBalanceConnectionEdgeResolvers<
ContextType = any,
ParentType extends
ResolversParentTypes['QueryBalanceConnectionEdge'] = ResolversParentTypes['QueryBalanceConnectionEdge'],
> = {
cursor?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
node?: Resolver<ResolversTypes['BalanceNode'], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};

export type QueryBlocksFromDepthConnectionResolvers<
ContextType = any,
ParentType extends
Expand Down Expand Up @@ -3526,6 +3600,7 @@ export type UserGuardResolvers<
};

export type Resolvers<ContextType = any> = {
BalanceNode?: BalanceNodeResolvers<ContextType>;
BigInt?: GraphQLScalarType;
Block?: BlockResolvers<ContextType>;
BlockEventsConnection?: BlockEventsConnectionResolvers<ContextType>;
Expand Down Expand Up @@ -3581,6 +3656,8 @@ export type Resolvers<ContextType = any> = {
PoolTransactionEdge?: PoolTransactionEdgeResolvers<ContextType>;
PoolTransactionsConnection?: PoolTransactionsConnectionResolvers<ContextType>;
Query?: QueryResolvers<ContextType>;
QueryBalanceConnection?: QueryBalanceConnectionResolvers<ContextType>;
QueryBalanceConnectionEdge?: QueryBalanceConnectionEdgeResolvers<ContextType>;
QueryBlocksFromDepthConnection?: QueryBlocksFromDepthConnectionResolvers<ContextType>;
QueryBlocksFromDepthConnectionEdge?: QueryBlocksFromDepthConnectionEdgeResolvers<ContextType>;
QueryBlocksFromHeightConnection?: QueryBlocksFromHeightConnectionResolvers<ContextType>;
Expand Down
32 changes: 32 additions & 0 deletions indexer/src/kadena-server/config/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ type Subscription {
}

type Query {
"""
Retrieve live balances for a given account with optional filtering by chains and module. Default page size is 20.
"""
balance(
accountName: String!
chainIds: [String!]
module: String
after: String
before: String
first: Int
last: Int
): QueryBalanceConnection! @complexity(value: 1, multipliers: ["first", "last"])

"""
Retrieve a block by hash.
"""
Expand Down Expand Up @@ -419,6 +432,25 @@ type Query {
tokenPrices(protocolAddress: String): [TokenPrice!]! @complexity(value: 1)
}

"""
Connection type for balance query results.
"""
type QueryBalanceConnection {
edges: [QueryBalanceConnectionEdge!]!
pageInfo: PageInfo!
}

type QueryBalanceConnectionEdge {
cursor: String!
node: BalanceNode!
}

type BalanceNode {
module: String!
chainId: String!
balance: String!
}

"""
A fungible-specific account.
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ export type TokenOutput = Token;
*/
export interface GetTokensParams extends PaginationsParams {}

/**
* Parameters for fetching account balances with pagination and filters.
*/
export interface GetAccountBalancesParams extends PaginationsParams {
accountName: string;
chainIds?: string[] | null;
module?: string | null;
}

/**
* Interface defining the contract for balance data access.
* Implementations of this interface handle the details of retrieving
Expand Down Expand Up @@ -154,6 +163,14 @@ export default interface BalanceRepository {
edges: ConnectionEdge<TokenOutput>[];
}>;

/**
* Retrieves live balances for an account with optional module and chain filters, paginated.
*/
getAccountBalances(params: GetAccountBalancesParams): Promise<{
pageInfo: PageInfo;
edges: ConnectionEdge<{ module: string; chainId: string; balance: string }>[];
}>;

/**
* Retrieves fungible token information for a specific account directly from a blockchain node.
* This provides real-time balance information rather than indexed data.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,98 @@ export default class BalanceDbRepository implements BalanceRepository {
return pageInfo;
}

/**
* Retrieves live balances for an account with optional module and chain filters, paginated.
*/
async getAccountBalances(params: {
accountName: string;
chainIds?: string[] | null;
module?: string | null;
after?: string | null;
before?: string | null;
first?: number | null;
last?: number | null;
}) {
const { accountName, chainIds, module } = params;
const { limit, order, after, before } = getPaginationParams(params);

const queryParams: any[] = [accountName, limit];
let conditions = 'WHERE b.account = $1';

if (module) {
queryParams.push(module);
conditions += ` AND b.module = $${queryParams.length}`;
}

if (chainIds && chainIds.length) {
queryParams.push(chainIds);
conditions += ` AND b."chainId" = ANY($${queryParams.length})`;
}

if (after) {
queryParams.push(after);
conditions += ` AND b.id < $${queryParams.length}`;
}

if (before) {
queryParams.push(before);
conditions += ` AND b.id > $${queryParams.length}`;
}

// If module specified, return at most one row per chain (latest by id)
let query = '';
if (module) {
query = `
WITH ranked AS (
SELECT b.id, b."chainId", b.module, b.account,
ROW_NUMBER() OVER (PARTITION BY b."chainId" ORDER BY b.id DESC) AS rn
FROM "Balances" b
${conditions}
)
SELECT id, "chainId", module, account
FROM ranked
WHERE rn = 1
ORDER BY id ${order}
LIMIT $2
`;
} else {
// Without module, allow multiple modules per chain
query = `
SELECT b.id, b."chainId", b.module, b.account
FROM "Balances" b
${conditions}
ORDER BY b.id ${order}
LIMIT $2
`;
}

const { rows } = await rootPgPool.query(query, queryParams);

// Query node for balances in parallel
const nodeQueries = rows.map(async row => {
const res = await handleSingleQuery({
chainId: String(row.chainId),
code: `(${row.module}.details \"${row.account}\")`,
});
const balance = formatBalance_NODE(res).toString();
return {
cursor: row.id.toString(),
node: { module: row.module, chainId: String(row.chainId), balance },
};
});

const edges = await Promise.all(nodeQueries);
const { pageInfo, edges: edgesProcessed } = getPageInfo({
order,
limit,
edges,
after,
before,
});

return { pageInfo, edges: edgesProcessed };
}

private async processAccounts(
rows: { account: string; chainId: string }[],
fungibleName: string,
Expand Down
2 changes: 2 additions & 0 deletions indexer/src/kadena-server/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import { fungibleAccountsByPublicKeyQueryResolver } from './query/fungible-accou
import { fungibleChainAccountQueryResolver } from './query/fungible-chain-account-query-resolver';
import { fungibleChainAccountsByPublicKeyQueryResolver } from './query/fungible-chain-accounts-by-public-key-query-resolver';
import { fungibleChainAccountsQueryResolver } from './query/fungible-chain-accounts-query-resolver';
import { balanceQueryResolver } from './query/balance-query-resolver';
import { gasLimitEstimateQueryResolver } from './query/gas-limit-estimate-query-resolver';
import { graphConfigurationQueryResolver } from './query/graph-configuration-query-resolver';
import { lastBlockHeightQueryResolver } from './query/last-block-height-query-resolver';
Expand Down Expand Up @@ -127,6 +128,7 @@ export const resolvers: Resolvers<ResolverContext> = {
events: eventsSubscriptionResolver,
},
Query: {
balance: balanceQueryResolver,
block: blockQueryResolver,
blocksFromDepth: blocksFromDepthQueryResolver,
blocksFromHeight: blocksFromHeightQueryResolver,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ResolverContext } from '../../config/apollo-server-config';
import { QueryResolvers } from '../../config/graphql-types';

export const balanceQueryResolver: QueryResolvers<ResolverContext>['balance'] = async (
_parent,
args,
context,
) => {
const { accountName, chainIds, module, after, before, first, last } = args;
const output = await context.balanceRepository.getAccountBalances({
accountName,
chainIds: chainIds ?? null,
module: module ?? null,
after: after ?? null,
before: before ?? null,
first: first ?? null,
last: last ?? null,
});
return output;
};