| name | nft-blockchain |
| description | Display NFT artwork and interact with blockchain from Decentraland scenes. Show NFTs using NftShape with frame styles, check player wallet with getPlayer, sign messages with signedFetch, interact with smart contracts using eth-connect and createEthereumProvider, and handle MANA transactions. Use when user wants NFTs, blockchain, wallet, smart contracts, Web3, or crypto. |
NFT and Blockchain in Decentraland
Display NFT Artwork
Show an NFT from Ethereum in a decorative frame:
import { engine, Transform, NftShape, NftFrameType } from '@dcl/sdk/ecs'
import { Vector3, Color4 } from '@dcl/sdk/math'
const nftFrame = engine.addEntity()
Transform.create(nftFrame, {
position: Vector3.create(8, 2, 8),
rotation: Quaternion.fromEulerDegrees(0, 0, 0)
})
NftShape.create(nftFrame, {
urn: 'urn:decentraland:ethereum:erc721:0x06012c8cf97bead5deae237070f9587f8e7a266d:558536',
color: Color4.White(),
style: NftFrameType.NFT_CLASSIC
})
NFT URN Format
urn:decentraland:ethereum:erc721:<contractAddress>:<tokenId>
- Works with any ERC-721 NFT on Ethereum mainnet
- The image is loaded automatically from the NFT's metadata
Available Frame Styles
NftFrameType.NFT_CLASSIC
NftFrameType.NFT_BAROQUE_ORNAMENT
NftFrameType.NFT_DIAMOND_ORNAMENT
NftFrameType.NFT_MINIMAL_WIDE
NftFrameType.NFT_MINIMAL_GREY
NftFrameType.NFT_BLOCKY
NftFrameType.NFT_GOLD_EDGES
NftFrameType.NFT_GOLD_CARVED
NftFrameType.NFT_GOLD_WIDE
NftFrameType.NFT_GOLD_ROUNDED
NftFrameType.NFT_METAL_MEDIUM
NftFrameType.NFT_METAL_WIDE
NftFrameType.NFT_METAL_SLIM
NftFrameType.NFT_METAL_ROUNDED
NftFrameType.NFT_PINS
NftFrameType.NFT_MINIMAL_BLACK
NftFrameType.NFT_MINIMAL_WHITE
NftFrameType.NFT_TAPE
NftFrameType.NFT_WOOD_SLIM
NftFrameType.NFT_WOOD_WIDE
NftFrameType.NFT_WOOD_TWIGS
NftFrameType.NFT_CANVAS
NftFrameType.NFT_NONE
Check Player Wallet
import { getPlayer } from '@dcl/sdk/src/players'
function checkWallet() {
const player = getPlayer()
if (player && !player.isGuest) {
console.log('Player wallet address:', player.userId)
} else {
console.log('Player is guest (no wallet)')
}
}
Always check isGuest before attempting any blockchain interaction — guest players don't have a connected wallet.
Signed Requests
Send authenticated requests to a backend, signed with the player's wallet:
import { signedFetch } from '@dcl/sdk/signed-fetch'
executeTask(async () => {
try {
const response = await signedFetch('https://example.com/api/action', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'claimReward',
amount: 100
})
})
const result = await response.json()
console.log('Result:', result)
} catch (error) {
console.log('Request failed:', error)
}
})
signedFetch automatically includes a cryptographic signature proving the player's identity. Your backend can verify this signature to authenticate requests.
MANA Transactions
import { manaUser } from '@dcl/sdk/ethereum'
executeTask(async () => {
try {
const balance = await manaUser.balance()
console.log('MANA balance:', balance)
const result = await manaUser.send('0x123...abc', 100)
console.log('MANA sent:', result)
} catch (error) {
console.log('MANA transaction failed:', error)
}
})
Smart Contract Interaction
Requires the eth-connect package:
npm install eth-connect
Store ABI in a Separate File
export default [
{
"constant": true,
"inputs": [{ "name": "_owner", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "name": "balance", "type": "uint256" }],
"type": "function"
}
]
Create Contract Instance
import { RequestManager, ContractFactory } from 'eth-connect'
import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'
import { abi } from '../contracts/myContract'
executeTask(async () => {
try {
const provider = createEthereumProvider()
const requestManager = new RequestManager(provider)
const factory = new ContractFactory(requestManager, abi)
const contract = await factory.at('0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb') as any
const balance = await contract.balanceOf('0x123...abc')
console.log('Balance:', balance)
} catch (error) {
console.log('Contract interaction failed:', error)
}
})
Write Operations (Require Gas)
executeTask(async () => {
try {
const userData = getPlayer()
if (userData.isGuest) return
const writeResult = await contract.transfer(
'0xRecipientAddress',
100,
{
from: userData.userId,
gas: 100000,
gasPrice: await requestManager.eth_gasPrice()
}
)
console.log('Transaction hash:', writeResult)
} catch (error) {
console.log('Transaction failed:', error)
}
})
Gas Price and Balance Checking
import { RequestManager } from 'eth-connect'
import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'
executeTask(async () => {
const provider = createEthereumProvider()
const requestManager = new RequestManager(provider)
const gasPrice = await requestManager.eth_gasPrice()
console.log('Current gas price:', gasPrice)
const balance = await requestManager.eth_getBalance('0x123...abc', 'latest')
console.log('Account balance:', balance)
})
Testing with Sepolia
For development, use the Sepolia testnet:
- Set MetaMask to Sepolia network
- Get test ETH from a Sepolia faucet
- Deploy your contracts to Sepolia
- Contract addresses differ between mainnet and testnet — use environment checks
Custom RPC Calls
Use sendAsync for low-level Ethereum RPC calls not covered by eth-connect helpers:
import { sendAsync } from '~system/EthereumController'
const result = await sendAsync({ method: 'eth_blockNumber', params: [] })
console.log('Current block:', result.body)
Opening URLs and NFT Dialogs
Use restricted actions to open external links and NFT detail views:
import { openExternalUrl, openNftDialog } from '~system/RestrictedActions'
openExternalUrl({ url: 'https://opensea.io/collection/...' })
openNftDialog({ urn: 'urn:decentraland:ethereum:erc721:0x06012c8cf97BEaD5deAe237070F9587f8E7A266d:558536' })
Best Practices
- Always check
isGuest before any blockchain interaction — guest players can't sign transactions
- Use
executeTask(async () => { ... }) for all async blockchain calls
- Store ABI files separately (e.g.,
contracts/) — don't inline large ABIs
- Handle errors gracefully — blockchain operations can fail (rejected by user, insufficient gas, network issues)
eth-connect must be installed as a dependency: npm install eth-connect
- Use
signedFetch for backend authentication instead of raw fetch — it proves the player's identity
- Read operations (view/pure functions) don't require gas; write operations prompt the user to sign
- Test on Sepolia before deploying to mainnet
- NFT URNs only work with Ethereum mainnet ERC-721 tokens