Admin Login

Tokenization

Deploy, mint, and burn ERC-20 tokens using the Institutional Vault Core Wallet Platform 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 from a sandbox.

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 Sepolia, 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)
  • An RPC URL for the target EVM testnet (for building unsigned transactions when using the raw signing path)

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 (shown only once)

Pass the API key on every CWP request in the authorization header (no Bearer prefix):

authorization: <your-api-key>

Docs: Quick Start — Get API Key


Step 2: Access the OpenAPI Specification

The full OpenAPI spec for your sandbox (including all CWP paths) is available at:

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

Download openapi.yaml from Swagger UI and generate a typed client 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 Chain, Address, and CAIP-19 Values

API requests use CAIP-19 asset identifiers and an address-only Source (or full AddressSpec).

List registered chains

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

Note the chain CAIP-2 (e.g. eip155:11155111 for Ethereum Sepolia).

Create an account and get an address

You need a master key, a named account under that master key, and an on-chain address for the chain you will use. If you already have an address from the wallet UI or a prior API call, skip to Resolve a wallet-managed address.

Use Default for MasterKey unless your deployment is multitenanted and you created a different master key name.

1. Create the account (skip if the account already exists under your master key):

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/cwp/operations/start/createAccount \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "<account-name>",
    "MasterKey": "Default",
    "InitiatorID": "<vault-user-email>"
  }' | jq

Poll GET /api/cwp/operations/id/{operationID}/status until Succeeded. Approve on the BD Approver app when required. Optional InitiatorID: registered Vault user email (recommended so the gateway can sync the account to the Vault UI).

2. Get the account's native address for your EVM chain (use the CAIP-2 from the chains list, e.g. Sepolia):

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/cwp/addresses/get \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "MasterKey": "Default",
    "CAIP2": "eip155:11155111",
    "Account": "<account-name>",
    "Index": 0
  }' | jq

The response Address is the value to use as Source.Address in later makeTransaction calls. Index defaults to 0 (main address) when omitted.

API reference:

Resolve a wallet-managed address

Confirm the signing address is owned by the vault:

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/cwp/addresses/resolve \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"Address": "<your-wallet-address>"}' | jq

The response includes MasterKey, Account, and Index for that address.

Native asset CAIP-19 (gas)

For EVM contract deploy, mint, and burn, set CAIP19 to the chain's native asset (slip44), not the ERC-20 token. Example for Ethereum Sepolia:

eip155:11155111/slip44:60

Fund the signing address with enough native token on that chain to pay gas before you deploy, mint, or burn (for example, send Sepolia ETH to the address from a faucet or another wallet). Deploy and mint transactions fail if the address has insufficient balance for fees.

API reference:


Step 4: Deploy the Token Contract

Contract deployment uses POST /api/cwp/operations/start/makeTransaction with a pre-built unsigned transaction in RawTransaction. The vault performs MPC signing only; you broadcast the returned SignedTransaction to the network.

4a. Compile the contract

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

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

forge init my-token && cd my-token
forge install OpenZeppelin/openzeppelin-contracts --no-commit
forge build

Extract the bytecode field from the compiler output JSON.

4b. Build an unsigned deploy transaction

Use your EVM library to construct an unsigned EIP-1559 transaction whose data field is the contract bytecode (no to address). Example with viem:

import { createPublicClient, http, serializeTransaction } from 'viem';
import { sepolia } from 'viem/chains';

const client = createPublicClient({ chain: sepolia, transport: http('<rpc-url>') });
const fromAddress = '<your-wallet-address>' as `0x${string}`;
const bytecode = '0x6080604052...' as `0x${string}`;

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

const rawTransaction = serializeTransaction({
  chainId: sepolia.id,
  nonce,
  value: 0n,
  gas,
  maxFeePerGas: maxFeePerGas!,
  maxPriorityFeePerGas: maxPriorityFeePerGas!,
  accessList: [],
  type: 'eip1559',
  data: bytecode,
});

4c. Start make transaction (sign only)

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/cwp/operations/start/makeTransaction \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "CAIP19": "eip155:11155111/slip44:60",
    "Source": { "Address": "<your-wallet-address>" },
    "RawTransaction": "0x02f8..."
  }' | jq

Optional InitiatorID: set to the Vault user's email when your policies require an identified initiator.

The response includes OperationID. Approve the operation on the BD Approver app when prompted.

4d. Poll status and broadcast

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

When Status is Succeeded, read Result.Transaction.SignedTransaction and publish it to your RPC:

cast publish --rpc-url <rpc-url> "0x<signed-transaction-hex>"

Find the deployed contract address from the transaction receipt on the block explorer (e.g. Sepolia Etherscan) using the broadcast tx hash.

API reference: Start Make Transaction


Step 5: Register the Token in the Wallet

Register the deployed ERC-20 so the vault tracks balances and policies can reference the token. To require approvals on deploy, mint, burn, or other on-chain operations, configure Transaction Restrictions.

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/cwp/operations/start/registerToken \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "CAIP19": "eip155:11155111/erc20:0x<deployed-contract-address>",
    "Symbol": "MYTKN",
    "Decimals": 18
  }' | jq

Poll GET /api/cwp/operations/id/{operationID}/status until Succeeded or Failed. Approve on the BD Approver app when required.

You can also register through the UI: Settings > Assets > Add Assets.

Confirm registration:

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

Docs: Add ERC-20 Assets

API reference: Start Register Token


Step 6: Mint Tokens

Option A: Structured contract call (vault builds, signs, and broadcasts)

Use makeTransaction with EVM.Data set to ABI-encoded mint(address,uint256) calldata. Point Destination at the token contract with Amount: "0" (native-value transfer to the contract). Use the chain's native CAIP19 for gas, same as deploy.

Encode calldata (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>' as `0x${string}`, parseEther('100')],
});

Submit:

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/cwp/operations/start/makeTransaction \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "CAIP19": "eip155:11155111/slip44:60",
    "Source": { "Address": "<your-wallet-address>" },
    "Destination": [
      { "Address": "<token-contract-address>", "Amount": "0" }
    ],
    "EVM": { "Data": "0x40c10f19..." }
  }' | jq

Poll operation status until Succeeded. On success, Result.Transaction.ID contains the on-chain transaction hash.

API reference: Start Make Transaction

Option B: Raw transaction (client builds unsigned tx, vault signs, you broadcast)

Build and serialize an unsigned EIP-1559 transaction to the token contract with mint calldata in data, then pass the hex string as RawTransaction (same pattern as Step 4). Do not set Destination or EVM when using RawTransaction.

After Status is Succeeded, broadcast Result.Transaction.SignedTransaction with your RPC client or cast publish.


Step 7: Burn Tokens

Burn uses the same makeTransaction patterns as mint; only the calldata changes.

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 Foundry cast:

cast calldata "burn(uint256)" 50000000000000000000

Structured contract call

curl -s -X POST https://<sandbox>.api.blockdaemon-wallet.com/api/cwp/operations/start/makeTransaction \
  -H "authorization: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "CAIP19": "eip155:11155111/slip44:60",
    "Source": { "Address": "<your-wallet-address>" },
    "Destination": [
      { "Address": "<token-contract-address>", "Amount": "0" }
    ],
    "EVM": { "Data": "0x42966c68..." }
  }' | jq

Raw transaction

Follow Step 6, Option B, substituting burn calldata. The signing address must hold at least the burn amount of tokens.


Step 8: Monitor Operations

All CWP operations (deploy, register token, mint, burn) follow the same asynchronous pipeline: policy evaluation, approvals, MPC signing, and (for structured makeTransaction calls) broadcasting.

Poll operation status

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

Terminal Status values:

StatusMeaning
SucceededCompleted successfully. For structured makeTransaction, Result.Transaction.ID is the tx hash. For RawTransaction flows, use Result.Transaction.SignedTransaction and broadcast locally.
FailedRejected by policy, approver, or an execution error. See ErrorDetails.
Pending approvalWaiting for approver action on the BD Approver app.
Pending confirmationWaiting for initiator confirmation when required by policy.
Executing / Pending signatureIn progress.

Approval details

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

Inspect the stored intent

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

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.