Skip to content

Commit 14cbaf4

Browse files
committed
fix: missing nfts
1 parent a3d42c4 commit 14cbaf4

20 files changed

+892
-741
lines changed

indexer/src/kadena-server/repository/gateway/pact-gateway.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* the application and the blockchain, allowing for different implementations (e.g., mock for testing).
88
*/
99

10+
import { INonFungibleTokenBalance } from '@/kadena-server/repository/application/balance-repository';
11+
1012
/**
1113
* Represents NFT token information retrieved from the blockchain
1214
*
@@ -31,16 +33,6 @@ export interface NftInfo {
3133
};
3234
}
3335

34-
/**
35-
* Parameters for querying information about multiple NFTs
36-
*
37-
* Each entry in the array represents an NFT to query, identified by:
38-
* - tokenId: The unique identifier for the NFT
39-
* - chainId: The Kadena chain where the NFT exists
40-
* - module: Optional module name if the NFT uses a non-standard namespace
41-
*/
42-
export type GetNftsInfoParams = Array<{ tokenId: string; chainId: string; module?: string }>;
43-
4436
/**
4537
* Interface defining operations for interacting with Pact smart contracts
4638
*
@@ -59,5 +51,8 @@ export default interface PactGateway {
5951
* @param account - Account address to check balances for
6052
* @returns Promise resolving to an array of NFT information objects
6153
*/
62-
getNftsInfo(data: GetNftsInfoParams, account: string): Promise<NftInfo[]>;
54+
getNftsInfo(
55+
accountName: string,
56+
nonFungibleTokenBalances: INonFungibleTokenBalance[],
57+
): Promise<NftInfo[]>;
6358
}

indexer/src/kadena-server/repository/infra/gateway/pact-api-gateway.ts

Lines changed: 83 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
* It specializes in retrieving NFT information from Marmalade v2 contracts.
77
*/
88

9+
import { INonFungibleTokenBalance } from '@/kadena-server/repository/application/balance-repository';
910
import { handleSingleQuery } from '../../../../utils/raw-query';
10-
import PactGateway, { GetNftsInfoParams } from '../../gateway/pact-gateway';
11+
import PactGateway, { NftInfo } from '../../gateway/pact-gateway';
1112

1213
/**
1314
* Concrete implementation of the PactGateway interface
@@ -33,53 +34,91 @@ export default class PactApiGateway implements PactGateway {
3334
* @param account - Account address to check balances for
3435
* @returns Promise resolving to an array of NFT information objects
3536
*/
36-
async getNftsInfo(data: GetNftsInfoParams, account: string) {
37-
// Execute queries for each NFT in parallel
38-
const promises = data.map(async nft => {
39-
// Query 1: Get basic token information
40-
const query = {
41-
chainId: nft.chainId,
42-
code: `(marmalade-v2.ledger.get-token-info \"${nft.tokenId}\")`,
43-
};
44-
const res = await handleSingleQuery(query);
45-
const result = JSON.parse(res.result ?? '{}');
37+
async getNftsInfo(account: string, nfts: INonFungibleTokenBalance[]) {
38+
const promises = nfts.map(async nft => {
39+
const result =
40+
nft.module === 'marmalade-v2.ledger'
41+
? await this.getTokenInfoV2(nft.chainId, nft.tokenId, account)
42+
: await this.getTokenInfoV1(nft.chainId, nft.tokenId, account);
43+
return result;
44+
});
45+
46+
const nftsInfo = await Promise.all(promises);
47+
return nftsInfo;
48+
}
4649

47-
// Query 2: Get token version information
48-
const versionQuery = {
49-
chainId: nft.chainId,
50-
code: `(marmalade-v2.ledger.get-version \"${nft.tokenId}\")`,
51-
};
52-
const versionRes = await handleSingleQuery(versionQuery);
53-
const versionResult = JSON.parse(versionRes.result ?? '{}');
50+
private async getTokenInfoV1(
51+
chainId: string,
52+
tokenId: string,
53+
account: string,
54+
): Promise<NftInfo> {
55+
const query = {
56+
chainId: chainId,
57+
code: `(marmalade.ledger.get-policy-info \"${tokenId}\")`,
58+
};
59+
const res = await handleSingleQuery(query);
60+
const result = JSON.parse(res.result ?? '{}');
5461

55-
// Query 3: Get account-specific balance and guard information
56-
const balanceQuery = {
57-
chainId: nft.chainId,
58-
code: `(marmalade-v2.ledger.details "${nft.tokenId}" "${account}")`,
59-
};
60-
const balanceRes = await handleSingleQuery(balanceQuery);
61-
const balanceResult = JSON.parse(balanceRes.result ?? '{}');
62+
const detailsQuery = {
63+
chainId: chainId,
64+
code: `(marmalade.ledger.details "${tokenId}" "${account}")`,
65+
};
66+
const detailsRes = await handleSingleQuery(detailsQuery);
67+
const detailsResult = JSON.parse(detailsRes.result ?? '{}');
68+
69+
return {
70+
version: 'v1',
71+
uri:
72+
result.token?.manifest?.uri?.data && result.token?.manifest?.uri?.scheme
73+
? `data:${result.token.manifest.uri.scheme},${result.token.manifest.uri.data}`
74+
: 'unknown',
75+
supply: result?.token?.supply ? Number(result.token.supply) : 0,
76+
precision: result?.token?.precision?.int ? Number(result.token?.precision.int) : 0,
77+
balance: detailsResult.balance ? Number(detailsResult.balance) : 0,
78+
guard: {
79+
keys: detailsResult?.guard?.keys ?? [],
80+
predicate: detailsResult?.guard?.pred ?? '',
81+
raw: JSON.stringify({
82+
keys: detailsResult?.guard?.keys ?? [],
83+
predicate: detailsResult?.guard?.pred ?? '',
84+
}),
85+
},
86+
};
87+
}
6288

63-
// Combine all query results into a single NFT information object
64-
return {
65-
version: versionResult.int ?? 'unknown',
66-
uri: result.uri ?? 'unknown',
67-
supply: result?.supply ? Number(result.supply) : 0,
68-
precision: result?.precision?.int ? Number(result.precision.int) : 0,
69-
balance: balanceResult.balance ? Number(balanceResult.balance) : 0,
70-
guard: {
89+
private async getTokenInfoV2(
90+
chainId: string,
91+
tokenId: string,
92+
account: string,
93+
): Promise<NftInfo> {
94+
const query = {
95+
chainId: chainId,
96+
code: `(marmalade-v2.ledger.get-token-info \"${tokenId}\")`,
97+
};
98+
const res = await handleSingleQuery(query);
99+
const result = JSON.parse(res.result ?? '{}');
100+
101+
const balanceQuery = {
102+
chainId: chainId,
103+
code: `(marmalade-v2.ledger.details "${tokenId}" "${account}")`,
104+
};
105+
const balanceRes = await handleSingleQuery(balanceQuery);
106+
const balanceResult = JSON.parse(balanceRes.result ?? '{}');
107+
108+
return {
109+
version: 'v2',
110+
uri: result.uri ?? 'unknown',
111+
supply: result?.supply ? Number(result.supply) : 0,
112+
precision: result?.precision?.int ? Number(result.precision.int) : 0,
113+
balance: balanceResult.balance ? Number(balanceResult.balance) : 0,
114+
guard: {
115+
keys: balanceResult?.guard?.keys ?? [],
116+
predicate: balanceResult?.guard?.pred ?? '',
117+
raw: JSON.stringify({
71118
keys: balanceResult?.guard?.keys ?? [],
72119
predicate: balanceResult?.guard?.pred ?? '',
73-
raw: JSON.stringify({
74-
keys: balanceResult?.guard?.keys ?? [],
75-
predicate: balanceResult?.guard?.pred ?? '',
76-
}),
77-
},
78-
};
79-
});
80-
81-
// Wait for all NFT queries to complete
82-
const nfts = await Promise.all(promises);
83-
return nfts;
120+
}),
121+
},
122+
};
84123
}
85124
}

indexer/src/kadena-server/repository/infra/repository/balance-db-repository.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,7 @@ export default class BalanceDbRepository implements BalanceRepository {
214214

215215
const { rows: accountRows } = await rootPgPool.query(balanceQuery, balanceQueryParams);
216216

217-
const output = accountRows
218-
.filter(r => r.hasTokenId)
219-
.map(r => fungibleChainAccountValidator.validate(r));
217+
const output = accountRows.map(r => fungibleChainAccountValidator.validate(r));
220218
return output;
221219
}
222220

@@ -235,15 +233,16 @@ export default class BalanceDbRepository implements BalanceRepository {
235233
SELECT b.id, b."chainId", b.balance, b."tokenId", b.account, b.module, b."hasTokenId"
236234
FROM "Balances" b
237235
WHERE b.account = $1
238-
AND b.module = 'marmalade-v2.ledger'
239-
`;
236+
AND (b.module = 'marmalade-v2.ledger' OR b.module = 'marmalade.ledger')
237+
ORDER BY b.id DESC
238+
`;
240239

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

243242
if (rows.length === 0) return null;
244243

245244
const nonFungibleTokenBalances = rows
246-
.filter(r => r.hasTokenId)
245+
.filter(row => row.hasTokenId && row.tokenId != '')
247246
.map(row => {
248247
return nonFungibleTokenBalanceValidator.validate(row);
249248
});
@@ -272,15 +271,16 @@ export default class BalanceDbRepository implements BalanceRepository {
272271
SELECT b.id, b."chainId", b.balance, b."tokenId", b.account, b.module, b."hasTokenId"
273272
FROM "Balances" b
274273
WHERE b.account = $1
275-
AND b.module = 'marmalade-v2.ledger'
274+
AND (b.module = 'marmalade-v2.ledger' OR b.module = 'marmalade.ledger')
275+
ORDER BY b.id DESC
276276
`;
277277

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

280280
if (rows.length === 0) return [];
281281

282282
const nonFungibleTokenBalances = rows
283-
.filter(row => row.hasTokenId)
283+
.filter(row => row.hasTokenId && row.tokenId != '')
284284
.map(row => {
285285
return nonFungibleTokenBalanceValidator.validate(row);
286286
});
@@ -315,16 +315,19 @@ export default class BalanceDbRepository implements BalanceRepository {
315315
FROM "Balances" b
316316
WHERE b.account = $1
317317
AND b."chainId" = $2
318-
AND b.module = 'marmalade-v2.ledger'
318+
AND (b.module = 'marmalade-v2.ledger' OR b.module = 'marmalade.ledger')
319+
ORDER BY b.id DESC
319320
`;
320321

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

323324
if (rows.length === 0) return null;
324325

325-
const nonFungibleTokenBalances = rows.map(row => {
326-
return nonFungibleTokenBalanceValidator.validate(row);
327-
});
326+
const nonFungibleTokenBalances = rows
327+
.filter(row => row.hasTokenId && row.tokenId != '')
328+
.map(row => {
329+
return nonFungibleTokenBalanceValidator.validate(row);
330+
});
328331

329332
return {
330333
id: getNonFungibleChainAccountBase64ID(chainId, accountName),
@@ -357,7 +360,9 @@ export default class BalanceDbRepository implements BalanceRepository {
357360
WHERE b.account = $1
358361
AND b."tokenId" = $2
359362
AND "chainId" = $3
363+
AND (b.module = 'marmalade-v2.ledger' OR b.module = 'marmalade.ledger')
360364
AND "hasTokenId" = true
365+
ORDER BY b.id DESC
361366
`;
362367

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

indexer/src/kadena-server/resolvers/fields/non-fungible-account/chain-accounts-non-fungible-account-resolver.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,9 @@ export const chainAccountsNonFungibleAccountResolver: NonFungibleAccountResolver
2929

3030
// For each chain account, fetch detailed NFT information from the blockchain
3131
const outputPromises = accounts.map(async account => {
32-
const params = account.nonFungibleTokenBalances.map(n => ({
33-
tokenId: n.tokenId,
34-
chainId: n.chainId,
35-
module: n.module,
36-
}));
37-
3832
const nftsInfo = await context.pactGateway.getNftsInfo(
39-
params ?? [],
40-
parent.accountName ?? '',
33+
account.accountName,
34+
account.nonFungibleTokenBalances,
4135
);
4236
return buildNonFungibleChainAccount(account, nftsInfo);
4337
});

indexer/src/kadena-server/resolvers/node-utils.ts

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,12 @@ export const getNode = async (context: ResolverContext, id: string) => {
131131
// Resolve NonFungibleAccount node - requires account name
132132
// Also fetches additional NFT information from the blockchain
133133
const account = await context.balanceRepository.getNonFungibleAccountInfo(params);
134-
const nftsInfoParams = (account?.nonFungibleTokenBalances ?? []).map(n => ({
135-
tokenId: n.tokenId,
136-
chainId: n.chainId,
137-
}));
134+
135+
if (!account) return null;
138136

139137
const nftsInfo = await context.pactGateway.getNftsInfo(
140-
nftsInfoParams ?? [],
141-
account?.accountName ?? '',
138+
account.accountName,
139+
account.nonFungibleTokenBalances,
142140
);
143141
const output = buildNonFungibleAccount(account, nftsInfo);
144142
return output;
@@ -155,14 +153,9 @@ export const getNode = async (context: ResolverContext, id: string) => {
155153

156154
if (!account) return null;
157155

158-
const nftsInfoParams = (account?.nonFungibleTokenBalances ?? []).map(n => ({
159-
tokenId: n.tokenId,
160-
chainId: n.chainId,
161-
}));
162-
163156
const nftsInfo = await context.pactGateway.getNftsInfo(
164-
nftsInfoParams ?? [],
165-
account?.accountName ?? '',
157+
account.accountName,
158+
account.nonFungibleTokenBalances,
166159
);
167160
return buildNonFungibleChainAccount(account, nftsInfo);
168161
}
@@ -179,12 +172,7 @@ export const getNode = async (context: ResolverContext, id: string) => {
179172

180173
if (!account) return null;
181174

182-
const nftsInfoParams = [{ tokenId, chainId }];
183-
184-
const [nftsInfo] = await context.pactGateway.getNftsInfo(
185-
nftsInfoParams ?? [],
186-
account?.accountName ?? '',
187-
);
175+
const [nftsInfo] = await context.pactGateway.getNftsInfo(account.accountName, [account]);
188176

189177
return {
190178
id: account.id,
@@ -193,12 +181,7 @@ export const getNode = async (context: ResolverContext, id: string) => {
193181
balance: account.balance,
194182
tokenId: account.tokenId,
195183
version: nftsInfo.version,
196-
// TODO: [OPTIMIZATION] Implement guard resolution for NFT balances
197-
guard: {
198-
keys: [],
199-
predicate: '',
200-
raw: JSON.stringify('{}'),
201-
},
184+
guard: nftsInfo.guard,
202185
info: {
203186
precision: nftsInfo.precision,
204187
supply: nftsInfo.supply,

indexer/src/kadena-server/resolvers/query/non-fungible-account-query-resolver.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@ import { buildNonFungibleAccount } from '../output/build-non-fungible-account-ou
44

55
export const nonFungibleAccountQueryResolver: QueryResolvers<ResolverContext>['nonFungibleAccount'] =
66
async (_parent, args, context) => {
7-
const account = await context.balanceRepository.getNonFungibleAccountInfo(args.accountName);
7+
const accountInfo = await context.balanceRepository.getNonFungibleAccountInfo(args.accountName);
88

9-
const params = (account?.nonFungibleTokenBalances ?? []).map(n => ({
10-
tokenId: n.tokenId,
11-
chainId: n.chainId,
12-
module: n.module,
13-
}));
9+
if (!accountInfo) return null;
1410

1511
const nftsInfo = await context.pactGateway.getNftsInfo(
16-
params ?? [],
17-
account?.accountName ?? '',
12+
accountInfo.accountName,
13+
accountInfo.nonFungibleTokenBalances,
1814
);
1915

20-
const output = buildNonFungibleAccount(account, nftsInfo);
16+
const output = buildNonFungibleAccount(accountInfo, nftsInfo);
2117
return output;
2218
};

indexer/src/kadena-server/resolvers/query/non-fungible-chain-account-query-resolver.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,9 @@ export const nonFungibleChainAccountQueryResolver: QueryResolvers<ResolverContex
1111

1212
if (!account) return null;
1313

14-
const params = (account?.nonFungibleTokenBalances ?? []).map(n => ({
15-
tokenId: n.tokenId,
16-
chainId: n.chainId,
17-
}));
18-
1914
const nftsInfo = await context.pactGateway.getNftsInfo(
20-
params ?? [],
21-
account?.accountName ?? '',
15+
account.accountName,
16+
account.nonFungibleTokenBalances,
2217
);
2318

2419
const output = buildNonFungibleChainAccount(account, nftsInfo);

0 commit comments

Comments
 (0)