Skip to content

Commit 0cc9189

Browse files
Merge pull request #172 from moleculeprotocol/feature/hubs-124-add-more-relevant-ipnft-metadata-fields-to-the-subgraph
HUBS-124 adds more relevant ipnft metadata fields to the subgraph
2 parents 4589881 + d221d3b commit 0cc9189

File tree

9 files changed

+208
-8
lines changed

9 files changed

+208
-8
lines changed

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ services:
3232
GRAPH_LOG: debug
3333
GRAPH_ALLOW_NON_DETERMINISTIC_IPFS: 1
3434
ipfs:
35-
image: ipfs/kubo:v0.30.0
35+
image: ipfs/kubo:master-2025-01-15-1768204
3636
ports:
3737
- '5001:5001'
3838
- '8080:8080'

subgraph/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ node_modules
22

33
build/
44
generated
5+
6+
tests/.bin
7+
.latest.json

subgraph/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
"build:sepolia": "graph codegen && graph build --network sepolia",
99
"build:mainnet": "graph codegen && graph build --network mainnet",
1010
"deploy:local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 moleculeprotocol/ipnft-subgraph",
11-
"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",
12-
"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",
11+
"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",
12+
"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",
1313
"create:local": "graph create --node http://localhost:8020/ moleculeprotocol/ipnft-subgraph",
1414
"remove:local": "graph remove --node http://localhost:8020/ moleculeprotocol/ipnft-subgraph",
1515
"test": "graph test"

subgraph/schema.graphql

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ type Ipnft @entity {
99
ipts: [IPT!] @derivedFrom(field: "ipnft")
1010
metadata: IpnftMetadata
1111
updatedAtTimestamp: BigInt
12+
13+
# as of 1.3 the Tokenizer only will create 1 ipt instance, in contrast to the ipts field above.
14+
ipToken: Bytes
1215
}
1316

1417
type IpnftMetadata @entity {
@@ -17,8 +20,21 @@ type IpnftMetadata @entity {
1720
image: String!
1821
description: String!
1922
externalURL: String!
23+
24+
initialSymbol: String
25+
organization: String
26+
topic: String
27+
28+
researchLead_name: String
29+
researchLead_email: String
30+
31+
fundingAmount_value: String
32+
fundingAmount_decimals: Int8
33+
fundingAmount_currency: String
34+
fundingAmount_currencyType: String
2035
}
2136

37+
2238
type IPT @entity {
2339
id: ID! # ipt address
2440
name: String!

subgraph/src/ipnftMapping.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@ export function handleMint(event: IPNFTMintedEvent): void {
106106
export function handleMetadataUpdated(event: MetadataUpdateEvent): void {
107107
let ipnft = Ipnft.load(event.params._tokenId.toString())
108108
if (!ipnft) {
109-
log.error('ipnft {} not found', [event.params._tokenId.toString()])
109+
log.error('[handleMetadataUpdated] ipnft {} not found', [event.params._tokenId.toString()])
110110
return
111111
}
112112

113113
//erc4906 is not emitting the new url, we must query it ourselves
114114
let _ipnftContract = IPNFTContract.bind(event.params._event.address)
115115
let newUri = _ipnftContract.tokenURI(event.params._tokenId)
116116
if (!newUri || newUri == '') {
117-
log.debug('no new uri found for token, likely just minted {}', [
117+
log.debug('[handleMetadataUpdated] no new uri found for token, likely just minted {}', [
118118
event.params._tokenId.toString()
119119
])
120120
return

subgraph/src/metadataMapping.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { json, Bytes, dataSource } from '@graphprotocol/graph-ts'
1+
import { json, Bytes, dataSource, log } from '@graphprotocol/graph-ts'
22
import { IpnftMetadata } from '../generated/schema'
33

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

12+
let ipnftMetadata = new IpnftMetadata(dataSource.stringParam())
13+
1214
if (name && image && description && externalURL) {
13-
let ipnftMetadata = new IpnftMetadata(dataSource.stringParam())
1415
ipnftMetadata.name = name.toString()
1516
ipnftMetadata.image = image.toString()
1617
ipnftMetadata.externalURL = externalURL.toString()
1718
ipnftMetadata.description = description.toString()
19+
1820
ipnftMetadata.save()
21+
} else {
22+
log.info("[handlemetadata] name, image, description, external_url not found", [])
1923
}
24+
25+
let _properties = value.get('properties')
26+
if (_properties) {
27+
let properties = _properties.toObject()
28+
let _initial_symbol = properties.get('initial_symbol')
29+
if (_initial_symbol) {
30+
ipnftMetadata.initialSymbol = _initial_symbol.toString()
31+
} else {
32+
ipnftMetadata.initialSymbol = ""
33+
log.info("[handlemetadata] initial_symbol not found", [])
34+
}
35+
36+
let _project_details = properties.get('project_details')
37+
38+
if (_project_details) {
39+
let projectDetails = _project_details.toObject()
40+
41+
let _organization = projectDetails.get('organization')
42+
if (_organization) {
43+
ipnftMetadata.organization = _organization.toString()
44+
}
2045

46+
let _topic = projectDetails.get('topic')
47+
if (_topic) {
48+
ipnftMetadata.topic = _topic.toString()
49+
}
50+
51+
let _research_lead = projectDetails.get('research_lead')
52+
53+
if (_research_lead) {
54+
let researchLead = _research_lead.toObject()
55+
let researchLead_email = researchLead.get('email')
56+
let researchLead_name = researchLead.get('name')
57+
58+
if (researchLead_email && researchLead_name) {
59+
ipnftMetadata.researchLead_email = researchLead_email.toString()
60+
ipnftMetadata.researchLead_name = researchLead_name.toString()
61+
}
62+
}
63+
64+
let _funding_amount = projectDetails.get('funding_amount')
65+
66+
if (_funding_amount) {
67+
let funding_amount = _funding_amount.toObject()
68+
let _fundingAmount_value = funding_amount.get('value')
69+
let _fundingAmount_decimals = funding_amount.get('decimals')
70+
let _fundingAmount_currency = funding_amount.get('currency')
71+
let _fundingAmount_currencyType = funding_amount.get('currency_type')
72+
73+
if (_fundingAmount_value && _fundingAmount_decimals && _fundingAmount_currency && _fundingAmount_currencyType) {
74+
// 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
75+
// https://thegraph.com/docs/en/subgraphs/developing/creating/ql-schema/#built-in-scalar-types
76+
ipnftMetadata.fundingAmount_value = _fundingAmount_value.toF64().toString()
77+
ipnftMetadata.fundingAmount_decimals = i8(_fundingAmount_decimals.toI64())
78+
ipnftMetadata.fundingAmount_currency = _fundingAmount_currency.toString()
79+
ipnftMetadata.fundingAmount_currencyType = _fundingAmount_currencyType.toString()
80+
}
81+
}
82+
}
83+
}
84+
ipnftMetadata.save()
2185
}
2286
}

subgraph/src/tokenizerMapping.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { TokensCreated as TokensCreatedEvent } from '../generated/Tokenizer/Toke
33

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

6-
import { IPT } from '../generated/schema'
6+
import { IPT, Ipnft } from '../generated/schema'
77

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

2525
ipt.save()
26+
27+
let ipnft = Ipnft.load(event.params.ipnftId.toString());
28+
if (ipnft) {
29+
ipnft.ipToken = event.params.tokenContract
30+
ipnft.save()
31+
}
2632
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
{
2+
"schema_version": "0.0.1",
3+
"name": "Our awesome test IP-NFT",
4+
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua...",
5+
"image": "ipfs://bafkreih5d2453qzkprn35zm4gj5ff6ezroevnaaygxqlwcbqq2tg7ebbe4",
6+
"external_url": "https://testnet.mint.molecule.to/ipnft/76",
7+
"terms_signature": "0x1f030b9f1a91d74610cb296aa87f12b2207d2b336766fffad8c53b825f1bee7d30f3665076102fe635dd3664d564ae1e5e746794b58e8861feafacfa6767ea491b",
8+
"properties": {
9+
"type": "IP-NFT",
10+
"initial_symbol": "AWSOME",
11+
"agreements": [
12+
{
13+
"type": "Research Agreement",
14+
"url": "ipfs://bafkreigqttkfe2vsgddhy4ohf3qs3eyacdz2vwindo33y7jefox3377yqq",
15+
"mime_type": "application/pdf",
16+
"content_hash": "bagaaiera7ftqs3jmnoph3zgq67bgjrszlqtxkk5ygadgjvnihukrqioipndq",
17+
"encryption": {
18+
"protocol": "lit",
19+
"encrypted_sym_key": "abcdefedcba0123443210",
20+
"access_control_conditions": [
21+
{
22+
"conditionType": "evmContract",
23+
"contractAddress": "0x152B444e60C526fe4434C721561a077269FcF61a",
24+
"chain": "sepolia",
25+
"functionName": "canRead",
26+
"functionParams": [":userAddress", "76"],
27+
"functionAbi": {
28+
"inputs": [
29+
{
30+
"internalType": "address",
31+
"name": "reader",
32+
"type": "address"
33+
},
34+
{
35+
"internalType": "uint256",
36+
"name": "tokenId",
37+
"type": "uint256"
38+
}
39+
],
40+
"name": "canRead",
41+
"outputs": [
42+
{
43+
"internalType": "bool",
44+
"name": "",
45+
"type": "bool"
46+
}
47+
],
48+
"stateMutability": "view",
49+
"type": "function"
50+
},
51+
"returnValueTest": {
52+
"key": "",
53+
"comparator": "=",
54+
"value": "true"
55+
}
56+
}
57+
]
58+
}
59+
},
60+
{
61+
"type": "Assignment Agreement",
62+
"url": "ipfs://bafkreihzm4ew2ldltz66juhxyjsmmwk4e52sxobqazsnlkb5cumcdsd3i4",
63+
"mime_type": "application/pdf",
64+
"content_hash": "bagaaiera7ftqs3jmnoph3zgq67bgjrszlqtxkk5ygadgjvnihukrqioipndq"
65+
}
66+
],
67+
"project_details": {
68+
"industry": "Space Exploration",
69+
"organization": "NASA",
70+
"topic": "Wormholes",
71+
"funding_amount": {
72+
"value": 1234.5678,
73+
"decimals": 2,
74+
"currency": "USD",
75+
"currency_type": "ISO4217"
76+
},
77+
"research_lead": {
78+
"name": "Carl Sagan",
79+
"email": "[email protected]"
80+
}
81+
}
82+
}
83+
}

subgraph/tests/metadata.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { ipfs } from "@graphprotocol/graph-ts"
2+
import { assert, describe, mockIpfsFile, test } from 'matchstick-as/assembly/index'
3+
import { handleMetadata } from '../src/metadataMapping'
4+
5+
const IPNFT_METADATA = "IpnftMetadata"
6+
7+
describe('Metadata', () => {
8+
//https://thegraph.com/docs/en/subgraphs/developing/creating/unit-testing-framework/#mocking-ipfs-files-from-matchstick-041
9+
10+
test('reads ipnft metadata', () => {
11+
12+
mockIpfsFile('ipfsCatBaseIpnft', 'tests/fixtures/ipnft_1.json')
13+
14+
let rawData = ipfs.cat("ipfsCatBaseIpnft")
15+
if (!rawData) {
16+
throw new Error("Failed to fetch ipfs data")
17+
}
18+
19+
handleMetadata(rawData)
20+
assert.entityCount(IPNFT_METADATA, 1)
21+
assert.fieldEquals(IPNFT_METADATA, '', 'topic', 'Wormholes')
22+
assert.fieldEquals(IPNFT_METADATA, '', 'fundingAmount_value', '1234.5678')
23+
assert.fieldEquals(IPNFT_METADATA, '', 'fundingAmount_currency', 'USD')
24+
25+
//logStore()
26+
})
27+
})
28+

0 commit comments

Comments
 (0)