Admin Login

Tokenization

Deploy, mint, and burn ERC-20 tokens using the Institutional Vault API on a sandbox environment.

Deploy, Mint, and Burn Tokens with Institutional Vault

This guide walks through deploying an ERC-20 token contract, registering it with the wallet, and performing mint and burn operations — all from a sandbox environment using the Institutional Vault API.

Base URL format: https://<sandbox>.api.blockdaemon-wallet.com/

Published documentation: https://vault.docs.blockdaemon.com/


Prerequisites

  • A provisioned Institutional Vault sandbox (enrollment process)
  • An admin user signed in to the wallet UI with the BD Approver app configured
  • The sandbox must have at least one EVM testnet chain enabled (e.g. Ethereum Hoodi, Base Sepolia)
  • The wallet account address must hold testnet native tokens (ETH) to pay gas fees
  • Node.js 18+ and npm / pnpm (for SDK generation and running examples)

Step 1: Create a System User and Obtain an API Key

System users provide programmatic API access. Only admin users can create them.

  1. Log in to the Institutional Vault UI at https://<sandbox>.blockdaemon-wallet.com/
  2. Navigate to Settings > System Users
  3. Click Create System Users
  4. Set the system user Name, Role (MarketOps is sufficient for transactions), and Confirmer
  5. Click Create and approve the operation on the BD Approver app
  6. Copy the API Key from the system users list — this is shown only once

The API key is passed as the authorization header (no Bearer prefix) on all subsequent API calls:

authorization: <your-api-key>

Docs: Quick Start — Get API Key


Step 2: Access the OpenAPI Specification

The full OpenAPI spec for your sandbox is available at:

https://<sandbox>.blockdaemon-wallet.com/swagger.html

Download the openapi.yaml file from the Swagger UI. This spec covers all v2 endpoints including transfers, raw transfers, accounts, addresses, assets, and operations. Optionally, generate a typed client SDK from the spec for TypeScript, Go, Python, Java, or C#.

Published API reference: https://vault.docs.blockdaemon.com/reference

Generate Client SDKs: https://vault.docs.blockdaemon.com/reference/generate-client-sdks


Step 3: Identify Your Account and Addresses

Before deploying or transacting, find the wallet-managed address that will sign transactions.

List accounts:

curl -s https://<sandbox>.api.blockdaemon-wallet.com/api/v2/accounts \
  -H "authorization: <your-api-key>" | jq

List addresses (filter by account and asset):

curl -s "https://<sandbox>.api.blockdaemon-wallet.com/api/v2/addresses?accountID=1&assetID=12" \
  -H "authorization: <your-api-key>" | jq

Note the address value (e.g. 0x6081ebA3...) and the assetID for the chain's native asset (e.g. 12 for Ethereum Hoodi). You will need both for the steps that follow.

List assets to find the numeric assetID for your chain:

curl -s https://<sandbox>.api.blockdaemon-wallet.com/api/v2/assets \
  -H "authorization: <your-api-key>" | jq

API Reference:


Step 4: Deploy the Token Contract

Institutional Vault can deploy smart contracts through the same POST /api/v2/transfers endpoint used for contract interactions. Set the destination address to an empty string and provide the compiled contract bytecode as calldata.

4a. Compile the contract

Use Foundry or Hardhat to compile your ERC-20 contract and extract the bytecode:

# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup

# Init project and install OpenZeppelin
forge init my-token && cd my-token
forge install OpenZeppelin/openzeppelin-contracts --no-commit

# Compile — bytecode is in out/<Contract>.sol/<Contract>.json
forge build

Extract the bytecode field from the compiler output JSON.

4b. Deploy via the Institutional Vault API

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/v2/transfers \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "contract",
    "assetID": 12,
    "toAddressAmountArray": [
      {
        "address": "",
        "calldata": "0x608060405234801561000f575f5ffd5b50..."
      }
    ],
    "fromAddressAmountArray": [
      {
        "address": "<your-wallet-address>",
        "amount": "0"
      }
    ],
    "reference": "Deploy ERC-20 token contract"
  }' | jq

Key fields:

  • type: "contract" — signals a smart contract interaction
  • toAddressAmountArray[0].address: empty string "" — tells the wallet this is a contract deployment (not a call)
  • toAddressAmountArray[0].calldata: the compiled contract bytecode (hex-encoded, 0x-prefixed)
  • fromAddressAmountArray[0].address: the wallet address that will deploy (becomes the contract owner)
  • assetID: the numeric ID of the chain's native asset

The response includes a transaction ID. Approve the operation on the BD Approver app (or via configured Automated Approvers) and monitor the transaction:

# Poll transaction status
curl -s https://<sandbox>.api.blockdaemon-wallet.com/api/v2/transactions/<transactionID> \
  -H "authorization: <your-api-key>" | jq '.status'

Once the transaction reaches status confirmed, find the deployed contract address from the transaction receipt on the block explorer (e.g. Etherscan/BaseScan) using the returned txHash.

API Reference: Create Transfer


Step 5: Register the Token in the Wallet

After deployment, register the new token so the wallet tracks its balances and enables transfers.

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/v2/assets \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "protocol": "ethereum",
    "network": "hoodi",
    "contractAddress": "<deployed-contract-address>"
  }' | jq

This triggers an asynchronous operation that fetches the token's name, symbol, and decimals from the contract. Approve it on the BD Approver app.

You can also register the token through the UI: Settings > Assets > Add Assets > enter the contract address and select the blockchain > Add Token.

After registration, note the new assetID returned — you will use it if you later want to track balances or perform high-level transfers of this token.

Docs: Add ERC-20 Assets


Step 6: Mint Tokens

Option A: Managed contract call (wallet handles gas and broadcasting)

Use POST /api/v2/transfers with type: "contract". The wallet builds, signs, and broadcasts the transaction.

Encode the mint(address,uint256) calldata. Using TypeScript with viem:

import { encodeFunctionData, parseAbi, parseEther } from 'viem';

const calldata = encodeFunctionData({
  abi: parseAbi(['function mint(address to, uint256 amount)']),
  functionName: 'mint',
  args: ['<recipient-address>', parseEther('100')],
});

Or using cast (Foundry):

cast calldata "mint(address,uint256)" <recipient-address> 100000000000000000000

Submit the mint:

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/v2/transfers \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "contract",
    "assetID": 12,
    "toAddressAmountArray": [
      {
        "address": "<token-contract-address>",
        "calldata": "0x40c10f19000000000000000000000000..."
      }
    ],
    "fromAddressAmountArray": [
      {
        "address": "<your-wallet-address>",
        "amount": "0"
      }
    ],
    "reference": "Mint 100 tokens"
  }' | jq

The wallet estimates gas, constructs the transaction, routes it through policy evaluation and approvals, signs via MPC, and broadcasts. Monitor via the returned transaction ID or the events WebSocket.

API Reference: Create Transfer

Option B: Raw transfer (client builds the unsigned transaction)

Use POST /api/v2/raw-transfers when you need full control over nonce, gas, and chain parameters. The wallet signs the raw bytes via MPC and returns the signed transaction — you broadcast it yourself.

Build and serialize an unsigned EIP-1559 transaction (TypeScript with viem):

import { createPublicClient, http, serializeTransaction, encodeFunctionData, parseAbi, parseEther } from 'viem';
import { hoodi } from 'viem/chains';

const client = createPublicClient({ chain: hoodi, transport: http('<rpc-url>') });
const fromAddress = '<your-wallet-address>' as `0x${string}`;
const contractAddress = '<token-contract-address>' as `0x${string}`;

const calldata = encodeFunctionData({
  abi: parseAbi(['function mint(address to, uint256 amount)']),
  functionName: 'mint',
  args: ['<recipient-address>' as `0x${string}`, parseEther('100')],
});

const nonce = await client.getTransactionCount({ address: fromAddress });
const gas = await client.estimateGas({ account: fromAddress, to: contractAddress, value: 0n, data: calldata });
const { maxFeePerGas, maxPriorityFeePerGas } = await client.estimateFeesPerGas();

const unsignedHex = serializeTransaction({
  chainId: hoodi.id, nonce, to: contractAddress, value: 0n,
  gas, maxFeePerGas: maxFeePerGas!, maxPriorityFeePerGas: maxPriorityFeePerGas!,
  accessList: [], type: 'eip1559', data: calldata,
});

Submit to the wallet for signing:

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/v2/raw-transfers \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "protocol": "ethereum",
    "network": "hoodi",
    "symbol": "ETH",
    "fromAddress": "<your-wallet-address>",
    "rawTransaction": "0x02f8..."
  }' | jq

Important: The symbol must be the chain's native token (e.g. ETH, BASE-ETH), not the ERC-20 token symbol. The raw signing path resolves the asset internally as a native (slip44) identifier.

Poll the operation for the signed transaction, then broadcast:

# Poll operation status
curl -s https://<sandbox>.api.blockdaemon-wallet.com/api/v2/operations/<asyncOperationID> \
  -H "authorization: <your-api-key>" | jq

# When status is "fin", extract outputs.transaction.signedTransaction and broadcast:
cast publish --rpc-url <rpc-url> "0x<signed-transaction-hex>"

API Reference: Create Raw Transfer


Step 7: Burn Tokens

Burn works identically to mint — the only difference is the contract function called.

Calldata encoding

Encode burn(uint256) (burns from the signer's balance):

import { encodeFunctionData, parseAbi, parseEther } from 'viem';

const calldata = encodeFunctionData({
  abi: parseAbi(['function burn(uint256 amount)']),
  functionName: 'burn',
  args: [parseEther('50')],
});

Or with cast:

cast calldata "burn(uint256)" 50000000000000000000

Submit via managed contract call

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/v2/transfers \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "contract",
    "assetID": 12,
    "toAddressAmountArray": [
      {
        "address": "<token-contract-address>",
        "calldata": "0x42966c68000000000000000000000000000000000000000000000002b5e3af16b1880000"
      }
    ],
    "fromAddressAmountArray": [
      {
        "address": "<your-wallet-address>",
        "amount": "0"
      }
    ],
    "reference": "Burn 50 tokens"
  }' | jq

Submit via raw transfer

Follow the same pattern as the mint raw transfer (Step 6, Option B), substituting burn(uint256) calldata for mint(address,uint256).

The signing address must hold at least the burn amount in tokens.


Step 8: Monitor Operations

All transactions (deploy, mint, burn) go through the same asynchronous pipeline: policy evaluation, approvals, MPC signing, and (for managed transfers) broadcasting.

Poll the operation

curl -s https://<sandbox>.api.blockdaemon-wallet.com/api/v2/operations/<operationID> \
  -H "authorization: <your-api-key>" | jq

Terminal statuses:

StatusMeaning
finCompleted successfully. For managed transfers, the tx is broadcast and outputs.transaction contains the tx hash. For raw transfers, outputs.transaction.signedTransaction contains the signed bytes.
rejRejected by policy or an approver.
errAn error occurred. Check errorDetails.
canCancelled.

Poll the transaction (managed transfers)

curl -s https://<sandbox>.api.blockdaemon-wallet.com/api/v2/transactions/<transactionID> \
  -H "authorization: <your-api-key>" | jq '.status'

Returns txHash, blockNumber, status, and fee once confirmed.

Events WebSocket

For real-time updates, connect to the events WebSocket:

wss://<sandbox>.api.blockdaemon-wallet.com/api/v2/events/ws-receive

Or poll GET /api/v2/events for event topics such as TransactionStable.


Reference Links

🗣️We Are Here to Help!

Please contact us via email or support chat if you encounter an issue, bug, or need assistance. Don't forget to include any relevant details about the problem. To request a wallet form and Institutional Vault Approver form, please click here or contact our sales team.