nft-blockchain
SKILL.md
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 // 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 '@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 {
// Check MANA balance
const balance = await manaUser.balance()
console.log('MANA balance:', balance)
// Send MANA to another address
const result = await manaUser.send('0x123...abc', 100) // 100 MANA
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
// 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
Weekly Installs
9
Repository
dcl-regenesisla…/opendclGitHub Stars
2
First Seen
Feb 25, 2026
Security Audits
Installed on
opencode9
gemini-cli9
github-copilot9
codex9
kimi-cli9
cursor9