From 0b5da35fc6d2617f95614bd139632cede4f3e306 Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Fri, 19 Oct 2018 13:05:36 +0300 Subject: [PATCH 01/12] Add ERC165 support --- src/abi/README.md | 10 ++++++---- src/abi/erc165.json | 21 +++++++++++++++++++++ src/core/resolvers/scalars.ts | 21 +++++++++++++++++++++ src/core/schema/index.ts | 2 ++ src/core/services/decoder/index.ts | 2 +- src/erc165/decoders/index.ts | 17 +++++++++++++++++ src/erc165/index.ts | 26 ++++++++++++++++++++++++++ src/erc165/model/index.ts | 16 ++++++++++++++++ src/erc165/resolvers/index.ts | 18 ++++++++++++++++++ src/erc165/schema/erc165.ts | 6 ++++++ src/index.ts | 3 ++- 11 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 src/abi/erc165.json create mode 100644 src/erc165/decoders/index.ts create mode 100644 src/erc165/index.ts create mode 100644 src/erc165/model/index.ts create mode 100644 src/erc165/resolvers/index.ts create mode 100644 src/erc165/schema/erc165.ts diff --git a/src/abi/README.md b/src/abi/README.md index 670cb5a..839223a 100644 --- a/src/abi/README.md +++ b/src/abi/README.md @@ -10,10 +10,11 @@ We classify supported ABIs in two types: ## Supported ABIs -| Standard | Type | Entity | Specification | Comments | -| -------- | --------- | ------ | ------------- | --------------------------------------------------------------- | -| ERC20 | Entity |  Token | [link][1] | | -| ERC223 | Extension |  Token | [link][2] | Private ERC20 implementation change. No action needed in ethql. | +| Standard | Type | Entity | Specification | Comments | +| -------- | --------- | --------- | ------------- | --------------------------------------------------------------- | +| ERC20 | Entity |  Token | [link][1] | | +| ERC223 | Extension |  Token | [link][2] | Private ERC20 implementation change. No action needed in ethql. | +| ERC165 | Entity | Interface | [link][3] | | ## ethql naming scheme @@ -28,3 +29,4 @@ logs pertaining to several standards that relate to the same entity. [1]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md [2]: https://github.com/ethereum/EIPs/issues/223 +[3]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md diff --git a/src/abi/erc165.json b/src/abi/erc165.json new file mode 100644 index 0000000..6a40662 --- /dev/null +++ b/src/abi/erc165.json @@ -0,0 +1,21 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/core/resolvers/scalars.ts b/src/core/resolvers/scalars.ts index 8d603ef..30841ae 100644 --- a/src/core/resolvers/scalars.ts +++ b/src/core/resolvers/scalars.ts @@ -55,6 +55,26 @@ const Bytes32 = new GraphQLScalarType({ }, }); +// tslint:disable-next-line +const Bytes4 = new GraphQLScalarType({ + name: 'Bytes4', + description: 'A 4-byte value in hex format.', + serialize: String, + parseValue: input => { + return !Web3.utils.isHexStrict(input) || Web3.utils.hexToBytes(input).length !== 4 ? undefined : input; + }, + parseLiteral: ast => { + if ( + ast.kind !== Kind.STRING || + !Web3.utils.isHexStrict(ast.value) || + Web3.utils.hexToBytes(ast.value).length !== 4 + ) { + return undefined; + } + return String(ast.value); + }, +}); + //tslint:disable-next-line const Long = new GraphQLScalarType({ name: 'Long', @@ -69,4 +89,5 @@ export default { BlockNumber, Address, Bytes32, + Bytes4, }; diff --git a/src/core/schema/index.ts b/src/core/schema/index.ts index 610bf71..b7ef9f5 100644 --- a/src/core/schema/index.ts +++ b/src/core/schema/index.ts @@ -451,4 +451,6 @@ scalar Bytes32 scalar BlockNumber scalar Address + +scalar Bytes4 `; diff --git a/src/core/services/decoder/index.ts b/src/core/services/decoder/index.ts index a830e37..46e40c4 100644 --- a/src/core/services/decoder/index.ts +++ b/src/core/services/decoder/index.ts @@ -18,7 +18,7 @@ declare module '../../../services' { // Defines the entity to which the standard belongs. // As we support new standards, this union type will expand. -type Entity = 'token' | undefined; +type Entity = 'token' | 'interface' | undefined; export type AbiDecoder = { decodeMethod: Function; diff --git a/src/erc165/decoders/index.ts b/src/erc165/decoders/index.ts new file mode 100644 index 0000000..4857260 --- /dev/null +++ b/src/erc165/decoders/index.ts @@ -0,0 +1,17 @@ +import { createAbiDecoder, DecoderDefinition, extractParamValue } from '../../core/services/decoder'; + +type Erc165LogBindings = {}; + +type Erc165TxBindings = {}; + +class Erc165InterfaceDecoder implements DecoderDefinition { + public readonly entity = 'interface'; + public readonly standard = 'ERC165'; + public readonly abiDecoder = createAbiDecoder(__dirname + '../../../abi/erc165.json'); + + public readonly txTransformers = {}; + + public readonly logTransformers = {}; +} + +export { Erc165InterfaceDecoder }; diff --git a/src/erc165/index.ts b/src/erc165/index.ts new file mode 100644 index 0000000..30a9bd2 --- /dev/null +++ b/src/erc165/index.ts @@ -0,0 +1,26 @@ +import { EthqlPluginFactory } from '../plugin'; +import { Erc165InterfaceDecoder } from './decoders'; +import resolvers from './resolvers'; +import erc165Schema from './schema/erc165'; + +const plugin: EthqlPluginFactory = config => ({ + name: 'erc165', + priority: 10, + schema: [erc165Schema], + resolvers, + // serviceDefinitions: { + // decoder: { + // config: { + // decoders: [new Erc165InterfaceDecoder()], + // }, + // }, + // }, + dependsOn: { + services: ['web3', 'ethService', 'decoder'], + }, + order: { + after: ['core'], + }, +}); + +export default plugin; diff --git a/src/erc165/model/index.ts b/src/erc165/model/index.ts new file mode 100644 index 0000000..f2991f0 --- /dev/null +++ b/src/erc165/model/index.ts @@ -0,0 +1,16 @@ +export class Erc165InterfaceContract { + private static ABI = require(__dirname + '../../../abi/erc165.json'); + private _contract: Contract; + + constructor(public readonly account: EthqlAccount, readonly context: EthqlContext) { + this._contract = new context.services.web3.eth.Contract(Erc165InterfaceContract.ABI, account.address); + } + + public async supportsInterface({ interfaceID }: { String }): Boolean { + console.log('hello'); + return contract.methods + .supportsInterface(interfaceID) + .call() + .catch(console.error); + } +} diff --git a/src/erc165/resolvers/index.ts b/src/erc165/resolvers/index.ts new file mode 100644 index 0000000..bcc05d1 --- /dev/null +++ b/src/erc165/resolvers/index.ts @@ -0,0 +1,18 @@ +import { EthqlContext } from '../../context'; +import { EthqlAccount } from '../../core/model'; + +async function supportsInterface(account: EthqlAccount, { interfaceID }, context: EthqlContext) { + const ABI = require(__dirname + '../../../abi/erc165.json'); + const contract = new context.services.web3.eth.Contract(ABI, account.address); + + return contract.methods + .supportsInterface(interfaceID) + .call() + .catch(console.error); +} + +export default { + Account: { + supportsInterface, + }, +}; diff --git a/src/erc165/schema/erc165.ts b/src/erc165/schema/erc165.ts new file mode 100644 index 0000000..dc4208b --- /dev/null +++ b/src/erc165/schema/erc165.ts @@ -0,0 +1,6 @@ +export default ` + +extend type Account { + supportsInterface(interfaceID: Bytes4!): Boolean +} +`; diff --git a/src/index.ts b/src/index.ts index 4ee721f..febf51e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import config from './config'; import core from './core'; +import erc165 from './erc165'; import erc20 from './erc20'; import { EthqlServer } from './server'; @@ -7,7 +8,7 @@ console.log(`Effective configuration:\n${JSON.stringify(config, null, 2)}`); const server = new EthqlServer({ config, - plugins: [core, erc20], + plugins: [core, erc20, erc165], }); process.on('SIGINT', async () => (await server.stop()) || process.exit(0)); From 517300c6978cc6760723a0e1485089decfe539ad Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Sun, 21 Oct 2018 18:04:35 +0300 Subject: [PATCH 02/12] adding tests for ERC165, starting implementing 721 --- src/__tests__/erc165/erc165.test.ts | 53 ++++++++++++++++ src/abi/erc721.json | 1 + src/core/services/decoder/impl/simple.ts | 25 +++++--- src/erc165/index.ts | 13 ++-- src/erc165/model/index.ts | 1 - src/erc165/resolvers/index.ts | 2 +- src/erc20/decoders/index.ts | 1 + src/erc721/decoders/index.ts | 80 ++++++++++++++++++++++++ src/erc721/index.ts | 24 +++++++ src/erc721/model/index.ts | 55 ++++++++++++++++ src/erc721/schema/erc721.ts | 42 +++++++++++++ src/index.ts | 4 +- 12 files changed, 285 insertions(+), 16 deletions(-) create mode 100644 src/__tests__/erc165/erc165.test.ts create mode 100644 src/abi/erc721.json create mode 100644 src/erc721/decoders/index.ts create mode 100644 src/erc721/index.ts create mode 100644 src/erc721/model/index.ts create mode 100644 src/erc721/schema/erc721.ts diff --git a/src/__tests__/erc165/erc165.test.ts b/src/__tests__/erc165/erc165.test.ts new file mode 100644 index 0000000..979b806 --- /dev/null +++ b/src/__tests__/erc165/erc165.test.ts @@ -0,0 +1,53 @@ +import core from '../../core'; +import erc165 from '../../erc165'; +import { testGraphql } from '../utils'; + +const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc165] } }); + +test('erc165: Cryptokities supports ERC165 interface', async () => { + const query = ` + { + account(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { + supportsInterface(interfaceID: "0x01ffc9a7") + } + } + `; + + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + expect(result.data.account.supportsInterface).toEqual(true); +}); + +test('erc165: Cryptokities supports ERC721 interface', async () => { + const query = ` + { + account(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { + supportsInterface(interfaceID: "0x9a20483d") + } + } + `; + + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data.account.supportsInterface).toEqual(true); +}); + +test('erc165: OmiseGO does not supports ERC165', async () => { + const query = ` + { + account(address:"0xd26114cd6EE289AccF82350c8d8487fedB8A0C07") { + address, + supportsInterface(interfaceID: "0x01ffc9a7") + } + } + `; + + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data.account.supportsInterface).toEqual(false); +}); diff --git a/src/abi/erc721.json b/src/abi/erc721.json new file mode 100644 index 0000000..ca58576 --- /dev/null +++ b/src/abi/erc721.json @@ -0,0 +1 @@ +[{"constant":true,"inputs":[{"name":"_interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cfoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"},{"name":"_preferredTransport","type":"string"}],"name":"tokenMetadata","outputs":[{"name":"infoUrl","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"promoCreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ceoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_STARTING_PRICE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSiringAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pregnantKitties","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isPregnant","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_AUCTION_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"siringAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setGeneScienceAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCEO","type":"address"}],"name":"setCEO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCOO","type":"address"}],"name":"setCOO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSaleAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"sireAllowedToAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"canBreedWith","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToApproved","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSiringAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"val","type":"uint256"}],"name":"setAutoBirthFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_sireId","type":"uint256"}],"name":"approveSiring","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCFO","type":"address"}],"name":"setCFO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"},{"name":"_owner","type":"address"}],"name":"createPromoKitty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"secs","type":"uint256"}],"name":"setSecondsPerBlock","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"owner","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"newContractAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSaleAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"count","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_v2Address","type":"address"}],"name":"setNewAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"secondsPerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"tokensOfOwner","outputs":[{"name":"ownerTokens","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"}],"name":"giveBirth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawAuctionBalances","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"cooldowns","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"cooAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"autoBirthFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"erc721Metadata","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"}],"name":"createGen0Auction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isReadyToBreed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PROMO_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_contractAddress","type":"address"}],"name":"setMetadataAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"saleAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getKitty","outputs":[{"name":"isGestating","type":"bool"},{"name":"isReady","type":"bool"},{"name":"cooldownIndex","type":"uint256"},{"name":"nextActionAt","type":"uint256"},{"name":"siringWithId","type":"uint256"},{"name":"birthTime","type":"uint256"},{"name":"matronId","type":"uint256"},{"name":"sireId","type":"uint256"},{"name":"generation","type":"uint256"},{"name":"genes","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sireId","type":"uint256"},{"name":"_matronId","type":"uint256"}],"name":"bidOnSiringAuction","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"gen0CreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"geneScience","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"breedWithAuto","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"cooldownEndBlock","type":"uint256"}],"name":"Pregnant","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"approved","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"kittyId","type":"uint256"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"genes","type":"uint256"}],"name":"Birth","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"}],"name":"ContractUpgrade","type":"event"}] diff --git a/src/core/services/decoder/impl/simple.ts b/src/core/services/decoder/impl/simple.ts index 3f807c5..0498458 100644 --- a/src/core/services/decoder/impl/simple.ts +++ b/src/core/services/decoder/impl/simple.ts @@ -23,11 +23,11 @@ class SimpleDecodingEngine implements DecodingEngine { if (!tx.inputData) { return; } - // Iterate through the registry until we find a decoder that's capable of decoding the function call. for (const decoder of this.registry) { const decoded = decoder.abiDecoder.decodeMethod(tx.inputData); - + console.log('decoded:'); + console.log(decoded); // If the ABI decoder recognised the function call, apply the transformer if available. if (decoded && decoded.name in decoder.txTransformers) { return { @@ -49,7 +49,9 @@ class SimpleDecodingEngine implements DecodingEngine { */ public decodeLog(log: EthqlLog, context: EthqlContext): DecodedLog | undefined { // Find a decoder that can process this log. + for (const decoder of this.registry) { + // console.log(log); const logs: any[] = decoder.abiDecoder.decodeLogs([log]); if (!logs || logs[0] === undefined) { continue; @@ -57,13 +59,18 @@ class SimpleDecodingEngine implements DecodingEngine { // Transform the returned log. const dlog = logs[0]; - return { - standard: decoder.standard, - event: dlog.name, - entity: decoder.entity, - __typename: `${decoder.standard}${_.upperFirst(dlog.name)}Event`, - ...decoder.logTransformers[dlog.name](dlog, log.transaction, context), - }; + + console.log(dlog.name); + console.log(decoder.logTransformers[dlog.name]); + if (decoder.logTransformers[dlog.name]) { + return { + standard: decoder.standard, + event: dlog.name, + entity: decoder.entity, + __typename: `${decoder.standard}${_.upperFirst(dlog.name)}Event`, + ...decoder.logTransformers[dlog.name](dlog, log.transaction, context), + }; + } } } } diff --git a/src/erc165/index.ts b/src/erc165/index.ts index 30a9bd2..0ab83b0 100644 --- a/src/erc165/index.ts +++ b/src/erc165/index.ts @@ -9,12 +9,17 @@ const plugin: EthqlPluginFactory = config => ({ schema: [erc165Schema], resolvers, // serviceDefinitions: { - // decoder: { - // config: { - // decoders: [new Erc165InterfaceDecoder()], - // }, + // ethService2: { + // implementation: { + // factory: () => context => new Web3EthService(context.services.web3), + // } + // } + // decoder: { + // config: { + // decoders: [new Erc165InterfaceDecoder()], // }, // }, + // }, dependsOn: { services: ['web3', 'ethService', 'decoder'], }, diff --git a/src/erc165/model/index.ts b/src/erc165/model/index.ts index f2991f0..73514d8 100644 --- a/src/erc165/model/index.ts +++ b/src/erc165/model/index.ts @@ -7,7 +7,6 @@ export class Erc165InterfaceContract { } public async supportsInterface({ interfaceID }: { String }): Boolean { - console.log('hello'); return contract.methods .supportsInterface(interfaceID) .call() diff --git a/src/erc165/resolvers/index.ts b/src/erc165/resolvers/index.ts index bcc05d1..924a875 100644 --- a/src/erc165/resolvers/index.ts +++ b/src/erc165/resolvers/index.ts @@ -8,7 +8,7 @@ async function supportsInterface(account: EthqlAccount, { interfaceID }, context return contract.methods .supportsInterface(interfaceID) .call() - .catch(console.error); + .catch(() => false); } export default { diff --git a/src/erc20/decoders/index.ts b/src/erc20/decoders/index.ts index 4964422..c252e6f 100644 --- a/src/erc20/decoders/index.ts +++ b/src/erc20/decoders/index.ts @@ -86,6 +86,7 @@ class Erc20TokenDecoder implements DecoderDefinition { + public readonly entity = 'token'; + public readonly standard = 'ERC721'; + public readonly abiDecoder = createAbiDecoder(__dirname + '../../../abi/erc721.json'); + + public readonly txTransformers = { + transfer: (decoded: any, tx: EthqlTransaction, context: EthqlContext) => { + const tokenContract = new Erc721TokenContract(tx.to, context); + const to = new EthqlAccount(extractParamValue(decoded.params, 'to')); + return { + tokenContract, + from: new Erc721TokenHolder(tx.from, tokenContract), + tokenId: extractParamValue(decoded.params, 'tokenId'), + to: new Erc721TokenHolder(to, tokenContract), + }; + }, + }; + + public readonly logTransformers = { + Approval: (decoded: any, tx: EthqlTransaction, context: EthqlContext): ERC721ApprovalEvent => { + const tokenContract = new Erc721TokenContract(tx.to, context); + const owner = new EthqlAccount(extractParamValue(decoded.events, 'owner')); + const approved = new EthqlAccount(extractParamValue(decoded.events, 'approved')); + + return { + owner: new Erc721TokenHolder(owner, tokenContract), + approved: new Erc721TokenHolder(approved, tokenContract), + tokenId: extractParamValue(decoded.events, 'tokenId'), + }; + }, + + ApprovalForAll: (decoded: any, tx: EthqlTransaction, context: EthqlContext): ERC721ApprovalForAllEvent => { + const tokenContract = new Erc721TokenContract(tx.to, context); + const owner = new EthqlAccount(extractParamValue(decoded.events, 'owner')); + const operator = new EthqlAccount(extractParamValue(decoded.events, 'operator')); + + return { + owner: new Erc721TokenHolder(owner, tokenContract), + operator: new Erc721TokenHolder(operator, tokenContract), + approved: extractParamValue(decoded.events, 'approved'), + }; + }, + Transfer: (decoded: any, tx: EthqlTransaction, context: EthqlContext): ERC721TransferEvent => { + const tokenContract = new Erc721TokenContract(tx.to, context); + const from = new EthqlAccount(extractParamValue(decoded.events, 'from')); + const to = new EthqlAccount(extractParamValue(decoded.events, 'to')); + + return { + from: new Erc721TokenHolder(from, tokenContract), + to: new Erc721TokenHolder(to, tokenContract), + tokenId: extractParamValue(decoded.events, 'tokenId'), + }; + }, + }; +} + +export { Erc721TokenDecoder }; diff --git a/src/erc721/index.ts b/src/erc721/index.ts new file mode 100644 index 0000000..4747cc9 --- /dev/null +++ b/src/erc721/index.ts @@ -0,0 +1,24 @@ +import { EthqlPluginFactory } from '../plugin'; +import { Erc721TokenDecoder } from './decoders'; +import erc721Schema from './schema/erc721'; + +const plugin: EthqlPluginFactory = config => ({ + name: 'erc721', + priority: 9, + schema: [erc721Schema], + serviceDefinitions: { + decoder: { + config: { + decoders: [new Erc721TokenDecoder()], + }, + }, + }, + dependsOn: { + services: ['web3', 'ethService', 'decoder'], + }, + order: { + after: ['core'], + }, +}); + +export default plugin; diff --git a/src/erc721/model/index.ts b/src/erc721/model/index.ts new file mode 100644 index 0000000..6750727 --- /dev/null +++ b/src/erc721/model/index.ts @@ -0,0 +1,55 @@ +import Contract from 'web3/eth/contract'; +import { EthqlContext } from '../../context'; +import { EthqlAccount } from '../../core/model'; + +export interface Erc721Transaction { + tokenContract: Erc721TokenContract; +} + +export interface Erc721Transfer extends Erc721Transaction { + from: Erc721TokenHolder; + to: Erc721TokenHolder; + tokenId: Long; +} + +export type ERC721TransferEvent = { + from: Erc721TokenHolder; + to: Erc721TokenHolder; + tokenId: Long; +}; + +export type ERC721ApprovalEvent = { + owner: Erc721TokenHolder; + approved: Erc721TokenHolder; + tokenId: Long; +}; + +export type ERC721ApprovalForAllEvent = { + owner: Erc721TokenHolder; + operator: Erc721TokenHolder; + approved: Boolean; +}; + +export class Erc721TokenContract { + private static ABI = require(__dirname + '../../../abi/erc721.json'); + private _contract: Contract; + + constructor(public readonly account: EthqlAccount, readonly context: EthqlContext) { + this._contract = new context.services.web3.eth.Contract(Erc721TokenContract.ABI, account.address); + } + + public async balanceOf({ address }: { address: string }) { + return this._contract.methods + .balanceOf(address) + .call() + .catch(() => undefined); + } +} + +export class Erc721TokenHolder { + constructor(public readonly account: EthqlAccount, private readonly contract: Erc721TokenContract) {} + + public async tokenBalance() { + return this.contract.balanceOf({ ...this.account }); + } +} diff --git a/src/erc721/schema/erc721.ts b/src/erc721/schema/erc721.ts new file mode 100644 index 0000000..3d61182 --- /dev/null +++ b/src/erc721/schema/erc721.ts @@ -0,0 +1,42 @@ +export default ` +interface ERC721Transaction { + tokenContract: TokenContract +} + +type ERC721Transfer implements DecodedTransaction & ERC721Transaction { + entity: Entity + standard: String + operation: String + from: TokenHolder + to: TokenHolder + tokenId: Long + tokenContract: TokenContract +} + +type ERC721TransferEvent implements DecodedLog { + entity: Entity + standard: String + event: String + from: TokenHolder + to: TokenHolder + tokenId: Long +} + +type ERC721ApprovalEvent implements DecodedLog { + entity: Entity + standard: String + event: String + owner: TokenHolder + approved: TokenHolder + tokenId: Long +} + +type ERC721ApprovalForAllEvent implements DecodedLog { + entity: Entity + standard: String + event: String + owner: TokenHolder + operator: TokenHolder + approved: Boolean +} +`; diff --git a/src/index.ts b/src/index.ts index febf51e..f50b62a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,15 @@ import config from './config'; import core from './core'; import erc165 from './erc165'; import erc20 from './erc20'; +import erc721 from './erc721'; + import { EthqlServer } from './server'; console.log(`Effective configuration:\n${JSON.stringify(config, null, 2)}`); const server = new EthqlServer({ config, - plugins: [core, erc20, erc165], + plugins: [core, erc20, erc165, erc721], }); process.on('SIGINT', async () => (await server.stop()) || process.exit(0)); From d1dacdbe776425b2f04a2e93efa70a09c05d65a7 Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Sat, 27 Oct 2018 22:02:54 +0300 Subject: [PATCH 03/12] adding more functions --- src/__tests__/erc721/erc721.test.ts | 170 ++++++++++++++++++++++++++++ src/erc721/decoders/index.ts | 33 ++++-- src/erc721/index.ts | 3 +- src/erc721/model/index.ts | 29 ++++- src/erc721/schema/erc721.ts | 16 ++- src/erc721/schema/token.ts | 8 ++ 6 files changed, 242 insertions(+), 17 deletions(-) create mode 100644 src/__tests__/erc721/erc721.test.ts create mode 100644 src/erc721/schema/token.ts diff --git a/src/__tests__/erc721/erc721.test.ts b/src/__tests__/erc721/erc721.test.ts new file mode 100644 index 0000000..5abaa90 --- /dev/null +++ b/src/__tests__/erc721/erc721.test.ts @@ -0,0 +1,170 @@ +import core from '../../core'; +import erc20 from '../../erc20'; +import erc721 from '../../erc721'; +import { testGraphql } from '../utils'; + +const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc20, erc721] } }); +//44 + +test('erc721: not decodable', async () => { + const query = ` + { + block(number: 5000000) { + hash + transactionAt(index: 66) { + value + decoded { + standard + operation + ... on ERC721TransferFrom { + from { + account { + address + } + } + to { + account { + address + } + } + tokenId + } + } + } + } + } + `; + + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + const tx = result.data.block.transactionAt; + expect(tx.value).toBeGreaterThan(0); + expect(tx.decoded).toEqual(null); +}); + +test('erc20: decode transfer log', async () => { + const query = ` + { + block(number: 5000000) { + hash + transactionAt(index: 64) { + logs { + decoded { + entity + standard + event + ... on ERC721TransferEvent { + from { + account { + address + } + } + to { + account { + address + } + } + tokenId + } + } + } + } + } + }`; + + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data).toEqual({ + block: { + hash: '0x7d5a4369273c723454ac137f48a4f142b097aa2779464e6505f1b1c5e37b5382', + transactionAt: { + logs: [ + { + decoded: { + entity: 'token', + standard: 'ERC721', + event: 'Transfer', + from: { + account: { + address: '0x89eacd3f14e387faa9f3d1f3f917ebdf8221d430', + }, + }, + to: { + account: { + address: '0xb1690c08e213a35ed9bab7b318de14420fb57d8c', + }, + }, + tokenId: 489475, + }, + }, + { + decoded: null, + }, + ], + }, + }, + }); +}); + +// +// test('erc721: transfer transaction', async () => { +// const query = ` +// { +// block(number: 5000000) { +// hash +// transactionAt(index: 64) { +// value +// hash +// decoded { +// standard +// operation +// ... on ERC721TransferFrom { +// from { +// account { +// address +// } +// } +// to { +// account { +// address +// } +// } +// tokenId +// } +// } +// } +// } +// } +// `; +// +// const result = await execQuery(query); +// console.log(result.data); +// +// expect(result.errors).toBeUndefined(); +// expect(result.data).not.toBeUndefined(); +// +// const decoded = { +// standard: 'ERC721', +// operation: 'transfer', +// from: { +// account: { +// address: '0x89eAcD3F14e387faA9F3D1F3f917eBdf8221D430', +// }, +// }, +// to: { +// account: { +// address: '0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C', +// }, +// }, +// tokenId: '489475', +// }; +// +// const tx = result.data.block.transactionAt; +// console.log(tx); +// expect(tx.value).toBe(0); +// expect(tx.decoded).toEqual(decoded); +// }); diff --git a/src/erc721/decoders/index.ts b/src/erc721/decoders/index.ts index 52aee1f..50c4ff1 100644 --- a/src/erc721/decoders/index.ts +++ b/src/erc721/decoders/index.ts @@ -4,18 +4,33 @@ import { createAbiDecoder, DecoderDefinition, extractParamValue } from '../../co import { ERC721ApprovalEvent, ERC721ApprovalForAllEvent, + Erc721SafeTransferFrom, Erc721TokenContract, Erc721TokenHolder, - Erc721Transfer, ERC721TransferEvent, + Erc721TransferFrom, } from '../model'; type Erc721LogBindings = { + Approval: ERC721ApprovalEvent; + ApprovalForAll: ERC721ApprovalForAllEvent; Transfer: ERC721TransferEvent; }; type Erc721TxBindings = { - transfer: Erc721Transfer; + transferFrom: Erc721TransferFrom; + safeTransferFrom: Erc721SafeTransferFrom; +}; + +const transfer = (decoded: any, tx: EthqlTransaction, context: EthqlContext) => { + const tokenContract = new Erc721TokenContract(tx.to, context); + const to = new EthqlAccount(extractParamValue(decoded.params, 'to')); + return { + tokenContract, + from: new Erc721TokenHolder(tx.from, tokenContract), + tokenId: extractParamValue(decoded.params, 'tokenId'), + to: new Erc721TokenHolder(to, tokenContract), + }; }; /** @@ -27,16 +42,9 @@ class Erc721TokenDecoder implements DecoderDefinition { - const tokenContract = new Erc721TokenContract(tx.to, context); - const to = new EthqlAccount(extractParamValue(decoded.params, 'to')); - return { - tokenContract, - from: new Erc721TokenHolder(tx.from, tokenContract), - tokenId: extractParamValue(decoded.params, 'tokenId'), - to: new Erc721TokenHolder(to, tokenContract), - }; - }, + transferFrom: transfer, + + safeTransferFrom: transfer, }; public readonly logTransformers = { @@ -63,6 +71,7 @@ class Erc721TokenDecoder implements DecoderDefinition { const tokenContract = new Erc721TokenContract(tx.to, context); const from = new EthqlAccount(extractParamValue(decoded.events, 'from')); diff --git a/src/erc721/index.ts b/src/erc721/index.ts index 4747cc9..976ca4e 100644 --- a/src/erc721/index.ts +++ b/src/erc721/index.ts @@ -1,11 +1,12 @@ import { EthqlPluginFactory } from '../plugin'; import { Erc721TokenDecoder } from './decoders'; import erc721Schema from './schema/erc721'; +import erc721TokenSchema from './schema/token'; const plugin: EthqlPluginFactory = config => ({ name: 'erc721', priority: 9, - schema: [erc721Schema], + schema: [erc721TokenSchema, erc721Schema], serviceDefinitions: { decoder: { config: { diff --git a/src/erc721/model/index.ts b/src/erc721/model/index.ts index 6750727..a595d76 100644 --- a/src/erc721/model/index.ts +++ b/src/erc721/model/index.ts @@ -6,7 +6,13 @@ export interface Erc721Transaction { tokenContract: Erc721TokenContract; } -export interface Erc721Transfer extends Erc721Transaction { +export interface Erc721SafeTransferFrom extends Erc721Transaction { + from: Erc721TokenHolder; + to: Erc721TokenHolder; + tokenId: Long; +} + +export interface Erc721TransferFrom extends Erc721Transaction { from: Erc721TokenHolder; to: Erc721TokenHolder; tokenId: Long; @@ -38,12 +44,33 @@ export class Erc721TokenContract { this._contract = new context.services.web3.eth.Contract(Erc721TokenContract.ABI, account.address); } + public async symbol() { + return this._contract.methods + .symbol() + .call() + .catch(() => undefined); + } + + public async totalSupply() { + return this._contract.methods + .totalSupply() + .call() + .catch(() => undefined); + } + public async balanceOf({ address }: { address: string }) { return this._contract.methods .balanceOf(address) .call() .catch(() => undefined); } + + public async ownerOf({ tokenId }: { tokenId: string }) { + return this._contract.methods + .ownerOf(tokenId) + .call() + .catch(() => undefined); + } } export class Erc721TokenHolder { diff --git a/src/erc721/schema/erc721.ts b/src/erc721/schema/erc721.ts index 3d61182..64260d0 100644 --- a/src/erc721/schema/erc721.ts +++ b/src/erc721/schema/erc721.ts @@ -1,16 +1,26 @@ export default ` interface ERC721Transaction { - tokenContract: TokenContract + tokenContract: ERC721TokenContract } -type ERC721Transfer implements DecodedTransaction & ERC721Transaction { +type ERC721SafeTransferFrom implements DecodedTransaction & ERC721Transaction { entity: Entity standard: String operation: String from: TokenHolder to: TokenHolder tokenId: Long - tokenContract: TokenContract + tokenContract: ERC721TokenContract +} + +type ERC721TransferFrom implements DecodedTransaction & ERC721Transaction { + entity: Entity + standard: String + operation: String + from: TokenHolder + to: TokenHolder + tokenId: Long + tokenContract: ERC721TokenContract } type ERC721TransferEvent implements DecodedLog { diff --git a/src/erc721/schema/token.ts b/src/erc721/schema/token.ts new file mode 100644 index 0000000..77f80db --- /dev/null +++ b/src/erc721/schema/token.ts @@ -0,0 +1,8 @@ +export default ` +type ERC721TokenContract { + account: Account + symbol: String + totalSupply: Long + ownerOf(tokenId: Long): Account +} +`; From c0ec9e1394781ee23979cd9f4c6367e9ec8ccc68 Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Sat, 3 Nov 2018 19:18:19 +0200 Subject: [PATCH 04/12] add erc165 as a service --- src/erc165/index.ts | 20 ++++++++----------- src/erc165/resolvers/index.ts | 7 +------ .../services/impl/web3-erc165-service.ts | 17 ++++++++++++++++ src/erc165/services/index.ts | 13 ++++++++++++ 4 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 src/erc165/services/impl/web3-erc165-service.ts create mode 100644 src/erc165/services/index.ts diff --git a/src/erc165/index.ts b/src/erc165/index.ts index 0ab83b0..2479df2 100644 --- a/src/erc165/index.ts +++ b/src/erc165/index.ts @@ -2,24 +2,20 @@ import { EthqlPluginFactory } from '../plugin'; import { Erc165InterfaceDecoder } from './decoders'; import resolvers from './resolvers'; import erc165Schema from './schema/erc165'; +import { Web3Erc165Service } from './services/impl/web3-erc165-service'; const plugin: EthqlPluginFactory = config => ({ name: 'erc165', priority: 10, schema: [erc165Schema], resolvers, - // serviceDefinitions: { - // ethService2: { - // implementation: { - // factory: () => context => new Web3EthService(context.services.web3), - // } - // } - // decoder: { - // config: { - // decoders: [new Erc165InterfaceDecoder()], - // }, - // }, - // }, + serviceDefinitions: { + erc165Service: { + implementation: { + factory: () => context => new Web3Erc165Service(context.services.web3), + }, + }, + }, dependsOn: { services: ['web3', 'ethService', 'decoder'], }, diff --git a/src/erc165/resolvers/index.ts b/src/erc165/resolvers/index.ts index 924a875..e868f11 100644 --- a/src/erc165/resolvers/index.ts +++ b/src/erc165/resolvers/index.ts @@ -3,12 +3,7 @@ import { EthqlAccount } from '../../core/model'; async function supportsInterface(account: EthqlAccount, { interfaceID }, context: EthqlContext) { const ABI = require(__dirname + '../../../abi/erc165.json'); - const contract = new context.services.web3.eth.Contract(ABI, account.address); - - return contract.methods - .supportsInterface(interfaceID) - .call() - .catch(() => false); + return context.services.erc165Service.supportsInterface(account.address, interfaceID); } export default { diff --git a/src/erc165/services/impl/web3-erc165-service.ts b/src/erc165/services/impl/web3-erc165-service.ts new file mode 100644 index 0000000..681d29f --- /dev/null +++ b/src/erc165/services/impl/web3-erc165-service.ts @@ -0,0 +1,17 @@ +import Web3 = require('web3'); +import { Erc165Service } from '..'; + +export class Web3Erc165Service implements Erc165Service { + private static ABI = require(__dirname + '../../../../abi/erc165.json'); + + constructor(private web3: Web3) {} + + public async supportsInterface(address, interfaceID: string): Promise { + const contract = new this.web3.eth.Contract(Web3Erc165Service.ABI, address); + + return contract.methods + .supportsInterface(interfaceID) + .call() + .catch(() => false); + } +} diff --git a/src/erc165/services/index.ts b/src/erc165/services/index.ts new file mode 100644 index 0000000..583f4a1 --- /dev/null +++ b/src/erc165/services/index.ts @@ -0,0 +1,13 @@ +declare module '../../services' { + interface EthqlServices { + erc165Service: Erc165Service; + } + + interface EthqlServiceDefinitions { + erc165Service: EthqlServiceDefinition<{}, Erc165Service>; + } +} + +export interface Erc165Service { + supportsInterface(address, interfaceId: string): Promise; +} From 67b07b7f9c4003bc4e89318cac2f305312bf9bbe Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Fri, 9 Nov 2018 16:26:43 +0200 Subject: [PATCH 05/12] adding all erc721 view methods --- src/__tests__/erc721/erc721.test.ts | 231 ++++++++++++++++++++-------- src/abi/erc721.json | 2 +- src/erc721/index.ts | 6 +- src/erc721/model/index.ts | 18 +-- src/erc721/resolvers/index.ts | 18 +++ src/erc721/schema/token.ts | 12 +- 6 files changed, 205 insertions(+), 82 deletions(-) create mode 100644 src/erc721/resolvers/index.ts diff --git a/src/__tests__/erc721/erc721.test.ts b/src/__tests__/erc721/erc721.test.ts index 5abaa90..d2903be 100644 --- a/src/__tests__/erc721/erc721.test.ts +++ b/src/__tests__/erc721/erc721.test.ts @@ -1,9 +1,106 @@ import core from '../../core'; +import erc165 from '../../erc165'; import erc20 from '../../erc20'; import erc721 from '../../erc721'; import { testGraphql } from '../utils'; -const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc20, erc721] } }); +const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc20, erc165, erc721] } }); + +test('erc721: nftToken balanceOf query #1', async () => { + const query = ` + { + nftToken(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { + balanceOf(owner: "0xD418c5d0c4a3D87a6c555B7aA41f13EF87485Ec6") + } + } + `; + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data).toEqual({ + nftToken: { + balanceOf: 0, + }, + }); +}); + +test('erc721: nftToken balanceOf query #2', async () => { + const query = ` + { + nftToken(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { + balanceOf(owner: "0x595a6aA36Ab9fFB4b5940D4E189d6F2AB3958aeb") + } + } + `; + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data).toEqual({ + nftToken: { + balanceOf: 72, + }, + }); +}); + +test('erc721: nftToken ownerOf query', async () => { + const query = ` + { + nftToken(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { + ownerOf(tokenId: 384978) + } + } + `; + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data).toEqual({ + nftToken: { + ownerOf: '0x595a6aA36Ab9fFB4b5940D4E189d6F2AB3958aeb', + }, + }); +}); + +test('erc721: nftToken getApproved query', async () => { + const query = ` + { + nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + getApproved(tokenId: 33525) + } + } + `; + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data).toEqual({ + nftToken: { + getApproved: '0x0000000000000000000000000000000000000000', + }, + }); +}); + +test('erc721: nftToken isApprovedForAll query', async () => { + const query = ` + { + nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + isApprovedForAll(owner: "0xb85e9bdfCA73a536BE641bB5eacBA0772eA3E61E", operator: "0xD418c5d0c4a3D87a6c555B7aA41f13EF87485Ec6") + } + } + `; + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + expect(result.data).toEqual({ + nftToken: { + isApprovedForAll: false, + }, + }); +}); + //44 test('erc721: not decodable', async () => { @@ -43,72 +140,72 @@ test('erc721: not decodable', async () => { expect(tx.value).toBeGreaterThan(0); expect(tx.decoded).toEqual(null); }); - -test('erc20: decode transfer log', async () => { - const query = ` - { - block(number: 5000000) { - hash - transactionAt(index: 64) { - logs { - decoded { - entity - standard - event - ... on ERC721TransferEvent { - from { - account { - address - } - } - to { - account { - address - } - } - tokenId - } - } - } - } - } - }`; - - const result = await execQuery(query); - expect(result.errors).toBeUndefined(); - expect(result.data).not.toBeUndefined(); - - expect(result.data).toEqual({ - block: { - hash: '0x7d5a4369273c723454ac137f48a4f142b097aa2779464e6505f1b1c5e37b5382', - transactionAt: { - logs: [ - { - decoded: { - entity: 'token', - standard: 'ERC721', - event: 'Transfer', - from: { - account: { - address: '0x89eacd3f14e387faa9f3d1f3f917ebdf8221d430', - }, - }, - to: { - account: { - address: '0xb1690c08e213a35ed9bab7b318de14420fb57d8c', - }, - }, - tokenId: 489475, - }, - }, - { - decoded: null, - }, - ], - }, - }, - }); -}); +// +// test('erc721: decode transfer log', async () => { +// const query = ` +// { +// block(number: 5000000) { +// hash +// transactionAt(index: 64) { +// logs { +// decoded { +// entity +// standard +// event +// ... on ERC721TransferEvent { +// from { +// account { +// address +// } +// } +// to { +// account { +// address +// } +// } +// tokenId +// } +// } +// } +// } +// } +// }`; +// +// const result = await execQuery(query); +// expect(result.errors).toBeUndefined(); +// expect(result.data).not.toBeUndefined(); +// +// expect(result.data).toEqual({ +// block: { +// hash: '0x7d5a4369273c723454ac137f48a4f142b097aa2779464e6505f1b1c5e37b5382', +// transactionAt: { +// logs: [ +// { +// decoded: { +// entity: 'token', +// standard: 'ERC721', +// event: 'Transfer', +// from: { +// account: { +// address: '0x89eacd3f14e387faa9f3d1f3f917ebdf8221d430', +// }, +// }, +// to: { +// account: { +// address: '0xb1690c08e213a35ed9bab7b318de14420fb57d8c', +// }, +// }, +// tokenId: 489475, +// }, +// }, +// { +// decoded: null, +// }, +// ], +// }, +// }, +// }); +// }); // // test('erc721: transfer transaction', async () => { diff --git a/src/abi/erc721.json b/src/abi/erc721.json index ca58576..37139d2 100644 --- a/src/abi/erc721.json +++ b/src/abi/erc721.json @@ -1 +1 @@ -[{"constant":true,"inputs":[{"name":"_interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cfoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"},{"name":"_preferredTransport","type":"string"}],"name":"tokenMetadata","outputs":[{"name":"infoUrl","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"promoCreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ceoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_STARTING_PRICE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSiringAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pregnantKitties","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isPregnant","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_AUCTION_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"siringAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setGeneScienceAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCEO","type":"address"}],"name":"setCEO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCOO","type":"address"}],"name":"setCOO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSaleAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"sireAllowedToAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"canBreedWith","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToApproved","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSiringAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"val","type":"uint256"}],"name":"setAutoBirthFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_sireId","type":"uint256"}],"name":"approveSiring","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCFO","type":"address"}],"name":"setCFO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"},{"name":"_owner","type":"address"}],"name":"createPromoKitty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"secs","type":"uint256"}],"name":"setSecondsPerBlock","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"owner","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"newContractAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSaleAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"count","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_v2Address","type":"address"}],"name":"setNewAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"secondsPerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"tokensOfOwner","outputs":[{"name":"ownerTokens","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"}],"name":"giveBirth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawAuctionBalances","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"cooldowns","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"cooAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"autoBirthFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"erc721Metadata","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"}],"name":"createGen0Auction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isReadyToBreed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PROMO_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_contractAddress","type":"address"}],"name":"setMetadataAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"saleAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getKitty","outputs":[{"name":"isGestating","type":"bool"},{"name":"isReady","type":"bool"},{"name":"cooldownIndex","type":"uint256"},{"name":"nextActionAt","type":"uint256"},{"name":"siringWithId","type":"uint256"},{"name":"birthTime","type":"uint256"},{"name":"matronId","type":"uint256"},{"name":"sireId","type":"uint256"},{"name":"generation","type":"uint256"},{"name":"genes","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sireId","type":"uint256"},{"name":"_matronId","type":"uint256"}],"name":"bidOnSiringAuction","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"gen0CreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"geneScience","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"breedWithAuto","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"cooldownEndBlock","type":"uint256"}],"name":"Pregnant","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"approved","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"kittyId","type":"uint256"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"genes","type":"uint256"}],"name":"Birth","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"}],"name":"ContractUpgrade","type":"event"}] +[{"constant":true,"inputs":[{"name":"_interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint16"}],"name":"getProto","outputs":[{"name":"exists","type":"bool"},{"name":"god","type":"uint8"},{"name":"season","type":"uint8"},{"name":"cardType","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"tribe","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"id","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"transferAllFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"governor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"migrated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"ids","type":"uint256[]"}],"name":"burnAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"InterfaceId_ERC165","outputs":[{"name":"","type":"bytes4"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"season","type":"uint8"}],"name":"makePermanantlyTradable","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addSpell","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"common","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getActiveCards","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"migrate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"mythic","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"index","type":"uint16"},{"name":"god","type":"uint8"},{"name":"cardType","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"tribe","type":"uint8"}],"name":"replaceProto","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"burnCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint8"}],"name":"seasonTradabilityLocked","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint16"},{"name":"limit","type":"uint64"}],"name":"setLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"transferAll","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint8"}],"name":"seasonTradable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposed","type":"address"},{"name":"id","type":"uint256"}],"name":"owns","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"approved","type":"address"}],"name":"addPack","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"purity","type":"uint16"}],"name":"getShine","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"cards","outputs":[{"name":"proto","type":"uint16"},{"name":"purity","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"ids","type":"uint256[]"}],"name":"migrateAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint256"}],"name":"getCard","outputs":[{"name":"proto","type":"uint16"},{"name":"purity","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint16"}],"name":"getLimit","outputs":[{"name":"limit","type":"uint64"},{"name":"set","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint16"}],"name":"limits","outputs":[{"name":"limit","type":"uint64"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"NAME","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"rare","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proto","type":"uint16"}],"name":"isTradable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"id","type":"uint256"}],"name":"transfer","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"proposed","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"ownsAll","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tokenMetadataBaseURI","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"packs","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"nextSeason","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"currentSeason","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_gov","type":"address"}],"name":"setGovernor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"season","type":"uint8"}],"name":"makeUntradable","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"rarity","type":"uint8"},{"name":"random","type":"uint16"}],"name":"getRandomCard","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"durability","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addWeapon","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"cardType","type":"uint8"},{"name":"tribe","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addProto","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"protoCount","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"epic","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"tribe","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addMinion","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"externalIDs","type":"uint16[]"},{"name":"gods","type":"uint8[]"},{"name":"rarities","type":"uint8[]"},{"name":"manas","type":"uint8[]"},{"name":"attacks","type":"uint8[]"},{"name":"healths","type":"uint8[]"},{"name":"cardTypes","type":"uint8[]"},{"name":"tribes","type":"uint8[]"},{"name":"packable","type":"bool[]"}],"name":"addProtos","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBurnCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"legendary","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"approveAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"season","type":"uint8"}],"name":"makeTradable","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SYMBOL","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"},{"name":"proto","type":"uint16"},{"name":"purity","type":"uint16"}],"name":"createCard","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"previous","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"proto","type":"uint16"},{"indexed":false,"name":"purity","type":"uint16"},{"indexed":false,"name":"owner","type":"address"}],"name":"CardCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":true,"name":"_tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_approved","type":"address"},{"indexed":true,"name":"_tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_operator","type":"address"},{"indexed":false,"name":"_approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint16"},{"indexed":false,"name":"season","type":"uint8"},{"indexed":false,"name":"god","type":"uint8"},{"indexed":false,"name":"rarity","type":"uint8"},{"indexed":false,"name":"mana","type":"uint8"},{"indexed":false,"name":"attack","type":"uint8"},{"indexed":false,"name":"health","type":"uint8"},{"indexed":false,"name":"cardType","type":"uint8"},{"indexed":false,"name":"tribe","type":"uint8"},{"indexed":false,"name":"packable","type":"bool"}],"name":"NewProtoCard","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}] diff --git a/src/erc721/index.ts b/src/erc721/index.ts index 976ca4e..2621ba4 100644 --- a/src/erc721/index.ts +++ b/src/erc721/index.ts @@ -1,11 +1,13 @@ import { EthqlPluginFactory } from '../plugin'; import { Erc721TokenDecoder } from './decoders'; +import resolvers from './resolvers'; import erc721Schema from './schema/erc721'; import erc721TokenSchema from './schema/token'; const plugin: EthqlPluginFactory = config => ({ name: 'erc721', - priority: 9, + priority: 12, + resolvers, schema: [erc721TokenSchema, erc721Schema], serviceDefinitions: { decoder: { @@ -15,7 +17,7 @@ const plugin: EthqlPluginFactory = config => ({ }, }, dependsOn: { - services: ['web3', 'ethService', 'decoder'], + services: ['web3', 'ethService', 'decoder', 'erc165Service'], }, order: { after: ['core'], diff --git a/src/erc721/model/index.ts b/src/erc721/model/index.ts index a595d76..5500c2c 100644 --- a/src/erc721/model/index.ts +++ b/src/erc721/model/index.ts @@ -44,30 +44,30 @@ export class Erc721TokenContract { this._contract = new context.services.web3.eth.Contract(Erc721TokenContract.ABI, account.address); } - public async symbol() { + public async balanceOf({ owner }: { owner: string }) { return this._contract.methods - .symbol() + .balanceOf(owner) .call() .catch(() => undefined); } - public async totalSupply() { + public async ownerOf({ tokenId }: { tokenId: Long }) { return this._contract.methods - .totalSupply() + .ownerOf(tokenId) .call() .catch(() => undefined); } - public async balanceOf({ address }: { address: string }) { + public async getApproved({ tokenId }: { tokenId: Long }) { return this._contract.methods - .balanceOf(address) + .getApproved(tokenId) .call() .catch(() => undefined); } - public async ownerOf({ tokenId }: { tokenId: string }) { + public async isApprovedForAll({ owner, operator }: { owner: String; operator: String }) { return this._contract.methods - .ownerOf(tokenId) + .isApprovedForAll(owner, operator) .call() .catch(() => undefined); } @@ -77,6 +77,6 @@ export class Erc721TokenHolder { constructor(public readonly account: EthqlAccount, private readonly contract: Erc721TokenContract) {} public async tokenBalance() { - return this.contract.balanceOf({ ...this.account }); + return this.contract.balanceOf({ owner: this.account.address }); } } diff --git a/src/erc721/resolvers/index.ts b/src/erc721/resolvers/index.ts new file mode 100644 index 0000000..7807c89 --- /dev/null +++ b/src/erc721/resolvers/index.ts @@ -0,0 +1,18 @@ +import { EthqlContext } from '../../context'; +import { EthqlAccount, EthqlAccountType, StorageAccessor } from '../../core/model'; +import { Erc721TokenContract } from '../model'; + +async function nftToken(obj: EthqlAccount, { address }, context: EthqlContext) { + if ( + (await context.services.erc165Service.supportsInterface(address, '0x9a20483d')) || + (await context.services.erc165Service.supportsInterface(address, '0x80ac58cd')) + ) { + return new Erc721TokenContract(new EthqlAccount(address), context); + } +} + +export default { + Query: { + nftToken, + }, +}; diff --git a/src/erc721/schema/token.ts b/src/erc721/schema/token.ts index 77f80db..f87ffab 100644 --- a/src/erc721/schema/token.ts +++ b/src/erc721/schema/token.ts @@ -1,8 +1,14 @@ export default ` +extend type Query { + "Selects an account." + nftToken(address: Address!): ERC721TokenContract +} + type ERC721TokenContract { account: Account - symbol: String - totalSupply: Long - ownerOf(tokenId: Long): Account + ownerOf(tokenId: Long): String + balanceOf(owner: String): Long + getApproved(tokenId: Long): String + isApprovedForAll(owner: String, operator: String): Boolean } `; From 4e1b86c9c003721067d02ae7c8dda29ad63cae7a Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Fri, 16 Nov 2018 13:36:56 +0200 Subject: [PATCH 06/12] adding function --- src/__tests__/erc721/erc721.test.ts | 186 ++++------- src/abi/erc721.json | 396 ++++++++++++++++++++++- src/core/services/decoder/impl/simple.ts | 6 +- src/erc20/decoders/index.ts | 1 - src/erc721/decoders/index.ts | 58 +++- src/erc721/model/index.ts | 10 + src/erc721/schema/erc721.ts | 38 ++- src/erc721/schema/token.ts | 5 + src/index.ts | 4 +- 9 files changed, 538 insertions(+), 166 deletions(-) diff --git a/src/__tests__/erc721/erc721.test.ts b/src/__tests__/erc721/erc721.test.ts index d2903be..a2f15dd 100644 --- a/src/__tests__/erc721/erc721.test.ts +++ b/src/__tests__/erc721/erc721.test.ts @@ -4,12 +4,12 @@ import erc20 from '../../erc20'; import erc721 from '../../erc721'; import { testGraphql } from '../utils'; -const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc20, erc165, erc721] } }); +const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc165, erc721] } }); test('erc721: nftToken balanceOf query #1', async () => { const query = ` { - nftToken(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { + nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { balanceOf(owner: "0xD418c5d0c4a3D87a6c555B7aA41f13EF87485Ec6") } } @@ -28,8 +28,8 @@ test('erc721: nftToken balanceOf query #1', async () => { test('erc721: nftToken balanceOf query #2', async () => { const query = ` { - nftToken(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { - balanceOf(owner: "0x595a6aA36Ab9fFB4b5940D4E189d6F2AB3958aeb") + nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + balanceOf(owner: "0x6f00cE7253bFD3A5A1c307b5E13814BF3433C72f") } } `; @@ -39,7 +39,7 @@ test('erc721: nftToken balanceOf query #2', async () => { expect(result.data).toEqual({ nftToken: { - balanceOf: 72, + balanceOf: 5, }, }); }); @@ -47,8 +47,8 @@ test('erc721: nftToken balanceOf query #2', async () => { test('erc721: nftToken ownerOf query', async () => { const query = ` { - nftToken(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { - ownerOf(tokenId: 384978) + nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + ownerOf(tokenId: 143880) } } `; @@ -58,7 +58,7 @@ test('erc721: nftToken ownerOf query', async () => { expect(result.data).toEqual({ nftToken: { - ownerOf: '0x595a6aA36Ab9fFB4b5940D4E189d6F2AB3958aeb', + ownerOf: '0x6f00cE7253bFD3A5A1c307b5E13814BF3433C72f', }, }); }); @@ -140,128 +140,50 @@ test('erc721: not decodable', async () => { expect(tx.value).toBeGreaterThan(0); expect(tx.decoded).toEqual(null); }); -// -// test('erc721: decode transfer log', async () => { -// const query = ` -// { -// block(number: 5000000) { -// hash -// transactionAt(index: 64) { -// logs { -// decoded { -// entity -// standard -// event -// ... on ERC721TransferEvent { -// from { -// account { -// address -// } -// } -// to { -// account { -// address -// } -// } -// tokenId -// } -// } -// } -// } -// } -// }`; -// -// const result = await execQuery(query); -// expect(result.errors).toBeUndefined(); -// expect(result.data).not.toBeUndefined(); -// -// expect(result.data).toEqual({ -// block: { -// hash: '0x7d5a4369273c723454ac137f48a4f142b097aa2779464e6505f1b1c5e37b5382', -// transactionAt: { -// logs: [ -// { -// decoded: { -// entity: 'token', -// standard: 'ERC721', -// event: 'Transfer', -// from: { -// account: { -// address: '0x89eacd3f14e387faa9f3d1f3f917ebdf8221d430', -// }, -// }, -// to: { -// account: { -// address: '0xb1690c08e213a35ed9bab7b318de14420fb57d8c', -// }, -// }, -// tokenId: 489475, -// }, -// }, -// { -// decoded: null, -// }, -// ], -// }, -// }, -// }); -// }); -// -// test('erc721: transfer transaction', async () => { -// const query = ` -// { -// block(number: 5000000) { -// hash -// transactionAt(index: 64) { -// value -// hash -// decoded { -// standard -// operation -// ... on ERC721TransferFrom { -// from { -// account { -// address -// } -// } -// to { -// account { -// address -// } -// } -// tokenId -// } -// } -// } -// } -// } -// `; -// -// const result = await execQuery(query); -// console.log(result.data); -// -// expect(result.errors).toBeUndefined(); -// expect(result.data).not.toBeUndefined(); -// -// const decoded = { -// standard: 'ERC721', -// operation: 'transfer', -// from: { -// account: { -// address: '0x89eAcD3F14e387faA9F3D1F3f917eBdf8221D430', -// }, -// }, -// to: { -// account: { -// address: '0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C', -// }, -// }, -// tokenId: '489475', -// }; -// -// const tx = result.data.block.transactionAt; -// console.log(tx); -// expect(tx.value).toBe(0); -// expect(tx.decoded).toEqual(decoded); -// }); +test('erc721: decode transfer log', async () => { + const query = ` + { + transaction(hash: "0x5e9e7570cde63860a7cb71542729deb6d4dd796af70bb464050ca06ec7fc4bc9") { + hash + logs{ + decoded { + ... on ERC721TransferEvent{ + from { + account { + address + } + } + to { + account { + address + } + }, + tokenId + } + } + } + } + }`; + + const result = await execQuery(query); + expect(result.errors).toBeUndefined(); + expect(result.data).not.toBeUndefined(); + + const tranferLog = result.data.transaction.logs[0]; + expect(tranferLog).toEqual({ + decoded: { + from: { + account: { + address: '0x0000000000000000000000000000000000000000', + }, + }, + to: { + account: { + address: '0xc93227eee6e77db998a1ff5b01049fec8a5694cc', + }, + }, + tokenId: 43820, + }, + }); +}); diff --git a/src/abi/erc721.json b/src/abi/erc721.json index 37139d2..5270e10 100644 --- a/src/abi/erc721.json +++ b/src/abi/erc721.json @@ -1 +1,395 @@ -[{"constant":true,"inputs":[{"name":"_interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint16"}],"name":"getProto","outputs":[{"name":"exists","type":"bool"},{"name":"god","type":"uint8"},{"name":"season","type":"uint8"},{"name":"cardType","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"tribe","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"id","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"transferAllFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"governor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"migrated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"ids","type":"uint256[]"}],"name":"burnAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"InterfaceId_ERC165","outputs":[{"name":"","type":"bytes4"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"season","type":"uint8"}],"name":"makePermanantlyTradable","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"id","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addSpell","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"common","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getActiveCards","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"migrate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"mythic","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"exists","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"index","type":"uint16"},{"name":"god","type":"uint8"},{"name":"cardType","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"tribe","type":"uint8"}],"name":"replaceProto","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"burnCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint8"}],"name":"seasonTradabilityLocked","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"id","type":"uint16"},{"name":"limit","type":"uint64"}],"name":"setLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"transferAll","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint8"}],"name":"seasonTradable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposed","type":"address"},{"name":"id","type":"uint256"}],"name":"owns","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"approved","type":"address"}],"name":"addPack","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"purity","type":"uint16"}],"name":"getShine","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"cards","outputs":[{"name":"proto","type":"uint16"},{"name":"purity","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"ids","type":"uint256[]"}],"name":"migrateAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint256"}],"name":"getCard","outputs":[{"name":"proto","type":"uint16"},{"name":"purity","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"id","type":"uint16"}],"name":"getLimit","outputs":[{"name":"limit","type":"uint64"},{"name":"set","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint16"}],"name":"limits","outputs":[{"name":"limit","type":"uint64"},{"name":"exists","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"NAME","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"rare","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proto","type":"uint16"}],"name":"isTradable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"id","type":"uint256"}],"name":"transfer","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"proposed","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"ownsAll","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tokenMetadataBaseURI","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"packs","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"nextSeason","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"currentSeason","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_gov","type":"address"}],"name":"setGovernor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"season","type":"uint8"}],"name":"makeUntradable","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"rarity","type":"uint8"},{"name":"random","type":"uint16"}],"name":"getRandomCard","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"durability","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addWeapon","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"cardType","type":"uint8"},{"name":"tribe","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addProto","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"protoCount","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"epic","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"externalID","type":"uint16"},{"name":"god","type":"uint8"},{"name":"rarity","type":"uint8"},{"name":"mana","type":"uint8"},{"name":"attack","type":"uint8"},{"name":"health","type":"uint8"},{"name":"tribe","type":"uint8"},{"name":"packable","type":"bool"}],"name":"addMinion","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"externalIDs","type":"uint16[]"},{"name":"gods","type":"uint8[]"},{"name":"rarities","type":"uint8[]"},{"name":"manas","type":"uint8[]"},{"name":"attacks","type":"uint8[]"},{"name":"healths","type":"uint8[]"},{"name":"cardTypes","type":"uint8[]"},{"name":"tribes","type":"uint8[]"},{"name":"packable","type":"bool[]"}],"name":"addProtos","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBurnCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"legendary","outputs":[{"name":"","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"ids","type":"uint256[]"}],"name":"approveAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"season","type":"uint8"}],"name":"makeTradable","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SYMBOL","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"owner","type":"address"},{"name":"proto","type":"uint16"},{"name":"purity","type":"uint16"}],"name":"createCard","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"previous","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"proto","type":"uint16"},{"indexed":false,"name":"purity","type":"uint16"},{"indexed":false,"name":"owner","type":"address"}],"name":"CardCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":true,"name":"_tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_approved","type":"address"},{"indexed":true,"name":"_tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_operator","type":"address"},{"indexed":false,"name":"_approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint16"},{"indexed":false,"name":"season","type":"uint8"},{"indexed":false,"name":"god","type":"uint8"},{"indexed":false,"name":"rarity","type":"uint8"},{"indexed":false,"name":"mana","type":"uint8"},{"indexed":false,"name":"attack","type":"uint8"},{"indexed":false,"name":"health","type":"uint8"},{"indexed":false,"name":"cardType","type":"uint8"},{"indexed":false,"name":"tribe","type":"uint8"},{"indexed":false,"name":"packable","type":"bool"}],"name":"NewProtoCard","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}] +[ + { + "constant": true, + "inputs": [ + { + "name": "_interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "_name", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "name": "_operator", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "exists", + "outputs": [ + { + "name": "_exists", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "_balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "_symbol", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_operator", + "type": "address" + }, + { + "name": "_approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_tokenId", + "type": "uint256" + }, + { + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_from", + "type": "address" + }, + { + "indexed": true, + "name": "_to", + "type": "address" + }, + { + "indexed": true, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_approved", + "type": "address" + }, + { + "indexed": true, + "name": "_tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "_owner", + "type": "address" + }, + { + "indexed": true, + "name": "_operator", + "type": "address" + }, + { + "indexed": false, + "name": "_approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + } +] diff --git a/src/core/services/decoder/impl/simple.ts b/src/core/services/decoder/impl/simple.ts index 0498458..ae23f1b 100644 --- a/src/core/services/decoder/impl/simple.ts +++ b/src/core/services/decoder/impl/simple.ts @@ -26,8 +26,7 @@ class SimpleDecodingEngine implements DecodingEngine { // Iterate through the registry until we find a decoder that's capable of decoding the function call. for (const decoder of this.registry) { const decoded = decoder.abiDecoder.decodeMethod(tx.inputData); - console.log('decoded:'); - console.log(decoded); + // If the ABI decoder recognised the function call, apply the transformer if available. if (decoded && decoded.name in decoder.txTransformers) { return { @@ -51,7 +50,6 @@ class SimpleDecodingEngine implements DecodingEngine { // Find a decoder that can process this log. for (const decoder of this.registry) { - // console.log(log); const logs: any[] = decoder.abiDecoder.decodeLogs([log]); if (!logs || logs[0] === undefined) { continue; @@ -60,8 +58,6 @@ class SimpleDecodingEngine implements DecodingEngine { // Transform the returned log. const dlog = logs[0]; - console.log(dlog.name); - console.log(decoder.logTransformers[dlog.name]); if (decoder.logTransformers[dlog.name]) { return { standard: decoder.standard, diff --git a/src/erc20/decoders/index.ts b/src/erc20/decoders/index.ts index c252e6f..4964422 100644 --- a/src/erc20/decoders/index.ts +++ b/src/erc20/decoders/index.ts @@ -86,7 +86,6 @@ class Erc20TokenDecoder implements DecoderDefinition { +const transferFrom = (decoded: any, tx: EthqlTransaction, context: EthqlContext) => { const tokenContract = new Erc721TokenContract(tx.to, context); - const to = new EthqlAccount(extractParamValue(decoded.params, 'to')); + const to = new EthqlAccount(extractParamValue(decoded.params, '_to')); + return { tokenContract, from: new Erc721TokenHolder(tx.from, tokenContract), - tokenId: extractParamValue(decoded.params, 'tokenId'), to: new Erc721TokenHolder(to, tokenContract), + tokenId: extractParamValue(decoded.params, '_tokenId'), + }; +}; + +const approve = (decoded: any, tx: EthqlTransaction, context: EthqlContext) => { + const tokenContract = new Erc721TokenContract(tx.to, context); + const approved = new EthqlAccount(extractParamValue(decoded.params, '_approved')); + + return { + tokenContract, + approved: new Erc721TokenHolder(approved, tokenContract), + tokenId: extractParamValue(decoded.params, '_tokenId'), + }; +}; + +const setApprovalForAll = (decoded: any, tx: EthqlTransaction, context: EthqlContext) => { + const tokenContract = new Erc721TokenContract(tx.to, context); + const operator = new EthqlAccount(extractParamValue(decoded.params, '_operator')); + + return { + tokenContract, + operator: new Erc721TokenHolder(operator, tokenContract), + approved: extractParamValue(decoded.params, '_approved'), }; }; @@ -42,45 +69,46 @@ class Erc721TokenDecoder implements DecoderDefinition { const tokenContract = new Erc721TokenContract(tx.to, context); - const owner = new EthqlAccount(extractParamValue(decoded.events, 'owner')); - const approved = new EthqlAccount(extractParamValue(decoded.events, 'approved')); + const owner = new EthqlAccount(extractParamValue(decoded.events, '_owner')); + const approved = new EthqlAccount(extractParamValue(decoded.events, '_approved')); return { owner: new Erc721TokenHolder(owner, tokenContract), approved: new Erc721TokenHolder(approved, tokenContract), - tokenId: extractParamValue(decoded.events, 'tokenId'), + tokenId: extractParamValue(decoded.events, '_tokenId'), }; }, ApprovalForAll: (decoded: any, tx: EthqlTransaction, context: EthqlContext): ERC721ApprovalForAllEvent => { const tokenContract = new Erc721TokenContract(tx.to, context); - const owner = new EthqlAccount(extractParamValue(decoded.events, 'owner')); - const operator = new EthqlAccount(extractParamValue(decoded.events, 'operator')); + const owner = new EthqlAccount(extractParamValue(decoded.events, '_owner')); + const operator = new EthqlAccount(extractParamValue(decoded.events, '_operator')); return { owner: new Erc721TokenHolder(owner, tokenContract), operator: new Erc721TokenHolder(operator, tokenContract), - approved: extractParamValue(decoded.events, 'approved'), + approved: extractParamValue(decoded.events, '_approved'), }; }, Transfer: (decoded: any, tx: EthqlTransaction, context: EthqlContext): ERC721TransferEvent => { const tokenContract = new Erc721TokenContract(tx.to, context); - const from = new EthqlAccount(extractParamValue(decoded.events, 'from')); - const to = new EthqlAccount(extractParamValue(decoded.events, 'to')); + const from = new EthqlAccount(extractParamValue(decoded.events, '_from')); + const to = new EthqlAccount(extractParamValue(decoded.events, '_to')); return { from: new Erc721TokenHolder(from, tokenContract), to: new Erc721TokenHolder(to, tokenContract), - tokenId: extractParamValue(decoded.events, 'tokenId'), + tokenId: extractParamValue(decoded.events, '_tokenId'), }; }, }; diff --git a/src/erc721/model/index.ts b/src/erc721/model/index.ts index 5500c2c..3d0231b 100644 --- a/src/erc721/model/index.ts +++ b/src/erc721/model/index.ts @@ -18,6 +18,16 @@ export interface Erc721TransferFrom extends Erc721Transaction { tokenId: Long; } +export interface ERC721Approve extends Erc721Transaction { + approved: Erc721TokenHolder; + tokenId: Long; +} + +export interface ERC721SetApprovalForAll extends Erc721Transaction { + operator: Erc721TokenHolder; + approved: Boolean; +} + export type ERC721TransferEvent = { from: Erc721TokenHolder; to: Erc721TokenHolder; diff --git a/src/erc721/schema/erc721.ts b/src/erc721/schema/erc721.ts index 64260d0..3ba280a 100644 --- a/src/erc721/schema/erc721.ts +++ b/src/erc721/schema/erc721.ts @@ -7,8 +7,8 @@ type ERC721SafeTransferFrom implements DecodedTransaction & ERC721Transaction { entity: Entity standard: String operation: String - from: TokenHolder - to: TokenHolder + from: ERC721TokenHolder + to: ERC721TokenHolder tokenId: Long tokenContract: ERC721TokenContract } @@ -17,18 +17,36 @@ type ERC721TransferFrom implements DecodedTransaction & ERC721Transaction { entity: Entity standard: String operation: String - from: TokenHolder - to: TokenHolder + from: ERC721TokenHolder + to: ERC721TokenHolder tokenId: Long tokenContract: ERC721TokenContract } +type ERC721Approve implements DecodedTransaction & ERC721Transaction { + entity: Entity + standard: String + operation: String + approved: ERC721TokenHolder + tokenId: Long + tokenContract: ERC721TokenContract +} + +type ERC721SetApprovalForAll implements DecodedTransaction & ERC721Transaction { + entity: Entity + standard: String + operation: String + operator: ERC721TokenHolder + approved: Boolean + tokenContract: ERC721TokenContract +} + type ERC721TransferEvent implements DecodedLog { entity: Entity standard: String event: String - from: TokenHolder - to: TokenHolder + from: ERC721TokenHolder + to: ERC721TokenHolder tokenId: Long } @@ -36,8 +54,8 @@ type ERC721ApprovalEvent implements DecodedLog { entity: Entity standard: String event: String - owner: TokenHolder - approved: TokenHolder + owner: ERC721TokenHolder + approved: ERC721TokenHolder tokenId: Long } @@ -45,8 +63,8 @@ type ERC721ApprovalForAllEvent implements DecodedLog { entity: Entity standard: String event: String - owner: TokenHolder - operator: TokenHolder + owner: ERC721TokenHolder + operator: ERC721TokenHolder approved: Boolean } `; diff --git a/src/erc721/schema/token.ts b/src/erc721/schema/token.ts index f87ffab..74a3953 100644 --- a/src/erc721/schema/token.ts +++ b/src/erc721/schema/token.ts @@ -4,6 +4,11 @@ extend type Query { nftToken(address: Address!): ERC721TokenContract } +type ERC721TokenHolder { + account: Account! + tokenBalance: Long +} + type ERC721TokenContract { account: Account ownerOf(tokenId: Long): String diff --git a/src/index.ts b/src/index.ts index f50b62a..e08f4d4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import config from './config'; import core from './core'; import erc165 from './erc165'; -import erc20 from './erc20'; +// import erc20 from './erc20'; import erc721 from './erc721'; import { EthqlServer } from './server'; @@ -10,7 +10,7 @@ console.log(`Effective configuration:\n${JSON.stringify(config, null, 2)}`); const server = new EthqlServer({ config, - plugins: [core, erc20, erc165, erc721], + plugins: [core, erc165, erc721], }); process.on('SIGINT', async () => (await server.stop()) || process.exit(0)); From 9b7df7d26d15e6a40aa759a79b27e30c65088543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Mon, 3 Dec 2018 19:13:32 +0200 Subject: [PATCH 07/12] Update src/__tests__/erc165/erc165.test.ts Co-Authored-By: leonprou --- src/__tests__/erc165/erc165.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/erc165/erc165.test.ts b/src/__tests__/erc165/erc165.test.ts index 979b806..3c3cdca 100644 --- a/src/__tests__/erc165/erc165.test.ts +++ b/src/__tests__/erc165/erc165.test.ts @@ -35,7 +35,7 @@ test('erc165: Cryptokities supports ERC721 interface', async () => { expect(result.data.account.supportsInterface).toEqual(true); }); -test('erc165: OmiseGO does not supports ERC165', async () => { +test('erc165: OmiseGO does not support ERC165', async () => { const query = ` { account(address:"0xd26114cd6EE289AccF82350c8d8487fedB8A0C07") { From bafc00f623d6e532cd6fc7b942aa48fe5d0a54f2 Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Mon, 3 Dec 2018 19:23:52 +0200 Subject: [PATCH 08/12] delete blank line --- src/core/services/decoder/impl/simple.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/services/decoder/impl/simple.ts b/src/core/services/decoder/impl/simple.ts index ae23f1b..78e4d56 100644 --- a/src/core/services/decoder/impl/simple.ts +++ b/src/core/services/decoder/impl/simple.ts @@ -48,7 +48,6 @@ class SimpleDecodingEngine implements DecodingEngine { */ public decodeLog(log: EthqlLog, context: EthqlContext): DecodedLog | undefined { // Find a decoder that can process this log. - for (const decoder of this.registry) { const logs: any[] = decoder.abiDecoder.decodeLogs([log]); if (!logs || logs[0] === undefined) { From 73abf18386b75acbc2ff8d36d4325b5c646ee1f5 Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Mon, 3 Dec 2018 19:48:16 +0200 Subject: [PATCH 09/12] small changes --- src/__tests__/erc165/erc165.test.ts | 4 ++-- src/abi/README.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/__tests__/erc165/erc165.test.ts b/src/__tests__/erc165/erc165.test.ts index 3c3cdca..945b9ea 100644 --- a/src/__tests__/erc165/erc165.test.ts +++ b/src/__tests__/erc165/erc165.test.ts @@ -4,7 +4,7 @@ import { testGraphql } from '../utils'; const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc165] } }); -test('erc165: Cryptokities supports ERC165 interface', async () => { +test('erc165: Cryptokitties supports ERC165 interface', async () => { const query = ` { account(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { @@ -19,7 +19,7 @@ test('erc165: Cryptokities supports ERC165 interface', async () => { expect(result.data.account.supportsInterface).toEqual(true); }); -test('erc165: Cryptokities supports ERC721 interface', async () => { +test('erc165: Cryptokitties supports ERC721 interface', async () => { const query = ` { account(address:"0x06012c8cf97BEaD5deAe237070F9587f8E7A266d") { diff --git a/src/abi/README.md b/src/abi/README.md index 839223a..33c96b8 100644 --- a/src/abi/README.md +++ b/src/abi/README.md @@ -15,6 +15,7 @@ We classify supported ABIs in two types: | ERC20 | Entity |  Token | [link][1] | | | ERC223 | Extension |  Token | [link][2] | Private ERC20 implementation change. No action needed in ethql. | | ERC165 | Entity | Interface | [link][3] | | +| ERC721 | Entity | Token | [link][4] | | ## ethql naming scheme @@ -30,3 +31,4 @@ logs pertaining to several standards that relate to the same entity. [1]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md [2]: https://github.com/ethereum/EIPs/issues/223 [3]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-165.md +[4]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md From d644ebc030ab471a3583cd481037abdf67cb7385 Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Mon, 3 Dec 2018 19:53:41 +0200 Subject: [PATCH 10/12] deleting unnecessary code --- src/erc165/decoders/index.ts | 17 ----------------- src/erc165/index.ts | 3 +-- src/erc165/model/index.ts | 15 --------------- 3 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 src/erc165/decoders/index.ts delete mode 100644 src/erc165/model/index.ts diff --git a/src/erc165/decoders/index.ts b/src/erc165/decoders/index.ts deleted file mode 100644 index 4857260..0000000 --- a/src/erc165/decoders/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createAbiDecoder, DecoderDefinition, extractParamValue } from '../../core/services/decoder'; - -type Erc165LogBindings = {}; - -type Erc165TxBindings = {}; - -class Erc165InterfaceDecoder implements DecoderDefinition { - public readonly entity = 'interface'; - public readonly standard = 'ERC165'; - public readonly abiDecoder = createAbiDecoder(__dirname + '../../../abi/erc165.json'); - - public readonly txTransformers = {}; - - public readonly logTransformers = {}; -} - -export { Erc165InterfaceDecoder }; diff --git a/src/erc165/index.ts b/src/erc165/index.ts index 2479df2..492d866 100644 --- a/src/erc165/index.ts +++ b/src/erc165/index.ts @@ -1,5 +1,4 @@ import { EthqlPluginFactory } from '../plugin'; -import { Erc165InterfaceDecoder } from './decoders'; import resolvers from './resolvers'; import erc165Schema from './schema/erc165'; import { Web3Erc165Service } from './services/impl/web3-erc165-service'; @@ -17,7 +16,7 @@ const plugin: EthqlPluginFactory = config => ({ }, }, dependsOn: { - services: ['web3', 'ethService', 'decoder'], + services: ['web3', 'decoder'], }, order: { after: ['core'], diff --git a/src/erc165/model/index.ts b/src/erc165/model/index.ts deleted file mode 100644 index 73514d8..0000000 --- a/src/erc165/model/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export class Erc165InterfaceContract { - private static ABI = require(__dirname + '../../../abi/erc165.json'); - private _contract: Contract; - - constructor(public readonly account: EthqlAccount, readonly context: EthqlContext) { - this._contract = new context.services.web3.eth.Contract(Erc165InterfaceContract.ABI, account.address); - } - - public async supportsInterface({ interfaceID }: { String }): Boolean { - return contract.methods - .supportsInterface(interfaceID) - .call() - .catch(console.error); - } -} From da4d6bcac819fee9209751e854d516d6df7b35b4 Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Sat, 15 Dec 2018 21:43:19 +0200 Subject: [PATCH 11/12] move nftToken to Account --- src/__tests__/erc721/erc721.test.ts | 60 +++++++++++++++++++---------- src/erc721/resolvers/index.ts | 14 +++++-- src/erc721/schema/token.ts | 5 ++- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/__tests__/erc721/erc721.test.ts b/src/__tests__/erc721/erc721.test.ts index a2f15dd..faab365 100644 --- a/src/__tests__/erc721/erc721.test.ts +++ b/src/__tests__/erc721/erc721.test.ts @@ -9,8 +9,10 @@ const { execQuery } = testGraphql({ optsOverride: { plugins: [core, erc165, erc7 test('erc721: nftToken balanceOf query #1', async () => { const query = ` { - nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { - balanceOf(owner: "0xD418c5d0c4a3D87a6c555B7aA41f13EF87485Ec6") + account(address: "0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + nftToken { + balanceOf(owner: "0xD418c5d0c4a3D87a6c555B7aA41f13EF87485Ec6") + } } } `; @@ -19,8 +21,10 @@ test('erc721: nftToken balanceOf query #1', async () => { expect(result.data).not.toBeUndefined(); expect(result.data).toEqual({ - nftToken: { - balanceOf: 0, + account: { + nftToken: { + balanceOf: 0, + }, }, }); }); @@ -28,8 +32,10 @@ test('erc721: nftToken balanceOf query #1', async () => { test('erc721: nftToken balanceOf query #2', async () => { const query = ` { - nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { - balanceOf(owner: "0x6f00cE7253bFD3A5A1c307b5E13814BF3433C72f") + account(address: "0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + nftToken { + balanceOf(owner: "0x6f00cE7253bFD3A5A1c307b5E13814BF3433C72f") + } } } `; @@ -38,8 +44,10 @@ test('erc721: nftToken balanceOf query #2', async () => { expect(result.data).not.toBeUndefined(); expect(result.data).toEqual({ - nftToken: { - balanceOf: 5, + account: { + nftToken: { + balanceOf: 5, + }, }, }); }); @@ -47,8 +55,10 @@ test('erc721: nftToken balanceOf query #2', async () => { test('erc721: nftToken ownerOf query', async () => { const query = ` { - nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { - ownerOf(tokenId: 143880) + account(address: "0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + nftToken { + ownerOf(tokenId: 143880) + } } } `; @@ -57,8 +67,10 @@ test('erc721: nftToken ownerOf query', async () => { expect(result.data).not.toBeUndefined(); expect(result.data).toEqual({ - nftToken: { - ownerOf: '0x6f00cE7253bFD3A5A1c307b5E13814BF3433C72f', + account: { + nftToken: { + ownerOf: '0x6f00cE7253bFD3A5A1c307b5E13814BF3433C72f', + }, }, }); }); @@ -66,8 +78,10 @@ test('erc721: nftToken ownerOf query', async () => { test('erc721: nftToken getApproved query', async () => { const query = ` { - nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { - getApproved(tokenId: 33525) + account(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + nftToken { + getApproved(tokenId: 33525) + } } } `; @@ -76,8 +90,10 @@ test('erc721: nftToken getApproved query', async () => { expect(result.data).not.toBeUndefined(); expect(result.data).toEqual({ - nftToken: { - getApproved: '0x0000000000000000000000000000000000000000', + account: { + nftToken: { + getApproved: '0x0000000000000000000000000000000000000000', + }, }, }); }); @@ -85,8 +101,10 @@ test('erc721: nftToken getApproved query', async () => { test('erc721: nftToken isApprovedForAll query', async () => { const query = ` { - nftToken(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { - isApprovedForAll(owner: "0xb85e9bdfCA73a536BE641bB5eacBA0772eA3E61E", operator: "0xD418c5d0c4a3D87a6c555B7aA41f13EF87485Ec6") + account(address:"0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab") { + nftToken { + isApprovedForAll(owner: "0xb85e9bdfCA73a536BE641bB5eacBA0772eA3E61E", operator: "0xD418c5d0c4a3D87a6c555B7aA41f13EF87485Ec6") + } } } `; @@ -95,8 +113,10 @@ test('erc721: nftToken isApprovedForAll query', async () => { expect(result.data).not.toBeUndefined(); expect(result.data).toEqual({ - nftToken: { - isApprovedForAll: false, + account: { + nftToken: { + isApprovedForAll: false, + }, }, }); }); diff --git a/src/erc721/resolvers/index.ts b/src/erc721/resolvers/index.ts index 7807c89..328a69c 100644 --- a/src/erc721/resolvers/index.ts +++ b/src/erc721/resolvers/index.ts @@ -2,17 +2,23 @@ import { EthqlContext } from '../../context'; import { EthqlAccount, EthqlAccountType, StorageAccessor } from '../../core/model'; import { Erc721TokenContract } from '../model'; -async function nftToken(obj: EthqlAccount, { address }, context: EthqlContext) { +const interfaceId = { + erc721: '0x80ac58cd', + cryptoKitities: '0x9a20483d', +}; + +async function nftToken(account: EthqlAccount, args, context: EthqlContext) { + const { address } = account; if ( - (await context.services.erc165Service.supportsInterface(address, '0x9a20483d')) || - (await context.services.erc165Service.supportsInterface(address, '0x80ac58cd')) + (await context.services.erc165Service.supportsInterface(address, interfaceId.cryptoKitities)) || + (await context.services.erc165Service.supportsInterface(address, interfaceId.erc721)) ) { return new Erc721TokenContract(new EthqlAccount(address), context); } } export default { - Query: { + Account: { nftToken, }, }; diff --git a/src/erc721/schema/token.ts b/src/erc721/schema/token.ts index 74a3953..9217a2f 100644 --- a/src/erc721/schema/token.ts +++ b/src/erc721/schema/token.ts @@ -1,7 +1,8 @@ export default ` -extend type Query { + +extend type Account { "Selects an account." - nftToken(address: Address!): ERC721TokenContract + nftToken: ERC721TokenContract } type ERC721TokenHolder { From 1feaef0e16d55a50f65c5c38e997129b9fb6f67f Mon Sep 17 00:00:00 2001 From: Leon Prouger Date: Fri, 28 Dec 2018 22:29:28 +0200 Subject: [PATCH 12/12] add ERC20 prefix to sgraphql types --- src/erc20/schema/erc20.ts | 30 +++++++++++++++--------------- src/erc20/schema/token.ts | 4 ++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/erc20/schema/erc20.ts b/src/erc20/schema/erc20.ts index 0f7f975..336b5d7 100644 --- a/src/erc20/schema/erc20.ts +++ b/src/erc20/schema/erc20.ts @@ -1,45 +1,45 @@ export default ` interface ERC20Transaction { - tokenContract: TokenContract + tokenContract: ERC20TokenContract } type ERC20Transfer implements DecodedTransaction & ERC20Transaction { entity: Entity standard: String operation: String - from: TokenHolder - to: TokenHolder + from: ERC20TokenHolder + to: ERC20TokenHolder value: String - tokenContract: TokenContract + tokenContract: ERC20TokenContract } type ERC20TransferFrom implements DecodedTransaction & ERC20Transaction { entity: Entity standard: String operation: String - from: TokenHolder - to: TokenHolder + from: ERC20TokenHolder + to: ERC20TokenHolder value: String - spender: TokenHolder - tokenContract: TokenContract + spender: ERC20TokenHolder + tokenContract: ERC20TokenContract } type ERC20Approve implements DecodedTransaction & ERC20Transaction { entity: Entity standard: String operation: String - from: TokenHolder - spender: TokenHolder + from: ERC20TokenHolder + spender: ERC20TokenHolder value: String - tokenContract: TokenContract + tokenContract: ERC20TokenContract } type ERC20TransferEvent implements DecodedLog { entity: Entity standard: String event: String - from: TokenHolder - to: TokenHolder + from: ERC20TokenHolder + to: ERC20TokenHolder value: String } @@ -47,8 +47,8 @@ type ERC20ApprovalEvent implements DecodedLog { entity: Entity standard: String event: String - owner: TokenHolder - spender: TokenHolder + owner: ERC20TokenHolder + spender: ERC20TokenHolder value: String } `; diff --git a/src/erc20/schema/token.ts b/src/erc20/schema/token.ts index ee8f6e2..1e8425c 100644 --- a/src/erc20/schema/token.ts +++ b/src/erc20/schema/token.ts @@ -1,10 +1,10 @@ export default ` -type TokenHolder { +type ERC20TokenHolder { account: Account! tokenBalance: Long } -type TokenContract { +type ERC20TokenContract { account: Account symbol: String totalSupply: Long