Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the reason to add this field when we have the ipts field? for the Tokenizer 1.3 we would have an array with only one element

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly. That's what the comment says. This is here as we since 1.3 only need 1 IPT instance but never updated the subgraph accordingly. Mainly as we need to also upgrade the frontend. If we'd agree that the only IPT is just one field, we can easily adopt the frontend and switch off the ipts array. The more pragmatic reason for this field being a plain address is that Tom can simply use it for ordering IPNFTs.

}

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": "[email protected]"
}
}
}
}
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()
})
})

Loading