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 mintedCode
// 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
}
}