Skip to content
Open
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
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CI

on:
pull_request:
push:
branches:
- main

jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"

- uses: oven-sh/setup-bun@v2
with:
bun-version: "1.2.19"

- name: Enforce lockfile policy
run: node scripts/check-lockfiles.mjs

- name: Install TypeScript workspace
working-directory: ts
run: bun install --frozen-lockfile

- name: Install Rust scripts workspace
working-directory: rs
run: npm ci

- name: Test admin app
working-directory: ts/apps/admin
run: bun run test

- name: Lint admin app
working-directory: ts/apps/admin
run: bun run lint

- name: Build admin app
working-directory: ts/apps/admin
run: bun run build

- name: Test web app
working-directory: ts/apps/web
run: bun run test

- name: Lint web app
working-directory: ts/apps/web
run: bun run lint

- name: Build web app
working-directory: ts/apps/web
run: bun run build

- name: Test Anchor program
working-directory: rs
run: cargo test -p bunkercash
10 changes: 7 additions & 3 deletions rs/Anchor.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[toolchain]
anchor_version = "0.31.1"
package_manager = "yarn"
package_manager = "npm"

[features]
resolution = true
Expand All @@ -19,9 +19,13 @@ bunkercash = "Fp8b6p287TL5oPMwLVdwGys5phHNLcmTKNvVoGdCJS6g"
url = "https://api.apr.dev"

[provider]
cluster = "devnet"
cluster = "localnet"
wallet = "~/.config/solana/id.json"

[scripts]
deploy-governed = "npx ts-node -P tsconfig.json scripts/deploy-governed.ts"
e2e = "bash -lc 'export USDC_MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU; npx ts-node -P tsconfig.json scripts/e2e-buy-sell.ts'"
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
require-upgrade-authority = "npx ts-node -P tsconfig.json scripts/require-program-upgrade-authority.ts"
test = "npx ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
verify-governance = "npx ts-node -P tsconfig.json scripts/verify-squads-setup.ts"
verify-upgrade-authority = "npx ts-node -P tsconfig.json scripts/verify-program-upgrade-authority.ts"
110 changes: 83 additions & 27 deletions rs/COMMANDS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# BunkerCash – Commands (Start to End)

All commands assume you are in the `rs` directory and use the **pool admin** wallet for admin-only steps. For **mainnet**, switch `ANCHOR_PROVIDER_URL` and `solana config` to a mainnet RPC and set `USDC_MINT` to mainnet USDC.
All commands assume you are in the `rs` directory. For production-like
privileged actions, `pool.master_wallet` must be the Squads Vault PDA and the
admin UI/scripts must create Squads Vault Transactions rather than signing
directly with a human member wallet. For **mainnet**, switch
`ANCHOR_PROVIDER_URL` and `solana config` to a mainnet RPC and set `USDC_MINT`
to canonical mainnet USDC.

---

Expand All @@ -11,6 +16,7 @@ cd rs
export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
export ANCHOR_WALLET=~/.config/solana/id.json
export USDC_MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
export SQUADS_PROGRAM_UPGRADE_AUTHORITY=<confirmed-squads-controlled-authority>
```

Or source the example env:
Expand All @@ -22,21 +28,64 @@ source scripts/devnet.env.example.sh

---

## 2. Build & Deploy (admin / deployer)
## 2. Build & deploy with upgrade-authority handoff (admin / deployer)

`SQUADS_PROGRAM_UPGRADE_AUTHORITY` is the expected authority for Solana program
upgrades. Confirm this address separately. Do not assume it is the same address
as `pool.master_wallet` / `SQUADS_VAULT_PUBKEY`.

```bash
cd rs
anchor build
solana config set --url devnet
solana airdrop 2
npm run -s require:upgrade-authority
npm run -s deploy:governed
solana program show Fp8b6p287TL5oPMwLVdwGys5phHNLcmTKNvVoGdCJS6g
npm run -s verify:upgrade-authority
```

Manual equivalent:

```bash
anchor deploy
solana program show Fp8b6p287TL5oPMwLVdwGys5phHNLcmTKNvVoGdCJS6g
solana program set-upgrade-authority Fp8b6p287TL5oPMwLVdwGys5phHNLcmTKNvVoGdCJS6g \
--new-upgrade-authority "$SQUADS_PROGRAM_UPGRADE_AUTHORITY" \
--skip-new-upgrade-authority-signer-check
solana program show Fp8b6p287TL5oPMwLVdwGys5phHNLcmTKNvVoGdCJS6g
npm run -s verify:governance
```

Blockage rule: if `SQUADS_PROGRAM_UPGRADE_AUTHORITY` is missing, deploy is not
allowed. If `solana program show` does not show that authority, or `none` for an
intentionally immutable program, do not bootstrap or fund.

---

## 3. Bootstrap pool (admin, first time only)
## 3. Verify governance before bootstrap/funding

Creates the pool and BunkerCash mint if they don’t exist. The wallet in `ANCHOR_WALLET` becomes **pool admin** (stored in `PoolState.admin`).
```bash
cd rs
npm run -s verify:governance
```

This verifies both governance layers:

- `pool.master_wallet` is the expected Squads vault PDA.
- program upgrade authority is the expected Squads-controlled authority, or the
program is explicitly immutable.

Mainnet funding is blocked in scripts that move USDC until this check passes.

---

## 4. Bootstrap pool (admin, first time only)

Creates the pool and BunkerCash mint if they don’t exist. Production-like
initialization must set `master_wallet` to the Squads Vault PDA. If a recovery
rotation is needed later, use `update_master_wallet` through a Squads Vault
Transaction signed by the current `pool.master_wallet`.

```bash
cd rs
Expand All @@ -54,7 +103,7 @@ TEST_BUY_USDC=2.5 npx ts-node -P tsconfig.json scripts/bootstrap-fixed-price.ts

---

## 4. User: Buy (buy_primary)
## 5. User: Buy (buy_primary)

User spends USDC and receives BunkerCash at the current pool price.

Expand All @@ -79,7 +128,7 @@ Use the bRENT/Buy UI: connect wallet, enter USDC amount, submit. The app calls `

---

## 5. User: Sell (register_sell)
## 6. User: Sell (register_sell)

User locks BunkerCash into the escrow vault and gets a claim. No burn; payouts happen when admin runs `process_claims`.

Expand Down Expand Up @@ -111,7 +160,7 @@ Use the Sell tab: connect wallet, enter token amount, submit. The app calls `reg

---

## 6. Admin: Update price (update_price)
## 7. Admin: Update price (update_price)

Only the pool admin can change the fixed price (USDC per token, in base units).

Expand All @@ -127,10 +176,13 @@ Example: `1000000` = 1 USDC (6 decimals) per 1 token (9 decimals).

---

## 7. Admin: Add liquidity (add_liquidity)
## 8. Admin: Add liquidity (add_liquidity)

Admin sends USDC into the payout vault so it can be distributed to sell claimants. No standalone script; use the E2E script with `LIQ_USDC`:

On mainnet, this script refuses to move funds unless the program upgrade
authority is verified against `SQUADS_PROGRAM_UPGRADE_AUTHORITY`.

```bash
cd rs
source scripts/devnet.env.example.sh
Expand All @@ -143,7 +195,7 @@ This runs the full e2e flow (buy/sell if configured) and also calls `add_liquidi

---

## 8. Admin: Process claims (process_claims)
## 9. Admin: Process claims (process_claims)

Admin triggers pro-rata USDC payouts from the payout vault to all open claims (and updates each claim’s `usdc_paid`). The E2E script does this when there are open claims and liquidity.

Expand All @@ -160,7 +212,7 @@ The script fetches open claims, ensures user USDC ATAs exist, then calls `proces

---

## 9. Admin: Init token metadata (init_mint_metadata)
## 10. Admin: Init token metadata (init_mint_metadata)

So wallets (e.g. Phantom) show the token name/symbol/icon instead of “Unknown Token”. Admin only, once per mint.

Expand All @@ -177,7 +229,7 @@ npm run -s init:metadata

---

## 10. Utility: Check pool & balances
## 11. Utility: Check pool & balances

```bash
cd rs
Expand All @@ -195,7 +247,7 @@ Prints pool PDA, pool state (price, claim_counter), and token balances (user USD

---

## 11. Utility: Inspect open claims
## 12. Utility: Inspect open claims

```bash
cd rs
Expand All @@ -209,23 +261,27 @@ Optional: `TOP_N=20` to show top 20 by locked amount (default 10).

## Quick reference

| Role | Action | Command / script / app |
|--------|------------------|-------------------------|
| User | Buy (USDC → BNKR)| Web app, or `TEST_BUY_USDC=…` bootstrap, or `BUY_USDC=…` e2e |
| User | Sell (lock BNKR) | Web app, or `register-sell-escrow.ts` (SELL_TOKEN_AMOUNT), or e2e (SELL_BNKR) |
| Admin | Update price | `update-price.ts` (NEW_PRICE_USDC_PER_TOKEN) |
| Admin | Add liquidity | e2e with `LIQ_USDC=…` (wallet = admin) |
| Admin | Process claims | e2e (wallet = admin; script calls process_claims when claims exist) |
| Admin | Init metadata | `npm run -s init:metadata` (TOKEN_NAME, TOKEN_SYMBOL, TOKEN_URI) |
| Anyone | Check balances | `check-pool-balances.ts` / `npm run -s check:pool` |
| Anyone | List open claims| `inspect-open-claims.ts` |
| Role | Action | Command / script / app |
| ------ | ----------------- | ----------------------------------------------------------------------------- |
| User | Buy (USDC → BNKR) | Web app, or `TEST_BUY_USDC=…` bootstrap, or `BUY_USDC=…` e2e |
| User | Sell (lock BNKR) | Web app, or `register-sell-escrow.ts` (SELL_TOKEN_AMOUNT), or e2e (SELL_BNKR) |
| Admin | Update price | `update-price.ts` (NEW_PRICE_USDC_PER_TOKEN) |
| Admin | Add liquidity | e2e with `LIQ_USDC=…` (wallet = admin) |
| Admin | Process claims | e2e (wallet = admin; script calls process_claims when claims exist) |
| Admin | Init metadata | `npm run -s init:metadata` (TOKEN_NAME, TOKEN_SYMBOL, TOKEN_URI) |
| Anyone | Check balances | `check-pool-balances.ts` / `npm run -s check:pool` |
| Anyone | List open claims | `inspect-open-claims.ts` |

---

## Summary flow

1. **Deploy** program and **bootstrap** pool (admin wallet becomes pool admin).
2. **Users** buy (USDC → BunkerCash) and sell (lock BunkerCash → claim).
3. **Admin** adds USDC to the payout vault (`add_liquidity` via e2e with `LIQ_USDC`).
4. **Admin** runs `process_claims` (e2e or custom tool) to distribute USDC pro-rata to claimants.
5. **Admin** can update price anytime (`update-price.ts`) and set token metadata once (`init:metadata`).
1. **Deploy** program.
2. **Verify** current program upgrade authority.
3. **Transfer** program upgrade authority to `SQUADS_PROGRAM_UPGRADE_AUTHORITY`.
4. **Verify** program upgrade authority again with `solana program show` and `npm run -s verify:governance`.
5. **Bootstrap/fund** only after governance verification passes.
6. **Users** buy (USDC → BunkerCash) and sell (lock BunkerCash → claim).
7. **Admin** adds USDC to the payout vault (`add_liquidity` via e2e with `LIQ_USDC`).
8. **Admin** runs `process_claims` (e2e or custom tool) to distribute USDC pro-rata to claimants.
9. **Admin** can update price anytime (`update-price.ts`) and set token metadata once (`init:metadata`).
40 changes: 35 additions & 5 deletions rs/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
# BunkerCash - Solana Program

Fixed-price primary buy + irreversible sell registration (escrow lock; no burn).
NAV-priced primary buy + escrowed sell claims with settlement epochs.

Current governance model:

- `pool.master_wallet` is the privileged authority and should be initialized to
the Squads Vault PDA for production-like environments.
- Privileged actions are executed through Squads Vault Transactions, not direct
human-wallet signatures.
- `update_master_wallet` exists for recovery/rotation and is gated by the
current `pool.master_wallet`, so a Squads Vault can rotate authority through a
vault transaction.
- Program upgrade authority is a separate, higher-power control and must be
Squads-controlled or revoked before mainnet funding.

Current pricing/settlement model:

- Buy quotes use available NAV (`pool.nav - pool.total_pending_claims`) divided
by circulating BunkerCash supply. UI surfaces the age of cached NAV reads.
- Sell requests create claim accounts and lock BunkerCash in escrow.
- Settlement eligibility uses monotonic claim sequence snapshots, not wall-clock
timestamp comparisons.

## Development

Expand All @@ -10,15 +30,23 @@ Prerequisites: Rust 1.70+, Solana CLI 1.18+, Anchor 0.31.1, Node.js 18+
# Build
anchor build

# Deploy to devnet
# Deploy to devnet with mandatory program upgrade-authority handoff.
# SQUADS_PROGRAM_UPGRADE_AUTHORITY must be a confirmed Squads-controlled address.
solana config set --url devnet
solana airdrop 2
anchor deploy
export SQUADS_PROGRAM_UPGRADE_AUTHORITY=<confirmed-squads-controlled-authority>
npm run -s deploy:governed
npm run -s verify:upgrade-authority
```

Do not bootstrap or fund mainnet until `npm run -s verify:governance` passes. The
program upgrade authority is above `pool.master_wallet`; setting `pool.master_wallet`
to a Squads vault does not by itself prevent a deployer wallet from upgrading the
program.

## Testing from the command line

Tests use the same IDL as the web app (`ts/apps/web/lib/bunkercash.fixed.idl.json`) so they match the current program (initialize, buy_primary, update_price, register_sell).
Tests use the same IDL as the web app (`ts/apps/web/lib/bunkercash.fixed.idl.json`) so they match the current program.

**1. Run Anchor tests (initialize, or skip if pool exists)**

Expand All @@ -33,7 +61,7 @@ Or run only the TypeScript test file:

```bash
cd rs
yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bunkercash.ts
npx ts-mocha -p ./tsconfig.json -t 1000000 tests/bunkercash.ts
```

**2. Bootstrap pool + optional buy (full manual flow)**
Expand All @@ -45,6 +73,8 @@ cd rs
export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
export ANCHOR_WALLET=~/.config/solana/id.json
export USDC_MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
export SQUADS_PROGRAM_UPGRADE_AUTHORITY=<confirmed-squads-controlled-authority>
npm run -s verify:governance
npx ts-node -P tsconfig.json scripts/bootstrap-fixed-price.ts
# Optional: run a test buy (e.g. 2.5 USDC worth)
TEST_BUY_USDC=2.5 npx ts-node -P tsconfig.json scripts/bootstrap-fixed-price.ts
Expand Down
13 changes: 12 additions & 1 deletion rs/migrations/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@
// configured from the workspace's Anchor.toml.

import * as anchor from "@coral-xyz/anchor";
import { requireProgramUpgradeAuthorityEnv } from "../scripts/governance";

module.exports = async function (provider: anchor.AnchorProvider) {
// Configure client to use the provider.
anchor.setProvider(provider);

// Add your deploy script here.
const upgradeAuthority = requireProgramUpgradeAuthorityEnv();
console.log(
"Program upgrade authority required:",
upgradeAuthority.toBase58()
);
console.log("After deploy, run:");
console.log(
" solana program set-upgrade-authority <PROGRAM_ID> --new-upgrade-authority " +
upgradeAuthority.toBase58() +
" --skip-new-upgrade-authority-signer-check"
);
};
Loading
Loading