APIs used:
  • Get logs  
  • Get a transaction  

If you think GameFi is dead, think again. The hype and money may have been drained from GameFi today, but the novelty of the mechanics remains. Specifically, Axie Inifinity’s P2E model and its popularity testify to the viability of this unique brand of in-game tokenomics. (If you’re unfamiliar with how it works, check out the GameFi e-book we’ve published last year!) In this article, I’ll be showing you how you can use the Covalent API to get the data of interesting in-game actions. Specifically, we’ll build a table of the latest Axie spawns that have occurred on the Ronin chain.

Ready? Let’s get started!

Prerequisites

  • Basic familiarity with React.

  • Some HTML/CSS knowledge.

  • Fetching data using APIs.

Tutorial: How to Get Axie Spawn Data

1

Identify the on-chain event you’re interested in

Every time an Axie is bred, an AxieSpawn is emitted from the transaction. This information is available on the Ronin block explorer, for instance in this transaction:

Whilst topic[0] is the topic hash that we can use to search for this event (0xb6fa90c3cb274eb5fe963b2deb623847e804c9d2b0791bee8e9883320f404a5d), topic[1] tells us what the Axie ID is.

2

Get the data of all AxieSpawn events

To get data of all AxieSpawn on-chain events, we can use Covalent’s Get logs endpoint. Here's a sample request:

https://api.covalenthq.com/v1/axie-mainnet/events/
?key={API_KEY}
&starting-block=23581942
&ending-block=latest
&topics=0xb6fa90c3cb274eb5fe963b2deb623847e804c9d2b0791bee8e9883320f404a5d

The Get logs endpoint takes the following parameters:

  • starting-block: the starting block to query for: 23581942 in this case. The Get logs endpoint currently supports a 2000 block range.

  • ending-block: ‘latest’ will get you the latest block. Can also specify a block number.

  • topics: 0xb6fa90c3cb274eb5fe963b2deb623847e804c9d2b0791bee8e9883320f404a5d

  • {API_KEY}: You can replace the <API_KEY> with your own Covalent API key and give it a call in your preferred environment. In the production environment, it’s best to supply the API key in the request header with Basic Auth.

Here is a sample response that I got for the query above (limited to the first 3 items):

JavaScript
{
  "data": {
    "updated_at": "2023-04-28T08:02:35.703133077Z",
    "chain_id": 2020,
    "chain_name": "axie-mainnet",
    "items": [
      {
        "block_signed_at": "2023-04-28T07:48:49Z",
        "block_height": 23614350,
        "block_hash": "0x0ae8e7880d83b001df1673e892e5fdf5d8c2a68c3e29f1bd9195186ffe9ed0af",
        "tx_offset": 1,
        "log_offset": 20,
        "tx_hash": "0x1ad6879026b04b27788e1902d05bda0b202ebb19338770b0146b7dfce35b0190",
        "raw_log_topics": [
          "0xb6fa90c3cb274eb5fe963b2deb623847e804c9d2b0791bee8e9883320f404a5d",
          "0x0000000000000000000000000000000000000000000000000000000000b2c861"
        ],
        "sender_contract_decimals": 0,
        "sender_name": "Axie",
        "sender_contract_ticker_symbol": "AXIE",
        "sender_address": "0x32950db2a7164ae833121501c797d79e7b79d74c",
        "sender_address_label": null,
        "sender_logo_url": "<https://logos.covalenthq.com/tokens/2020/0x32950db2a7164ae833121501c797d79e7b79d74c.png>",
        "raw_log_data": null,
        "decoded": null
      },
      {
        "block_signed_at": "2023-04-28T07:49:49Z",
        "block_height": 23614370,
        "block_hash": "0x0709060d5b712cab57d708e37d84d977814f5cbd9cf28c8cd66b41df5e467180",
        "tx_offset": 0,
        "log_offset": 5,
        "tx_hash": "0x7416d55904fba32ecc75aaa518756ed7a55bb7994e8fbdef8716c65640322266",
        "raw_log_topics": [
          "0xb6fa90c3cb274eb5fe963b2deb623847e804c9d2b0791bee8e9883320f404a5d",
          "0x0000000000000000000000000000000000000000000000000000000000b2c862"
        ],
        "sender_contract_decimals": 0,
        "sender_name": "Axie",
        "sender_contract_ticker_symbol": "AXIE",
        "sender_address": "0x32950db2a7164ae833121501c797d79e7b79d74c",
        "sender_address_label": null,
        "sender_logo_url": "<https://logos.covalenthq.com/tokens/2020/0x32950db2a7164ae833121501c797d79e7b79d74c.png>",
        "raw_log_data": null,
        "decoded": null
      },
      {
        "block_signed_at": "2023-04-28T07:55:37Z",
        "block_height": 23614486,
        "block_hash": "0xd4656681b3369e92eba7af3f3a330187d7fec8363dcae9590c43b16b76db30d7",
        "tx_offset": 1,
        "log_offset": 8,
        "tx_hash": "0x8bf924ed835707c5f8675f2e1976c81a2d956371a28b720982bb3aaaddbbeab2",
        "raw_log_topics": [
          "0xb6fa90c3cb274eb5fe963b2deb623847e804c9d2b0791bee8e9883320f404a5d",
          "0x0000000000000000000000000000000000000000000000000000000000b2c863"
        ],
        "sender_contract_decimals": 0,
        "sender_name": "Axie",
        "sender_contract_ticker_symbol": "AXIE",
        "sender_address": "0x32950db2a7164ae833121501c797d79e7b79d74c",
        "sender_address_label": null,
        "sender_logo_url": "<https://logos.covalenthq.com/tokens/2020/0x32950db2a7164ae833121501c797d79e7b79d74c.png>",
        "raw_log_data": null,
        "decoded": null
      }
				...
    ],
    "pagination": null
  },
  "error": false,
  "error_message": null,
  "error_code": null
}

There are a few data points to highlight:

  • the second item of the raw_log_topics array tells us the Axie ID. It is in hexadecimal so we’ll have to convert it to decimals to display.

  • the tx_hash tells us the transaction that emitted this event.

  • block_signed_at tells us the date and time this event is emitted.

With this data, we can know how many Axies have been spawned within the last 1000 blocks, as well as the time it’s spawned, and what Axie IDs they are. Pretty powerful data already!

However, this is still not enough for us to build a richer picture of who the owners are and how many SLP or AXS tokens it took to spawn the Axies. For us to get more information, we’ll have to use the tx_hash we have and make further calls using Covalent’s Get a transaction endpoint.

3

Getting more data for each Axie spawn

Here is a sample request made with Covalent’s Get a transaction endpoint:

https://api.covalenthq.com/v1/axie-mainnet/transaction_v2/0x1ad6879026b04b27788e1902d05bda0b202ebb19338770b0146b7dfce35b0190/?key={API_KEY}

…using the tx_hash of the first item from our response earlier.

This is what the response looks like:

JavaScript
{
  "data": {
    "updated_at": "2023-04-28T08:25:42.900771069Z",
    "chain_id": 2020,
    "chain_name": "axie-mainnet",
    "items": [
				{
				"block_signed_at": "2023-04-28T07:48:49Z",
				"block_height": 23614350,
				"tx_hash": "0x1ad6879026b04b27788e1902d05bda0b202ebb19338770b0146b7dfce35b0190",
				"tx_offset": 1,
				"successful": true,
				"from_address": "0xb1e6a87f71564d15cdba14546d0462f8b9787ed7",
				"from_address_label": null,
				"to_address": "0x32950db2a7164ae833121501c797d79e7b79d74c",
				"to_address_label": null,
				"value": "0",
				"value_quote": null,
				"gas_offered": 437659,
				"gas_spent": 431230,
				"gas_price": 20000000000,
				"fees_paid": "8624600000000000",
				"gas_quote": null,
				"gas_quote_rate": null,
				"log_events": [
						...
					{
						"block_signed_at": "2023-04-28T07:48:49Z",
						"block_height": 23614350,
						"tx_offset": 1,
						"log_offset": 18,
						"tx_hash": "0x1ad6879026b04b27788e1902d05bda0b202ebb19338770b0146b7dfce35b0190",
						"raw_log_topics": [
						"0xd145ab8597dad93dd168121d96688fa7d06040c6bcffcfda8b8d24b1669e8478",
						"0x0000000000000000000000000000000000000000000000000000000000b1cf6e",
						"0x0000000000000000000000000000000000000000000000000000000000000001"
						],
						"sender_contract_decimals": 0,
						"sender_name": "Axie",
						"sender_contract_ticker_symbol": "AXIE",
						"sender_address": "0x32950db2a7164ae833121501c797d79e7b79d74c",
						"sender_address_label": null,
						"sender_logo_url": "<https://logos.covalenthq.com/tokens/2020/0x32950db2a7164ae833121501c797d79e7b79d74c.png>",
						"raw_log_data": null,
						"decoded": null
					},
          {
            "block_signed_at": "2023-04-28T07:48:49Z",
            "block_height": 23614350,
            "tx_offset": 1,
            "log_offset": 16,
            "tx_hash": "0x1ad6879026b04b27788e1902d05bda0b202ebb19338770b0146b7dfce35b0190",
            "raw_log_topics": [
              "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
              "0x000000000000000000000000b1e6a87f71564d15cdba14546d0462f8b9787ed7",
              "0x0000000000000000000000000000000000000000000000000000000000000000"
            ],
            "sender_contract_decimals": 0,
            "sender_name": "Smooth Love Potion",
            "sender_contract_ticker_symbol": "SLP",
            "sender_address": "0xa8754b9fa15fc18bb59458815510e40a12cd2014",
            "sender_address_label": null,
            "sender_logo_url": "<https://logos.covalenthq.com/tokens/2020/0xa8754b9fa15fc18bb59458815510e40a12cd2014.png>",
            "raw_log_data": "0x0000000000000000000000000000000000000000000000000000000000000708",
            "decoded": {
              "name": "Transfer",
              "signature": "Transfer(indexed address from, indexed address to, uint256 value)",
              "params": [
                {
                  "name": "from",
                  "type": "address",
                  "indexed": true,
                  "decoded": true,
                  "value": "0xb1e6a87f71564d15cdba14546d0462f8b9787ed7"
                },
                {
                  "name": "to",
                  "type": "address",
                  "indexed": true,
                  "decoded": true,
                  "value": "0x0000000000000000000000000000000000000000"
                },
                {
                  "name": "value",
                  "type": "uint256",
                  "indexed": false,
                  "decoded": true,
                  "value": "1800"
                }
              ]
            }
          },
          {
            "block_signed_at": "2023-04-28T07:48:49Z",
            "block_height": 23614350,
            "tx_offset": 1,
            "log_offset": 15,
            "tx_hash": "0x1ad6879026b04b27788e1902d05bda0b202ebb19338770b0146b7dfce35b0190",
            "raw_log_topics": [
              "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
              "0x000000000000000000000000b1e6a87f71564d15cdba14546d0462f8b9787ed7",
              "0x000000000000000000000000a99cacd1427f493a95b585a5c7989a08c86a616b"
            ],
            "sender_contract_decimals": 18,
            "sender_name": "Axie Infinity Shard",
            "sender_contract_ticker_symbol": "AXS",
            "sender_address": "0x97a9107c1793bc407d6f527b77e7fff4d812bece",
            "sender_address_label": null,
            "sender_logo_url": "<https://logos.covalenthq.com/tokens/2020/0x97a9107c1793bc407d6f527b77e7fff4d812bece.png>",
            "raw_log_data": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000",
            "decoded": {
              "name": "Transfer",
              "signature": "Transfer(indexed address from, indexed address to, uint256 value)",
              "params": [
                {
                  "name": "from",
                  "type": "address",
                  "indexed": true,
                  "decoded": true,
                  "value": "0xb1e6a87f71564d15cdba14546d0462f8b9787ed7"
                },
                {
                  "name": "to",
                  "type": "address",
                  "indexed": true,
                  "decoded": true,
                  "value": "0xa99cacd1427f493a95b585a5c7989a08c86a616b"
                },
                {
                  "name": "value",
                  "type": "uint256",
                  "indexed": false,
                  "decoded": true,
                  "value": "500000000000000000"
                }
              ]
            }
          }
        ]
      }
    ],
    "pagination": null
  },
  "error": false,
  "error_message": null,
  "error_code": null
}

As you can see, within the log_events field, we have a lot more raw data to work with. In particular, we have information on how much SLP and AXS tokens are transferred.

"sender_contract_ticker_symbol": "SLP",
						...                 
				 "value": "1800"

						...
"sender_contract_ticker_symbol": "AXS",
						...
				"value": "500000000000000000"

Furthermore, we also get the breed count of our Axie within the log event with the topic hash of 0xd145ab8597dad93dd168121d96688fa7d06040c6bcffcfda8b8d24b1669e8478. This insight is derived from cross-referencing Ronin explorer to find the matching topic hash:

Now that we have most of the data, let’s try our hands at building the recently spawned Axie table!

4

Initialize the project

Enter the following commands:

git clone https://github.com/xiaogit00/building-gamefi.git

cd building-gamefi

npm i

npm start

You should now be able to see this:

This is just a template to get you started. The two main files that are responsible for rendering this are: App.js and /components/AxieRow.js. Everything is styled with App.css.

In the following steps, we’ll fetch the data, clean it, and supply it to our <AxieRow /> component.

5

Fetch the data

There’s two kinds of data to fetch. First, we’ll fetch all the AxieSpawn events in App.js using the Get Logs endpoint, and then use the tx_hash returned to fetch additional data of each transaction using Get a transaction endpoint.

But before that, we’ll need go use the Get a block endpoint to fetch the latest block from the Ronin blockchain. This is to ensure we can supply the correct starting-block to fetch for our Get logs endpoint later.

const apiKey = process.env.REACT_APP_APIKEY
const chainName = 'axie-mainnet'
const topicHash = '0xb6fa90c3cb274eb5fe963b2deb623847e804c9d2b0791bee8e9883320f404a5d'
const getLatestBlockEndpoint = `https://api.covalenthq.com/v1/${chainName}/block_v2/latest/`
const latestBlocksToFetch = 1000

Be sure to create an .env file in the project root and add your API key, like so:

REACT_APP_APIKEY='ckey_8bdxxxxxxxxxxxxxxxxxxxxxxxxxx99'

Then, we shall use an effect hook to call the two endpoints:

useEffect(() => {
    const getLogs = async () => {

      //Fetching latest block
      setLoading(true)
      let latestBlockRes = await fetch(getLatestBlockEndpoint, {method: 'GET', headers: {
        "Authorization": `Basic ${btoa(apiKey + ':')}`
      }})
      latestBlockRes = await latestBlockRes.json()
      const latestBlock = latestBlockRes.data.items[0].height
      
      //Fetching logs
			const getLogsEndpoint = `https://api.covalenthq.com/v1/${chainName}/events/?starting-block=${Number(latestBlock)-latestBlocksToFetch}&ending-block=latest&topics=${topicHash}`
      let getLogsRes = await fetch(getLogsEndpoint, {method: 'GET', headers: {
        "Authorization": `Basic ${btoa(apiKey + ':')}`
      }})
      getLogsRes = await getLogsRes.json()
      setData(getLogsRes.data.items)
      setLoading(false)
    }
    
    getLogs()
      .catch(console.error)
  }, [getLatestBlockEndpoint, apiKey])

The logic for getting the starting block is simply the previous 1000 blocks prior to the latest block: ?starting-block=${Number(latestBlock)-latestBlocksToFetch}.

This will allow us to get data of all the transactions with the AxieSpawn event. If you console log the response, you’ll be able to see the following:

With these inputs in place, we’ll be able to make further calls to the Get a transaction endpoint.

We shall supply each item’s tx_hash into the AxieRow component:

...
if (data) {
    return (
      <div className='container'>
        <p className='title'>Axies spawned in the latest {latestBlocksToFetch} blocks</p>
        {data.map(item => {
          return(
            <AxieRow txHash={item.tx_hash}/>
          )
        })}
      </div>
    );
  }

And within our <AxieRow /> component, define the getTxEndpoint using our tx_hash prop, and fetch the data:

		const apiKey = process.env.REACT_APP_APIKEY
    const chainName = 'axie-mainnet'
    const getTxEndpoint = `https://api.covalenthq.com/v1/${chainName}/transaction_v2/${txHash}/`

    useEffect(() => {
        setLoading(true)
        fetch(getTxEndpoint, {method: 'GET', headers: {
          "Authorization": `Basic ${btoa(apiKey + ':')}`
        }})
          .then(res => res.json())
          .then(res => {
            setLoading(false)
            console.log(res.data.items)
            setData(res.data.items)
          })
          .catch(err => console.log(err.message))
      }, [getTxEndpoint, apiKey])

Running npm start, you should be able to see the following in the dev console:

Congratulations, we’ve fetched all the data that we need.

6

Supply it as props to our components

Finally, simply replace the placeholder values within the JSX of the <AxieRow /> component with our live data to render the component. Here’s what it looks like (with some data cleaning involved):

return (
  <>
      <div className='sectionContainer'>
          {data.map(item => {
              return (
                  <div className='rowTxn' key={item}>
                      <div className='txnContainer'> 
                          <SpawnDate date={new Date(item.block_signed_at).toLocaleString('en-US', { day: 'numeric', month: 'short' })} />
                          <AxieLogo imgUrl={'<https://res.cloudinary.com/dl4murstw/image/upload/v1682603151/axie_logo_kb5omk.png>'}/>
                          <TwoRowText 
                              top={'Axie #' + parseInt(item.log_events[1].raw_log_topics[1], 16) + ' Spawned'} 
                              bottom={'ronin:' + truncateEthAddress(item.from_address.slice(2))} 
                              address={item.from_address}
                          />
                          <TokensTransferred axs={Number(item.log_events[6].decoded.params[2].value)/(10**18)} slp={item.log_events[5].decoded.params[2].value}/>
                          <Breed breed={parseInt(item.log_events[3].raw_log_topics[2], 16)} />
                          <Gas feesPaid={(Number(item.fees_paid) / (10**18)).toFixed(5)}/>
                          <ViewTxn txHash={item.tx_hash}/>
                      </div>
                  </div>
                  )
              }
          )}
      </div>
  </>
)

And running npm start:

All in a day’s work

Congratulations. You’ve successfully managed to build a react component that gets live on-chain data from the Ronin chain of an important Axie in-game event. Hopefully, you’ll be able to see the process of how that’s done with the Covalent API, and tried it yourself. The concepts illustrated in this guide can also be applied to analyze other kinds of in-game events, provided you know the topic hash of the event emitted - making this a very powerful tool for diving deep into GameFi on-chain actions.