> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dfns.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Issue tokenized bonds

> Manage the tokenized corporate bond lifecycle on Ethereum: subscription, coupon payments, and redemption, all secured by Dfns wallets and policies.

Issue tokenized corporate bonds on Ethereum where investors subscribe with stablecoins, receive ERC-20 bond tokens, collect periodic coupon payments, and redeem principal at maturity. All transactions signed through Dfns managed wallets.

<Card title="Get the code" icon="github" href="https://github.com/dfns/dfns-solutions/tree/m/bond-issuance">
  dfns/dfns-solutions: bond-issuance
</Card>

## Overview

The system models a bond issuance with two actors:

* **Issuer**: deploys the bond contract, manages the lifecycle (close issuance, fund coupons, return principal)
* **Investor**: subscribes with stablecoins, claims bond tokens, collects coupons, redeems at maturity

The bond lifecycle works as follows:

1. The Issuer deploys a stablecoin (EURC) and the Bond contract
2. Investors subscribe by depositing EURC into escrow
3. The Issuer closes the issuance. The interest accrual clock starts
4. Investors claim ERC-20 bond tokens proportional to their investment
5. Each coupon period, the Issuer funds and Investors claim their pro-rata share
6. At maturity, the Issuer deposits principal and Investors redeem bonds for EURC

```text theme={null}
Issuer (Dfns Wallet)                Bond Contract               Investor (Dfns Wallet)
       |                                  |                              |
       |-- deploy bond ----------------->|                              |
       |                                  |                              |
       |                                  |<--- approve + subscribe ----|
       |                                  |     EURC held in escrow      |
       |                                  |                              |
       |-- close issuance -------------->|                              |
       |-- withdraw proceeds ----------->|                              |
       |                                  |                              |
       |                                  |<--- claim bond -------------|
       |                                  |     ERC-20 tokens minted     |
       |                                  |                              |
       |-- deposit coupon (periodic) --->|                              |
       |                                  |<--- claim coupon -----------|
       |                                  |     pro-rata EURC payout     |
       |                                  |                              |
       |-- return principal ------------>|  (at maturity)               |
       |                                  |<--- redeem -----------------|
       |                                  |     bonds burned, EURC back  |
```

The contract also includes **default protection**: if the Issuer fails to fund a coupon within a 5-day grace period, anyone can trigger a default flag that halts further operations.

| Component  | Technology                                                    |
| ---------- | ------------------------------------------------------------- |
| Contracts  | Solidity 0.8.28 (OpenZeppelin, Hardhat v3)                    |
| Signing    | Dfns KMS via `@dfns/sdk`                                      |
| Scripts    | TypeScript (tsx, viem)                                        |
| Web UI     | Single-page Express app with split Issuer/Investor dashboards |
| Stablecoin | ERC-20 with mint/burn/pause (6 decimals)                      |
| Network    | Ethereum Sepolia                                              |

## Prerequisites

* A [Dfns account](https://app.dfns.io) with a [service account](/guides/developers/service-account) for API access
* Two Dfns wallets on Ethereum Sepolia (Issuer and Investor)
* Node.js v22+
* Testnet ETH in both wallets for gas fees (use a [Sepolia faucet](https://www.alchemy.com/faucets/ethereum-sepolia))

## Project Structure

```text theme={null}
contracts/
  Bond.sol              ERC-20 bond token with full lifecycle management
  BondMath.sol          Library for accrued interest calculation
  StableCoin.sol        ERC-20 stablecoin with mint, burn, pause, and roles

scripts/
  dfns.ts               Shared Dfns API client and viem public client
  server.ts             Express server with REST API for the web UI
  ui.html               Single-page web UI (Issuer + Investor dashboards)
  e2e.ts                Non-interactive end-to-end lifecycle script
  deploy-stablecoin.ts  Deploy a stablecoin contract
  deploy-bond.ts        Deploy a bond contract with configurable terms
  mint-stablecoin.ts    Mint stablecoin to any address
  issuer-ops.ts         Interactive CLI for issuer operations
  holder-ops.ts         Interactive CLI for investor operations
  stablecoin-ops.ts     Interactive CLI for stablecoin management

test/
  Bond.test.ts          Core lifecycle tests
  BondExtended.test.ts  Over/under-subscription, grace period, interest math
  BondFull.test.ts      Full lifecycle with 4 investors
  BondSpecific.test.ts  Short-duration bond scenario
```

## Configuration

<Steps>
  <Step title="Clone and install">
    ```bash theme={null}
    git clone https://github.com/dfns/dfns-solutions.git
    cd dfns-solutions/bond-issuance
    npm install
    ```
  </Step>

  <Step title="Set up environment variables">
    Copy the example environment file and fill in your values:

    ```bash theme={null}
    cp .env.example .env
    ```

    ```bash .env theme={null}
    # Dfns API Configuration
    DFNS_API_URL=https://api.dfns.io
    DFNS_ORG_ID=or-xxx-xxx
    DFNS_AUTH_TOKEN=eyJ...
    DFNS_CRED_ID=cred-xxx
    DFNS_PRIVATE_KEY="-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----"

    # Dfns Wallet IDs
    ISSUER_WALLET_ID=wa-xxx-xxx
    INVESTOR_WALLET_ID=wa-xxx-xxx

    # Blockchain Configuration (optional)
    SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
    ```

    | Variable             | Description                                                                                                 |
    | -------------------- | ----------------------------------------------------------------------------------------------------------- |
    | `DFNS_API_URL`       | Dfns API base URL (`api.dfns.io`)                                                                           |
    | `DFNS_ORG_ID`        | Your Dfns organization ID. See [how to locate it](/guides/find-organization-id).                            |
    | `DFNS_AUTH_TOKEN`    | Service account auth token. Refer to [creating a service account](/guides/developers/service-account).      |
    | `DFNS_CRED_ID`       | Credential ID for the service account signing key. Find it on the dashboard, on the Service Account page.   |
    | `DFNS_PRIVATE_KEY`   | Service account private key for request signing (PEM format, including the `----BEGIN/END PRIVATE KEY----`) |
    | `ISSUER_WALLET_ID`   | Wallet ID for the bond issuer (`wa-xxxx...`)                                                                |
    | `INVESTOR_WALLET_ID` | Wallet ID for the bond investor (`wa-xxxx...`)                                                              |
    | `SEPOLIA_RPC_URL`    | RPC endpoint (defaults to public Sepolia endpoint if omitted)                                               |
  </Step>

  <Step title="Compile contracts">
    ```bash theme={null}
    npm run compile
    ```
  </Step>

  <Step title="Run tests">
    The test suite runs against a local Hardhat network (no Dfns credentials needed):

    ```bash theme={null}
    npm test
    ```

    16 tests cover the full lifecycle: subscription, over/under-subscription, coupon claims, redemption, grace periods, default triggering, and interest math.
  </Step>
</Steps>

## Deploy

### Step 1: Deploy the stablecoin

```bash theme={null}
npm run deploy:stablecoin
```

The script prompts for a name and symbol (defaults to "Euro Coin" / "EURC") and deploys from the Issuer wallet. Save the contract address.

### Step 2: Mint stablecoin to the investor

```bash theme={null}
npm run mint:stablecoin
```

Enter the stablecoin address, the investor's wallet address, and the amount to mint.

### Step 3: Deploy the bond

```bash theme={null}
npm run deploy:bond
```

The script prompts for bond parameters:

| Parameter        | Default              | Description                             |
| ---------------- | -------------------- | --------------------------------------- |
| Name             | Corporate Bond       | Bond token name                         |
| Symbol           | CB                   | Bond token symbol                       |
| Currency         | none                 | Stablecoin contract address (required)  |
| Notional         | 100                  | Face value per bond in stablecoin units |
| APR              | 400 (4%)             | Annual rate in basis points             |
| Coupon Frequency | 7,776,000 (3 months) | Seconds between coupon periods          |
| Duration         | 31,536,000 (1 year)  | Seconds until maturity                  |
| Cap              | 1,000,000            | Maximum stablecoin to raise             |

Save the bond contract address.

## End-to-End Bond Lifecycle

### Step 1: Investor subscribes

```bash theme={null}
npm run ops:holder
# Select option 2: Subscribe
# Enter the subscription amount in stablecoin units
```

This approves the Bond contract to spend the investor's EURC, then calls `subscribe()`. The stablecoin is held in escrow.

### Step 2: Issuer closes issuance

```bash theme={null}
npm run ops:issuer
# Select option 2: Close Issuance
```

This locks the subscription total, starts the interest accrual clock, and calculates the coupon amount per period.

### Step 3: Issuer withdraws proceeds

```bash theme={null}
npm run ops:issuer
# Select option 3: Withdraw Proceeds
```

The issuer receives the raised stablecoin.

### Step 4: Investor claims bond tokens

```bash theme={null}
npm run ops:holder
# Select option 3: Claim Bond
```

The investor receives ERC-20 bond tokens. The number of tokens is calculated as:

```text theme={null}
bondTokens = (investmentAmount * 10^decimals) / notional
```

Where `decimals` is 6 (matching the stablecoin).

### Step 5: Coupon payments (each period)

**Issuer deposits the coupon:**

```bash theme={null}
npm run ops:issuer
# Select option 5: Deposit Coupon
```

The required amount is calculated automatically: `(totalPrincipal * APR * frequency) / (365 days * 10000)`.

**Investor claims:**

```bash theme={null}
npm run ops:holder
# Select option 4: Claim Coupon
```

The CLI auto-detects all due, funded, unclaimed coupons and claims them in sequence. Each investor's share is proportional to their bond holdings:

```text theme={null}
share = (userBalance / totalSupply) * couponAmount
```

### Step 6: Redemption at maturity

**Issuer returns principal:**

```bash theme={null}
npm run ops:issuer
# Select option 4: Return Principal
```

**Investor redeems:**

```bash theme={null}
npm run ops:holder
# Select option 5: Redeem
```

Bond tokens are burned and the original investment amount is returned in EURC.

## Web UI

The browser UI covers the full bond lifecycle. It shows two dashboards side by side, **Issuer** (blue) and **Investor** (green), with numbered steps to follow in order.

```bash theme={null}
npm run ui
```

Open [http://localhost:3000](http://localhost:3000). The UI calls a lightweight Express server that signs and broadcasts transactions via the Dfns API. Bond status auto-refreshes every 5 seconds.

Each step in the UI maps to a contract function call:

1. **Deploy StableCoin**: deploy the EURC contract
2. **Mint EURC**: fund both wallets
3. **Deploy Bond**: configure face value, APR, coupon frequency, and duration
4. **Subscribe**: investor deposits EURC into escrow
5. **Close Issuance**: lock subscriptions, start interest clock
6. **Withdraw Proceeds**: issuer collects raised EURC
7. **Claim Bond Tokens**: investor mints ERC-20 bond tokens
8. **Deposit Coupon**: issuer funds the next coupon period
9. **Claim Coupon**: investor collects pro-rata interest (auto-detects coupon index)
10. **Return Principal**: issuer deposits EURC for redemption
11. **Redeem**: investor burns bonds, gets principal back

## End-to-End Script

Run the full lifecycle non-interactively:

```bash theme={null}
npm run e2e
```

This deploys both contracts, mints stablecoins, subscribes, closes issuance, claims bonds, deposits and claims a coupon, all in a single run. Useful for verifying the setup end to end.

## Smart Contract Reference

### Bond.sol

An ERC-20 token representing a corporate bond with full lifecycle management:

| Function                  | Caller   | Description                                          |
| ------------------------- | -------- | ---------------------------------------------------- |
| `subscribe(amount)`       | Investor | Deposit stablecoin into escrow during issuance       |
| `closePrimaryIssuance()`  | Issuer   | Lock subscriptions, start interest clock             |
| `withdrawProceeds()`      | Issuer   | Withdraw raised stablecoin                           |
| `claimBond()`             | Investor | Mint bond tokens proportional to subscription        |
| `depositCoupon()`         | Issuer   | Fund the next coupon period                          |
| `claimCoupon(index)`      | Investor | Claim pro-rata coupon payment                        |
| `returnPrincipal(amount)` | Issuer   | Deposit stablecoin for redemption                    |
| `redeem()`                | Investor | Burn bonds, receive principal                        |
| `checkDefault(index)`     | Anyone   | Trigger default if coupon unfunded past grace period |
| `accruedInterest(user)`   | View     | Real-time accrued interest for a holder              |
| `timeToNextCoupon()`      | View     | Seconds until the next coupon date                   |

Bond lifecycle: `Open` → `Issuance Closed` → `Coupons` → `Maturity / Redemption`

Default path: `Coupon Due` → `Grace Period (5 days)` → `Default`

### BondMath.sol

Library for interest calculations:

```solidity theme={null}
Accrued = (principal * APR * timeElapsed) / (365 days * 10000)
```

APR is in basis points (400 = 4%). The contract treats a year as exactly 365 days.

### StableCoin.sol

ERC-20 stablecoin used as the settlement currency:

| Feature            | Implementation                                                   |
| ------------------ | ---------------------------------------------------------------- |
| **Standard**       | ERC-20 with 6 decimals (matching USDC/EURC convention)           |
| **Minting**        | Role-based via `MINTER_ROLE`, only authorized addresses can mint |
| **Burning**        | Any holder can burn their own tokens via `ERC20Burnable`         |
| **Pausing**        | Owner can pause/unpause all transfers                            |
| **Permits**        | ERC-2612 gasless approvals via `ERC20Permit`                     |
| **Access control** | OpenZeppelin `AccessControl` for role management                 |

## How Dfns Is Used

All on-chain transactions are signed and broadcast through the Dfns API. No private keys are stored locally.

| Operation              | Dfns SDK call                                                                                           |
| ---------------------- | ------------------------------------------------------------------------------------------------------- |
| Get wallet address     | `wallets.getWallet({ walletId })`                                                                       |
| Deploy contract        | `wallets.broadcastTransaction({ walletId, body: { kind: "Evm", data: encodedBytecode } })`              |
| Call contract function | `wallets.broadcastTransaction({ walletId, body: { kind: "Evm", to: address, data: encodedCalldata } })` |

The scripts use **viem** to encode deployment data and function calldata, then broadcast via Dfns. A viem `PublicClient` reads on-chain state (balances, bond status, coupon dates) without requiring signing.

## Adapting for Production

**Different networks**: Replace the RPC URL in `.env` with one for your target network (Ethereum mainnet, Polygon, Arbitrum). Update the chain import in `scripts/dfns.ts`.

**Real stablecoins**: Replace the demo `StableCoin` contract with USDC or EURC addresses. The Bond contract accepts any ERC-20 with 6 decimals as the settlement currency.

**Multiple investors**. The system supports any number of investors. Each uses a separate Dfns wallet. Add more wallet IDs to `.env` or build a backend that maps users to Dfns wallets (see the [accept cryptocurrencies](/solutions/accept-cryptocurrencies) blueprint for this pattern).

**Secondary trading**: Bond tokens are standard ERC-20 and can be traded on any DEX. For institutional use, consider an RFQ model (UniswapX or CowSwap) where professional market makers provide quotes based on yield-to-maturity.

**Approval policies**: Dfns policies can enforce approval quorums or velocity limits on sensitive operations (closing issuance, large coupon deposits). This adds governance without changing the smart contracts.
