Skip to main content

ETH Minter Warehousing Adapter

Virtual warehousing for tokenized (NFT) products. Creates token records for ERC-721 and ERC-1155 contracts and serves ERC-compatible token metadata.

Not for event tickets

The ETH minter knows nothing about events: no entry window, cancelled tokens still count against the supply. For tickets use the ticket issuer of @unchainedshop/ticketing (Event Ticketing). Only run one active VIRTUAL provider per tokenized product: every active one issues tokens.

Included in Crypto Preset

Registered automatically by registerCryptoPlugins() and registerAllPlugins().

If you register plugins individually:

import { pluginRegistry } from '@unchainedshop/core';
import { ETHMinterPlugin } from '@unchainedshop/plugins/warehousing/eth-minter';

pluginRegistry.register(ETHMinterPlugin);

Setup​

mutation CreateETHMinter {
createWarehousingProvider(warehousingProvider: {
type: VIRTUAL
adapterKey: "shop.unchained.warehousing.infinite-minter"
}) {
_id
}
}

Adapter Details​

PropertyValue
Keyshop.unchained.warehousing.infinite-minter
TypeVIRTUAL
Order Index0
Sourcewarehousing/eth-minter

Environment Variables​

VariableDescriptionDefault
MINTER_TOKEN_OFFSETStarting token serial number offset (ERC-721)0
ROOT_URLBase URL for the metadata localization URIhttp://localhost:4010
ERC_METADATA_API_PATHPath of the metadata route in the localization URI/erc-metadata

Configuration Options​

KeyDescriptionDefault
chainIdChain ID stored on created tokens0

Product Setup​

Create a TOKENIZED_PRODUCT and configure its tokenization:

mutation CreateTokenizedProduct {
createProduct(product: { type: TOKENIZED_PRODUCT }) {
_id
}
}

mutation ConfigureTokenization {
updateProductTokenization(
productId: "product-id"
tokenization: {
contractAddress: "0x..."
contractStandard: ERC721
tokenId: "1"
supply: 1000
ercMetadataProperties: { attributes: [{ trait_type: "Rarity", value: "Legendary" }] }
}
) {
_id
}
}

ERC-721 (non-fungible): every mint creates one token record per quantity with a unique, incrementing tokenSerialNumber (starting at MINTER_TOKEN_OFFSET + 1).

ERC-1155 (semi-fungible): every mint creates a single token record with the configured tokenId as serial number and the ordered quantity. tokenId is required — configurationError() reports INCOMPLETE_CONFIGURATION without it.

Behavior​

  • isActive() returns true only for TOKENIZED_PRODUCT products.
  • stock() returns supply - tokensCreated (or 0 when no supply is set). Cancelled tokens count as created.
  • tokenize() creates the token records described above when an order position is fulfilled — actual on-chain minting is up to you. Tokens store meta: { contractStandard }.
  • isInvalidateable() allows invalidating a token until it is invalidated; there is no time window.
  • tokenMetadata(tokenSerialNumber) returns EIP-721/EIP-1155-compatible JSON built from the localized product texts, the first product media file, and ercMetadataProperties:
{
"name": "Product Title #1",
"description": "Product description",
"image": "https://cdn.example.com/image.png",
"properties": { "attributes": [{ "trait_type": "Rarity", "value": "Legendary" }] },
"localization": {
"uri": "https://example.com/erc-metadata/{productId}/{locale}/{tokenSerialNumber}.json",
"default": "en",
"locales": ["en", "de", "fr"]
}
}

Metadata Endpoint​

The metadata HTTP route is served by the separate shop.unchained.warehousing.erc-metadata plugin (warehousing/erc-metadata, part of the base preset). The path prefix defaults to /erc-metadata and is configurable via the ERC_METADATA_API_PATH env var.

Querying Tokens​

query MyTokens {
me {
tokens {
_id
quantity
status
contractAddress
chainId
walletAddress
}
}
}

Custom Minting Adapter​

For custom blockchain integrations, use the registerVirtualWarehousing factory:

import { registerVirtualWarehousing } from '@unchainedshop/core';

registerVirtualWarehousing({
adapterId: 'custom-minter',
stock: async () => {
const totalSupply = await contract.totalSupply();
const maxSupply = await contract.maxSupply();
return maxSupply - totalSupply;
},
tokenize: async (configuration, { product, orderPosition }) => {
const tx = await contract.mint(orderPosition.quantity);
const receipt = await tx.wait();

return receipt.events
.filter((event) => event.event === 'Transfer')
.map((event) => ({
_id: crypto.randomUUID(),
tokenSerialNumber: event.args.tokenId.toString(),
contractAddress: product.tokenization.contractAddress,
quantity: 1,
meta: { txHash: tx.hash },
}));
},
});