Content Creators
Scene blockchain operations

Scene blockchain operations

A Decentraland scene can interface with the Ethereum blockchain. This can serve to obtain data about the user’s wallet and the tokens in it, or to trigger transactions that could involve any Ethereum token, fungible or non-fungible. This can be used in many ways, for example to sell tokens, to reward tokens as part of a game-mechanic, to change how a player interacts with a scene if they own certain tokens, etc.

You use the Ethers.js library in Decentraland scenes, this is a popular 3rd party library to interact with the Ethereum blockchain.

Note that all transactions in the Ethereum mainnet that are triggered by a scene will require a player to approve and pay a gas fee.

All blockchain operations also need to be carried out as asynchronous functions , since the timing depends on external events.

When running a preview of a scene that uses one of the ethereum libraries, you must open the preview in a separate browser window, have Metamask open in your browser, and manually include the string &ENABLE_WEB3.

If using the CLI, run the preview with:

npm run start -- --web3

Get a player’s ethereum account #

To get a player’s Ethereum account, use the getPlayer() function.

import { getPlayer } from '@dcl/sdk/src/players'

export function main() {
	let userData = getPlayer()
	if (!userData.isGuest) {
		console.log(userData.userId)
	} else {
		log('Player is not connected with Web3')
	}
}

Note that if a player has entered Decentraland as a guest, they will not have a connected ethereum wallet. If they are connected as guests, the isGuest field in the response from getPlayer() will be true. If hasConnectedWeb3 is true, then you can obtain the player’s address from the field publicKey. Learn more about the data you can obtain from a player in get player data

You should wrap the function in an async() function, learn more about this in async functions

📔 Note: Even though the eth address may contain upper case characters, some browsers convert the returned string to lower case automatically. If you wish compare address values and have it work on all browsers, use the .toLowerCase() method to convert the value into lower case.
-->

Check gas price #

After importing the eth-connect library, you must instance a web3 provider and a request manager, which will will allow you to connect via web3 to Metamask in the player’s browser.

The function below fetches the current gas price in the Ethereum main network and prints it.

import { RequestManager } from 'eth-connect'
import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'

executeTask(async function () {
	// create an instance of the web3 provider to interface with Metamask
	const provider = createEthereumProvider()
	// Create the object that will handle the sending and receiving of RPC messages
	const requestManager = new RequestManager(provider)
	// Check the current gas price on the Ethereum network
	const gasPrice = await requestManager.eth_gasPrice()
	// log response
	console.log({ gasPrice })
})
💡 Tip: Note that the functions handled by the requestManager must be called using await, since they rely on fetching external data and can take some time to be completed.

Import a contract ABI #

An ABI (Application Binary Interface) describes how to interact with an Ethereum contract, determining what functions are available, what inputs they take and what they output. Each Ethereum contract has its own ABI, you should import the ABIs of all the contracts you wish to use in your project.

For example, here’s an example of one function in the MANA ABI:

{
  anonymous: false,
  inputs: [
    {
      indexed: true,
      name: 'burner',
      type: 'address'
    },
    {
      indexed: false,
      name: 'value',
      type: 'uint256'
    }
  ],
  name: 'Burn',
  type: 'event'
}

ABI definitions can be quite lengthy, as they often include a lot of functions, so we recommend pasting the JSON contents of an ABI file into a separate .ts file and importing it into other scene files from there. We also recommend holding all ABI files in a separate folder of your scene, named /contracts.

import { abi } from '../contracts/mana'

Here are links to different Decentraland contracts. Obtain the ABI for each contract by clicking Export ABI and choosing JSON Format.

These are the contracts for the various wearable collections: (each collection was emitted as a separate contract)

💡 Tip: To clearly see the functions exposed by a contract, open it in abitopic.io . Just paste the contract address there and open the functions tab to see the full list of supported functions and their arguments. You can even test calling the functions with different parameters via the webpage.

Configuring TypeScript to be able to import from a JSON file has its difficulties. The recommended easier workaround is to change the ABI.JSON file’s extension to .ts and modifying it slightly so that it its content starts with export default.

For example, if the ABI file’s contents starts with [{"constant":true,"inputs":[{"internalType":"bytes4" ...etc, modify it so that it starts with export default [{"constant":true,"inputs":[{"internalType":"bytes4" ...etc.

Instance a contract #

After importing the eth-connect library and a contract’s abi, you must instance several objects that will allow you to use the functions in the contract and connect to Metamask in the player’s browser.

You must also import the web3 provider. This is because Metamask in the player’s browser uses web3, so we need a way to interact with that.

import { RequestManager, ContractFactory } from 'eth-connect'
import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'
import { abi } from '../contracts/mana'

executeTask(async () => {
	// create an instance of the web3 provider to interface with Metamask
	const provider = createEthereumProvider()
	// Create the object that will handle the sending and receiving of RPC messages
	const requestManager = new RequestManager(provider)
	// Create a factory object based on the abi
	const factory = new ContractFactory(requestManager, abi)
	// Use the factory object to instance a `contract` object, referencing a specific contract
	const contract = (await factory.at(
		'0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb'
	)) as any
})
💡 Tip: For contracts that follow a same standard, such as ERC20 or ERC721, you can import a single generic ABI for all. You then generate a single ContractFactory object with that ABI and use that same factory to instance interfaces for each contract.

Call the methods in a contract #

Once you’ve created a contract object, you can easily call the functions that are defined in its ABI, passing it the specified input parameters.

import { getPlayer } from '@dcl/sdk/src/players'
import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'
import { RequestManager, ContractFactory } from 'eth-connect'
import { abi } from '../contracts/mana'

executeTask(async () => {
	try {
		// Setup steps explained in the section above
		const provider = createEthereumProvider()
		const requestManager = new RequestManager(provider)
		const factory = new ContractFactory(requestManager, abi)
		const contract = (await factory.at(
			'0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb'
		)) as any
		let userData = getPlayer()
		if (userData.isGuest) {
			return
		}

		// Perform a function from the contract
		const res = await contract.setBalance(
			'0xaFA48Fad27C7cAB28dC6E970E4BFda7F7c8D60Fb',
			100,
			{
				from: userData.userId,
			}
		)
		// Log response
		console.log(res)
	} catch (error: any) {
		console.log(error.toString())
	}
})

The example above uses the abi for the Ropsten MANA contract and transfers 100 fake MANA to your account in the Ropsten test network.

Other functions #

The eth-connect library includes a number of other helpers you can use. For example to:

  • Get an estimated gas price
  • Get the balance of a given address
  • Get a transaction receipt
  • Get the number of transactions sent from an address
  • Convert between various formats including hexadecimal, binary, utf8, etc.

Using the Ethereum test network #

While testing your scene, to avoid transferring real MANA currency, you can use the Ethereum Goerli test network and transfer fake MANA instead.

To use the test network you must set your Metamask Chrome extension to use the Goerli test network instead of Main network.

You must acquire Goerli Ether, which you can obtain for free from various external faucets like this one .

If your transactions also involve MANA, you can also obtain free Goerli MANA from our Goerli MANA faucet .

💡 Tip: To run the transaction of transferring Goerli MANA to your wallet, you will need to pay a gas fee in Goerli Ether.

To preview your scene using the test network, add the DEBUG property to the URL you’re using to access the scene preview on your browser. For example, if you’re accessing the scene via http://127.0.0.1:8000/?position=0%2C-1, you should set the URL to http://127.0.0.1:8000/?DEBUG&position=0%2C-1.

Any transactions that you accept while viewing the scene in this mode will only occur in the test network and not affect the MANA balance in your real wallet.

If you need to test transactions in the Polygon Testnet and need to have MANA on that testnet, you’ll need to swap MANA to that network after acquiring it in Goerli. To bridge Goerli MANA to the Polygon Testnet, visit your Decentraland account page in Goerli and click on ‘swap’ on the Ethereum MANA side.

Send custom RPC messages #

Use the function sendAsync() to send messages over RPC protocol .

import { sendAsync } from '~system/EthereumController'

// send a message
await sendAsync({
	id: 1,
	method: 'myMethod',
	jsonParams: '{ myParam: myValue }',
})

Decentraland smart contracts #

In the following link you can find a list of Etherum smart contracts relevant to the Decentraland ecosystem. The list includes the contracts in mainnet as well as in other Ethereum test networks.

contracts.decentraland.org