Dynamic NFT Development (Updatable Metadata)

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Dynamic NFT Development (Updatable Metadata)
Complex
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of Dynamic NFT (Updatable Metadata)

A static NFT is a picture file tied to a token forever. A dynamic NFT is a token whose metadata changes in response to events: time passes, a game is played, asset price changes, user reaches a new level. Technically, this means tokenURI() returns different data at different times. There are several implementations, and the choice between them determines decentralization, cost of updates, and development complexity.

Three Dynamic NFT Architectures

On-chain Metadata via SVG Generation

The most decentralized option: metadata and image are generated directly in the smart contract, no external storage. tokenURI() returns base64-encoded JSON with base64-encoded SVG inside.

function tokenURI(uint256 tokenId) public view override returns (string memory) {
    uint256 level = playerLevel[tokenId];
    string memory svg = generateSVG(level);
    string memory json = Base64.encode(bytes(string(abi.encodePacked(
        '{"name": "Warrior #', tokenId.toString(), '",',
        '"attributes": [{"trait_type": "Level", "value": ', level.toString(), '}],',
        '"image": "data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '"}'
    ))));
    return string(abi.encodePacked("data:application/json;base64,", json));
}

Advantage: no dependency on IPFS, servers, or oracles. Disadvantage: complex visual content is limited by SVG capabilities. Suitable for gamified NFTs, badges, achievement tokens.

A common mistake with on-chain SVG: concatenating strings via abi.encodePacked with user data. If playerName contains quotes—the JSON breaks. Always sanitize string data before including it in JSON.

Chainlink Functions for External Data

When metadata must reflect real data—ETH price, match result, weather—an oracle is needed. Chainlink Functions allows running arbitrary JavaScript off-chain and delivering the result on-chain.

// Request via Chainlink Functions
function requestMetadataUpdate(uint256 tokenId) external {
    FunctionsRequest.Request memory req;
    req.initializeRequestForInlineJavaScript(
        "const price = await fetch('https://api.coingecko.com/...')..."
    );
    bytes32 requestId = _sendRequest(req.encodeCBOR(), subscriptionId, gasLimit, donId);
    requestToToken[requestId] = tokenId;
}

// Receiving the result
function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) 
    internal override {
    uint256 tokenId = requestToToken[requestId];
    tokenData[tokenId] = abi.decode(response, (uint256));
    emit MetadataUpdate(tokenId); // EIP-4906
}

EIP-4906—a standard for notifying marketplaces about metadata changes. OpenSea and others listen to the MetadataUpdate(tokenId) event and refresh the cache. Without this event, the marketplace shows stale data until the next manual sync.

Cost of one request via Chainlink Functions: 0.2-2 LINK depending on computations. For daily updates of 1,000 tokens—~$150-1,500/month. This needs to be factored into the project's tokenomics.

Upgradeable URI with IPFS and Timelock

Less decentralized but most flexible: the contract stores baseURI which can be updated by the owner. Each metadata update—a new version is uploaded to IPFS, the owner changes baseURI to the new CID.

This approach carries trust risk: the owner can substitute metadata arbitrarily (rug the art). For mitigation—a timelock on setBaseURI() with a 48-72 hour delay. Users see the planned change in advance.

Best practice: store the history of all baseURI on-chain (append-only array)—users can always check previous metadata versions.

Gaming NFTs: State Machine On-chain

For gaming projects, dynamic NFTs often implement a state machine: Egg → Baby → Adult → Legendary. Each transition is a separate transaction checking conditions (time passed, experience gained, quest completed).

enum State { Egg, Baby, Adult, Legendary }
mapping(uint256 => State) public tokenState;
mapping(uint256 => uint256) public experience;

function evolve(uint256 tokenId) external {
    require(ownerOf(tokenId) == msg.sender, "Not owner");
    State current = tokenState[tokenId];
    
    if (current == State.Egg) {
        require(block.timestamp >= hatchTime[tokenId], "Not ready");
        tokenState[tokenId] = State.Baby;
    } else if (current == State.Baby) {
        require(experience[tokenId] >= 1000, "Insufficient XP");
        tokenState[tokenId] = State.Adult;
    }
    
    emit MetadataUpdate(tokenId); // EIP-4906
}

State is stored on-chain, images for each state—on IPFS. tokenURI() returns different CIDs depending on tokenState[tokenId].

Marketplace Integration Considerations

OpenSea caches metadata aggressively. Even with the correct MetadataUpdate event, UI updates may take up to 24 hours. For projects with critical updates (e.g., tournament results), we recommend:

  • Adding a forceUpdate endpoint via OpenSea API
  • Showing current state on your own site via direct RPC call

Blur, Gem, Reservoir handle EIP-4906 events more quickly, usually within an hour.

Development Process

Analysis (1-2 days). Define update triggers (time, event, external data), frequency, data sources. Choose architecture: on-chain SVG, Chainlink Functions, or upgradeable URI.

Development (3-6 days). Depends on architecture: on-chain SVG with simple generation—3 days; Chainlink Functions integration with external API—5-6 days; gaming state machine—4-5 days.

Testing (1-2 days). Fork-tests with Chainlink Functions on testnet (Fuji for Avalanche, Sepolia for Ethereum), verify EIP-4906 events on OpenSea testnet.

Cost is calculated after choosing architecture and determining data sources.