Skip to content

Commit b39fb4e

Browse files
musicboy0322ryjones
authored andcommitted
feat(ledger-browser): handle ERC721 token metadata
Primary Change: * Modify 'TestERC721.json' to update 'BaseUrl' for 'TokenUri()' testing * Add logic to fetch data from 'TokenUri()' * Extend 'name', 'description', and 'image' columns in 'token_erc721' table * Add a simple API server to simulate 'TokenUri()' data responses Fixes #3552 Signed-off-by: musicboy0322 <plusultra0322@gmail.com>
1 parent 8beaa46 commit b39fb4e

File tree

7 files changed

+190
-12
lines changed

7 files changed

+190
-12
lines changed

packages/cactus-plugin-persistence-ethereum/src/main/sql/schema.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ CREATE TABLE IF NOT EXISTS ethereum.token_erc721
158158
token_address text COLLATE pg_catalog."default" NOT NULL,
159159
uri text COLLATE pg_catalog."default" NOT NULL,
160160
token_id numeric NOT NULL,
161+
nft_name text COLLATE pg_catalog."default" NOT NULL,
162+
nft_description text COLLATE pg_catalog."default" NOT NULL,
163+
nft_image text COLLATE pg_catalog."default" NOT NULL,
161164
last_owner_change timestamp without time zone NOT NULL DEFAULT now(),
162165
CONSTRAINT token_erc721_pkey1 PRIMARY KEY (id),
163166
CONSTRAINT token_erc721_contract_tokens_unique UNIQUE (token_address, token_id),
@@ -437,6 +440,9 @@ BEGIN
437440
token_transfer.token_address,
438441
'',
439442
token_transfer.token_id,
443+
'',
444+
'',
445+
'',
440446
token_transfer.created_at
441447
);
442448
ELSE

packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/database.types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ export interface Database {
6262
uri: string;
6363
token_id: number;
6464
id: string;
65+
nft_name: string;
66+
nft_description: string;
67+
nft_image: string;
6568
last_owner_change: string;
6669
};
6770
Insert: {
@@ -70,6 +73,9 @@ export interface Database {
7073
uri: string;
7174
token_id: number;
7275
id?: string;
76+
nft_name?: string;
77+
nft_description?: string;
78+
nft_image?: string;
7379
last_owner_change?: string;
7480
};
7581
Update: {
@@ -78,6 +84,9 @@ export interface Database {
7884
uri?: string;
7985
token_id?: number;
8086
id?: string;
87+
nft_name?: string;
88+
nft_description?: string;
89+
nft_image?: string;
8190
last_owner_change?: string;
8291
};
8392
};

packages/cactus-plugin-persistence-ethereum/src/main/typescript/db-client/db-client.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,21 @@ export default class PostgresDatabaseClient {
298298

299299
this.log.debug("Insert ERC721 token if not present yet:", token);
300300
const insertResponse = await this.client.query(
301-
`INSERT INTO ethereum.token_erc721("account_address", "token_address", "uri", "token_id")
302-
VALUES ($1, $2, $3, $4)
301+
`INSERT INTO ethereum.token_erc721("account_address", "token_address", "uri", "token_id", "nft_name", "nft_description", "nft_image")
302+
VALUES ($1, $2, $3, $4, $5, $6, $7)
303303
ON CONFLICT ON CONSTRAINT token_erc721_contract_tokens_unique
304304
DO
305305
UPDATE SET account_address = EXCLUDED.account_address;
306306
`,
307-
[token.account_address, token.token_address, token.uri, token.token_id],
307+
[
308+
token.account_address,
309+
token.token_address,
310+
token.uri,
311+
token.token_id,
312+
token.nft_name,
313+
token.nft_description,
314+
token.nft_image,
315+
],
308316
);
309317
this.log.debug(
310318
`Inserted ${insertResponse.rowCount} rows into table token_erc721`,

packages/cactus-plugin-persistence-ethereum/src/main/typescript/plugin-persistence-ethereum.ts

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { v4 as uuidv4 } from "uuid";
4343
import type { TransactionInfo, TransactionReceipt } from "web3";
4444
import type { Express } from "express";
4545
import type { Subscription } from "rxjs";
46+
import axios from "axios";
4647

4748
/**
4849
* Constructor parameter for Ethereum persistence plugin.
@@ -238,7 +239,6 @@ export class PluginPersistenceEthereum
238239
);
239240
return false;
240241
}
241-
242242
if (parseInt(ownerAddress, 16) === 0) {
243243
this.log.debug(`Found token ID ${tokenId} without the owner - stop.`);
244244
return false;
@@ -249,17 +249,50 @@ export class PluginPersistenceEthereum
249249
const checkedOwnerAddress = normalizeAddress(ownerAddress);
250250
const tokenUri = await tokenClient.tokenURI(tokenId);
251251

252+
// Fetch token metadata using axios
253+
const metadataResponse = await axios.get(tokenUri);
254+
255+
// Get and check token metadata
256+
const metadata = metadataResponse.data;
257+
if (typeof metadata.properties.name.description !== "string") {
258+
throw new Error(
259+
`Invalid type of 'name' from asset metadata for token ID: ${tokenId}`,
260+
);
261+
}
262+
if (typeof metadata.properties.description.description !== "string") {
263+
throw new Error(
264+
`Invalid type of 'description' from asset metadata for token ID: ${tokenId}`,
265+
);
266+
}
267+
if (typeof metadata.properties.image.description !== "string") {
268+
throw new Error(
269+
`Invalid type of 'image' from asset metadata for token ID: ${tokenId}`,
270+
);
271+
}
272+
273+
// Upsert token information in DB
252274
await this.dbClient.upsertTokenERC721({
253275
account_address: checkedOwnerAddress,
254276
token_address: normalizeAddress(tokenClient.address),
255277
uri: tokenUri,
256278
token_id: tokenId,
279+
nft_name: metadata.properties.name.description,
280+
nft_description: metadata.properties.description.description,
281+
nft_image: metadata.properties.image.description,
257282
});
258283
} catch (err) {
259-
this.log.error(`Could not store issued ERC721 token: ID ${tokenId}`, err);
260-
// We return true since failure here means that there might be more tokens to synchronize
284+
if (err instanceof SyntaxError) {
285+
this.log.error("Error parsing JSON:", err.message);
286+
} else if (axios.isAxiosError(err)) {
287+
this.log.error(`Error fetching metadata using axios: ${err.response}`);
288+
} else {
289+
this.log.error(
290+
`Could not store issued ERC721 token: ID ${tokenId}`,
291+
err,
292+
);
293+
}
261294
}
262-
295+
// We return true since failure here means that there might be more tokens to synchronize
263296
return true;
264297
}
265298

@@ -847,7 +880,6 @@ export class PluginPersistenceEthereum
847880
);
848881

849882
const tokenClient = new TokenClientERC721(this.apiClient, checkedAddress);
850-
851883
try {
852884
await this.dbClient.insertTokenMetadataERC721({
853885
address: checkedAddress,
@@ -860,7 +892,6 @@ export class PluginPersistenceEthereum
860892
getRuntimeErrorCause(err),
861893
);
862894
}
863-
864895
await this.refreshMonitoredTokens();
865896
}
866897

packages/cactus-plugin-persistence-ethereum/src/test/solidity/TestERC721.json

Lines changed: 72 additions & 2 deletions
Large diffs are not rendered by default.

packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-functional.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import { pruneDockerAllIfGithubAction } from "@hyperledger/cactus-test-tooling";
4646
import "jest-extended";
4747
import http from "http";
4848
import { AddressInfo } from "net";
49-
import express from "express";
49+
import express, { Request, Response } from "express";
5050
import bodyParser from "body-parser";
5151
import { Server as SocketIoServer } from "socket.io";
5252
import { v4 as uuidV4 } from "uuid";
@@ -94,6 +94,11 @@ describe("Ethereum persistence plugin tests", () => {
9494
});
9595
let connector: PluginLedgerConnectorEthereum;
9696

97+
// setting for stimulating about gathering ERC721 metadata from TokenUri()
98+
const erc721MetadataApp = express();
99+
const erc721MetadataPort = 3000;
100+
let erc721MetadataServer: http.Server;
101+
97102
//////////////////////////////////
98103
// Helper Functions
99104
//////////////////////////////////
@@ -296,6 +301,35 @@ describe("Ethereum persistence plugin tests", () => {
296301
const apiConfig = new Configuration({ basePath: apiHost });
297302
const apiClient = new EthereumApiClient(apiConfig);
298303

304+
// activate a server for for stimulating about gathering ERC721 metadata from TokenUri()
305+
erc721MetadataApp.get("/metadata/:id", (req: Request, res: Response) => {
306+
// get id parameter from route
307+
const { id } = req.params;
308+
const metadata = {
309+
title: "Asset Metadata",
310+
type: "object",
311+
properties: {
312+
name: {
313+
type: "string",
314+
description: `NFT${id}`,
315+
},
316+
description: {
317+
type: "string",
318+
description: `NFT${id} description`,
319+
},
320+
image: {
321+
type: "string",
322+
description: `https://example.com/${id}`,
323+
},
324+
},
325+
};
326+
res.json(metadata);
327+
});
328+
erc721MetadataServer = http.createServer(erc721MetadataApp);
329+
erc721MetadataServer.listen(erc721MetadataPort, "127.0.0.1", () => {
330+
log.info(`ERC721 TokenUri server is running at http://localhost:${port}`);
331+
});
332+
299333
// Create Ethereum persistence plugin
300334
instanceId = "functional-test";
301335
DatabaseClientMock.mockClear();
@@ -334,6 +368,11 @@ describe("Ethereum persistence plugin tests", () => {
334368
await ledger.destroy();
335369
}
336370

371+
if (erc721MetadataServer) {
372+
log.info("Stop ERC721 TokenUri server...");
373+
await erc721MetadataServer.close();
374+
}
375+
337376
log.info("Prune Docker...");
338377
await pruneDockerAllIfGithubAction({ logLevel: testLogLevel });
339378
}, setupTimeout);
@@ -555,6 +594,9 @@ describe("Ethereum persistence plugin tests", () => {
555594
expect([1, 2, 3]).toInclude(token.token_id);
556595
expect(token.account_address).toBeTruthy();
557596
expect(token.uri).toBeTruthy();
597+
expect(token.nft_name).toBeTruthy();
598+
expect(token.nft_description).toBeTruthy();
599+
expect(token.nft_image).toBeTruthy();
558600
});
559601
});
560602
});

packages/cactus-plugin-persistence-ethereum/src/test/typescript/integration/persistence-ethereum-postgresql-db-client.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ describe("Ethereum persistence PostgreSQL PostgresDatabaseClient tests", () => {
291291
token_address: contractAddress,
292292
uri: "test.uri",
293293
token_id: 1,
294+
nft_name: "NFT",
295+
nft_description: "NFT description",
296+
nft_image: "test.uri",
294297
};
295298
await dbClient.upsertTokenERC721(issuedToken);
296299

@@ -620,6 +623,9 @@ describe("Ethereum persistence PostgreSQL PostgresDatabaseClient tests", () => {
620623
token_address: contractAddress,
621624
uri: issuedTokenUri,
622625
token_id: 1,
626+
nft_name: "NFT",
627+
nft_description: "NFT description",
628+
nft_image: issuedTokenUri,
623629
});
624630

625631
// Transfer our token
@@ -693,6 +699,9 @@ describe("Ethereum persistence PostgreSQL PostgresDatabaseClient tests", () => {
693699
token_address: contractAddress,
694700
uri: issuedTokenUri,
695701
token_id: 1,
702+
nft_name: "NFT",
703+
nft_description: "NFT description",
704+
nft_image: issuedTokenUri,
696705
});
697706

698707
// Transfer our token
@@ -737,6 +746,9 @@ describe("Ethereum persistence PostgreSQL PostgresDatabaseClient tests", () => {
737746
token_address: contractAddress,
738747
uri: issuedTokenUri,
739748
token_id: "1",
749+
nft_name: "NFT",
750+
nft_description: "NFT description",
751+
nft_image: issuedTokenUri,
740752
});
741753
});
742754
});

0 commit comments

Comments
 (0)