diff --git a/subquery/abis/dummy.json b/subquery/abis/dummy.json new file mode 100644 index 0000000..003ef77 --- /dev/null +++ b/subquery/abis/dummy.json @@ -0,0 +1,4 @@ +[ + "event Transfer(address indexed from, address indexed to, uint256 value)", + "event Approval(address indexed owner, address indexed spender, uint256 value)" +] \ No newline at end of file diff --git a/subquery/abis/erc20.abi.json b/subquery/abis/erc20.abi.json new file mode 100644 index 0000000..fd1f2c1 --- /dev/null +++ b/subquery/abis/erc20.abi.json @@ -0,0 +1,602 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MINTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/subquery/abis/erc721-a.abi.json b/subquery/abis/erc721-a.abi.json new file mode 100644 index 0000000..9b6cae7 --- /dev/null +++ b/subquery/abis/erc721-a.abi.json @@ -0,0 +1,661 @@ +[ + { + "inputs": [ + { + "internalType": "contract NODLMigration", + "name": "_migration", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxHolders", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "_levels", + "type": "uint256[]" + }, + { + "internalType": "string[]", + "name": "_levelToTokenURI", + "type": "string[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AlreadyClaimed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721IncorrectOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721InsufficientApproval", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC721InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "ERC721InvalidOperator", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "ERC721InvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC721InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC721InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ERC721NonexistentToken", + "type": "error" + }, + { + "inputs": [], + "name": "NoLevelUp", + "type": "error" + }, + { + "inputs": [], + "name": "NotExecuted", + "type": "error" + }, + { + "inputs": [], + "name": "ProposalDoesNotExist", + "type": "error" + }, + { + "inputs": [], + "name": "SoulboundIsNotTransferrable", + "type": "error" + }, + { + "inputs": [], + "name": "TooManyHolders", + "type": "error" + }, + { + "inputs": [], + "name": "UnequalLengths", + "type": "error" + }, + { + "inputs": [], + "name": "UnsortedLevelsList", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "claimed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "holderToNextLevel", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "individualHolders", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "levelToTokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "levels", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxHolders", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "migration", + "outputs": [ + { + "internalType": "contract NODLMigration", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nextTokenId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "txHash", + "type": "bytes32" + } + ], + "name": "safeMint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokenIdToNextLevel", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/subquery/abis/general.json b/subquery/abis/general.json new file mode 100644 index 0000000..888b674 --- /dev/null +++ b/subquery/abis/general.json @@ -0,0 +1,410 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "accountAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "enumIContractDeployer.AccountNonceOrdering", + "name": "nonceOrdering", + "type": "uint8" + } + ], + "name": "AccountNonceOrderingUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "accountAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "enumIContractDeployer.AccountAbstractionVersion", + "name": "aaVersion", + "type": "uint8" + } + ], + "name": "AccountVersionUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "deployerAddress", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "bytecodeHash", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "contractAddress", + "type": "address" + } + ], + "name": "ContractDeployed", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_salt", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_bytecodeHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_input", + "type": "bytes" + } + ], + "name": "create", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_salt", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_bytecodeHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_input", + "type": "bytes" + } + ], + "name": "create2", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_salt", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_bytecodeHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_input", + "type": "bytes" + }, + { + "internalType": "enumIContractDeployer.AccountAbstractionVersion", + "name": "_aaVersion", + "type": "uint8" + } + ], + "name": "create2Account", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_bytecodeHash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_input", + "type": "bytes" + }, + { + "internalType": "enumIContractDeployer.AccountAbstractionVersion", + "name": "_aaVersion", + "type": "uint8" + } + ], + "name": "createAccount", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "extendedAccountVersion", + "outputs": [ + { + "internalType": "enumIContractDeployer.AccountAbstractionVersion", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "bytecodeHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "newAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "callConstructor", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "input", + "type": "bytes" + } + ], + "internalType": "structContractDeployer.ForceDeployment", + "name": "_deployment", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_sender", + "type": "address" + } + ], + "name": "forceDeployOnAddress", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "bytecodeHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "newAddress", + "type": "address" + }, + { + "internalType": "bool", + "name": "callConstructor", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "input", + "type": "bytes" + } + ], + "internalType": "structContractDeployer.ForceDeployment[]", + "name": "_deployments", + "type": "tuple[]" + } + ], + "name": "forceDeployOnAddresses", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "getAccountInfo", + "outputs": [ + { + "components": [ + { + "internalType": "enumIContractDeployer.AccountAbstractionVersion", + "name": "supportedAAVersion", + "type": "uint8" + }, + { + "internalType": "enumIContractDeployer.AccountNonceOrdering", + "name": "nonceOrdering", + "type": "uint8" + } + ], + "internalType": "structIContractDeployer.AccountInfo", + "name": "info", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_senderNonce", + "type": "uint256" + } + ], + "name": "getNewAddressCreate", + "outputs": [ + { + "internalType": "address", + "name": "newAddress", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sender", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_bytecodeHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_input", + "type": "bytes" + } + ], + "name": "getNewAddressCreate2", + "outputs": [ + { + "internalType": "address", + "name": "newAddress", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enumIContractDeployer.AccountAbstractionVersion", + "name": "_version", + "type": "uint8" + } + ], + "name": "updateAccountVersion", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enumIContractDeployer.AccountNonceOrdering", + "name": "_nonceOrdering", + "type": "uint8" + } + ], + "name": "updateNonceOrdering", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/subquery/abis/migration.abi.json b/subquery/abis/migration.abi.json new file mode 100644 index 0000000..3c0138e --- /dev/null +++ b/subquery/abis/migration.abi.json @@ -0,0 +1,331 @@ +[ + { + "inputs": [ + { + "internalType": "address[]", + "name": "bridgeOracles", + "type": "address[]" + }, + { + "internalType": "contract NODL", + "name": "token", + "type": "address" + }, + { + "internalType": "uint8", + "name": "minVotes", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "minDelay", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + } + ], + "name": "AlreadyExecuted", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "AlreadyVoted", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "NotAnOracle", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + } + ], + "name": "NotEnoughVotes", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + } + ], + "name": "NotYetWithdrawable", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + } + ], + "name": "ParametersChanged", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "VoteStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "Voted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "proposal", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paraTxHash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "bridge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOracle", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nodl", + "outputs": [ + { + "internalType": "contract NODL", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "proposals", + "outputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastVote", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "totalVotes", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "executed", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "threshold", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "voted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "paraTxHash", + "type": "bytes32" + } + ], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/subquery/click-mainnet.ts b/subquery/click-mainnet.ts new file mode 100644 index 0000000..b2b8eb2 --- /dev/null +++ b/subquery/click-mainnet.ts @@ -0,0 +1,241 @@ +import { + EthereumProject, + EthereumDatasourceKind, + EthereumHandlerKind, +} from "@subql/types-ethereum"; + +const project: EthereumProject = { + specVersion: "1.0.0", + version: "0.0.1", + name: "Nodle-zksync-subquery", + description: "__", + runner: { + node: { + name: "@subql/node-ethereum", + version: ">=3.0.0", + options: { + unsafe: true, + }, + }, + query: { + name: "@subql/query", + version: "*", + }, + }, + schema: { + file: "./schema.graphql", + }, + network: { + chainId: "324", // zKsync mainnet + endpoint: [ + "https://wandering-distinguished-tree.zksync-mainnet.quiknode.pro/20c0bc25076ea895aa263c9296c6892eba46077c/", + ], + }, + dataSources: [ + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 35438288, // This is the block that the contract was deployed on + options: { + abi: "erc721", + address: "0x95b3641d549f719eb5105f9550Eca4A7A2F305De", + }, + assets: new Map([ + [ + "erc721", + { + file: "./abis/erc721.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Call, + handler: "handleSafeMint", + filter: { + function: "safeMint(address,string)", + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApproval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApprovalForAll", + filter: { + topics: ["ApprovalForAll(address,address,bool)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleTransfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 35438288, // This is the block that the contract was deployed on + options: { + abi: "erc721", + address: "0xe980886e4072d32784187D547F9663eFef50f58F", + }, + assets: new Map([ + [ + "erc721", + { + file: "./abis/erc721.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Call, + handler: "handleSafeMint", + filter: { + function: "safeMint(address,string)", + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApproval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApprovalForAll", + filter: { + topics: ["ApprovalForAll(address,address,bool)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleTransfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 35438288, // This is the block that the contract was deployed on + options: { + abi: "erc721", + address: "0x6FE81f2fDE5775355962B7F3CC9b0E1c83970E15", + }, + assets: new Map([ + [ + "erc721", + { + file: "./abis/erc721.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Call, + handler: "handleSafeMint", + filter: { + function: "safeMint(address,string)", + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApproval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApprovalForAll", + filter: { + topics: ["ApprovalForAll(address,address,bool)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleTransfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 35438288, // This is the block that the contract was deployed on + options: { + abi: "erc721", + address: "0x48e5c6f97b00Db0A4F74B1C1bc8ecd78452dDF6F", + }, + assets: new Map([ + [ + "erc721", + { + file: "./abis/erc721.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Call, + handler: "handleSafeMint", + filter: { + function: "safeMint(address,string)", + }, + }, + { + kind: EthereumHandlerKind.Call, + handler: "handleApproval", + filter: { + function: "approve(address,uint256)", + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApproval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApprovalForAll", + filter: { + topics: ["ApprovalForAll(address,address,bool)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleTransfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + ], +}; + +export default project; diff --git a/subquery/docker-compose.yml b/subquery/docker-compose.yml index 878fc18..e599393 100644 --- a/subquery/docker-compose.yml +++ b/subquery/docker-compose.yml @@ -39,6 +39,7 @@ services: - --batch-size=30 - --unfinalized-blocks=false - --unsafe + - --timeout=3600 healthcheck: test: ["CMD", "curl", "-f", "http://subquery-node:3000/ready"] diff --git a/subquery/enrich-dist.js b/subquery/enrich-dist.js new file mode 100644 index 0000000..f9236aa --- /dev/null +++ b/subquery/enrich-dist.js @@ -0,0 +1,44 @@ +const fs = require('fs'); + +const filePath = './dist/index.js'; +const contentToAdd = `var base64chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + var atob = function (input) { + var str = String(input).replace(/=+$/, ""); + if (str.length % 4 == 1) { + throw new Error( + "'atob' failed: The string to be decoded is not correctly encoded." + ); + } + for ( + var bc = 0, bs, buffer, idx = 0, output = ""; + (buffer = str.charAt(idx++)); + ~buffer && ((bs = bc % 4 ? bs * 64 + buffer : buffer), bc++ % 4) + ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6)))) + : 0 + ) { + buffer = base64chars.indexOf(buffer); + } + return output; + };`; +const lineToAdd = 3; // Specify the line number where the content should be added + +fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + console.error('Error reading file:', err); + } else { + const lines = data.split('\n'); + lines.splice(lineToAdd - 1, 0, ''); // Add a new line + lines.splice(lineToAdd, 0, contentToAdd); // Insert the content at the specified line number + const updatedContent = lines.join('\n'); + + fs.writeFile(filePath, updatedContent, 'utf8', (err) => { + if (err) { + console.error('Error writing file:', err); + } else { + console.log('Build completed.'); + } + }); + } +}); diff --git a/subquery/nodle-mainnet.ts b/subquery/nodle-mainnet.ts new file mode 100644 index 0000000..63bf6e1 --- /dev/null +++ b/subquery/nodle-mainnet.ts @@ -0,0 +1,115 @@ +import { + EthereumProject, + EthereumDatasourceKind, + EthereumHandlerKind, +} from "@subql/types-ethereum"; + +const project: EthereumProject = { + specVersion: "1.0.0", + version: "0.0.1", + name: "Nodle-zksync-subquery", + description: "__", + runner: { + node: { + name: "@subql/node-ethereum", + version: ">=3.0.0", + options: { + unsafe: true, + }, + }, + query: { + name: "@subql/query", + version: "*", + }, + }, + schema: { + file: "./schema.graphql", + }, + network: { + chainId: "324", // zKsync mainnet + endpoint: [ + "https://wandering-distinguished-tree.zksync-mainnet.quiknode.pro/20c0bc25076ea895aa263c9296c6892eba46077c/", + ], + }, + dataSources: [ + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 33664641, // This is the block that the contract was deployed on + options: { + abi: "erc20", + address: "0xBD4372e44c5eE654dd838304006E1f0f69983154", + }, + assets: new Map([ + [ + "erc20", + { + file: "./abis/erc20.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Event, + handler: "handleERC20Approval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleERC20Transfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 37375246, + options: { + abi: "migration", + address: "0x5de7fe085ee66Fb48447e75AA8fb0598a080AEe0", + }, + assets: new Map([ + [ + "migration", + { + file: "./abis/migration.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Event, + handler: "handleProposal", + filter: { + topics: ["VoteStarted(bytes32, address, address, uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleWithdrawn", + filter: { + topics: ["Withdrawn(bytes32, address, uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleVote", + filter: { + topics: ["Voted(bytes32, address)"], + }, + }, + ], + }, + }, + ], +}; + +export default project; diff --git a/subquery/package.json b/subquery/package.json index 98e0f98..ff9286d 100644 --- a/subquery/package.json +++ b/subquery/package.json @@ -4,7 +4,7 @@ "description": "SubQuery Project for Nodle Zk contracts", "main": "dist/index.js", "scripts": { - "build": "subql build", + "build": "subql build && node enrich-dist.js", "codegen": "subql codegen", "publish": "subql publish", "start:docker": "docker-compose pull && docker-compose up --remove-orphans", diff --git a/subquery/project-testnet.ts b/subquery/project-testnet.ts new file mode 100644 index 0000000..5d35e01 --- /dev/null +++ b/subquery/project-testnet.ts @@ -0,0 +1,241 @@ +import { + EthereumProject, + EthereumDatasourceKind, + EthereumHandlerKind, +} from "@subql/types-ethereum"; + +const project: EthereumProject = { + specVersion: "1.0.0", + version: "0.0.1", + name: "Nodle-zksync-subquery", + description: "__", + runner: { + node: { + name: "@subql/node-ethereum", + version: ">=3.0.0", + options: { + unsafe: true, + }, + }, + query: { + name: "@subql/query", + version: "*", + }, + }, + schema: { + file: "./schema.graphql", + }, + network: { + chainId: "300", // zKsync sepolia testnet + endpoint: [ + "https://shy-cosmopolitan-telescope.zksync-sepolia.quiknode.pro/7dca91c43e87ec74294608886badb962826e62a0/", + ], + }, + dataSources: [ + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 1993364, // This is the block that the contract was deployed on + options: { + abi: "erc721", + address: "0x999368030Ba79898E83EaAE0E49E89B7f6410940", + }, + assets: new Map([ + [ + "erc721", + { + file: "./abis/erc721.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Call, + handler: "handleSafeMint", + filter: { + function: "safeMint(address,string)", + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApproval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApprovalForAll", + filter: { + topics: ["ApprovalForAll(address,address,bool)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleTransfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 1993364, // This is the block that the contract was deployed on + options: { + abi: "erc721", + address: "0x195e4E251c41e8Ae9E9E961366C73e2CFbfB115A", + }, + assets: new Map([ + [ + "erc721", + { + file: "./abis/erc721.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Call, + handler: "handleSafeMint", + filter: { + function: "safeMint(address,string)", + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApproval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleApprovalForAll", + filter: { + topics: ["ApprovalForAll(address,address,bool)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleTransfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 2178049, // This is the block that the contract was deployed on + options: { + abi: "erc20", + address: "0xb4B74C2BfeA877672B938E408Bae8894918fE41C", + }, + assets: new Map([ + [ + "erc20", + { + file: "./abis/erc20.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Event, + handler: "handleERC20Approval", + filter: { + topics: ["Approval(address,address,uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleERC20Transfer", + filter: { + topics: ["Transfer(address,address,uint256)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 2157320, + options: { + abi: "migration", + address: "0x1427d38B967435a3F8f476Cda0bc4F51fe66AF4D", + }, + assets: new Map([ + [ + "migration", + { + file: "./abis/migration.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Event, + handler: "handleProposal", + filter: { + topics: ["VoteStarted(bytes32, address, address, uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleWithdrawn", + filter: { + topics: ["Withdrawn(bytes32, address, uint256)"], + }, + }, + { + kind: EthereumHandlerKind.Event, + handler: "handleVote", + filter: { + topics: ["Voted(bytes32, address)"], + }, + }, + ], + }, + }, + { + kind: EthereumDatasourceKind.Runtime, + startBlock: 3001690, // This is the block that the contract was deployed on + options: { + abi: "erc721-a", + address: "0x9Fed2d216DBE36928613812400Fd1B812f118438", + }, + assets: new Map([ + [ + "erc721-a", + { + file: "./abis/erc721-a.abi.json", + }, + ], + ]), + mapping: { + file: "./dist/index.js", + handlers: [ + { + kind: EthereumHandlerKind.Event, + handler: "handleRewardTransfer", + filter: { + topics: ["Transfer (address from, address to, uint256 tokenId)"], + }, + }, + ], + }, + }, + ], +}; + +export default project; diff --git a/subquery/project.ts b/subquery/project.ts index 53b7e96..5cb93bb 100644 --- a/subquery/project.ts +++ b/subquery/project.ts @@ -26,17 +26,15 @@ const project: EthereumProject = { file: "./schema.graphql", }, network: { - chainId: "300", // zKsync sepolia testnet - endpoint: ["https://sepolia.era.zksync.dev"], + chainId: "324", // zKsync mainnet + endpoint: [ + "https://wandering-distinguished-tree.zksync-mainnet.quiknode.pro/20c0bc25076ea895aa263c9296c6892eba46077c/", + ], }, dataSources: [ { kind: EthereumDatasourceKind.Runtime, - startBlock: 1453992, // This is the block that the contract was deployed on - options: { - abi: "erc721", - address: "0x999368030Ba79898E83EaAE0E49E89B7f6410940", - }, + startBlock: 33492869, // This is the block that the contract was deployed on assets: new Map([ [ "erc721", @@ -44,44 +42,19 @@ const project: EthereumProject = { file: "./abis/erc721.abi.json", }, ], + [ + "erc20", + { + file: "./abis/erc20.abi.json", + }, + ], ]), mapping: { file: "./dist/index.js", handlers: [ { - kind: EthereumHandlerKind.Call, - handler: "handleSafeMint", - filter: { - function: "safeMint(address,string)", - }, - }, - { - kind: EthereumHandlerKind.Call, - handler: "handleApprove", - filter: { - function: "approve(address,uint256)", - }, - }, - { - kind: EthereumHandlerKind.Event, - handler: "handleApproval", - filter: { - topics: ["Approval(address,address,uint256)"], - }, - }, - { - kind: EthereumHandlerKind.Event, - handler: "handleApprovalForAll", - filter: { - topics: ["ApprovalForAll(address,address,bool)"], - }, - }, - { - kind: EthereumHandlerKind.Event, - handler: "handleTransfer", - filter: { - topics: ["Transfer(address from,address to,uint256 tokenId)"], - }, + kind: EthereumHandlerKind.Block, + handler: "handleBlock", }, ], }, diff --git a/subquery/schema.graphql b/subquery/schema.graphql index 43b877a..dffb874 100644 --- a/subquery/schema.graphql +++ b/subquery/schema.graphql @@ -13,6 +13,11 @@ type Account @entity { ERC721operatorOperator: [ERC721Operator!]! @derivedFrom(field: "operator") ERC721transferFromEvent: [ERC721Transfer!]! @derivedFrom(field: "from") ERC721transferToEvent: [ERC721Transfer!]! @derivedFrom(field: "to") + ERC20transferFromEvent: [ERC20Transfer!]! @derivedFrom(field: "from") + ERC20transferToEvent: [ERC20Transfer!]! @derivedFrom(field: "to") + ERC20approvalOwnerEvent: [ERC20Approval!]! @derivedFrom(field: "owner") + ERC20approvalSpenderEvent: [ERC20Approval!]! @derivedFrom(field: "spender") + Proposals: [Proposal!]! @derivedFrom(field: "recipient") asAccessControl: AccessControl membership: [AccessControlRoleMember!]! @derivedFrom(field: "account") roleGranted: [RoleGranted!]! @derivedFrom(field: "account") @@ -25,6 +30,7 @@ type ERC721Contract @entity { id: ID! asAccount: Account! supportsMetadata: Boolean + isValid: Boolean name: String symbol: String tokens: [ERC721Token!]! @derivedFrom(field: "contract") @@ -40,8 +46,10 @@ type ERC721Token @entity { uri: String timestamp: BigInt content: String + thumbnail: String channel: String name: String + contentType: String transactionHash: String description: String transfers: [ERC721Transfer!]! @derivedFrom(field: "token") @@ -122,3 +130,85 @@ type Transaction @entity { blockNumber: BigInt! events: [Event!]! @derivedFrom(field: "transaction") } + +# ERC20 + +type ERC20Contract @entity { + id: ID! + asAccount: Account! + isValid: Boolean + name: String + symbol: String + decimals: BigInt + totalSupply: BigInt + transfers: [ERC20Transfer!]! @derivedFrom(field: "contract") + approvals: [ERC20Approval!]! @derivedFrom(field: "contract") +} + +type ERC20Transfer implements Event @entity { + id: ID! + emitter: Account! + transaction: Transaction! + timestamp: BigInt! @index + contract: ERC20Contract + hash: String @index + from: Account! + to: Account! + value: BigInt! +} + +type ERC20Approval implements Event @entity { + id: ID! + emitter: Account! + transaction: Transaction! + timestamp: BigInt! + contract: ERC20Contract + owner: Account! + spender: Account! + value: BigInt! + hash: String @index +} + +# Migration + +type ProposalContract @entity { + id: ID! + asAccount: Account! + proposals: [Proposal!]! @derivedFrom(field: "contract") +} + +type Proposal @entity { + id: ID! + contract: ProposalContract + proposal: String @index + amount: BigInt + recipient: Account + initiator: Account + withdrawn: Boolean + votes: [ProposalVote!]! @derivedFrom(field: "proposal") + hash: String @index + timestamp: BigInt @index +} + +type ProposalVote @entity { + id: ID! + proposal: Proposal! + voter: Account! + timestamp: BigInt! @index + hash: String @index +} + +type NativeTransaction @entity { + id: ID! + hash: String @index + timestamp: BigInt! @index + blockNumber: BigInt! @index + from: Account! + to: Account + value: BigInt! + gas: BigInt! + gasPrice: BigInt! + input: String + nonce: BigInt! +} + diff --git a/subquery/src/index.ts b/subquery/src/index.ts index e79ce4c..a5dc8a7 100644 --- a/subquery/src/index.ts +++ b/subquery/src/index.ts @@ -1,2 +1,2 @@ //Exports all handler functions -export * from "./mappings/erc721"; +export * from "./mappings/native"; diff --git a/subquery/src/mappings/common.ts b/subquery/src/mappings/common.ts new file mode 100644 index 0000000..3a8880f --- /dev/null +++ b/subquery/src/mappings/common.ts @@ -0,0 +1,319 @@ +import { + ERC20Approval, + ERC20Contract, + ERC20Transfer, + ERC721Contract, + ERC721Token, + ERC721Transfer, +} from "../types"; +import { ApprovalLog, TransferLog } from "../types/abi-interfaces/Erc721Abi"; +import { TransferLog as TransferLogERC20 } from "../types/abi-interfaces/Erc20Abi"; +import { + fetchAccount, + fetchMetadata, + fetchTransaction, + getContractDetails, +} from "../utils/utils"; +import { abi, callContract, nodleContracts } from "../utils/const"; +import { fetchToken } from "../utils/erc721"; + +const knownAddresses = [ + "0x000000000000000000000000000000000000800a", + "0x5a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e", +]; + +export async function handleTransfer(event: TransferLog) { + try { + const lowercaseAddress = event?.address?.toLowerCase(); + + if (knownAddresses.includes(lowercaseAddress)) { + return; + } + + let contract: ERC20Contract | ERC721Contract | undefined = + (await ERC20Contract.get(lowercaseAddress)) || + (await ERC721Contract.get(lowercaseAddress)); + + if (!contract) { + const { symbol, name, isErc20, isErc721 } = await getContractDetails( + lowercaseAddress + ); + logger.info( + `Contract details in handleTransfer: ${symbol}, ${name}, ${isErc721}, ${isErc20}` + ); + let newContract = isErc721 + ? new ERC721Contract(lowercaseAddress, lowercaseAddress) + : new ERC20Contract(lowercaseAddress, lowercaseAddress); + + newContract.isValid = isErc20 || isErc721 ? true : false; + + newContract.symbol = symbol; + newContract.name = name; + + await newContract.save(); + contract = newContract.isValid ? newContract : undefined; + } + + if (contract?.isValid) { + if (contract._name === "ERC721Contract") { + return handleNFTTransfer(event, contract); + } else if (contract._name === "ERC20Contract") { + return handleERC20Transfer(event as TransferLogERC20, contract); + } + } + } catch (error) { + logger.error(JSON.stringify(error)); + } +} + +export async function handleERC20Transfer( + event: TransferLogERC20, + contract: ERC20Contract +) { + if (!event.args) { + logger.error("No event.args"); + return; + } + + if (contract?.isValid) { + const from = await fetchAccount(event.args[0]); + const to = await fetchAccount(event.args[1]); + const emmiter = await fetchAccount(event.transaction.from); + const value = event.args[2].toBigInt(); + const timestamp = event.block.timestamp * BigInt(1000); + const hash = event.transaction.hash; + + const transaction = await fetchTransaction( + hash, + timestamp, + BigInt(event.blockNumber) + ); + + const transferEvent = new ERC20Transfer( + contract.id + .concat("/") + .concat(hash) + .concat("/") + .concat(`${event.logIndex}`), + emmiter.id, + transaction.id, + timestamp, + from.id, + to.id, + value + ); + + transferEvent.hash = hash; + transferEvent.contractId = contract.id; + transferEvent.emitterId = emmiter.id; + return [transferEvent, transaction]; + } +} + +export async function handleNFTTransfer( + event: TransferLog, + contract: ERC721Contract +) { + if (!event.args) { + logger.error("No event.args: " + JSON.stringify(event)); + return; + } + + if (contract?.isValid) { + const from = await fetchAccount(event.args[0]); + const to = await fetchAccount(event.args[1]); + const tokenId = event.args[2]; + + const transaction = await fetchTransaction( + event.transaction.hash, + event.block.timestamp * BigInt(1000), + BigInt(event.block.number) + ); + + const token = await fetchToken( + `${contract.id}/${tokenId}`, + contract.id, + BigInt(tokenId as any), + to.id, + "", + from.id !== "0x0000000000000000000000000000000000000000" + ); + + const transfer = new ERC721Transfer( + `${contract.id}/${token.id}`, + from.id, + transaction.id, + event.block.timestamp * BigInt(1000), + contract.id, + token.id, + from.id, + to.id + ); + + token.ownerId = to.id; + token.transactionHash = event.transaction.hash; + token.timestamp = event.block.timestamp * BigInt(1000); + + const tokenToSave = await getTokenWithUri( + contract.id, + tokenId.toBigInt(), + token + ); + + return [transfer, transaction, tokenToSave]; + } +} + +export async function handleApproval(event: ApprovalLog) { + try { + const lowercaseAddress = event?.address?.toLowerCase(); + + if (knownAddresses.includes(lowercaseAddress)) { + return; + } + + let contract: ERC20Contract | ERC721Contract | undefined = + (await ERC20Contract.get(lowercaseAddress)) || + (await ERC721Contract.get(lowercaseAddress)); + + if (!contract) { + logger.info( + `Contract not found for lowercaseAddress in handleApproval: ${lowercaseAddress}` + ); + + const { symbol, name, isErc20, isErc721 } = await getContractDetails( + lowercaseAddress + ); + + let newContract = isErc721 + ? new ERC721Contract(lowercaseAddress, lowercaseAddress) + : new ERC20Contract(lowercaseAddress, lowercaseAddress); + + newContract.isValid = isErc20 || isErc721 ? true : false; + + newContract.symbol = symbol; + newContract.name = name; + + await newContract.save(); + contract = newContract.isValid ? newContract : undefined; + } + + if (contract?.isValid) { + if (contract._name === "ERC20Contract") { + return handleERC20Approval(event, contract); + } else if (contract._name === "ERC721Contract") { + return handleERC721Approval(event, contract); + } + } + } catch (error) { + logger.error(JSON.stringify(error)); + } +} + +export async function handleERC20Approval( + event: ApprovalLog, + contract: ERC20Contract +) { + if (!event.args) { + logger.error("No event.args"); + return; + } + + if (contract?.isValid) { + const owner = await fetchAccount(event.args[0]); + const spender = await fetchAccount(event.args[1]); + const value = event.args[2].toBigInt(); + const timestamp = event.block.timestamp * BigInt(1000); + const hash = event.transaction.hash; + const emmiter = await fetchAccount(event.transaction.from); + + const transaction = await fetchTransaction( + hash, + timestamp, + BigInt(event.blockNumber) + ); + + const approval = new ERC20Approval( + contract.id.concat("/").concat(hash), + emmiter.id, + transaction.id, + timestamp, + owner.id, + spender.id, + value + ); + + approval.contractId = contract.id; + approval.hash = hash; + + return [approval, transaction]; + } +} + +export async function handleERC721Approval( + event: ApprovalLog, + contract: ERC721Contract +) { + if (!event.args) { + logger.error("No event.args: " + JSON.stringify(event)); + return; + } + + if (contract?.isValid) { + const to = await fetchAccount(event.args[1]); + const from = await fetchAccount(event.args[0]); + const tokenId = BigInt(event.args[2] as any); + + const token = await fetchToken( + `${contract.id}/${tokenId}`, + contract.id, + tokenId, + to.id, + "", + from.id !== "0x0000000000000000000000000000000000000000" + ); + + token.ownerId = to.id; + token.transactionHash = event.transaction.hash; + token.timestamp = event.block.timestamp * BigInt(1000); + + const toSave = await getTokenWithUri(contract.id, tokenId, token); + + return [toSave]; + } +} + +const getTokenWithUri = async ( + contractId: string, + tokenId: bigint, + token: ERC721Token +): Promise => { + if ( + !token.uri && + nodleContracts.includes(String(contractId).toLocaleLowerCase()) + ) { + const tokenUri = await callContract(contractId, abi, "tokenURI", [ + tokenId, + ]).catch((error) => { + return null; + }); + // logger.info("Token URI: " + tokenUri); + if (tokenUri) { + const metadata = await fetchMetadata(tokenUri, [ + "nodle-community-nfts.myfilebase.com", + "pinning.infura-ipfs.io", + "nodle-web-wallet.infura-ipfs.io", + "cloudflare-ipfs.com", + ]); + + if (metadata) { + token.content = metadata.content || metadata.image || ""; + token.name = metadata.title || metadata.name || ""; + token.description = metadata.description || ""; + } + } + token.uri = String(tokenUri); + } + + return token; +}; diff --git a/subquery/src/mappings/erc721.ts b/subquery/src/mappings/erc721.ts deleted file mode 100644 index 122df2e..0000000 --- a/subquery/src/mappings/erc721.ts +++ /dev/null @@ -1,112 +0,0 @@ -import assert from "assert"; -import { fetchContract, fetchERC721Operator, fetchToken, getApprovalLog } from "../utils/erc721"; -import { TransferLog, ApprovalForAllLog, SafeMintTransaction } from "../types/abi-interfaces/Erc721Abi"; -import { fetchAccount, fetchMetadata, fetchTransaction } from "../utils/utils"; -import { ERC721Contract } from "../types"; - -export async function handleTransfer(event: TransferLog): Promise { - assert(event.args, "No event.args"); - - const contract = await ERC721Contract.get(event.address); - - if (contract != null) { - const from = await fetchAccount(event.args.from); - const to = await fetchAccount(event.args.to); - - const tokenId = event.args.tokenId; - - const token = await fetchToken( - `${contract.id}/${tokenId}`, - contract.id, - tokenId.toBigInt(), - from.id, - from.id - ); - - token.ownerId = to.id; - - return token.save(); - } -} - -// This event is not being emitted by the contract, it is an issue? -/* export function handleApproval(event: ApprovalLog): Promise { - logger.info("handleApproval: " + JSON.stringify(event)); - // const account = await fetchAccount(); - - return Promise.resolve(); -} */ - -export async function handleApprovalForAll(event: ApprovalForAllLog) { - assert(event.args, "No event.args"); - - const contract = await fetchContract(event.address); - if (contract != null) { - const owner = await fetchAccount(event.args.owner); - const operator = await fetchAccount(event.args.operator); - - const delegation = await fetchERC721Operator(contract, owner, operator); - - delegation.approved = event.args.approved; - - return delegation.save(); - } -} - -export async function handleSafeMint(tx: SafeMintTransaction) { - assert(tx.args, "No tx.args"); - assert(tx.logs, "No tx.logs"); - - // Call to the contract - const contract = await fetchContract(tx.to); - - const safeMintTx = await fetchTransaction( - tx.hash, - tx.blockTimestamp, - BigInt(tx.blockNumber) - ); - - // Caller - const caller = await fetchAccount(tx.from); - - const owner = await fetchAccount(await tx.args[0]); - const uri = await tx.args[1]; - - const tokenId = getApprovalLog(tx.logs, owner.id)![2].toBigInt(); - - const token = await fetchToken( - `${contract.id}/${tokenId}`, - contract.id, - tokenId, - owner.id, - caller.id - ); - - token.timestamp = BigInt(tx.blockTimestamp); - - token.transactionHash = tx.hash; - - token.uri = uri; - - if (uri) { - const metadata = await fetchMetadata(uri, [ - "pinning.infura-ipfs.io", - "nodle-web-wallet.infura-ipfs.io", - "cloudflare-ipfs.com", - ]); - - if (metadata) { - token.content = metadata.content || metadata.image || ""; - token.channel = metadata.channel || ""; - } - } - - - return Promise.all([ - contract.save(), - safeMintTx.save(), - caller.save(), - owner.save(), - token.save(), - ]); -} diff --git a/subquery/src/mappings/native.ts b/subquery/src/mappings/native.ts new file mode 100644 index 0000000..cd144f6 --- /dev/null +++ b/subquery/src/mappings/native.ts @@ -0,0 +1,132 @@ +import { EthereumBlock } from "@subql/types-ethereum"; +import { + ERC20Approval, + ERC20Transfer, + ERC721Token, + ERC721Transfer, + Transaction, +} from "../types"; +import { ethers } from "ethers"; +import { abi, erc721Abi } from "../utils/const"; +import { handleApproval, handleTransfer } from "./common"; +import { ApprovalLog, TransferLog } from "../types/abi-interfaces/Erc721Abi"; + +export const handleBlock = async (block: EthereumBlock): Promise => { + logger.info(`Processing block ${block.number}`); + const nftTransfersToSave: ERC721Transfer[] = []; + const erc20TransfersToSave: ERC20Transfer[] = []; + const erc20ApprovalsToSave: ERC20Approval[] = []; + const tokenToSave: ERC721Token[] = []; + const transactionToSave: Transaction[] = []; + + for (const log of block.logs) { + try { + const isPossibleERC721 = log.topics.length === 4; + let iface = new ethers.utils.Interface( + isPossibleERC721 ? erc721Abi : abi + ); + let _log = iface.parseLog(log); + // logger.info(`Processing log ${_log.name}`); + + if (_log.name === "Transfer") { + log.args = _log.args as any; + + const tosave = await handleTransfer(log as TransferLog); + + if (tosave?.length) { + tosave?.forEach(async (t) => { + switch (t?._name) { + case "ERC20Transfer": + erc20TransfersToSave.push(t as ERC20Transfer); + break; + + case "ERC721Token": + tokenToSave.push(t as ERC721Token); + break; + + case "Transaction": + transactionToSave.push(t as Transaction); + break; + + default: + nftTransfersToSave.push(t as ERC721Transfer); + break; + } + }); + } + } + + if (_log.name === "Approval") { + log.args = _log.args as any; + const tosave = await handleApproval(log as ApprovalLog); + + if (tosave?.length) { + tosave?.forEach(async (t) => { + switch (t?._name) { + case "Transaction": + transactionToSave.push(t as Transaction); + break; + + case "ERC721Token": + tokenToSave.push(t as ERC721Token); + break; + + default: + erc20ApprovalsToSave.push(t as ERC20Approval); + break; + } + }); + } + } + } catch (error: any) { + if (error?.reason === "no matching event") { + // logger.info(`No matching event for log`); + } else { + logger.error(`Error processing log: ${JSON.stringify(error)}`); + } + } + } + + await Promise.all([ + transactionToSave.length && + store.bulkCreate("Transaction", transactionToSave), + tokenToSave.length && store.bulkCreate("ERC721Token", tokenToSave), + ]); + + Promise.all([ + nftTransfersToSave.length && + store.bulkCreate("ERC721Transfer", nftTransfersToSave), + erc20TransfersToSave.length && + store.bulkCreate("ERC20Transfer", erc20TransfersToSave), + erc20ApprovalsToSave.length && + store.bulkCreate("ERC20Approval", erc20ApprovalsToSave), + ]); + + // logger.info(`Processed block ${block.number}`); +}; + +/* export const handleContractDeployed = async ( + event: ContractDeployedEvent +): Promise => { + const [, , contractAddress] = event.args; + + const { symbol, name, isErc721, isErc20 } = await getContractDetails( + contractAddress + ); + + logger.info(`Contract details: ${symbol}, ${name}, ${isErc721}, ${isErc20}`); + + if (isErc721) { + const contract = new ERC721Contract(contractAddress, contractAddress); + contract.isValid = isErc721; + contract.symbol = symbol; + contract.name = name; + return contract.save(); + } else if (isErc20) { + const contract = new ERC20Contract(contractAddress, contractAddress); + contract.isValid = isErc20; + contract.symbol = symbol; + contract.name = name; + await contract.save(); + } +}; */ diff --git a/subquery/src/utils/const.ts b/subquery/src/utils/const.ts new file mode 100644 index 0000000..b514d60 --- /dev/null +++ b/subquery/src/utils/const.ts @@ -0,0 +1,109 @@ +import { ethers } from "ethers"; +import fetch from "node-fetch"; + +export const SEPOLIA_RPC_URL = + "https://wandering-distinguished-tree.zksync-mainnet.quiknode.pro/20c0bc25076ea895aa263c9296c6892eba46077c/"; + +export async function checkERC20(address: string) { + try { + // Check if the contract implements the ERC-20 functions + await callContract(address, abi, "totalSupply"); + await callContract(address, abi, "balanceOf", [ + "0x0000000000000000000000000000000000000000", + ]); + await callContract(address, abi, "allowance", [ + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + ]); + + return true; + } catch (error) { + logger.info( + `Error checking ERC20 for ${address} with error ${JSON.stringify(error)}` + ); + return false; + } +} + +export async function callContract( + contractAddress: string, + abi: any[], + methodName: string, + params: any[] = [] +): Promise { + // Create an instance of the ethers.js Interface for encoding the data + const iface = new ethers.utils.Interface(abi); + + // Encode the function call + const data = iface.encodeFunctionData(methodName, params); + + // Define the JSON-RPC payload + const payload = { + jsonrpc: "2.0", + method: "eth_call", + params: [ + { + to: contractAddress, + data: data, + }, + "latest", + ], + id: 1, + }; + + try { + // Make the fetch call + const response = await fetch(SEPOLIA_RPC_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + const responseData = await response.json(); + + if (responseData.result) { + // Decode the result if needed + const decodedResult = iface.decodeFunctionResult( + methodName, + responseData.result + ); + return decodedResult; + } else { + throw new Error(`RPC Error: ${responseData.error.message}`); + } + } catch (error: any) { + throw new Error(`Fetch Error: ${error.message}`); + } +} + +export const nodleContracts = [ + "0x95b3641d549f719eb5105f9550eca4a7a2f305de", + "0xd837cfb550b7402665499f136eee7a37d608eb18", + "0x9Fed2d216DBE36928613812400Fd1B812f118438".toLowerCase(), + "0x999368030Ba79898E83EaAE0E49E89B7f6410940".toLowerCase(), +]; + +export const abi = [ + // Minimal ERC721 ABI with only the tokenURI method + "function tokenURI(uint256 tokenId) view returns (string)", + "function symbol() view returns (string)", + "function name() view returns (string)", + "function supportsInterface(bytes4 interfaceId) view returns (bool)", + // ERC20 ABI + "function totalSupply() view returns (uint256)", + "function balanceOf(address account) view returns (uint256)", + "function allowance(address owner, address spender) view returns (uint256)", + "event Transfer(address indexed from, address indexed to, uint256 value)", + "event Approval(address indexed owner, address indexed spender, uint256 value)", +]; + +export const erc721Abi = [ + "function tokenURI(uint256 tokenId) view returns (string)", + "function symbol() view returns (string)", + "function name() view returns (string)", + "function supportsInterface(bytes4 interfaceId) view returns (bool)", + "event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)", + "event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)", +]; \ No newline at end of file diff --git a/subquery/src/utils/erc20.ts b/subquery/src/utils/erc20.ts new file mode 100644 index 0000000..f53691b --- /dev/null +++ b/subquery/src/utils/erc20.ts @@ -0,0 +1,30 @@ +import { ERC20Contract } from "../types"; +import { getContractDetails } from "./utils"; + +export const fetchContract = async ( + address: string +): Promise => { + // rewrite to lowercase + const lowercaseAddress = address?.toLowerCase(); + + const contract = await ERC20Contract.get(lowercaseAddress); + + if (!contract) { + logger.info(`Contract not found for lowercaseAddress: ${lowercaseAddress}`); + const newContract = new ERC20Contract(lowercaseAddress, lowercaseAddress); + + const { symbol, name, isErc20 } = await getContractDetails( + lowercaseAddress + ); + + newContract.isValid = isErc20; + + newContract.symbol = symbol; + newContract.name = name; + await newContract.save(); + + return newContract.isValid ? newContract : null; + } + + return contract.isValid ? contract : null; +}; diff --git a/subquery/src/utils/erc721.ts b/subquery/src/utils/erc721.ts index cbccc1d..078d99c 100644 --- a/subquery/src/utils/erc721.ts +++ b/subquery/src/utils/erc721.ts @@ -1,19 +1,43 @@ import { EthereumLog, EthereumResult } from "@subql/types-ethereum"; import { Account, ERC721Contract, ERC721Operator, ERC721Token } from "../types"; import assert from "assert"; +import { getContractDetails } from "./utils"; -export const fetchContract = async (address: string): Promise => { - const contract = await ERC721Contract.get(address); +const knownAddresses = [ + "0x000000000000000000000000000000000000800a", + "0x5a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e", +]; + +export const fetchContract = async ( + address: string +): Promise => { + // rewrite to lowercase + const lowercaseAddress = address?.toLowerCase(); + + if (knownAddresses.includes(lowercaseAddress)) { + return null; + } + + const contract = await ERC721Contract.get(lowercaseAddress); if (!contract) { - logger.error(`Contract not found for address: ${address}`); - const newContract = new ERC721Contract(address, address); - newContract.save(); + logger.info(`Contract not found for lowercaseAddress: ${lowercaseAddress}`); + const newContract = new ERC721Contract(lowercaseAddress, lowercaseAddress); + + const { symbol, name, isErc721 } = await getContractDetails( + lowercaseAddress + ); + + newContract.isValid = isErc721; + + newContract.symbol = symbol; + newContract.name = name; + await newContract.save(); - return newContract; + return newContract.isValid ? newContract : null; } - return contract; + return contract.isValid ? contract : null; }; export const fetchToken = async ( @@ -21,20 +45,28 @@ export const fetchToken = async ( contractId: string, identifier: bigint, ownerId: string, - approvalId: string + approvalId: string, + shouldLookup = true ): Promise => { + const newToken = new ERC721Token( + id, + contractId, + identifier, + ownerId, + approvalId + ); + + if (!shouldLookup) { + logger.info(`Token id: ${id}`); + return newToken; + } + const token = await ERC721Token.get(id); if (!token) { - logger.error(`Token not found for id: ${id}`); - const newToken = new ERC721Token( - id, - contractId, - identifier, - ownerId, - approvalId - ); - newToken.save(); + logger.info(`Token not found for id: ${id}`); + + // newToken.save(); return newToken; } @@ -66,12 +98,18 @@ export const fetchERC721Operator = async ( const op = await ERC721Operator.get(id); if (!op) { - logger.error(`Operator not found for id: ${id}`); - const newOp = new ERC721Operator(id, contract.id, owner.id, operator.id, false); + logger.info(`Operator not found for id: ${id}`); + const newOp = new ERC721Operator( + id, + contract.id, + owner.id, + operator.id, + false + ); newOp.save(); return newOp; } return op; -}; \ No newline at end of file +}; diff --git a/subquery/src/utils/migration.ts b/subquery/src/utils/migration.ts new file mode 100644 index 0000000..d114244 --- /dev/null +++ b/subquery/src/utils/migration.ts @@ -0,0 +1,42 @@ +import { Proposal, ProposalContract } from "../types"; + +export const fetchContract = async ( + address: string +): Promise => { + // rewrite to lowercase + const lowercaseAddress = address?.toLowerCase(); + + const contract = await ProposalContract.get(lowercaseAddress); + + if (!contract) { + logger.error( + `Contract not found for lowercaseAddress: ${lowercaseAddress}` + ); + const newContract = new ProposalContract( + lowercaseAddress, + lowercaseAddress + ); + newContract.save(); + + return newContract; + } + + return contract; +}; + + +export const fetchProposal = async ( + proposal: string +): Promise => { + const prop = await Proposal.get(proposal); + + if (!prop) { + logger.error(`Proposal not found for: ${proposal}`); + const newProp = new Proposal(proposal); + newProp.save(); + + return newProp; + } + + return prop; +}; diff --git a/subquery/src/utils/utils.ts b/subquery/src/utils/utils.ts index e94daf5..b960437 100644 --- a/subquery/src/utils/utils.ts +++ b/subquery/src/utils/utils.ts @@ -1,7 +1,56 @@ -import { - Account, Transaction, -} from "../types"; +import { Account, Transaction } from "../types"; import fetch from "node-fetch"; +import { abi, callContract, checkERC20 } from "./const"; + +export const getContractDetails = async ( + address: string +): Promise<{ + symbol: string; + name: string; + isErc721: boolean; + isErc20: boolean; +}> => { + try { + const symbol = await callContract(address, abi, "symbol"); + const name = await callContract(address, abi, "name"); + const [isErc721] = await callContract(address, abi, "supportsInterface", [ + "0x80ac58cd", + ]).catch((error: any) => { + logger.info(`Error calling supportsInterface for ${address}`); + logger.info(JSON.stringify(error)); + return [false]; + }); + + const [erc1155] = isErc721 + ? [isErc721] + : await callContract(address, abi, "supportsInterface", [ + "0xd9b67a26", + ]).catch((error: any) => { + logger.info(`Error calling supportsInterface for ${address}`); + logger.info(JSON.stringify(error)); + return [false]; + }); + + const isErc20 = isErc721 || erc1155 ? false : await checkERC20(address); + + return { + symbol: String(symbol), + name: String(name), + isErc721: Boolean(isErc721 || erc1155), + isErc20: Boolean(isErc20), + }; + } catch (error: any) { + logger.info(`Error getting contract details for ${address}`); + logger.info(JSON.stringify(error)); + + return { + symbol: "", + name: "", + isErc721: false, + isErc20: false, + }; + } +}; export async function fetchAccount(address: string): Promise { let account = await Account.get(address); @@ -19,17 +68,11 @@ export const fetchTransaction = async ( timestamp: bigint, blocknumber: bigint ): Promise => { - const tx = await Transaction.get(txHash); - - if (!tx) { - logger.error(`Transaction not found for hash: ${txHash}`); - const newTx = new Transaction(txHash, timestamp, blocknumber); - newTx.save(); - - return newTx; - } + // logger.info(`Transaction not found for hash: ${txHash}`); + const newTx = new Transaction(txHash, timestamp, blocknumber); + // newTx.save(); - return tx; + return newTx; }; export const fetchMetadata = async ( @@ -39,8 +82,8 @@ export const fetchMetadata = async ( if (gateways.length === 0) { return null; } - - const strppedCid = cid.replace("ipfs://", ""); + logger.info(`Fetching metadata for CID: ${cid}`); + const strppedCid = String(cid).replace("ipfs://", ""); const gateway = gateways[0]; const url = `https://${gateway}/ipfs/${strppedCid}`; @@ -49,14 +92,13 @@ export const fetchMetadata = async ( const res = await fetch(url); return await res.json(); } catch (err) { - logger.error(err); + logger.info(err); const toMatch = ["Unexpected token I in JSON at position 0"]; if (err instanceof SyntaxError && toMatch.includes(err.message)) { return null; } - + return fetchMetadata(cid, gateways.slice(1)); } }; - diff --git a/subquery/test.ts b/subquery/test.ts new file mode 100644 index 0000000..08aafde --- /dev/null +++ b/subquery/test.ts @@ -0,0 +1,164 @@ +import { ethers } from "ethers"; +import fetch from "node-fetch"; + +export const SEPOLIA_RPC_URL = "https://mainnet.era.zksync.io"; + +export async function checkERC20(address: string) { + try { + // Check if the contract implements the ERC-20 functions + await callContract(address, abi, "totalSupply"); + await callContract(address, abi, "balanceOf", [ + "0x0000000000000000000000000000000000000000", + ]); + await callContract(address, abi, "allowance", [ + "0x0000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000", + ]); + + console.log("checkERC20", true); + return true; + } catch (error) { + console.log(error) + return false; + } +} + +export async function callContract( + contractAddress: string, + abi: any[], + methodName: string, + params: any[] = [] +): Promise { + // Create an instance of the ethers.js Interface for encoding the data + const iface = new ethers.utils.Interface(abi); + + // Encode the function call + const data = iface.encodeFunctionData(methodName, params); + + // Define the JSON-RPC payload + const payload = { + jsonrpc: "2.0", + method: "eth_call", + params: [ + { + to: contractAddress, + data: data, + }, + "latest", + ], + id: 1, + }; + + try { + // Make the fetch call + const response = await fetch(SEPOLIA_RPC_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + const responseData = await response.json(); + + if (responseData.result) { + // Decode the result if needed + const decodedResult = iface.decodeFunctionResult( + methodName, + responseData.result + ); + return decodedResult; + } else { + throw new Error(`RPC Error: ${responseData.error.message}`); + } + } catch (error: any) { + throw new Error(`Fetch Error: ${error.message}`); + } +} + +export const nodleContracts = [ + "0x95b3641d549f719eb5105f9550eca4a7a2f305de", + "0xd837cFb550b7402665499f136eeE7a37D608Eb18", + "0x9Fed2d216DBE36928613812400Fd1B812f118438", + "0x999368030Ba79898E83EaAE0E49E89B7f6410940", +]; + +export const abi = [ + // Minimal ERC721 ABI with only the tokenURI method + "function tokenURI(uint256 tokenId) view returns (string)", + "function symbol() view returns (string)", + "function name() view returns (string)", + "function supportsInterface(bytes4 interfaceId) view returns (bool)", + // ERC20 ABI + "function totalSupply() view returns (uint256)", + "function balanceOf(address account) view returns (uint256)", + "function transfer(address recipient, uint256 amount) returns (bool)", + "function allowance(address owner, address spender) view returns (uint256)", + "function approve(address spender, uint256 amount) returns (bool)", + "function transferFrom(address sender, address recipient, uint256 amount) returns (bool)", +]; + + +const logger = console; +export const getContractDetails = async ( + address: string +): Promise<{ + symbol: string; + name: string; + isErc721: boolean; + isErc20: boolean; +}> => { + try { + const symbol = await callContract(address, abi, "symbol"); + const name = await callContract(address, abi, "name"); + const [isErc721] = await callContract(address, abi, "supportsInterface", [ + "0x80ac58cd", + ]).catch((error: any) => { + logger.info(`Error calling supportsInterface for ${address}`); + logger.info(JSON.stringify(error)); + return [false]; + }); + + console.log("isErc721", isErc721, Array.isArray(isErc721)); + + const [erc1155] = await callContract(address, abi, "supportsInterface", [ + "0xd9b67a26", + ]).catch((error: any) => { + logger.info(`Error calling supportsInterface for ${address}`); + logger.info(JSON.stringify(error)); + return [false]; + }); + + console.log("erc1155", erc1155); + + logger.info(`isErc721: ${isErc721}`); + const isErc20 = isErc721 || erc1155 ? false : await checkERC20(address); + + return { + symbol: String(symbol), + name: String(name), + isErc721: Boolean(isErc721 || erc1155), + isErc20: Boolean(isErc20), + }; + } catch (error: any) { + logger.info(`Error getting contract details for ${address}`); + logger.info(JSON.stringify(error)); + + return { + symbol: "", + name: "", + isErc721: false, + isErc20: false, + }; + } +}; + +const main = async () => { + const res = await getContractDetails( + "0x80115c708E12eDd42E504c1cD52Aea96C547c05c" + ); + console.log("res", res); + console.log(res); +}; + +main(); diff --git a/test.ts b/test.ts new file mode 100644 index 0000000..e69de29