From 5950d0b920056c2aa04df6f080f26548f447aa17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Vi=C3=A9not?= Date: Wed, 11 Dec 2024 20:03:28 +0100 Subject: [PATCH] feat: add HCS-1 support for NFT metadata (#1557) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Simon ViƩnot --- src/components/token/TokenMetadataAnalyzer.ts | 54 ++++++++++++------- src/components/values/BlobValue.vue | 25 ++++++++- src/utils/HCSURI.ts | 47 ++++++++++++++++ src/utils/URLUtils.ts | 23 -------- tests/unit/Mocks.ts | 22 ++++---- .../analyzer/TokenMetadataAnalyzer.spec.ts | 38 ++----------- 6 files changed, 119 insertions(+), 90 deletions(-) create mode 100644 src/utils/HCSURI.ts diff --git a/src/components/token/TokenMetadataAnalyzer.ts b/src/components/token/TokenMetadataAnalyzer.ts index d0d986b9c..59212f760 100644 --- a/src/components/token/TokenMetadataAnalyzer.ts +++ b/src/components/token/TokenMetadataAnalyzer.ts @@ -23,8 +23,9 @@ import axios from "axios"; import {Timestamp} from "@/utils/Timestamp"; import {TopicMessageByTimestampCache} from "@/utils/cache/TopicMessageByTimestampCache.ts"; import {AssetCache} from "@/utils/cache/AssetCache.ts"; -import {LastTopicMessageByIdCache} from "@/utils/cache/LastTopicMessageByIdCache.ts"; -import {blob2Topic, blob2URL} from "@/utils/URLUtils.ts"; +import {blob2URL} from "@/utils/URLUtils.ts"; +import {HCSAssetCache} from "@/utils/cache/HCSAssetCache.ts"; +import {HCSURI} from "@/utils/HCSURI.ts"; export interface NftAttribute { trait_type: string @@ -189,11 +190,7 @@ export class TokenMetadataAnalyzer { return result }) - public imageUrl = computed( - () => { - const uri = this.getProperty('image') ?? this.getProperty(('picture')) - return blob2URL(uri, this.ipfsGatewayPrefix, this.arweaveServer) ?? uri - }) + public imageUrl = ref(null) // // Private @@ -206,16 +203,15 @@ export class TokenMetadataAnalyzer { Content type | Example syntax | See token example ===================+======================================================================+================================================ - IPFS URL | "ipfs://QmSoJYWXvds2qcPeRGJdirP7YTCYvZv4fo43TadwmbvV8H" | https://hashscan.io/mainnet/token/0.0.5679552/1 + IPFS URI | "ipfs://QmSoJYWXvds2qcPeRGJdirP7YTCYvZv4fo43TadwmbvV8H" | https://hashscan.io/mainnet/token/0.0.5679552/1 -------------------+----------------------------------------------------------------------+------------------------------------------------ - IPFS CID | "QmSoJYWXvds2qcPeRGJdirP7YTCYvZv4fo43TadwmbvV8H" | https://hashscan.io/mainnet/token/0.0.5844106/1 + IPFS CID | "QmSoJYWXvds2qcPeRGJdirP7YTCYvZv4fo43TadwmbvV8H" | -------------------+----------------------------------------------------------------------+------------------------------------------------ - HCS URL | "hcs://6/0.0.5671138" | https://hashscan.io/mainnet/token/0.0.5671193/1 + Arweave URI | "ar://VkeESz5eDWA6RWn2cOYafGEvZDIgWzHi91GM3X3N7eI" | https://hashscan.io/mainnet/token/0.0.6096205/1 -------------------+----------------------------------------------------------------------+------------------------------------------------ - Plain Topic ID | "0.0.5679050" | https://hashscan.io/mainnet/token/0.0.5679054/1 + Arweave CID | "sFcjESRXMJmSJxuHo4f606jZgSb4Si0IPrIYD9kQfko" | https://hashscan.io/mainnet/token/0.0.1518294/1 -------------------+----------------------------------------------------------------------+------------------------------------------------ - HCS SUBMIT MESSAGE | "1713509435.878762003" | https://hashscan.io/mainnet/token/0.0.5488525/1 - tx timestamp | | + HCS-1 URL | "hcs://1/0.0.5016827" | https://hashscan.io/mainnet/token/0.0.5016839/1 -------------------+----------------------------------------------------------------------+------------------------------------------------ Plain HTTPS URL | "https://fliggs-nfts-metadata.s3.us-east-2.amazonaws.com/degen.json" | https://hashscan.io/mainnet/token/0.0.6029502/1 | (Note this one causes a CORS error) | @@ -240,9 +236,9 @@ export class TokenMetadataAnalyzer { if (url !== null) { content.value = await this.readMetadataFromUrl(url) } else { - const topic = blob2Topic(metadata.value) - if (topic !== null) { - content.value = await this.readMetadataFromTopic(topic) + const hcsUri = HCSURI.parse(metadata.value) + if (hcsUri && hcsUri.version === '1') { // HCS-1 topic + content.value = await this.readMetadataFromTopic(hcsUri.topicId) } else if (Timestamp.parse(metadata.value) !== null) { content.value = await this.readMetadataFromTimestamp(metadata.value) } else { @@ -252,6 +248,26 @@ export class TokenMetadataAnalyzer { } else { content.value = null } + await this.updateImage() + } + + private async updateImage(): Promise { + const uri = this.getProperty('image') ?? this.getProperty(('picture')) + if (uri !== null) { + let url = blob2URL(uri, this.ipfsGatewayPrefix, this.arweaveServer) + if (url === null) { + const hcsUri = HCSURI.parse(uri) + if (hcsUri && hcsUri.version === '1') { // HCS-1 topic + let content = await HCSAssetCache.instance.lookup(hcsUri.topicId) + url = content?.getDataURL() ?? uri + } else { + url = uri + } + } + this.imageUrl.value = url + } else { + this.imageUrl.value = null + } } private async readMetadataFromUrl(url: string): Promise { @@ -276,10 +292,10 @@ export class TokenMetadataAnalyzer { // console.log(`readMetadataFromTopic: ${id}`) let result: any try { - const topicMessage = await LastTopicMessageByIdCache.instance.lookup(id) + const content = await HCSAssetCache.instance.lookup(id) this.loadSuccessRef.value = true - if (topicMessage !== null) { - result = JSON.parse(Buffer.from(topicMessage.message, 'base64').toString()) + if (content?.content) { + result = JSON.parse(Buffer.from(content.content).toString()) } else { result = null } diff --git a/src/components/values/BlobValue.vue b/src/components/values/BlobValue.vue index f633bb327..165b8e764 100644 --- a/src/components/values/BlobValue.vue +++ b/src/components/values/BlobValue.vue @@ -40,6 +40,12 @@ + +