diff --git a/subgraph/abis/IPToken.json b/subgraph/abis/IPToken.json index 3927254a..37a16bc2 100644 --- a/subgraph/abis/IPToken.json +++ b/subgraph/abis/IPToken.json @@ -193,12 +193,12 @@ "internalType": "uint256" }, { - "name": "name", + "name": "name_", "type": "string", "internalType": "string" }, { - "name": "symbol", + "name": "symbol_", "type": "string", "internalType": "string" }, diff --git a/subgraph/abis/TermsAcceptedPermissioner.json b/subgraph/abis/TermsAcceptedPermissioner.json index 5155046a..7e771b1f 100644 --- a/subgraph/abis/TermsAcceptedPermissioner.json +++ b/subgraph/abis/TermsAcceptedPermissioner.json @@ -6,7 +6,7 @@ { "name": "tokenContract", "type": "address", - "internalType": "contract IPToken" + "internalType": "contract IIPToken" }, { "name": "_for", @@ -29,7 +29,7 @@ { "name": "tokenContract", "type": "address", - "internalType": "contract IPToken" + "internalType": "contract IIPToken" }, { "name": "signer", @@ -94,7 +94,7 @@ { "name": "tokenContract", "type": "address", - "internalType": "contract IPToken" + "internalType": "contract IIPToken" } ], "outputs": [ diff --git a/subgraph/abis/Tokenizer.json b/subgraph/abis/Tokenizer.json index 8f5fdb9b..e3a15b3d 100644 --- a/subgraph/abis/Tokenizer.json +++ b/subgraph/abis/Tokenizer.json @@ -4,6 +4,40 @@ "inputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "attachIpt", + "inputs": [ + { + "name": "ipnftId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "agreementCid", + "type": "string", + "internalType": "string" + }, + { + "name": "signedAgreement", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "tokenContract", + "type": "address", + "internalType": "contract IERC20Metadata" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract IIPToken" + } + ], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "cap", @@ -146,6 +180,11 @@ "type": "function", "name": "reinit", "inputs": [ + { + "name": "_wrappedIpTokenImplementation", + "type": "address", + "internalType": "contract WrappedIPToken" + }, { "name": "_ipTokenImplementation", "type": "address", @@ -175,6 +214,19 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "setWrappedIPTokenImplementation", + "inputs": [ + { + "name": "_wrappedIpTokenImplementation", + "type": "address", + "internalType": "contract WrappedIPToken" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "synthesized", @@ -189,7 +241,7 @@ { "name": "", "type": "address", - "internalType": "contract IPToken" + "internalType": "contract IIPToken" } ], "stateMutability": "view" @@ -277,6 +329,19 @@ "outputs": [], "stateMutability": "payable" }, + { + "type": "function", + "name": "wrappedTokenImplementation", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract WrappedIPToken" + } + ], + "stateMutability": "view" + }, { "type": "event", "name": "AdminChanged", @@ -317,13 +382,13 @@ "name": "old", "type": "address", "indexed": true, - "internalType": "contract IPToken" + "internalType": "contract IIPToken" }, { "name": "_new", "type": "address", "indexed": true, - "internalType": "contract IPToken" + "internalType": "contract IIPToken" } ], "anonymous": false @@ -379,6 +444,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "TokenWrapped", + "inputs": [ + { + "name": "tokenContract", + "type": "address", + "indexed": false, + "internalType": "contract IERC20Metadata" + }, + { + "name": "wrappedIpt", + "type": "address", + "indexed": false, + "internalType": "contract IIPToken" + } + ], + "anonymous": false + }, { "type": "event", "name": "TokensCreated", @@ -447,6 +531,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "WrappedIPTokenImplementationUpdated", + "inputs": [ + { + "name": "old", + "type": "address", + "indexed": true, + "internalType": "contract WrappedIPToken" + }, + { + "name": "_new", + "type": "address", + "indexed": true, + "internalType": "contract WrappedIPToken" + } + ], + "anonymous": false + }, { "type": "error", "name": "AlreadyTokenized", @@ -457,6 +560,16 @@ "name": "IPTNotControlledByTokenizer", "inputs": [] }, + { + "type": "error", + "name": "InvalidTokenContract", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidTokenDecimals", + "inputs": [] + }, { "type": "error", "name": "MustControlIpnft", diff --git a/subgraph/package.json b/subgraph/package.json index 6afe895e..69610956 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -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.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", + "deploy:sepolia": "env-cmd -x -f ../.env graph deploy ip-nft-sepolia --version-label 1.3.2 --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.2 --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" diff --git a/subgraph/src/metadataMapping.ts b/subgraph/src/metadataMapping.ts index dbcbc9f4..a074eaa9 100644 --- a/subgraph/src/metadataMapping.ts +++ b/subgraph/src/metadataMapping.ts @@ -1,13 +1,38 @@ -import { json, Bytes, dataSource, log } from '@graphprotocol/graph-ts' +import { json, Bytes, dataSource, log, JSONValue } from '@graphprotocol/graph-ts' import { IpnftMetadata } from '../generated/schema' export function handleMetadata(content: Bytes): void { - const value = json.fromBytes(content).toObject() - if (value) { - const image = value.get('image') - const name = value.get('name') - const description = value.get('description') - const externalURL = value.get('external_url') + // Validate that content is not empty + if (content.length == 0) { + log.warning('[handleMetadata] Empty content received for {}', [dataSource.stringParam()]) + return + } + + // Check if content looks like JSON (starts with { or [) + if (content[0] != 123 && content[0] != 91) { // 123 = '{', 91 = '[' + log.warning('[handleMetadata] Content does not appear to be JSON for {}. First bytes: {}', [ + dataSource.stringParam(), + content.toHexString().slice(0, 20) + ]) + return + } + + // Try to parse JSON with error handling + let value = json.try_fromBytes(content) + if (value.isError) { + log.error('[handleMetadata] Failed to parse JSON for {}: {}', [ + dataSource.stringParam(), + value.value.toString() + ]) + return + } + + let parsedValue = value.value.toObject() + if (parsedValue) { + const image = parsedValue.get('image') + const name = parsedValue.get('name') + const description = parsedValue.get('description') + const externalURL = parsedValue.get('external_url') let ipnftMetadata = new IpnftMetadata(dataSource.stringParam()) @@ -22,7 +47,7 @@ export function handleMetadata(content: Bytes): void { log.info("[handlemetadata] name, image, description, external_url not found", []) } - let _properties = value.get('properties') + let _properties = parsedValue.get('properties') if (_properties) { let properties = _properties.toObject() let _initial_symbol = properties.get('initial_symbol') diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 26fbffc7..5f0bf55b 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -165,7 +165,7 @@ dataSources: abi: StakedLockingCrowdSale address: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" startBlock: 0 - mapping: &stakedLockingCrowdSaleMapping + mapping: kind: ethereum/events apiVersion: 0.0.7 language: wasm/assemblyscript @@ -178,7 +178,7 @@ dataSources: file: ./abis/StakedLockingCrowdSale.json - name: IERC20Metadata file: ./abis/IERC20Metadata.json - eventHandlers: + eventHandlers: - event: Started(indexed uint256,indexed address,(address,address,address,uint256,uint256,uint64,address),(address,address,uint256),address,uint256,uint256) handler: handleStartedLegacy @@ -204,16 +204,6 @@ dataSources: - event: ClaimedAuctionTokens(indexed uint256) handler: handleClaimedFailedSale file: ./src/stakedLockingCrowdSaleMapping.ts - # in case you need to watch another crowdsale contract this is how it could work: - # - kind: ethereum/contract - # name: StakedLockingCrowdSale2 - # network: foundry - # source: - # abi: StakedLockingCrowdSale - # address: "0xbaadf00dbaadf00dbaadf00dbaadf00dbaadf00d" - # startBlock: 0 - # mapping: *stakedLockingCrowdSaleMapping - - kind: ethereum/contract name: TermsAcceptedPermissioner network: foundry