About nft-blockchain
A Decentraland development skill covering NFT display and blockchain operations in SDK scenes. It outlines wallet checks, signed backend requests, contract calls, token gating, and common MANA, token, NFT, and marketplace tasks.
Key features
- 01Display NFT artwork with frames
- 02Check player wallet and balances
- 03Send signed backend requests
- 04Read and write smart contracts
- 05Token gating by NFT or balance
- 06MANA, token, NFT, and marketplace operations
Use cases
- 01Showing NFT artwork in a scene
- 02Restricting access by token ownership
- 03Sending tokens or transferring NFTs
- 04Buying or listing NFTs in a marketplace
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ decentraland/ sdk-skills --skill nft-blockchainAfter installing, type/nft-blockchainin Claude Code to confirm it loaded.
Bundled filesreferences/blockchain-patterns.md
Details
From the project READMENFT and Blockchain in Decentraland
Display NFT Artwork
Use NftShape to show an NFT in a decorative picture frame. Provide the NFT URN and choose a frame style. The image is loaded automatically from the NFT's metadata (fetched through Decentraland's OpenSea proxy, opensea.decentraland.org) — any NFT that OpenSea supports can be displayed, across multiple chains.
NFT URN format: urn:decentraland:<chain>:<contractStandard>:<contractAddress>:<tokenId>
- Example:
urn:decentraland:ethereum:erc721:0x06012c8cf97bead5deae237070f9587f8e7a266d:558536 <chain>supported values (forwarded verbatim to the OpenSea v2 API; unrecognized chains are rejected):ethereum,matic,klaytn,bsc,arbitrum,arbitrum_nova,avalanche,optimism,solana,base,blast,zora.<contractStandard>is typicallyerc721. The image is served from the OpenSea response, so contracts OpenSea indexes aserc1155also resolve.
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
Use getPlayer() from @dcl/sdk/src/players to get the player's Ethereum address via player.userId. Always check isGuest before any blockchain interaction -- guest players don't have a connected wallet.
Signed Requests
Use signedFetch from ~system/SignedFetch to send authenticated requests to a backend. It automatically injects signed identity headers (ADR-44) that your backend verifies — you do not build or pass them yourself.
- Signature:
signedFetch({ url, init: { method?, headers?, body? } }). - Response is
{ ok, status, statusText, headers, body };bodyis a string — callJSON.parse(response.body)(there is no.json()). - It does not require prior player interaction (unlike restricted actions).
- Need only the signed headers for a library that does its own fetching? Use
getHeaders({ url, init? })from the same module — it returns{ headers }.
Smart Contract Interaction
For direct smart contract calls, use eth-connect with createEthereumProvider from @dcl/sdk/ethereum-provider. Store ABIs in separate files, create a contract instance via ContractFactory, then call read (no gas) or write (requires gas, prompts user to sign) functions.
npm install eth-connect
Read operations (view/pure functions) don't require gas. Write operations prompt the player to sign and require gas.
Wrap all async blockchain calls in executeTask(async () => { ... }). Handle errors gracefully — blockchain operations can fail (rejected by user, insufficient gas, network issues).
Gas Price and Balance
Use requestManager.eth_gasPrice() and requestManager.eth_getBalance() from eth-connect to check current gas prices and account ETH balances.
Custom RPC Calls
Use sendAsync from ~system/EthereumController for low-level Ethereum RPC calls not covered by eth-connect helpers.
Opening External URLs / NFT Dialogs
Use openExternalUrl and openNftDialog from ~system/RestrictedActions to open external links and NFT detail views.
Testing with Sepolia
For development, use the Sepolia testnet: set MetaMask to Sepolia, get test ETH from a faucet, deploy contracts to Sepolia. Contract addresses differ between mainnet and testnet.
dcl-crypto-toolkit (Higher-Level API)
For common blockchain operations, use dcl-crypto-toolkit instead of raw eth-connect. It provides a cleaner API for the most frequent tasks.
npm install dcl-crypto-toolkit
Import: import * as crypto from 'dcl-crypto-toolkit'. Modules: ethereum, mana, currency, nft, marketplace, services, wearable, contract. There is NO top-level crypto.signMessage.
Capabilities:
- MANA operations: send, check balance (
crypto.mana.send/.myBalance/.balance) - ERC20 tokens: send, check balance, check/set allowance (
crypto.currency.*) - ERC721/NFT: check tokens held (token gating), transfer, approval management (
crypto.nft.*) - Marketplace: buy (
executeOrder), sell (createOrder), cancel (cancelOrder), check authorization (crypto.marketplace.*) - Sign message: sign EIP-712 typed data with player wallet (
crypto.ethereum.signMessageAdvanced())
Token Gating
By NFT ownership: Use crypto.nft.checkTokens(contractAddress, tokenIds?) — returns whether the player holds tokens of that contract. Omit tokenIds to check any token of the contract. Grant or deny access on the result.
By MANA balance: Check crypto.mana.myBalance() (or crypto.currency.balance(contractAddress, address) for other ERC20 tokens) to gate access based on holdings.
Quick Decision Guide
| Task | Use |
|---|---|
| Send MANA | crypto.mana.send() |
| Check own MANA balance | crypto.mana.myBalance() |
| Check any address's MANA balance | crypto.mana.balance(address) |
| Send any ERC20 token | crypto.currency.send() |
| Check ERC20 balance | crypto.currency.balance(contract, address) |
| Transfer an NFT | crypto.nft.transfer() |
| Check NFT ownership / token gating | crypto.nft.checkTokens() |
| Buy from marketplace | crypto.marketplace.executeOrder() |
| List NFT for sale | crypto.marketplace.createOrder() |
| Sign a message | crypto.ethereum.signMessageAdvanced() |
| Custom smart contract | eth-connect (see above) |
| Authenticated API call | signedFetch (see above) |
Example scenes
Engine-team test scenes exercising these APIs against the real runtime:
- https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/5,90-scene-bounds-check —
NftShape.create()with a liveurn:decentraland:ethereum:erc721:<contract>:<tokenId>URN, mounted on a moving platform that carries it across the parcel boundary (so it also shows an NFT frame being culled out of bounds). - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/80,-4-restricted-actions —
openNftDialog({ urn })andopenExternalUrldriven from React-ECS buttons, alongside every other RestrictedAction. Note the scene declares neitherOPEN_EXTERNAL_LINKnor an NFT permission inrequiredPermissionsand both still run. - https://github.com/decentraland/sdk7-test-scenes/tree/main/scenes/66,6-signed-fetch —
signedFetchon click; readsresponse.ok/.status/.bodyand inspects the auto-added signature headers, with an emptyrequiredPermissions.
For full code examples and implementation patterns, including the dcl-crypto-toolkit library API, see '{baseDir}/references/blockchain-patterns.md'.