Coder Social home page Coder Social logo

blockcoders / ink-substrate-explorer-api Goto Github PK

View Code? Open in Web Editor NEW
7.0 2.0 2.0 801 KB

Ink Explorer is an application that provides Ink contracts related information on Substrate based blockchains.

License: MIT License

JavaScript 0.63% Dockerfile 1.08% TypeScript 98.13% Shell 0.15%
polkadot polkadot-js substrate blockchain dot erc1155 erc20 erc721 kusama nft

ink-substrate-explorer-api's Introduction

Ink! Explorer API

App Build Coverage Status Codacy Badge BCH compliance Docker Image Version Docker Image Size License

Demo

ink-explorer-api.blockcoders.io

About the explorer

Ink Explorer is an application that provides Ink contracts related information on Substrate based blockchains. It subscribes to blockchain and Ink modules events and store the information on its own database. The backend exposes an API that can interact with the DB and run fast queries to get specific information in a short time.

The idea of this project is to provide a tool that allows developers of Ink! explore and analyze the contracts found on the blockchain. This tool can be used to analyze the contracts found on Substrate based blockchains that are using Ink! modules. It can also be used to analyze contracts that are on a local blockchain.

This project serves useful information that is not available anywhere else. Since the back end is in charge of obtaining information related to the balances, transactions and more, of the contracts that use Ink modules. Ink Explorer uses polkadot.js to communicate with the Substrate / Polkadot networks. It is safe to say that this project is a must.

Get Started

Running the service locally

Environment setup

  • Install Node.js
    • Recommended method is by using NVM
    • Recommendeded Node.js version is v16.13
  • Install Docker

Install all the dependencies

pnpm i --frozen-lockfile

Configure the environment variables

Note: The .env file has the configuration for GraphQL, the Mongo database, Node and the RPC url of the Substrate Blockchain.

cp .env.sample .env

Service configurations

NODE_ENV=development
PORT=8080
LOG_NAME=ink-substrate-explorer-api
LOG_LEVEL=debug

GraphQL configurations

GRAPHQL_DEBUG=true
GRAPHQL_PLAYGROUND=true
GRAPHQL_SORT_SCHEMA=true
GRAPHQL_INTROSPECTION=true

Database configurations

DATABASE_HOST=mongo
DATABASE_NAME=ink
DATABASE_USERNAME=mongodb
DATABASE_PASSWORD=mongodb
DATABASE_RETRY_ATTEMPTS=5
DATABASE_RETRY_DELAY=3000

Blockchain and Sync configurations

WS_PROVIDER=wss://rococo-contracts-rpc.polkadot.io
# Set to true to process every block from FIRST_BLOCK_TO_LOAD to the current block. Set to false to only start processing blocks from the last existing block in the database.
LOAD_ALL_BLOCKS=false
# Block number from which the service will start to process blocks. (Can be genesis or some other block. For example, the first block supporting contracts)
FIRST_BLOCK_TO_LOAD=0
# Number of blocks to process concurrently. This can speed up or down the syncing process.
BLOCK_CONCURRENCY=1000

Starting the project (DEV)

Start a Mongo DB using docker (optional)

To start the project a Mongo DB is needed. For that, the dev-docker-compose.yaml file already has an image set up ready to use. Running this command it will also start a container for Mongo Express:

docker-compose -f dev-docker-compose.yaml up -d

Once the service is running, Mongo Express can be accessed following the link that is shown in the terminal (In this case localhost:8081).

Start a local Substrate Node (optional)

The service needs to connect to a Substrate Blockchain. For that, the dev-docker-compose.yaml file already has an image set up ready to use. Run this command:

docker-compose -f dev-docker-compose.yaml up -d

Another way to run a local node is with this paritytech guide.

Note: Change the WS_PROVIDER var in the .env file to be ws://127.0.0.1:9944

Start the service

pnpm start:dev

Runs the service in the development mode. The service will reload if you make edits.

Note: A Mongo DB up and running and a valid connection to a substrate node are required.

Starting the project (PROD)

To start the backend service, Mongo DB and Mongo Express containers run the following command:

docker-compose up -d

Note: A Mongo DB up and running and a valid connection to a substrate node are required. Optionally comment the backend service in the docker-compose file if you want to run the image locally.

Running the Back-end service Docker image

Download the image from DockerHub

docker pull blockcoders/ink-substrate-explorer-api:latest

Run

# Create a docker network
docker network create ink-explorer-network

# Run the service
docker run -it -p 5000:5000 --network ink-explorer-network --env-file {pathToEnvFile} blockcoders/ink-substrate-explorer-api:latest

Verify the image started running

docker ps

The result should look like this:

CONTAINER ID   IMAGE                                    COMMAND                  CREATED          STATUS          PORTS                                       NAMES
f31a7d0fd6c8   blockcoders/ink-substrate-explorer-api   "docker-entrypoint.sā€¦"   15 seconds ago   Up 14 seconds   0.0.0.0:5000->5000/tcp, :::5000->5000/tcp   funny_lumiere

The service will connect to the DB container and start processing blocks.

Testing

Running the unit tests.

pnpm test

Running the test coverage.

pnpm test:cov

Testing the GraphQL queries.

{"level":30,"time":1664298430389,"pid":1388770,"hostname":"username","name":"ink-substrate-explorer-api","msg":"App listening on http://0.0.0.0:5000"}

Once the back-end service is running, the GraphQL Playground can be accessed at http://localhost:5000/graphql

backend

API definition

With the service up and running an API is provided by using GraphQL queries.

Queries

Status: Retrieves the status of the application

query {
  status
}

Response:

{
  "data": {
    "status": "running"
  }
}

Version: Retrieves the version of the application

query {
  status
}

Response:

{
  "data": {
    "version": "v1.1.1"
  }
}

getBlock: Retrieves the block by hash

query {
  getBlock(hash: "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe") {
    hash
    number
    parentHash
    timestamp
    encodedLength
    transactions {
      hash
    }
  }
}

Response:

{
  "data": {
    "getBlock": {
      "hash": "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe",
      "number": 7,
      "parentHash": "0xd8ecc752f280a3786c5cdd4d441d71488414fd6132ace481dd6ddb23fd8000b0",
      "timestamp": 1666888006111,
      "encodedLength": 312,
      "transactions": [
        {
          "hash": "0xdb561ee4432e07a959292acf9895ce379e2474a52160b93fd62496806fdf26cd"
        },
        {
          "hash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b"
        }
      ]
    }
  }
}

getBlocks: Retrieves blocks. Use 'skip' and 'take' to paginate. Use 'orderByNumber: false' to order by timestamp instead and 'orderAsc: true' to see older blocks first.

query {
  getBlocks(skip: 0, take: 1, orderByNumber: false, orderAsc: false) {
    hash
    number
    parentHash
    timestamp
    encodedLength
    transactions {
      hash
    }
  }
}

Response:

{
  "data": {
    "getBlocks": [
      {
        "hash": "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe",
        "number": 7,
        "parentHash": "0xd8ecc752f280a3786c5cdd4d441d71488414fd6132ace481dd6ddb23fd8000b0",
        "timestamp": 1666888006111,
        "encodedLength": 312,
        "transactions": [
          {
            "hash": "0xdb561ee4432e07a959292acf9895ce379e2474a52160b93fd62496806fdf26cd"
          },
          {
            "hash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b"
          }
        ]
      }
    ]
  }
}

getTransaction: Retrieves a single transaction by hash

query {
  getTransaction(hash: "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b") {
    args
    blockHash
    callIndex
    decimals
    encodedLength
    era
    events {
      method
    }
    hash
    method
    nonce
    section
    signature
    signer
    ss58
    timestamp
    tip
    tokens
    type
    version
  }
}

Response:

{
  "data": {
    "getTransaction": {
      "args": "{\"dest\":{\"id\":\"5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8\"},\"value\":0,\"gas_limit\":75000000000,\"storage_deposit_limit\":null,\"data\":\"0x84a15da18eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48005039278c0400000000000000000000\"}",
      "blockHash": "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe",
      "callIndex": "7,0",
      "decimals": "12",
      "encodedLength": 201,
      "era": "{\"mortalEra\":\"0x0b00\"}",
      "events": [
        {
          "method": "ContractEmitted"
        }
      ],
      "hash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b",
      "method": "call",
      "nonce": 3,
      "section": "contracts",
      "signature": "0x78582786706e947a6d77ac5b49ba140b4c88ebc644421136bbfa8b66577e1e3efdbc1d981948546fff41600be9a716e4c38a8531a867853f26ba11ee21128f82",
      "signer": "5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc",
      "ss58": "42",
      "timestamp": 1666888006111,
      "tip": "0",
      "tokens": "Unit",
      "type": 4,
      "version": 132
    }
  }
}

getTransactionsByContract: Retrieves a list of transactions of a contract.

query {
  getTransactionsByContract(
    address: "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8"
    skip: 0
    take: 1
    orderAsc: false
  ) {
    args
    blockHash
    callIndex
    decimals
    encodedLength
    era
    events {
      method
    }
    hash
    method
    nonce
    section
    signature
    signer
    ss58
    timestamp
    tip
    tokens
    type
    version
  }
}

Response:

{
  "data": {
    "getTransactionsByContract": [
      {
        "args": "{\"dest\":{\"id\":\"5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8\"},\"value\":0,\"gas_limit\":75000000000,\"storage_deposit_limit\":null,\"data\":\"0x84a15da18eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48005039278c0400000000000000000000\"}",
        "blockHash": "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe",
        "callIndex": "7,0",
        "decimals": "12",
        "encodedLength": 201,
        "era": "{\"mortalEra\":\"0x0b00\"}",
        "events": [
          {
            "method": "ContractEmitted"
          }
        ],
        "hash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b",
        "method": "call",
        "nonce": 3,
        "section": "contracts",
        "signature": "0x78582786706e947a6d77ac5b49ba140b4c88ebc644421136bbfa8b66577e1e3efdbc1d981948546fff41600be9a716e4c38a8531a867853f26ba11ee21128f82",
        "signer": "5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc",
        "ss58": "42",
        "timestamp": 1666888006111,
        "tip": "0",
        "tokens": "Unit",
        "type": 4,
        "version": 132
      }
    ]
  }
}

getTransactions: Retrieves transactions by block hash (use 'skip' and 'take' to paginate. use 'orderAsc' to see older or newer first)

query {
  getTransactions(skip: 0, take: 1, orderAsc: false) {
    args
    blockHash
    callIndex
    decimals
    encodedLength
    era
    events {
      method
    }
    hash
    method
    nonce
    section
    signature
    signer
    ss58
    timestamp
    tip
    tokens
    type
    version
  }
}

Response:

{
  "data": {
    "getTransactions": [
      {
        "args": "{\"now\":1666888006111}",
        "blockHash": "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe",
        "callIndex": "2,0",
        "decimals": "12",
        "encodedLength": 11,
        "era": "{\"immortalEra\":\"0x00\"}",
        "events": [],
        "hash": "0xdb561ee4432e07a959292acf9895ce379e2474a52160b93fd62496806fdf26cd",
        "method": "set",
        "nonce": 0,
        "section": "timestamp",
        "signature": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
        "signer": "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM",
        "ss58": "42",
        "timestamp": 1666888006111,
        "tip": "0",
        "tokens": "Unit",
        "type": 4,
        "version": 4
      }
    ]
  }
}

getEvent: Retrieves an event by id

query {
  getEvent(id: "81735cc9-76d3-5984-83af-5872bc9eaeb7") {
    id
    index
    method
    section
    timestamp
    topics
    transactionHash
    data
    decodedData
    formattedData
  }
}

Response:

{
  "data": {
    "getEvent": {
      "id": "81735cc9-76d3-5984-83af-5872bc9eaeb7",
      "index": "0x0703",
      "method": "ContractEmitted",
      "section": "contracts",
      "timestamp": 1666888006111,
      "topics": "[0x0045726332303a3a5472616e7366657200000000000000000000000000000000, 0x08be862c40d599dc6f4f28076712bb324c0cd2197c30f07459887b41fadff2c8, 0x2b00c7d40fe6d84d660f3e6bed90f218e022a0909f7e1a7ea35ada8b6e003564]",
      "transactionHash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b",
      "data": "[\"5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8\",\"0x0001c40e2006bbebf9022c317f9337ad376e56d392917e5ac1397fe09b07c765c050018eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48005039278c0400000000000000000000\"]",
      "decodedData": "{\"args\":[\"5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc\",\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"0x00000000000000000000048c27395000\"],\"event\":{\"args\":[{\"name\":\"from\",\"type\":{\"sub\":{\"docs\":[],\"info\":10,\"type\":\"AccountId\",\"namespace\":\"ink_env::types::AccountId\",\"lookupIndex\":2,\"lookupNameRoot\":\"InkEnvAccountId\"},\"docs\":[],\"info\":9,\"type\":\"Option<AccountId>\",\"namespace\":\"Option\",\"lookupIndex\":11}},{\"name\":\"to\",\"type\":{\"sub\":{\"docs\":[],\"info\":10,\"type\":\"AccountId\",\"namespace\":\"ink_env::types::AccountId\",\"lookupIndex\":2,\"lookupNameRoot\":\"InkEnvAccountId\"},\"docs\":[],\"info\":9,\"type\":\"Option<AccountId>\",\"namespace\":\"Option\",\"lookupIndex\":11}},{\"name\":\"value\",\"type\":{\"info\":10,\"type\":\"Balance\"}}],\"docs\":[\" Event emitted when a token transfer occurs.\"],\"index\":0,\"identifier\":\"Transfer\"}}",
      "formattedData": "{\"from\":\"5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc\",\"to\":\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"value\":5}"
    }
  }
}

getEvents: Retrieves events by contract address or transaction hash (use 'skip' and 'take' to paginate, 'orderAsc' to see older or newer first)

query {
  getEvents(contract: "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8", skip: 0, take: 1, orderAsc: false) {
    id
    index
    method
    section
    timestamp
    topics
    transactionHash
    data
    decodedData
    formattedData
  }
}

Response:

{
  "data": {
    "getEvents": [
      {
        "id": "81735cc9-76d3-5984-83af-5872bc9eaeb7",
        "index": "0x0703",
        "method": "ContractEmitted",
        "section": "contracts",
        "timestamp": 1666888006111,
        "topics": "[0x0045726332303a3a5472616e7366657200000000000000000000000000000000, 0x08be862c40d599dc6f4f28076712bb324c0cd2197c30f07459887b41fadff2c8, 0x2b00c7d40fe6d84d660f3e6bed90f218e022a0909f7e1a7ea35ada8b6e003564]",
        "transactionHash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b",
        "data": "[\"5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8\",\"0x0001c40e2006bbebf9022c317f9337ad376e56d392917e5ac1397fe09b07c765c050018eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48005039278c0400000000000000000000\"]",
        "decodedData": "{\"args\":[\"5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc\",\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"0x00000000000000000000048c27395000\"],\"event\":{\"args\":[{\"name\":\"from\",\"type\":{\"sub\":{\"docs\":[],\"info\":10,\"type\":\"AccountId\",\"namespace\":\"ink_env::types::AccountId\",\"lookupIndex\":2,\"lookupNameRoot\":\"InkEnvAccountId\"},\"docs\":[],\"info\":9,\"type\":\"Option<AccountId>\",\"namespace\":\"Option\",\"lookupIndex\":11}},{\"name\":\"to\",\"type\":{\"sub\":{\"docs\":[],\"info\":10,\"type\":\"AccountId\",\"namespace\":\"ink_env::types::AccountId\",\"lookupIndex\":2,\"lookupNameRoot\":\"InkEnvAccountId\"},\"docs\":[],\"info\":9,\"type\":\"Option<AccountId>\",\"namespace\":\"Option\",\"lookupIndex\":11}},{\"name\":\"value\",\"type\":{\"info\":10,\"type\":\"Balance\"}}],\"docs\":[\" Event emitted when a token transfer occurs.\"],\"index\":0,\"identifier\":\"Transfer\"}}",
        "formattedData": "{\"from\":\"5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc\",\"to\":\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"value\":5}"
      }
    ]
  }
}

getContract: Retrieves a contract by address

query {
  getContract(address: "5G24svh2w4QXNhsHU5XBxf8N3Sw2MPu7sAemofv1bCuyxAzc") {
    address
    metadata
    hasMetadata
  }
}

Response:

{
  "data": {
    "getContract": {
      "address": "5G24svh2w4QXNhsHU5XBxf8N3Sw2MPu7sAemofv1bCuyxAzc",
      "metadata": "{\n  \"source\": {\n    \"hash\": ...   }\n}\n",
      "hasMetadata": true
    }
  }
}

getContracts: Retrieves a list of contracts

query {
  getContracts(skip: 0, take: 10) {
    address
    metadata
    hasMetadata
    events {
      method
    }
  }
}

Response:

{
  "data": {
    "getContracts": [
      {
        "address": "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8",
        "hasMetadata": true,
        "metadata": "{ ... }",
        "events": [
          {
            "method": "ContractEmitted"
          },
        ]
      }
    ]
  }
}

getContractQueries: Retrieves a contract. If this contract has uploaded metadata it will also retrieve the queries and transaction methods that can be executed.

query {
  getContractQueries(address: "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8") {
    address
    hasMetadata
    queries {
      args
      docs
      method
      name
    }
  }
}

Response:

{
  "data": {
    "getContractQueries": {
      "address": "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8",
      "hasMetadata": true,
      "queries": [
        {
          "args": [],
          "docs": [
            " Returns the total token supply."
          ],
          "method": "totalSupply",
          "name": "Total supply"
        },
        {
          "args": [
            "{\"name\":\"to\",\"type\":{\"info\":10,\"type\":\"AccountId\"}}",
            "{\"name\":\"value\",\"type\":{\"info\":10,\"type\":\"Balance\"}}"
          ],
          "docs": [
            " Transfers `value` amount of tokens from the caller's account to account `to`.",
            "",
            " On success a `Transfer` event is emitted.",
            "",
            " # Errors",
            "",
            " Returns `InsufficientBalance` error if there are not enough tokens on",
            " the caller's account balance."
          ],
          "method": "transfer",
          "name": "Transfer"
        }
      ]
    }
  }
}

Mutations

decodeEvent: Decodes the event data for a specific event. Requires that the contract's metadata was already uploaded using the mutation uploadMetadata

mutation {
  decodeEvent(
    contractAddress: "5ELpkDtq7werhT5ybZZMbVBcQTPNomvJP7j5kJQifv7GzVik"
    id: "972e782c-2517-5648-9bf1-4c693d2fed90"
  )
}

Response:

{
  "data": {
    "decodeEvent": "{\"identifier\":\"Transfer\",\"decodedData\":{\"args\":[\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y\",\"0x000000000000000000002d79883d2000\"],\"event\":{\"args\":[{\"name\":\"from\",\"type\":{\"info\":9,\"lookupIndex\":11,\"type\":\"Option<AccountId>\",\"docs\":[],\"namespace\":\"Option\",\"sub\":{\"info\":10,\"lookupIndex\":2,\"type\":\"AccountId\",\"docs\":[],\"namespace\":\"ink_env::types::AccountId\",\"lookupNameRoot\":\"InkEnvAccountId\"}}},{\"name\":\"to\",\"type\":{\"info\":9,\"lookupIndex\":11,\"type\":\"Option<AccountId>\",\"docs\":[],\"namespace\":\"Option\",\"sub\":{\"info\":10,\"lookupIndex\":2,\"type\":\"AccountId\",\"docs\":[],\"namespace\":\"ink_env::types::AccountId\",\"lookupNameRoot\":\"InkEnvAccountId\"}}},{\"name\":\"value\",\"type\":{\"info\":10,\"type\":\"Balance\"}}],\"docs\":[\" Event emitted when a token transfer occurs.\"],\"identifier\":\"Transfer\",\"index\":0}},\"formattedData\":{\"from\":\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"to\":\"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y\",\"value\":50}}"
  }
}

decodeEvents: Decodes the events data for a specific contract (use 'skip' and 'take' to select the events and 'orderAsc' to order by timestamp). Requires that the contract's metadata was already uploaded using the mutation uploadMetadata

mutation {
  decodeEvents(contract: "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8", skip: 0, take: 1, orderAsc: false)
}

Response:

{
  "data": {
    "decodeEvents": "[{\"identifier\":\"Transfer\",\"decodedData\":{\"args\":[\"5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc\",\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"0x00000000000000000000048c27395000\"],\"event\":{\"args\":[{\"name\":\"from\",\"type\":{\"info\":9,\"lookupIndex\":11,\"type\":\"Option<AccountId>\",\"docs\":[],\"namespace\":\"Option\",\"sub\":{\"info\":10,\"lookupIndex\":2,\"type\":\"AccountId\",\"docs\":[],\"namespace\":\"ink_env::types::AccountId\",\"lookupNameRoot\":\"InkEnvAccountId\"}}},{\"name\":\"to\",\"type\":{\"info\":9,\"lookupIndex\":11,\"type\":\"Option<AccountId>\",\"docs\":[],\"namespace\":\"Option\",\"sub\":{\"info\":10,\"lookupIndex\":2,\"type\":\"AccountId\",\"docs\":[],\"namespace\":\"ink_env::types::AccountId\",\"lookupNameRoot\":\"InkEnvAccountId\"}}},{\"name\":\"value\",\"type\":{\"info\":10,\"type\":\"Balance\"}}],\"docs\":[\" Event emitted when a token transfer occurs.\"],\"identifier\":\"Transfer\",\"index\":0}},\"formattedData\":{\"from\":\"5GVmSPghWsjACADGYi78dmhuZEgfgDwfixR7BM3aMEoNuTBc\",\"to\":\"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty\",\"value\":5}}]"
  }
}

uploadMetadata: To decode events it is necessary to upload the contract's ABI. Passing a base64 string ABI to this mutation will save that to DB. After that run a decodeEvents query to see the decoded data on the events.

mutation Upload {
  uploadMetadata(
    contractAddress: "5G24svh2w4QXNhsHU5XBxf8N3Sw2MPu7sAemofv1bCuyxAzc"
    metadata: "ewogICJzb3VyY2UiOiB7CiAgICAiaGFzaCI6I...(base64)"
  )
}

Response:

{
  "data": {
    "uploadMetadata": true
  }
}

About decoding events

To see the decoded data of the events there is one requirement, the contract metadata needs to uploaded at least once.

Example of an ERC20 contract metadata:

{
  "source": {
    "hash": "0x3aa1c8ba5f59034a42a93c00ee039a9464d6fa63d70b6889a2596f4528b28a19",
    "language": "ink! 3.3.0",
    "compiler": "rustc 1.64.0-nightly"
  },
  "contract": {
    "name": "erc20",
    "version": "0.1.0",
    "authors": [
      "[your_name] <[your_email]>"
    ]
  },
  "V3": {
    "spec": {
      "constructors": [
        {
          "args": [
            {
              "label": "initial_supply",
              "type": {
                "displayName": [
                  "Balance"
                ],
                "type": 0
              }
            }
          ],
          "docs": [
            "Creates a new ERC-20 contract with the specified initial supply."
          ],
          "label": "new",
          "payable": false,
          "selector": "0x9bae9d5e"
        }
      ],
      "docs": [],
      "events": [
        {
          "args": [
            {
              "docs": [],
              "indexed": true,
              "label": "from",
              "type": {
                "displayName": [
                  "Option"
                ],
                "type": 11
              }
            },
            {
              "docs": [],
              "indexed": true,
              "label": "to",
              "type": {
                "displayName": [
                  "Option"
                ],
                "type": 11
              }
            },
            {
              "docs": [],
              "indexed": false,
              "label": "value",
              "type": {
                "displayName": [
                  "Balance"
                ],
                "type": 0
              }
            }
          ],
          "docs": [
            " Event emitted when a token transfer occurs."
          ],
          "label": "Transfer"
        },
        {
          "args": [
            {
              "docs": [],
              "indexed": true,
              "label": "owner",
              "type": {
                "displayName": [
                  "AccountId"
                ],
                "type": 2
              }
            },
            {
              "docs": [],
              "indexed": true,
              "label": "spender",
              "type": {
                "displayName": [
                  "AccountId"
                ],
                "type": 2
              }
            },
            {
              "docs": [],
              "indexed": false,
              "label": "value",
              "type": {
                "displayName": [
                  "Balance"
                ],
                "type": 0
              }
            }
          ],
          "docs": [
            " Event emitted when an approval occurs that `spender` is allowed to withdraw",
            " up to the amount of `value` tokens from `owner`."
          ],
          "label": "Approval"
        }
      ],
      ...

Once it is uploaded the events can be decoded using the decodeEvent or decodeEvents mutation that can be found on section Mutations.

Note: The metadata should be uploaded as a base64 string.

For more on uploading the metadata go to the Mutations section a search for uploadMetadata.

Subscriptions

The first time the node is started, it may need to start from the block 0 and load all blocks (LOAD_ALL_BLOCKS env var should be set to true). If you want to start from a specific block, you can use the FIRST_BLOCK_TO_LOAD env var to start from another block.

In case of a downtime of the node, the subscriptions will be reconnected automatically recovering all new blocks from the last block that was processed.

Note: Load all blocks may take a long time depending on the number of blocks that need to be loaded. It is recommended to use a node with a fast internet connection. The node will be able to process all blocks in a few hours.

Some benchmarks

Using BLOCK_CONCURRENCY = 100

  • 100 blocks ~ 6 seconds
  • 1000 blocks ~ 30.5 seconds
  • 10000 blocks ~ 4:24 minutes
  • 100000 blocks ~ 39.57 minutes

Using BLOCK_CONCURRENCY = 1000

  • 100 blocks ~ 0.5 seconds
  • 1000 blocks ~ 5 seconds
  • 10000 blocks ~ 3 minutes
  • 100000 blocks ~ 24 minutes

Change Log

See Changelog for more information.

Contributing

Contributions welcome! See Contributing.

Collaborators

License

Licensed under the Apache 2.0 - see the LICENSE file for details.

ink-substrate-explorer-api's People

Contributors

0xslipk avatar fersirni avatar rubenguc avatar snyk-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    šŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. šŸ“ŠšŸ“ˆšŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ā¤ļø Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.