# 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/](https://vault.docs.blockdaemon.com/)

* * *

## Prerequisites

- A provisioned Institutional Vault sandbox ( [enrollment process](https://vault.docs.blockdaemon.com/docs/blockdaemon-wallet-sandbox-enrollment-process))
- An admin user signed in to the wallet UI with the [BD Approver app](https://vault.docs.blockdaemon.com/docs/onboarding-the-wallet) 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):

```undefined
authorization: <your-api-key>
```

**Docs:** [Quick Start — Get API Key](https://vault.docs.blockdaemon.com/reference/quick-start-blockdaemon-wallet)

* * *

## Step 2: Access the OpenAPI Specification

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

```undefined
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](https://vault.docs.blockdaemon.com/reference)

**Generate Client SDKs:** [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

Bash

```shell
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](https://vault.docs.blockdaemon.com/docs/tokenization#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):

Bash

```shell
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):

Bash

```shell
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.

### Resolve a wallet-managed address

Confirm the signing address is owned by the vault:

Bash

```shell
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:

```undefined
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:**
- [List chains](https://vault.docs.blockdaemon.com/reference/cwplistchains)
- [Get address](https://vault.docs.blockdaemon.com/reference/cwpgetaddresscaip2)
- [Resolve address](https://vault.docs.blockdaemon.com/reference/cwpresolveaddress)
- [List tokens](https://vault.docs.blockdaemon.com/reference/cwplisttokens)

* * *

## 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](https://github.com/foundry-rs/foundry) or Hardhat to compile your ERC-20 contract and extract the bytecode:

Bash

```shell
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](https://viem.sh/):

TypeScript

```typescript
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)

Bash

```shell
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

Bash

```shell
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:

Bash

```shell
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](https://vault.docs.blockdaemon.com/reference/cwpstartmaketransaction)

* * *

## 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](https://vault.docs.blockdaemon.com/docs/transaction-restrictions).

Bash

```shell
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:

Bash

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

**Docs:** [Add ERC-20 Assets](https://vault.docs.blockdaemon.com/docs/add-erc-20-assets)

**API reference:** [Start Register Token](https://vault.docs.blockdaemon.com/reference/cwpstartregistertoken)

* * *

## 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):

TypeScript

```typescript
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:

Bash

```shell
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](https://vault.docs.blockdaemon.com/reference/cwpstartmaketransaction)

### 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):

TypeScript

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

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

Or with Foundry `cast`:

Bash

```shell
cast calldata "burn(uint256)" 50000000000000000000
```

### Structured contract call

Bash

```shell
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

Bash

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

Terminal **`Status`** values:

| Status | Meaning |
| --- | --- |
| `Succeeded` | Completed successfully. For structured **`makeTransaction`**, **`Result.Transaction.ID`** is the tx hash. For **`RawTransaction`** flows, use **`Result.Transaction.SignedTransaction`** and broadcast locally. |
| `Failed` | Rejected by policy, approver, or an execution error. See **`ErrorDetails`**. |
| `Pending approval` | Waiting for approver action on the BD Approver app. |
| `Pending confirmation` | Waiting for initiator confirmation when required by policy. |
| `Executing` / `Pending signature` | In progress. |

### Approval details

Bash

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

### Inspect the stored intent

Bash

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

* * *

## Reference Links

| Topic | URL |
| --- | --- |
| Getting Started | [https://vault.docs.blockdaemon.com/docs/getting-started](https://vault.docs.blockdaemon.com/docs/getting-started) |
| Sandbox Enrollment | [https://vault.docs.blockdaemon.com/docs/blockdaemon-wallet-sandbox-enrollment-process](https://vault.docs.blockdaemon.com/docs/blockdaemon-wallet-sandbox-enrollment-process) |
| Onboarding / Approver App | [https://vault.docs.blockdaemon.com/docs/onboarding-the-wallet](https://vault.docs.blockdaemon.com/docs/onboarding-the-wallet) |
| Quick Start / API Key | [https://vault.docs.blockdaemon.com/reference/quick-start-blockdaemon-wallet](https://vault.docs.blockdaemon.com/reference/quick-start-blockdaemon-wallet) |
| Generate SDKs | [https://vault.docs.blockdaemon.com/reference/generate-client-sdks](https://vault.docs.blockdaemon.com/reference/generate-client-sdks) |
| Add ERC-20 Assets | [https://vault.docs.blockdaemon.com/docs/add-erc-20-assets](https://vault.docs.blockdaemon.com/docs/add-erc-20-assets) |
| Start Make Transaction | [https://vault.docs.blockdaemon.com/reference/cwpstartmaketransaction](https://vault.docs.blockdaemon.com/reference/cwpstartmaketransaction) |
| Start Register Token | [https://vault.docs.blockdaemon.com/reference/cwpstartregistertoken](https://vault.docs.blockdaemon.com/reference/cwpstartregistertoken) |
| Start Create Account | [https://vault.docs.blockdaemon.com/reference/cwpstartcreateaccount](https://vault.docs.blockdaemon.com/reference/cwpstartcreateaccount) |
| Get address | [https://vault.docs.blockdaemon.com/reference/cwpgetaddresscaip2](https://vault.docs.blockdaemon.com/reference/cwpgetaddresscaip2) |
| Get Operation Status | [https://vault.docs.blockdaemon.com/reference/cwpgetoperationstatus](https://vault.docs.blockdaemon.com/reference/cwpgetoperationstatus) |
| Transaction Flows | [https://vault.docs.blockdaemon.com/docs/transaction-flows](https://vault.docs.blockdaemon.com/docs/transaction-flows) |
| User Types & Privileges | [https://vault.docs.blockdaemon.com/docs/user-types-and-privileges](https://vault.docs.blockdaemon.com/docs/user-types-and-privileges) |
| OpenAPI Spec (Swagger UI) | `https://<sandbox>.blockdaemon-wallet.com/swagger.html` |
