Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions examples/private-mint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Private Mint Example

Demonstrates a cross-program bridge flow on Aleo where a Circle XReserve attestation mints tokens publicly to a wrapper program, which immediately converts them into a private token record for the end user — all in a single atomic transaction. This flow enables the final recipient's Aleo address to remain fully confidential.

## What it does

1. Uses a deposit payload signed by Circle's off-chain attester that authorizes a mint of a specific amount to `bridge_demo_v1.aleo`.
2. The wrapper calls `test_usdcx_bridge.aleo::mint_public`, which verifies the attestation and mints USDCx to the wrapper's public balance.
3. The wrapper then calls `test_usdcx_stablecoin.aleo::transfer_public_to_private`, converting the public balance into a private `Token` record sent to `self.signer` (the end user).
4. The nullifier for the attestation nonce is set on-chain, preventing replay.

## Programs

| Program | Role |
|---|---|
| `bridge_demo_v1.aleo` | Wrapper — orchestrates mint + private transfer |
| `test_usdcx_bridge.aleo` | Verifies attestation, sets nullifier, mints to caller |
| `test_usdcx_stablecoin.aleo` | USDCx token contract with public/private balance support |
| `test_usdcx_freezelist.aleo` | Freeze list for the USDCx stablecoin |
| `test_usdcx_multisig_core.aleo` | Multisig wallet required for gated operations |
| `merkle_tree.aleo` | Merkle tree utilities used to verify non-inclusion in freeze list |

## Directory structure

```
private-mint/
├── private-mint-wrapper/
│ ├── src/main.leo # Leo source for bridge_demo_v1.aleo
│ ├── imports/ # Pre-compiled .aleo bytecode for all dependencies
│ ├── build/ # leo build output (main.aleo + compiled imports)
│ └── program.json # Leo project manifest
└── tests/
└── private-mint.test.ts # E2E test suite
```

## Running the tests

The test suite starts a local Devnode, deploys all programs, initializes the stablecoin, and verifies the full mint flow.

```bash
PRIVATE_MINT_E2E=1 pnpm vitest run examples/private-mint/tests/private-mint.test.ts
```

The `PRIVATE_MINT_E2E=1` flag is required — without it the suite is skipped.

### Prerequisites

- `leo` CLI installed and on `$PATH` (used to rebuild the wrapper before deployment)
- `@veil/devnode` available (pulled in as a workspace dependency)

## Rebuilding the wrapper

If you modify `src/main.leo`, rebuild before running the tests:

```bash
cd examples/private-mint/private-mint-wrapper
leo build
```

The test suite calls `leo.build()` automatically via `@veil/leo` before deploying, so a manual rebuild is only needed if you want to inspect the generated bytecode.

## Important constraint — program name

The wrapper **must** remain named `bridge_demo_v1.aleo`. The test payload encodes the bech32 address of `bridge_demo_v1.aleo` in bytes 76–107. The bridge mints tokens to that address, so the wrapper's on-chain address must match. Renaming the program changes its address, causing finalize to fail with an insufficient balance error.

## How `veil` is used

```typescript
import { createTestClient, http } from '@veil/core'
import { createDevnodeClient } from '@veil/provable'
import { createLeoClient } from '@veil/leo'

// Start devnode and build clients
const devnode = await startDevnode({ manualBlockCreation: false })
const { walletClient, publicClient } = createDevnodeClient({ socketAddr: devnode.socketAddr })

// Deploy and call contracts
await walletClient.deployContract({ program: source })
await walletClient.writeContract({ program: 'bridge_demo_v1.aleo', function: 'mint_and_send_private', inputs: [...] })

// Read mapping state
const nullifier = await publicClient.readMapping({
programId: 'test_usdcx_bridge.aleo',
mapping: 'nullifier',
key: nonce, // array-type key, e.g. '[180u8, 125u8, ...]'
})
```
Loading