nft-blockchain
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, Quaternion, 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 // Simple classic frame
NftFrameType.NFT_BAROQUE_ORNAMENT // Ornate baroque
NftFrameType.NFT_DIAMOND_ORNAMENT // Diamond pattern
NftFrameType.NFT_MINIMAL_WIDE // Minimal wide border
NftFrameType.NFT_MINIMAL_GREY // Minimal grey border
NftFrameType.NFT_BLOCKY // Pixelated/blocky
NftFrameType.NFT_GOLD_EDGES // Gold edge trim
NftFrameType.NFT_GOLD_CARVED // Carved gold
NftFrameType.NFT_GOLD_WIDE // Wide gold border
NftFrameType.NFT_GOLD_ROUNDED // Rounded gold
NftFrameType.NFT_METAL_MEDIUM // Medium metal
NftFrameType.NFT_METAL_WIDE // Wide metal
NftFrameType.NFT_METAL_SLIM // Slim metal
NftFrameType.NFT_METAL_ROUNDED // Rounded metal
NftFrameType.NFT_PINS // Pinned to wall
NftFrameType.NFT_MINIMAL_BLACK // Minimal black
NftFrameType.NFT_MINIMAL_WHITE // Minimal white
NftFrameType.NFT_TAPE // Taped to wall
NftFrameType.NFT_WOOD_SLIM // Slim wood
NftFrameType.NFT_WOOD_WIDE // Wide wood
NftFrameType.NFT_WOOD_TWIGS // Twig/branch wood
NftFrameType.NFT_CANVAS // Canvas style
NftFrameType.NFT_NONE // No frame
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)
// userId is the Ethereum wallet address
} 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 '~system/SignedFetch'
executeTask(async () => {
try {
const response = await signedFetch({
url: 'https://example.com/api/action',
init: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'claimReward',
amount: 100
})
}
})
if (!response.ok) {
console.error('HTTP error:', response.status)
return
}
const result = JSON.parse(response.body)
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.
Smart Contract Interaction
Requires the eth-connect package:
npm install eth-connect
Store ABI in a Separate File
// contracts/myContract.ts
export default [
{
"constant": true,
"inputs": [{ "name": "_owner", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "name": "balance", "type": "uint256" }],
"type": "function"
}
// ... rest of ABI
]
Create Contract Instance
import { RequestManager, ContractFactory } from 'eth-connect'
import { createEthereumProvider } from '@dcl/sdk/ethereum-provider'
import { abi } from '../contracts/myContract'
executeTask(async () => {
try {
// Create web3 provider
const provider = createEthereumProvider()
const requestManager = new RequestManager(provider)
// Create contract at a specific address
const factory = new ContractFactory(requestManager, abi)
const contract = await factory.at('0x2a8fd99c19271f4f04b1b7b9c4f7cf264b626edb') as any
// Read data (no gas required)
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
// Write operation — prompts the player to sign the transaction
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
isGuestbefore 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-connectmust be installed as a dependency:npm install eth-connect- Use
signedFetchfor backend authentication instead of rawfetch— 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
More from dcl-regenesislabs/opendcl
optimize-scene
Optimize Decentraland scene performance. Scene limit formulas (triangles, entities, materials, textures, height per parcel count), object pooling, LOD patterns, texture optimization, system throttling, and asset preloading. Use when the user wants to optimize, improve performance, fix lag, reduce load time, check limits, or reduce entity/triangle count. Do NOT use for deployment (see deploy-scene).
51game-design
Plan and design Decentraland games and interactive experiences. Scene limit formulas, performance budgets, texture requirements, asset preloading, state management patterns (module-level, component-based, state machines), object pooling, UX/UI guidelines, input design, and MVP planning. Use when the user wants game design advice, scene architecture, performance planning, or help structuring a game. Do NOT use for specific implementation (see add-interactivity, build-ui, multiplayer-sync).
30audio-video
Add sound effects, music, audio streaming, and video players to Decentraland scenes. Covers AudioSource (local files), AudioStream (streaming URLs), VideoPlayer (video surfaces), video events, and media permissions. Use when the user wants sound, music, audio, video screens, radio, or media playback. Do NOT use for 3D model animations (see animations-tweens).
30add-3d-models
Add 3D models (.glb/.gltf) to a Decentraland scene using GltfContainer. Covers loading, positioning, scaling, colliders, parenting, and browsing 5,700+ free assets from the OpenDCL catalog. Use when the user wants to add models, import GLB files, find free 3D assets, or set up model colliders. Do NOT use for materials/textures (see advanced-rendering) or model animations (see animations-tweens).
28build-ui
Build 2D screen-space UI for Decentraland scenes using React-ECS (JSX). Create HUDs, menus, health bars, scoreboards, dialogs, buttons, inputs, and dropdowns. Use when the user wants screen overlays, on-screen UI, HUD elements, menus, or form inputs. Do NOT use for 3D in-world text (see advanced-rendering) or clickable 3D objects (see add-interactivity).
26advanced-input
Advanced input handling in Decentraland. PointerLock (cursor capture state), InputModifier (freeze/restrict player movement), PrimaryPointerInfo (cursor position and world ray), WASD keyboard patterns, and action bar slots. Use when the user wants movement restriction, cursor control, FPS controls, input polling, or cutscene freezing. Do NOT use for basic click/hover events on entities (see add-interactivity).
26