Skip to content

Commit

Permalink
Merge pull request #172 from moleculeprotocol/feature/hubs-124-add-mo…
Browse files Browse the repository at this point in the history
…re-relevant-ipnft-metadata-fields-to-the-subgraph

HUBS-124 adds more relevant ipnft metadata fields to the subgraph
  • Loading branch information
elmariachi111 authored Jan 17, 2025
2 parents 4589881 + d221d3b commit 0cc9189
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ services:
GRAPH_LOG: debug
GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1
ipfs:
image: ipfs/kubo:v0.30.0
image: ipfs/kubo:master-2025-01-15-1768204
ports:
- '5001:5001'
- '8080:8080'
Expand Down
3 changes: 3 additions & 0 deletions subgraph/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ node_modules

build/
generated

tests/.bin
.latest.json
4 changes: 2 additions & 2 deletions subgraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"build:sepolia": "graph codegen && graph build --network sepolia",
"build:mainnet": "graph codegen && graph build --network mainnet",
"deploy:local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 moleculeprotocol/ipnft-subgraph",
"deploy:sepolia": "env-cmd -x -f ../.env graph deploy ip-nft-sepolia --version-label 1.3.0 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key \\$SATSUMA_DEPLOY_KEY",
"deploy:mainnet": "env-cmd -x -f ../.env graph deploy ip-nft-mainnet --version-label 1.3.0 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key \\$SATSUMA_DEPLOY_KEY",
"deploy:sepolia": "env-cmd -x -f ../.env graph deploy ip-nft-sepolia --version-label 1.3.1 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key \\$SATSUMA_DEPLOY_KEY",
"deploy:mainnet": "env-cmd -x -f ../.env graph deploy ip-nft-mainnet --version-label 1.3.1 --node https://subgraphs.alchemy.com/api/subgraphs/deploy --ipfs https://ipfs.satsuma.xyz --deploy-key \\$SATSUMA_DEPLOY_KEY",
"create:local": "graph create --node http://localhost:8020/ moleculeprotocol/ipnft-subgraph",
"remove:local": "graph remove --node http://localhost:8020/ moleculeprotocol/ipnft-subgraph",
"test": "graph test"
Expand Down
16 changes: 16 additions & 0 deletions subgraph/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type Ipnft @entity {
ipts: [IPT!] @derivedFrom(field: "ipnft")
metadata: IpnftMetadata
updatedAtTimestamp: BigInt

# as of 1.3 the Tokenizer only will create 1 ipt instance, in contrast to the ipts field above.
ipToken: Bytes
}

type IpnftMetadata @entity {
Expand All @@ -17,8 +20,21 @@ type IpnftMetadata @entity {
image: String!
description: String!
externalURL: String!

initialSymbol: String
organization: String
topic: String

researchLead_name: String
researchLead_email: String

fundingAmount_value: String
fundingAmount_decimals: Int8
fundingAmount_currency: String
fundingAmount_currencyType: String
}


type IPT @entity {
id: ID! # ipt address
name: String!
Expand Down
4 changes: 2 additions & 2 deletions subgraph/src/ipnftMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,15 @@ export function handleMint(event: IPNFTMintedEvent): void {
export function handleMetadataUpdated(event: MetadataUpdateEvent): void {
let ipnft = Ipnft.load(event.params._tokenId.toString())
if (!ipnft) {
log.error('ipnft {} not found', [event.params._tokenId.toString()])
log.error('[handleMetadataUpdated] ipnft {} not found', [event.params._tokenId.toString()])
return
}

//erc4906 is not emitting the new url, we must query it ourselves
let _ipnftContract = IPNFTContract.bind(event.params._event.address)
let newUri = _ipnftContract.tokenURI(event.params._tokenId)
if (!newUri || newUri == '') {
log.debug('no new uri found for token, likely just minted {}', [
log.debug('[handleMetadataUpdated] no new uri found for token, likely just minted {}', [
event.params._tokenId.toString()
])
return
Expand Down
68 changes: 66 additions & 2 deletions subgraph/src/metadataMapping.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { json, Bytes, dataSource } from '@graphprotocol/graph-ts'
import { json, Bytes, dataSource, log } from '@graphprotocol/graph-ts'
import { IpnftMetadata } from '../generated/schema'

export function handleMetadata(content: Bytes): void {
Expand All @@ -9,14 +9,78 @@ export function handleMetadata(content: Bytes): void {
const description = value.get('description')
const externalURL = value.get('external_url')

let ipnftMetadata = new IpnftMetadata(dataSource.stringParam())

if (name && image && description && externalURL) {
let ipnftMetadata = new IpnftMetadata(dataSource.stringParam())
ipnftMetadata.name = name.toString()
ipnftMetadata.image = image.toString()
ipnftMetadata.externalURL = externalURL.toString()
ipnftMetadata.description = description.toString()

ipnftMetadata.save()
} else {
log.info("[handlemetadata] name, image, description, external_url not found", [])
}

let _properties = value.get('properties')
if (_properties) {
let properties = _properties.toObject()
let _initial_symbol = properties.get('initial_symbol')
if (_initial_symbol) {
ipnftMetadata.initialSymbol = _initial_symbol.toString()
} else {
ipnftMetadata.initialSymbol = ""
log.info("[handlemetadata] initial_symbol not found", [])
}

let _project_details = properties.get('project_details')

if (_project_details) {
let projectDetails = _project_details.toObject()

let _organization = projectDetails.get('organization')
if (_organization) {
ipnftMetadata.organization = _organization.toString()
}

let _topic = projectDetails.get('topic')
if (_topic) {
ipnftMetadata.topic = _topic.toString()
}

let _research_lead = projectDetails.get('research_lead')

if (_research_lead) {
let researchLead = _research_lead.toObject()
let researchLead_email = researchLead.get('email')
let researchLead_name = researchLead.get('name')

if (researchLead_email && researchLead_name) {
ipnftMetadata.researchLead_email = researchLead_email.toString()
ipnftMetadata.researchLead_name = researchLead_name.toString()
}
}

let _funding_amount = projectDetails.get('funding_amount')

if (_funding_amount) {
let funding_amount = _funding_amount.toObject()
let _fundingAmount_value = funding_amount.get('value')
let _fundingAmount_decimals = funding_amount.get('decimals')
let _fundingAmount_currency = funding_amount.get('currency')
let _fundingAmount_currencyType = funding_amount.get('currency_type')

if (_fundingAmount_value && _fundingAmount_decimals && _fundingAmount_currency && _fundingAmount_currencyType) {
// on json metadata this can be a decimal value. I'm using a string to store as there's imo no f64 compatible decimal type on the schema scalar types
// https://thegraph.com/docs/en/subgraphs/developing/creating/ql-schema/#built-in-scalar-types
ipnftMetadata.fundingAmount_value = _fundingAmount_value.toF64().toString()
ipnftMetadata.fundingAmount_decimals = i8(_fundingAmount_decimals.toI64())
ipnftMetadata.fundingAmount_currency = _fundingAmount_currency.toString()
ipnftMetadata.fundingAmount_currencyType = _fundingAmount_currencyType.toString()
}
}
}
}
ipnftMetadata.save()
}
}
8 changes: 7 additions & 1 deletion subgraph/src/tokenizerMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TokensCreated as TokensCreatedEvent } from '../generated/Tokenizer/Toke

import { IPToken } from '../generated/templates'

import { IPT } from '../generated/schema'
import { IPT, Ipnft } from '../generated/schema'

export function handleIPTsCreated(event: TokensCreatedEvent): void {
let ipt = new IPT(event.params.tokenContract.toHexString())
Expand All @@ -23,4 +23,10 @@ export function handleIPTsCreated(event: TokensCreatedEvent): void {
IPToken.create(event.params.tokenContract)

ipt.save()

let ipnft = Ipnft.load(event.params.ipnftId.toString());
if (ipnft) {
ipnft.ipToken = event.params.tokenContract
ipnft.save()
}
}
83 changes: 83 additions & 0 deletions subgraph/tests/fixtures/ipnft_1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{
"schema_version": "0.0.1",
"name": "Our awesome test IP-NFT",
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua...",
"image": "ipfs://bafkreih5d2453qzkprn35zm4gj5ff6ezroevnaaygxqlwcbqq2tg7ebbe4",
"external_url": "https://testnet.mint.molecule.to/ipnft/76",
"terms_signature": "0x1f030b9f1a91d74610cb296aa87f12b2207d2b336766fffad8c53b825f1bee7d30f3665076102fe635dd3664d564ae1e5e746794b58e8861feafacfa6767ea491b",
"properties": {
"type": "IP-NFT",
"initial_symbol": "AWSOME",
"agreements": [
{
"type": "Research Agreement",
"url": "ipfs://bafkreigqttkfe2vsgddhy4ohf3qs3eyacdz2vwindo33y7jefox3377yqq",
"mime_type": "application/pdf",
"content_hash": "bagaaiera7ftqs3jmnoph3zgq67bgjrszlqtxkk5ygadgjvnihukrqioipndq",
"encryption": {
"protocol": "lit",
"encrypted_sym_key": "abcdefedcba0123443210",
"access_control_conditions": [
{
"conditionType": "evmContract",
"contractAddress": "0x152B444e60C526fe4434C721561a077269FcF61a",
"chain": "sepolia",
"functionName": "canRead",
"functionParams": [":userAddress", "76"],
"functionAbi": {
"inputs": [
{
"internalType": "address",
"name": "reader",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "canRead",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
"returnValueTest": {
"key": "",
"comparator": "=",
"value": "true"
}
}
]
}
},
{
"type": "Assignment Agreement",
"url": "ipfs://bafkreihzm4ew2ldltz66juhxyjsmmwk4e52sxobqazsnlkb5cumcdsd3i4",
"mime_type": "application/pdf",
"content_hash": "bagaaiera7ftqs3jmnoph3zgq67bgjrszlqtxkk5ygadgjvnihukrqioipndq"
}
],
"project_details": {
"industry": "Space Exploration",
"organization": "NASA",
"topic": "Wormholes",
"funding_amount": {
"value": 1234.5678,
"decimals": 2,
"currency": "USD",
"currency_type": "ISO4217"
},
"research_lead": {
"name": "Carl Sagan",
"email": "carl@example.com"
}
}
}
}
28 changes: 28 additions & 0 deletions subgraph/tests/metadata.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ipfs } from "@graphprotocol/graph-ts"
import { assert, describe, mockIpfsFile, test } from 'matchstick-as/assembly/index'
import { handleMetadata } from '../src/metadataMapping'

const IPNFT_METADATA = "IpnftMetadata"

describe('Metadata', () => {
//https://thegraph.com/docs/en/subgraphs/developing/creating/unit-testing-framework/#mocking-ipfs-files-from-matchstick-041

test('reads ipnft metadata', () => {

mockIpfsFile('ipfsCatBaseIpnft', 'tests/fixtures/ipnft_1.json')

let rawData = ipfs.cat("ipfsCatBaseIpnft")
if (!rawData) {
throw new Error("Failed to fetch ipfs data")
}

handleMetadata(rawData)
assert.entityCount(IPNFT_METADATA, 1)
assert.fieldEquals(IPNFT_METADATA, '', 'topic', 'Wormholes')
assert.fieldEquals(IPNFT_METADATA, '', 'fundingAmount_value', '1234.5678')
assert.fieldEquals(IPNFT_METADATA, '', 'fundingAmount_currency', 'USD')

//logStore()
})
})

0 comments on commit 0cc9189

Please sign in to comment.