sigil
All patterns
advancedcookbook

Image-and-Mint

Generate an image in a TEE, mint it as an ERC-721 in one flow.

User pays, contract calls the Image precompile (two-phase async), receives content URI in callback, mints NFT pointing at the verified output.

Architecture


  ┌───────┐  pay+prompt   ┌───────────┐  Phase 1   ┌──────────┐
  │ User  │ ────────────▶ │ Minter    │ ─────────▶ │ Image    │
  └───────┘               │ contract  │  (commit)  │ 0x0818   │
                          └───────────┘            └─────┬────┘
                                ▲                        │
                                │   Phase 2 callback     │ render
                                │   (AsyncDelivery)      │ in TEE
                                └────────────────────────┘
                                            │
                                            ▼
                                      ERC-721 minted

Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {PrecompileConsumer} from "./utils/PrecompileConsumer.sol";

contract Minter is ERC721, PrecompileConsumer {
    address constant IMAGE          = address(0x818);
    address constant ASYNC_DELIVERY = 0x5A16214fF555848411544b005f7Ac063742f39F6;

    mapping(bytes32 => address) public pending;
    mapping(uint256 => string) public uri;
    uint256 public nextId;

    constructor() ERC721("Sigil Mint", "SGL") {}

    function commission(string calldata prompt) external payable returns (bytes32 jobId) {
        require(msg.value >= 0.01 ether, "fee");
        // Submit image generation (two-phase). Returns jobId.
        // Body simplified — see /playground/image for full ABI.
        jobId = _submitImage(prompt);
        pending[jobId] = msg.sender;
    }

    /// @notice AsyncDelivery callback after Phase 2 settles.
    function onLongRunningResult(bytes32 jobId, bytes calldata result) external {
        require(msg.sender == ASYNC_DELIVERY, "unauth");
        address recipient = pending[jobId];
        require(recipient != address(0), "unknown");

        (string memory contentUri, , , ) =
            abi.decode(result, (string, bytes32, uint256, uint256));

        uint256 id = ++nextId;
        uri[id] = contentUri;
        _mint(recipient, id);
        delete pending[jobId];
    }

    function _submitImage(string calldata prompt) internal returns (bytes32) {
        // truncated — the Image precompile uses ModalInput[] + OutputConfig
    }
}