Ink Explorer es una aplicacion que provee informacion relativa a los contratos que utilizan Ink! en blockchains basadas en Substrate. Se suscribe a la blockchain y a los eventos emitidos por los modulos de Ink! y guarda la informacion en su propia base de datos PostgreSQL. El back-end expone una API que puede interactuar con la base de datos y ejecutar consultas rapidas para obtener informacion especifica en poco tiempo.
La idea de este proyecto es brindar una herramienta que permita a los desarrolladores de Ink! explorar y analizar los contratos que se encuentran en blockchain. Esta herramienta se puede utilizar para analizar los contratos que se encuentran en blockchains basadas en Substrate que utilizan módulos Ink!. También se puede usar para analizar contratos que están en una blockchain local.
Este proyecto ofrece información útil que no está disponible en ningún otro lugar. Ya que el back end se encarga de obtener información relacionada con los saldos, transacciones y más, de los contratos que utilizan módulos Ink!. El explorador utiliza polkadot.js para comunicarse con las redes Substrate/Polkadot. Es seguro decir que este proyecto es imprescindible.
- Instalar Node.js
- El metodo recomentado es utilizando NVM
- La verision de Node.js recomendada es v16.13
- Instalar Docker
pnpm i --frozen-lockfile
Nota: El archivo .env tiene la configuracion para GraphQL, la base de datos PostgreSQL, Node y la url del RPC de la blockchain basada en Substrate.
cp .env.sample .env
NODE_ENV=development
PORT=8080
LOG_NAME=ink-substrate-explorer-api
LOG_LEVEL=debug
SCHEMA_URL=https://ink-explorer-api.blockcoders.io/graphql
GRAPHQL_DEBUG=true
GRAPHQL_PLAYGROUND=true
GRAPHQL_SORT_SCHEMA=true
GRAPHQL_INTROSPECTION=true
DATABASE_HOST=postgres
DATABASE_NAME=ink
DATABASE_USERNAME=root
DATABASE_PASSWORD=password
DATABASE_RETRY_ATTEMPTS=5
DATABASE_RETRY_DELAY=3000
WS_PROVIDER=wss://rococo-contracts-rpc.polkadot.io
# Asignar el valor _true_ para procesar cada bloque desde FIRST_BLOCK_TO_LOAD hasta el ultimo bloque de la cadena. Asignar el valor _false_ para solo comenzar a procesar los bloques desde el ultimo bloque existente en la base de datos.
LOAD_ALL_BLOCKS=false
# Número de bloque a partir del cual el servicio comenzará a procesar bloques. (Puede ser génesis o algún otro bloque. Por ejemplo, el primer bloque admite contratos)
FIRST_BLOCK_TO_LOAD=0
# Número de bloques a procesar simultáneamente. Esto puede acelerar o retrasar el proceso de sincronización.
BLOCK_CONCURRENCY=1000
Para ejecutar en modo dev, aún se necesita el backend. Para eso, el archivo docker-compose.yaml ya cuenta con todos los servicios necesarios.
Ejecutar este comando también iniciará un contenedor para la interfaz (no querrás hacer esto en modo DEV). Para evitar esto, comente el servicio 'frontend'. Luego ejecute el comando
docker-compose up -d
Una vez que el servicio se está ejecutando, se puede acceder a pgAdmin siguiendo el enlace que se muestra en la terminal (en este caso localhost:80)
Las credenciales para acceder a pgAdmin son (establecidas en el archivo docker-compose):
- PGADMIN_DEFAULT_EMAIL: "[email protected]"
- PGADMIN_DEFAULT_PASSWORD: "admin"
Registre un nuevo servidor en pgAdmin y establezca las credenciales para la base de datos PostgreSQL:
Click derecho en 'Servers' y seleccione "Register" -> "Server"
Elija un nombre para el servidor (por ejemplo, "Docker")
Establezca las credenciales para la base de datos PostgreSQL (esto se puede encontrar en el archivo docker-compose):
Para ejecutar un nodo de Substrate localmente, agregue al archivo docker-compose.yaml el siguiente servicio:
substrate:
image: blockcoders/substrate-contracts-node:latest
restart: on-failure
ports:
- 9944:9944
command: '--dev --ws-external'
networks:
ink-explorer-network:
aliases:
- "substrate"
Esto iniciará un nuevo contenedor con un nodo local de Substrate.
Otra forma de ejecutar un nodo local es con [esta guía de paritytech] (https://github.com/paritytech/substrate-contracts-node).
Nota: cambie la variable WS_PROVIDER en el archivo .env para que sea ws://127.0.0.1:9944
To run the frontend service in DEV mode run:
The service will reload if you make edits.
Note: A postgresDB up and running and a valid connection to a substrate node are required.
Para iniciar los servicios de back-end y front-end, ejecute:
docker pull blockcoders/ink-substrate-explorer-frontend:latest
# Crear la red de docker
docker network create ink-explorer-network
# Correr servicio
docker run -it -p 3000:3000 --network ink-explorer-network blockcoders/ink-substrate-explorer-frontend:latest
docker ps
El resultado debería verse así:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f31a7d0fd6c8 blockcoders/ink-substrate-explorer-frontend "docker-entrypoint.s…" 15 seconds ago Up 14 seconds 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp funny_lumiere
Ejecución de las pruebas unitarias.
pnpm test
Ejecución de la cobertura de pruebas.
pnpm test:cov
Probando las consultas de GraphQL.
{"level":30,"time":1664298430389,"pid":1388770,"hostname":"username","name":"ink-substrate-explorer-api","msg":"App listening on http://0.0.0.0:8080"}
Una vez que el servicio back-end se está ejecutando, se puede acceder a GraphQL Playground en http://localhost:8080/graphql
Una vez que el servicio esta levantado y corriendo correctamente se provee una API que puede utilizarse enviado consultas de GraphQL.
Status: Recupera el estado de la aplicación
query {
status
}
Respuesta:
{
"data": {
"status": "running"
}
}
Version: Recupera la version de la aplicación
query {
status
}
Respuesta:
{
"data": {
"version": "v1.0.6"
}
}
getBlock: Recupera el bloque por hash
query {
getBlock(hash: "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe") {
hash
number
parentHash
timestamp
encodedLength
transactions {
hash
}
}
}
Respuesta:
{
"data": {
"getBlock": {
"hash": "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe",
"number": 7,
"parentHash": "0xd8ecc752f280a3786c5cdd4d441d71488414fd6132ace481dd6ddb23fd8000b0",
"timestamp": 1666888006111,
"encodedLength": 312,
"transactions": [
{
"hash": "0xdb561ee4432e07a959292acf9895ce379e2474a52160b93fd62496806fdf26cd"
},
{
"hash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b"
}
]
}
}
}
getBlocks: Recupera bloques. Use 'skip' y 'take' para paginar. Use 'orderByNumber: false' para ordenar por tiempo y 'orderAsc: true' para ver primero los bloques más antiguos.
query {
getBlocks(skip: 0, take: 1, orderByNumber: false, orderAsc: false) {
hash
number
parentHash
timestamp
encodedLength
transactions {
hash
}
}
}
Respuesta:
{
"data": {
"getBlocks": [
{
"hash": "0x0f615cf7edf8a1e8591893a594fe0ef67d5d56c4d9b1a89d8d120c5f821127fe",
"number": 7,
"parentHash": "0xd8ecc752f280a3786c5cdd4d441d71488414fd6132ace481dd6ddb23fd8000b0",
"timestamp": 1666888006111,
"encodedLength": 312,
"transactions": [
{
"hash": "0xdb561ee4432e07a959292acf9895ce379e2474a52160b93fd62496806fdf26cd"
},
{
"hash": "0x33831da6b804e82cd7613e0d780823c7455c773546ea5e76c945ed10a6f6554b"
}
]
}
]
}
}
getTransaction: Recupera una sola transacción por 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
}
}
Respuesta:
{
"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: Recupera una lista de transacciones de un contrato.
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
}
}
Respuesta:
{
"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: Recupera transacciones por hash de bloque (use 'skip' y 'take' para paginar. use 'orderAsc' para ver primero las más antiguas o las más nuevas)
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
}
}
Respuesta:
{
"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: Recupera un evento por su id
query {
getEvent(id: "81735cc9-76d3-5984-83af-5872bc9eaeb7") {
id
index
method
section
timestamp
topics
transactionHash
data
decodedData
formattedData
}
}
Respuesta:
{
"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: Recupera eventos por dirección de contrato o hash de transacción (use 'skip' y 'take' para paginar, 'orderAsc' para ver primero los más antiguos o los más nuevos)
query {
getEvents(contract: "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8", skip: 0, take: 1, orderAsc: false) {
id
index
method
section
timestamp
topics
transactionHash
data
decodedData
formattedData
}
}
Respuesta:
{
"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: Recupera un contrato por address
query {
getContract(address: "5G24svh2w4QXNhsHU5XBxf8N3Sw2MPu7sAemofv1bCuyxAzc") {
address
metadata
hasMetadata
}
}
Respuesta:
{
"data": {
"getContract": {
"address": "5G24svh2w4QXNhsHU5XBxf8N3Sw2MPu7sAemofv1bCuyxAzc",
"metadata": "{\n \"source\": {\n \"hash\": ... }\n}\n",
"hasMetadata": true
}
}
}
getContracts: Recupera una lista de contratos
query {
getContracts(skip: 0, take: 10) {
address
metadata
hasMetadata
events {
method
}
}
}
Respuesta:
{
"data": {
"getContracts": [
{
"address": "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8",
"hasMetadata": true,
"metadata": "{ ... }",
"events": [
{
"method": "ContractEmitted"
},
]
}
]
}
}
getContractQueries: Recupera un contrato. Si este contrato ha cargado metadatos, también recuperará las consultas y los métodos de transacción que se pueden ejecutar.
query {
getContractQueries(address: "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8") {
address
hasMetadata
queries {
args
docs
method
name
}
}
}
Respuesta:
{
"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"
}
]
}
}
}
decodeEvent: Decodifica los datos del evento para un evento específico. Requiere que los metadatos del contrato ya se hayan subido usando la mutación uploadMetadata
mutation {
decodeEvent(
contractAddress: "5ELpkDtq7werhT5ybZZMbVBcQTPNomvJP7j5kJQifv7GzVik"
id: "972e782c-2517-5648-9bf1-4c693d2fed90"
)
}
Respuesta:
{
"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: Decodifica los datos de eventos para un contrato específico (use 'skip' y 'take' para seleccionar los eventos y 'orderAsc' para ordenar por tiempo). Requiere que los metadatos del contrato ya se hayan subido usando la mutación uploadMetadata
mutation {
decodeEvents(contract: "5DfG5TyaffuJ78rHP71cvkYEtktRkpeMiJNJyxd8Q5924GR8", skip: 0, take: 1, orderAsc: false)
}
Respuesta:
{
"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: Para decodificar eventos es necesario cargar el ABI del contrato. Pasar un ABI en base64 a esta mutación lo guardará en la base de datos. Después de eso, ejecute una consulta decodeEvents para ver los datos decodificados en los eventos.
mutation Upload {
uploadMetadata(
contractAddress: "5G24svh2w4QXNhsHU5XBxf8N3Sw2MPu7sAemofv1bCuyxAzc"
metadata: "ewogICJzb3VyY2UiOiB7CiAgICAiaGFzaCI6I...(base64)"
)
}
Respuesta:
{
"data": {
"uploadMetadata": true
}
}
Para ver los datos decodificados de los eventos, hay un requisito: los metadatos del contrato deben cargarse al menos una vez.
Ejemplo de metadatos de un contrato ERC20:
{
"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"
}
],
...
Una vez cargado, los eventos se pueden decodificar mediante las mutaciones decodeEvent o decodeEvents que se encuentran en la sección Mutaciones.
Nota: Los metadatos deben cargarse como un texto (string) en base64.
Para obtener más información sobre la carga de metadatos, vaya a la sección Mutaciones y busque uploadMetadata.
La primera vez que se inicia el nodo, es posible que deba comenzar desde el bloque 0 y cargar todos los bloques (LOAD_ALL_BLOCKS env var debe establecerse en verdadero). Si desea comenzar desde un bloque específico, puede usar FIRST_BLOCK_TO_LOAD env var para comenzar desde otro bloque.
En caso de un tiempo de inactividad del nodo, las suscripciones se reconectarán automáticamente recuperando todos los bloques nuevos desde el último bloque que se procesó.
Nota: Cargar todos los bloques puede llevar mucho tiempo dependiendo de la cantidad de bloques que deban cargarse. Se recomienda utilizar un nodo con una conexión rápida a Internet. El nodo podrá procesar todos los bloques en unas pocas horas.
- 100 bloques en ~ 6 segundos
- 1000 bloques en ~ 30.5 segundos
- 10000 bloques en ~ 4:24 minutos
- 100000 bloques en ~ 39.57 minutos
- 100 bloques en ~ 0.5 segundos
- 1000 bloques en ~ 5 segundos
- 10000 bloques en ~ 3 minutos
- 100000 bloques en ~ 24 minutos
Consulte Changelog para más información.
¡Las contribuciones son bienvenidas! Consulte Contributing.
Con licencia de Apache 2.0 - consulte el archivo LICENSE para obtener más información.