Minting With Voucher
This functionality is currently in beta and has only been tested and deployed on the Goerli testnet
Voucher minting (also known as lazy minting or "just-in-time minting") allows a number of use-cases that enable developers to implement mechanics such as play-to-earn (P2E). The basic idea is that a developer can cryptographically sign a voucher (containing the data of the NFT to be minted) allowing a player to present this signature together with the NFT data to the contract to mint the NFT. Vouchers that are issued to the player are single-use vouchers to prevent replay attacks where the same voucher is used to mint multiple items. The minting is paid for by the user minting the asset so that the When a player calls the redeem function on the ERC721 or ERC1155 contract, the signature is validated and if this signature is valid, then the NFT is minted. One of the major advantages of voucher minting is that the Gas cost of minting the NFT is paid by the user calling the redeem function and generating the vouchers off-chain costs the developer nothing other than time. The Voucher Signer server component can be a standalone service or it could be integrated into existing backend services of the game. This service must be run by the developer themselves, as game For more information on how voucher minting works, you can refer to this NFT School article.
The general flow for voucher minting is as follows:
  1. 1.
    A player completes an in-game action (eg. wins a battle in the game) earning themselves the right to mint an NFT.
  2. 2.
    The game makes a request to the Voucher Signer Server for a voucher (providing any information required for the Signer to validate that the user did complete the task required).
  3. 3.
    The Voucher Signer Server will validate that the conditions for issuing of this NFT were indeed met (eg. check against the game's internal database that the player with a specific address won a battle)
  4. 4.
    Once the above check has passed, the voucher signer should prepare the metadata of the NFT to be minted, and upload this to IPFS. This step will result in a unique CID linking the token (to be minted) and the CID together.
  5. 5.
    The Voucher Signer Server generates a message which includes the CID of the NFT to be minted and signs this using the Voucher Signer Server private key. This signature and the data about the NFT to be minted are sent back to the game, and stored in the PlayerPrefs
  6. 6.
    A player would redeem the voucher that is now in their possession, by calling the ERC721 or ERC1155 contract Redeem(...) function (using the Unity SDK) passing in the voucher received in step 5. The signature and all NFT data is validated by the respective token contract, the NFT is minted and transferred to the player.

Lazy Minting Signer

The sample code provided here shows a NodeJS (Express server) implementation of a voucher signer, with no validation of whether the conditions for issuing a voucher have been met. This example implements methods for both ERC721 and ERC1155 vouchers, though you will likely only use one of these in your application. These are both included for demo purposes.
Prerequisites:
  • An API key for Chainsafe Storage (https://app.storage.chainsafe.io/api-keys) - This is required for the Voucher Signer to upload the metadata to IPFS using Chainsafe Storage.
  • A signer private key/mnemonic. This can be generated here or extracted from a wallet such as Metamask. This account does not need to have any funds on any network, as the key is just used to cryptographically sign the voucher (no gas is required for this).
  • NodeJS 16+

Generate an ERC721 Voucher

1. Validate whether user is entitled to mint the NFT (This has been hard-coded to true in the example server). In real-world cases the game state would be queried to ensure that the user requesting the voucher is indeed entitled to receive it. // Query game state to determine whether the user making the request is authorized to mint const const voucherEarned = true
if (!voucherEarned) { throw new Error("Voucher is not yet earned"); }
2. Generate metadata for the NFT and upload to Chainsafe Storage or another IPFS host of your choice. You will now have the CID of the metadata. const metadata = { name: test lazy mint ERC721 nft, description: ipsum lorem, image }
const axiosClient = axios.create({ transformResponse: [] }) const apiClient = new FilesApiClient({}, storageApiUrl, axiosClient) apiClient.setToken(storageApiKey) try { const result = await apiClient.uploadNFT(metadata) } catch e { console.error(e) } result is the CID of the metadata for which the voucher will now be signed.
3. Generate an EIP712 TypedData signed message using the LazyMinter utility
const provider = getDefaultProvider(5)
const wallet = (recoverWalletFromMnemonic(signerMnemonic)).connect(provider)
const minterContract = GeneralERC721__factory.connect(minter721Address, wallet)
const minter = new LazyMinter({ contract: minterContract, signer: wallet })
const voucher = await minter.createGamingVoucher721({
minPrice: 0,
uri: result.cid,
signer: wallet.address
})
4. Return the voucher and signature to the user
res.send(voucher)

Generate an ERC1155 Voucher

1. Validate whether user is entitled to mint the NFT (This has been hard-coded to true in the example server). In real-world cases the game state would be queried to ensure that the user requesting the voucher is indeed entitled to receive it. // Query game state to determine whether the user making the request is authorized to mint const const voucherEarned = true
if (!voucherEarned) { throw new Error("Voucher is not yet earned"); } 2. Generate metadata for the NFT and upload to Chainsafe storage (IPFS). You will now have the CID of the metadata which is necessary for minting the token. When uploading metadata to be used for an ERC1155 the hashing algorithm used should be set to blake2b-208 to ensure that the resulting CID can be used as a token Id. result is the CID of the metadata for which the voucher will now be signed. 3. Generate an EIP712 TypedDatasigned message using the LazyMinter utility. const metadata = { name: "test lazy mint ERC1155 nft", description: "ipsum lorem", image } const axiosClient = axios.create({ transformResponse: [] }) const apiClient = new FilesApiClient({}, storageApiUrl, axiosClient) apiClient.setToken(storageApiKey) try { const uploadResult = await apiClient.uploadNFT(metadata, "blake2b-208") } catch (e) { console.log(e) } The nonce can be set to any random value. In this case the timestamp of the request is used. Please note that the tokenId that is passed needs to be formatted correctly using the cidToTokenId() helper provided.
const provider = getDefaultProvider(5)
const wallet = (recoverWalletFromMnemonic(signerMnemonic)).connect(provider)
const minterContract = GeneralERC1155__factory.connect(minter1155Address, wallet)
const minter = new LazyMinter({ contract: minterContract, signer: wallet })
const voucher = await minter.createGamingVoucher1155({
minPrice: 0,
tokenId: cidToTokenId(uploadResult.cid),
amount: 1,
nonce: dayjs().valueOf(),
signer: wallet.address
})
4. Return the voucher and signature to the user
res.send(voucher)

Generating a Voucher in Unity

This will generate a voucher for your user when clicked within unity
public async void Get721VouchecrButton()
{
var voucherResponse721 = await EVM.Get721Voucher();
Debug.Log("Voucher Response 721 Signature : " + voucherResponse721.signature);
Debug.Log("Voucher Response 721 Uri : " + voucherResponse721.uri);
Debug.Log("Voucher Response 721 Signer : " + voucherResponse721.signer);
Debug.Log("Voucher Response 721 Min Price : " + voucherResponse721.minPrice);
// saves signer acc for authentication later
PlayerPrefs.SetString("WebGLVoucher721Signer", voucherResponse721.signer);
}

Using a Voucher to Authenticate in Unity

You may use the voucher as a means of authentication by checking it against an address
public async void Autheticate()
{
// validates the account that sent the voucher, you can change this if you like to fit your system
if (PlayerPrefs.GetString("WebGLVoucher721Signer") == "0x1372199B632bd6090581A0588b2f4F08985ba2d4")
{
// do stuff here
}

Using a Voucher to Mint in Unity

Once the voucher has been issued to the user, this can be presented to the redeem function on the ERC721/ERC1155 contract as shown below in unity and used to mint.
async public void VoucherMintNFT()
{
string account = PlayerPrefs.GetString("Account");
string contract = "0x93B6032b5F4Ff3a607A5FdAB21Dd2f89A0761880";
string method = "redeem";
// value in wei
string value = "0";
// gas limit OPTIONAL
string gasLimit = "";
// gas price OPTIONAL
string gasPrice = "";
(string, string, string, string) tuple = ('"' + PlayerPrefs.GetString("WebGLVoucher721MinPrice") + '"', '"' + PlayerPrefs.GetString("WebGLVoucher721URI") + '"', '"' + PlayerPrefs.GetString("WebGLVoucher721Signer") + '"', '"' + PlayerPrefs.GetString("WebGLVoucher721Sig") + '"');
string v = tuple.ToString();
string v1 = v.Replace("(", string.Empty);
string v2 = v1.Replace(")", string.Empty);
string voucher = "[" + v2 + "]";
string args = "[\"" + account + "\"," + voucher + "]";
Debug.Log("Args Sent : " + args);
try {
string response = await Web3GL.SendContract(method, abi, contract, args, value, gasLimit, gasPrice);
Debug.Log(response);
PlayerPrefs.SetString("WebGLVoucher721", "");
} catch (Exception e) {
Debug.LogException(e, this);
}
}

Using a Voucher to get an IPFS image in Unity

You may also use the voucher to generate an IPFS image within unity as shown below
public class GetVoucherWebGL721 : MonoBehaviour
{
public GameObject IPFSImage; // set this to an quad/image
string gateway = "https://ipfs.chainsafe.io/ipfs/";
string metadataUri;
string downloadURI;
public async void Get721VoucherButton()
{
var voucherResponse721 = await EVM.Get721Voucher();
metadataUri = gateway + voucherResponse721.uri;
// start json web request
StartCoroutine(GetURIData());
}
IEnumerator GetURIData() {
// json web request
UnityWebRequest www = UnityWebRequest.Get(metadataUri);
yield return www.SendWebRequest();
// error or display result
if (www.result != UnityWebRequest.Result.Success) {
Debug.Log(www.error);
}
else
{
// Show results as text
downloadURI = www.downloadHandler.text;
// gets ipfs image from voucher uri
GetIPFSImage(downloadURI);
}
}
public async void GetIPFSImage(string downloadURI)
{
// map key values to model and deserialize
URIData deserializedProduct = JsonConvert.DeserializeObject<URIData>(downloadURI);
Debug.Log("Updating Image...Please Wait!");
// get ipfs image texture via uri substring (substring removes x amount of chars from the start of the string, we remove 7 here to get rid of the ipfs://)
UnityWebRequest textureRequest = UnityWebRequestTexture.GetTexture(gateway + deserializedProduct.image.Substring(7));
await textureRequest.SendWebRequest();
IPFSImage.GetComponent<Renderer>().material.mainTexture = (DownloadHandlerTexture.GetContent(textureRequest));
Debug.Log("Image Updated!");
}

Disclaimer

The contracts have been internally audited by Chainsafe and usage is subject to the ToS
Copy link
On this page
Lazy Minting Signer
Generate an ERC721 Voucher
Generate an ERC1155 Voucher
Generating a Voucher in Unity
Using a Voucher to Authenticate in Unity
Using a Voucher to Mint in Unity
Using a Voucher to get an IPFS image in Unity
Disclaimer