Deno & Fetch
A Deno-based script for minting CIP-68 assets on the Cardano blockchain using the Anvil API. This script generates wallets, creates policies, customizes asset metadata, builds and signs transactions,
This a a very specific example, the goal is to show how flexible and versatile the Anvil API is.
This example will be entirely done into one file named index.ts
Objectives
Mint a CIP-68 Asset
Create a Policy (an NFT Collection)
Create a custom function to customize the asset metadata
Create 3 wallets, one for the policy, one for the metadata manager and one to act as the customer
Submit transaction to the network
This is only an example showing the anvil api versatility
The example is built on Cardano Preprod
Code
Dependencies
import { Buffer } from "node:buffer";
import {
FixedTransaction,
PrivateKey,
} from "npm:@emurgo/cardano-serialization-lib-nodejs@13.2.0";
Import Helper Functions
To reduce the amount of content in this guide, you only have to import all functions defined here:
Load wallets
We have a Simple CLI tool that can help you generating wallets.
// NOTE: Be sure to send ADA in this address, it will be used to pay the tx fee.
const customerWallet = JSON.parse(Deno.readTextFileSync("customer.json"));
const policyWallet = JSON.parse(Deno.readTextFileSync("policy-cip68.json"));
const metaManagerWallet = JSON.parse(
Deno.readTextFileSync("meta-manager-cip68.json")
);
Cardano Wallets
~/Downloads/cardano-wallet-macos-latest --name policy-cip68 --mnemonic
~/Downloads/cardano-wallet-macos-latest --name customer --mnemonic
~/Downloads/cardano-wallet-macos-latest --name meta-manager-cip68 --mnemonic
Setup policy and asset template
Don't forget to increase the counter, otherwise you will double mint.
// VARIABLES
const expirationDate = "2026-01-01";
const counter = 202501171652;
// Date before you can interact with the policy
const slot = dateToSlot(new Date(expirationDate));
const keyhash = get_keyhash(policyWallet.skey);
if (!keyhash) {
throw new Error("Unable to get key hash for policy, missing or invalid skey");
}
const policyAnvilApi = create_policy_script(keyhash, slot);
const policyAnvilApiScript = {
type: "all",
scripts: [
{
type: "sig",
keyHash: keyhash.to_hex(),
},
{
type: "before",
slot: slot,
},
],
};
const assets: {
label: number; // 100, 222
version: string;
assetName: string;
metadata?: {
name: string;
image: string | string[];
mediaType: string;
description: string;
epoch: number;
};
policyId: string;
quantity: 1;
destAddress: string;
}[] = [];
const assetMetadataTemplate = {
name: "TBD",
image: [
"https://ada-anvil.s3.ca-central-1.amazonaws.com/",
"logo_pres_V2_3.png",
],
mediaType: "image/png",
description: "Testing CIP-68 using anvil API",
};
Create the transaction
Label 100
must be sent to the metaManagerWallet
Label 222
must be send to the customer
(in this case)
assets.push(
{
label: 100,
version: "cip68",
assetName: `anvilapicip68_${counter}`,
metadata: {
...assetMetadataTemplate,
// Adding custom data just to test the flow
name: `anvil-api-${counter}`,
epoch: new Date().getTime(), // dummy data
},
policyId: get_policy_id(policyAnvilApi.mint_script),
quantity: 1,
destAddress: metaManagerWallet.enterprise_address_preprod,
},
{
label: 222,
version: "cip68",
assetName: `anvilapicip68_${counter}`,
policyId: get_policy_id(policyAnvilApi.mint_script),
quantity: 1,
destAddress: customerWallet.enterprise_address_preprod,
}
);
const data = {
changeAddress: customerWallet.enterprise_address_preprod,
mint: assets,
preloadedScripts: [
{
type: "simple",
script: policyAnvilApiScript,
hash: get_policy_id(policyAnvilApi.mint_script),
},
],
};
const urlTx =
"https://preprod.api.ada-anvil.app/v2/services/transactions/build";
const output = await fetch(urlTx, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
"X-Api-Key": "testnet_EyrkvCWDZqjkfLSe1pxaF0hXxUcByHEhHuXIBjt9",
},
}).then((res) => res.json());
Sign the transaction with Policy Wallet
// Sign transaction with policy key
const transactionToSignWithCustomerKey = FixedTransaction.from_bytes(
Buffer.from(output.complete, "hex")
);
transactionToSignWithCustomerKey.sign_and_add_vkey_signature(
PrivateKey.from_bech32(policyWallet.skey)
);
Sign the transaction with Customer Wallet
// Sign transaction with metaManager key
const txToSubmitOnChain = FixedTransaction.from_bytes(
Buffer.from(transactionToSignWithCustomerKey.to_hex(), "hex")
);
// This sign the tx and add vkeys to the txToSubmitOnChain, so in submit we don't need to provide signautres
txToSubmitOnChain.sign_and_add_vkey_signature(
PrivateKey.from_bech32(customerWallet.skey)
);
Submit Transaction
const urlSubmit =
"https://preprod.api.ada-anvil.app/v2/services/transactions/submit";
const submitted = await fetch(urlSubmit, {
method: "POST",
body: JSON.stringify({
signatures: [], // This empty because the txToSubmitOnChain has the vkeys
transaction: txToSubmitOnChain.to_hex(),
}),
headers: {
"Content-Type": "application/json",
"X-Api-Key": "testnet_EyrkvCWDZqjkfLSe1pxaF0hXxUcByHEhHuXIBjt9",
},
}).then((res) => res.json());
console.debug(submitted);
deno run -A index.ts
File Content - (customer.json, policy.json, metatemplate.json)
customer.json
{
"skey": "REDACTED",
"skey_hex": "REDACTED",
"pkey": "REDACTED",
"pkey_hex": "REDACTED",
"key_hash": "REDACTED",
"base_address_preview": "addr_test1qqc0f6py9qeyp5k2vme7pxct0t0ut5mczmq8yfumx8cfpfx7kdf62usuu92g463pyumktcckmd9x7nmrvfxc9w04dnfqz7ycf9",
"base_address_preprod": "addr_test1qqc0f6py9qeyp5k2vme7pxct0t0ut5mczmq8yfumx8cfpfx7kdf62usuu92g463pyumktcckmd9x7nmrvfxc9w04dnfqz7ycf9",
"base_address_mainnet": "addr1qyc0f6py9qeyp5k2vme7pxct0t0ut5mczmq8yfumx8cfpfx7kdf62usuu92g463pyumktcckmd9x7nmrvfxc9w04dnfqpgec96",
"enterprise_address_mainnet": "addr1vyc0f6py9qeyp5k2vme7pxct0t0ut5mczmq8yfumx8cfpfq5xuxsn",
"enterprise_address_preview": "addr_test1vqc0f6py9qeyp5k2vme7pxct0t0ut5mczmq8yfumx8cfpfq0wg6lk",
"enterprise_address_preprod": "addr_test1vqc0f6py9qeyp5k2vme7pxct0t0ut5mczmq8yfumx8cfpfq0wg6lk",
"reward_address_mainnet": "stake1u80tx5a9wgwwz4y2agsjwdm9uvtdkjn0fa3kynvzh86ke5se4shkt",
"reward_address_preview": "stake_test1ur0tx5a9wgwwz4y2agsjwdm9uvtdkjn0fa3kynvzh86ke5s7l64jk",
"reward_address_preprod": "stake_test1ur0tx5a9wgwwz4y2agsjwdm9uvtdkjn0fa3kynvzh86ke5s7l64jk",
"mnemonic": "REDACTED"
}
policy.json
{
"skey": "REDACTED",
"skey_hex": "REDACTED",
"pkey": "REDACTED",
"pkey_hex": "REDACTED",
"key_hash": "REDACTED",
"base_address_preview": "addr_test1qzmdaaddmhut3e3kyjsv5f3nsws0mcxk7tu2gpwql8f8pfschsxexl9yakag0rdpf7m4rgg4dvlwuzklghq7trdr4qkqpckdye",
"base_address_preprod": "addr_test1qzmdaaddmhut3e3kyjsv5f3nsws0mcxk7tu2gpwql8f8pfschsxexl9yakag0rdpf7m4rgg4dvlwuzklghq7trdr4qkqpckdye",
"base_address_mainnet": "addr1qxmdaaddmhut3e3kyjsv5f3nsws0mcxk7tu2gpwql8f8pfschsxexl9yakag0rdpf7m4rgg4dvlwuzklghq7trdr4qkqzwtdgx",
"enterprise_address_mainnet": "addr1vxmdaaddmhut3e3kyjsv5f3nsws0mcxk7tu2gpwql8f8pfsc3x6e8",
"enterprise_address_testnet": "addr_test1vzmdaaddmhut3e3kyjsv5f3nsws0mcxk7tu2gpwql8f8pfsrejxkz",
"reward_address_mainnet": "stake1uyvtcrvn0jjwmw583ks5ld635y2kk0hwpt05ts093k36stqfn5cxe",
"reward_address_testnet": "stake_test1uqvtcrvn0jjwmw583ks5ld635y2kk0hwpt05ts093k36stqwe76zy",
"mnemonic": "REDACTED"
}
The code will override the name key
The whole file (Deno Version)
index.ts
import { Buffer } from "node:buffer";
import {
Credential,
type Ed25519KeyHash,
FixedTransaction,
NativeScript,
NativeScripts,
PrivateKey,
ScriptAll,
ScriptPubkey,
TimelockExpiry,
} from "npm:@emurgo/cardano-serialization-lib-nodejs@13.2.0";
const dateToSlot = (date: Date) => {
return Math.floor(date.getTime() / 1000) - 1596491091 + 4924800;
};
export function get_keyhash(private_key: string): Ed25519KeyHash | undefined {
return Credential.from_keyhash(
PrivateKey.from_bech32(private_key).to_public().hash()
).to_keyhash();
}
export function create_policy_script(
policy_key_hash: Ed25519KeyHash,
ttl: number,
with_timelock = true
): { mint_script: NativeScript; policy_ttl: number } {
const scripts = NativeScripts.new();
const key_hash_script = NativeScript.new_script_pubkey(
ScriptPubkey.new(policy_key_hash)
);
scripts.add(key_hash_script);
const policy_ttl: number = ttl;
if (with_timelock) {
const timelock = TimelockExpiry.new(policy_ttl);
const timelock_script = NativeScript.new_timelock_expiry(timelock);
scripts.add(timelock_script);
}
const mint_script = NativeScript.new_script_all(ScriptAll.new(scripts));
return { mint_script, policy_ttl };
}
export function bytes_to_hex(input: Uint8Array): string {
return Buffer.from(input).toString("hex");
}
export function get_policy_id(mint_script: NativeScript): string {
return bytes_to_hex(mint_script.hash().to_bytes());
}
const customerWallet = JSON.parse(Deno.readTextFileSync("customer.json"));
const policyWallet = JSON.parse(Deno.readTextFileSync("policy-cip68.json"));
const metaManagerWallet = JSON.parse(
Deno.readTextFileSync("meta-manager-cip68.json")
);
// VARIABLES
const expirationDate = "2026-01-01";
const counter = 202501171643;
const slot = dateToSlot(new Date(expirationDate));
const keyhash = get_keyhash(policyWallet.skey);
console.log("Keyhash", keyhash?.to_hex());
if (!keyhash) {
throw new Error("Unable to get key hash for policy, missing or invalid skey");
}
const policyAnvilApi = create_policy_script(keyhash, slot);
const policyAnvilApiScript = {
type: "all",
scripts: [
{
type: "sig",
keyHash: keyhash.to_hex(),
},
{
type: "before",
slot: slot,
},
],
};
const assets: {
label: number; // 100, 222
version: string;
assetName: string;
metadata?: {
name: string;
image: string | string[];
mediaType: string;
description: string;
epoch: number;
};
policyId: string;
quantity: 1;
destAddress: string;
}[] = [];
const assetMetadataTemplate = {
name: "TBD",
image: [
"https://ada-anvil.s3.ca-central-1.amazonaws.com/",
"logo_pres_V2_3.png",
],
mediaType: "image/png",
description: "Testing CIP-68 using anvil API",
};
assets.push(
{
label: 100,
version: "cip68",
assetName: `anvilapicip68_${counter}`,
metadata: {
...assetMetadataTemplate,
// Adding custom data just to test the flow
name: `anvil-api-${counter}`,
epoch: new Date().getTime(), // dummy data
},
policyId: get_policy_id(policyAnvilApi.mint_script),
quantity: 1,
destAddress: metaManagerWallet.enterprise_address_preprod,
},
{
label: 222,
version: "cip68",
assetName: `anvilapicip68_${counter}`,
policyId: get_policy_id(policyAnvilApi.mint_script),
quantity: 1,
destAddress: customerWallet.enterprise_address_preprod,
}
);
const data = {
changeAddress: customerWallet.enterprise_address_preprod,
mint: assets,
preloadedScripts: [
{
type: "simple",
script: policyAnvilApiScript,
hash: get_policy_id(policyAnvilApi.mint_script),
},
],
};
const urlTx =
"https://preprod.api.ada-anvil.app/v2/services/transactions/build";
const output = await fetch(urlTx, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
"X-Api-Key": "testnet_EyrkvCWDZqjkfLSe1pxaF0hXxUcByHEhHuXIBjt9",
},
}).then((res) => res.json());
const transactionToSignWithCustomerKey = FixedTransaction.from_bytes(
Buffer.from(output.complete, "hex")
);
transactionToSignWithCustomerKey.sign_and_add_vkey_signature(
PrivateKey.from_bech32(policyWallet.skey)
);
const txToSubmitOnChain = FixedTransaction.from_bytes(
Buffer.from(transactionToSignWithCustomerKey.to_hex(), "hex")
);
txToSubmitOnChain.sign_and_add_vkey_signature(
PrivateKey.from_bech32(customerWallet.skey)
);
const urlSubmit =
"https://preprod.api.ada-anvil.app/v2/services/transactions/submit";
const submitted = await fetch(urlSubmit, {
method: "POST",
body: JSON.stringify({
signatures: [],
transaction: txToSubmitOnChain.to_hex(),
}),
headers: {
"Content-Type": "application/json",
"X-Api-Key": "testnet_EyrkvCWDZqjkfLSe1pxaF0hXxUcByHEhHuXIBjt9",
},
}).then((res) => res.json());
console.debug(submitted);
Last updated
Was this helpful?