diff --git a/indexer/src/kadena-server/config/graphql-types.ts b/indexer/src/kadena-server/config/graphql-types.ts index 29956e80..370be599 100644 --- a/indexer/src/kadena-server/config/graphql-types.ts +++ b/indexer/src/kadena-server/config/graphql-types.ts @@ -29,6 +29,7 @@ export type Scalars = { export type BalanceNode = { __typename?: 'BalanceNode'; + accountName: Scalars['String']['output']; balance: Scalars['String']['output']; chainId: Scalars['String']['output']; module: Scalars['String']['output']; @@ -807,7 +808,7 @@ export type Query = { }; export type QueryBalanceArgs = { - accountName: Scalars['String']['input']; + accountName?: InputMaybe; after?: InputMaybe; before?: InputMaybe; chainIds?: InputMaybe>; @@ -2171,6 +2172,7 @@ export type BalanceNodeResolvers< ContextType = any, ParentType extends ResolversParentTypes['BalanceNode'] = ResolversParentTypes['BalanceNode'], > = { + accountName?: Resolver; balance?: Resolver; chainId?: Resolver; module?: Resolver; @@ -2972,7 +2974,7 @@ export type QueryResolvers< ResolversTypes['QueryBalanceConnection'], ParentType, ContextType, - RequireFields + Partial >; block?: Resolver< Maybe, diff --git a/indexer/src/kadena-server/config/schema.graphql b/indexer/src/kadena-server/config/schema.graphql index 5353c125..25ec8a2b 100644 --- a/indexer/src/kadena-server/config/schema.graphql +++ b/indexer/src/kadena-server/config/schema.graphql @@ -124,7 +124,7 @@ type Query { Retrieve live balances for a given account with optional filtering by chains and module. Default page size is 20. """ balance( - accountName: String! + accountName: String chainIds: [String!] module: String after: String @@ -478,6 +478,7 @@ type QueryBalanceConnectionEdge { } type BalanceNode { + accountName: String! module: String! chainId: String! balance: String! diff --git a/indexer/src/kadena-server/repository/application/balance-repository.ts b/indexer/src/kadena-server/repository/application/balance-repository.ts index e78ca9ac..7d2147d6 100644 --- a/indexer/src/kadena-server/repository/application/balance-repository.ts +++ b/indexer/src/kadena-server/repository/application/balance-repository.ts @@ -99,7 +99,7 @@ export interface GetTokensParams extends PaginationsParams {} * Parameters for fetching account balances with pagination and filters. */ export interface GetAccountBalancesParams extends PaginationsParams { - accountName: string; + accountName?: string | null; chainIds?: string[] | null; module?: string | null; } @@ -168,7 +168,12 @@ export default interface BalanceRepository { */ getAccountBalances(params: GetAccountBalancesParams): Promise<{ pageInfo: PageInfo; - edges: ConnectionEdge<{ module: string; chainId: string; balance: string }>[]; + edges: ConnectionEdge<{ + accountName: string; + module: string; + chainId: string; + balance: string; + }>[]; }>; /** diff --git a/indexer/src/kadena-server/repository/infra/repository/balance-db-repository.ts b/indexer/src/kadena-server/repository/infra/repository/balance-db-repository.ts index 3407741a..bb2ea677 100644 --- a/indexer/src/kadena-server/repository/infra/repository/balance-db-repository.ts +++ b/indexer/src/kadena-server/repository/infra/repository/balance-db-repository.ts @@ -471,7 +471,7 @@ export default class BalanceDbRepository implements BalanceRepository { * Retrieves live balances for an account with optional module and chain filters, paginated. */ async getAccountBalances(params: { - accountName: string; + accountName?: string | null; chainIds?: string[] | null; module?: string | null; after?: string | null; @@ -481,37 +481,83 @@ export default class BalanceDbRepository implements BalanceRepository { }) { const { accountName, chainIds, module } = params; const { limit, order, after, before } = getPaginationParams(params); + let queryParams: any[] = []; + let query = ''; - 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 (accountName) { + // Original behavior: filter by account, optionally by module and chains + queryParams = [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) + 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 { + query = ` + SELECT b.id, b."chainId", b.module, b.account + FROM "Balances" b + ${conditions} + ORDER BY b.id ${order} + LIMIT $2 + `; + } + } else { + // Holders mode: require module; get latest per (account, chainId) + // Build conditions starting with module filter + queryParams = [module, limit]; + let conditions = 'WHERE b.module = $1'; + + 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 + ROW_NUMBER() OVER (PARTITION BY b.account, b."chainId" ORDER BY b.id DESC) AS rn FROM "Balances" b ${conditions} ) @@ -521,15 +567,6 @@ export default class BalanceDbRepository implements BalanceRepository { 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); @@ -543,7 +580,12 @@ export default class BalanceDbRepository implements BalanceRepository { const balance = formatBalance_NODE(res).toString(); return { cursor: row.id.toString(), - node: { module: row.module, chainId: String(row.chainId), balance }, + node: { + accountName: row.account, + module: row.module, + chainId: String(row.chainId), + balance, + }, }; }); diff --git a/indexer/src/kadena-server/resolvers/query/balance-query-resolver.ts b/indexer/src/kadena-server/resolvers/query/balance-query-resolver.ts index 8107ed5c..9ed9dee2 100644 --- a/indexer/src/kadena-server/resolvers/query/balance-query-resolver.ts +++ b/indexer/src/kadena-server/resolvers/query/balance-query-resolver.ts @@ -7,6 +7,9 @@ export const balanceQueryResolver: QueryResolvers['balance'] = context, ) => { const { accountName, chainIds, module, after, before, first, last } = args; + if (!accountName && !module) { + throw new Error('Either accountName or module must be provided.'); + } const output = await context.balanceRepository.getAccountBalances({ accountName, chainIds: chainIds ?? null,