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

on:
push:
branches:
- omni-main
pull_request:

permissions:
checks: write

jobs:
rust-checks:
runs-on: warp-ubuntu-latest-x64-4x
steps:
- uses: actions/checkout@v5

- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libudev-dev

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
cache-provider: warpbuild

- name: Run lint
run: make lint

- name: Install cargo-near
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh

- name: Run tests
run: make test

43 changes: 43 additions & 0 deletions .github/workflows/update-contracts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
on:
push:
tags:
- 'btc-bridge-v[0-9]+.[0-9]+.[0-9]+*'

workflow_dispatch:

name: Update Contracts
jobs:
update-contracts:
runs-on: ubuntu-latest
name: Update Contracts
permissions:
contents: write
steps:
- name: Clone the repository
uses: actions/checkout@v3

- name: Install cargo-near
run: |
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/near/cargo-near/releases/latest/download/cargo-near-installer.sh | sh

- name: Build NEAR contracts
run: |
make release
timeout-minutes: 60

- name: Archive built WASM files
env:
RAW_TAG: ${{ github.ref_name }}
run: |
SAFE_TAG="${RAW_TAG//./-}"
ZIP_NAME="${SAFE_TAG}.zip"
mkdir -p artifacts
find ./res -name "*.wasm" -exec cp {} artifacts/ \;
zip -j "$ZIP_NAME" artifacts/*
shell: bash

- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: "*.zip"
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CLAUDE.md
149 changes: 149 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# NEAR BTC/Zcash Bridge

Bridge between Bitcoin/Zcash and NEAR Protocol. Users deposit BTC/ZEC to receive nBTC/nZEC (NEP-141 token) and withdraw nBTC/nZEC to receive BTC/ZEC back.

**Trust Model:**
- **BTC → NEAR (deposit):** Trustless verification via BTC Light Client (Merkle proof validation)
- **NEAR → BTC (withdraw):** Requires trust in NEAR validator set for Chain Signatures (MPC)

---

## Build / Test / Lint

```bash
# Build for development (non-reproducible)
make build-local-bitcoin # Bitcoin bridge
make build-local-zcash # Zcash bridge

# Run tests
make test

# Format and clippy
cargo fmt --all # Format all code
make clippy-bitcoin # Clippy for Bitcoin
make clippy-zcash # Clippy for Zcash
```

---

## Key Architecture

**Contracts:** `contracts/nbtc/` (NEP-141 token), `contracts/satoshi-bridge/` (main bridge), `contracts/mock-*` (testing)

**External Dependencies:** BTC Light Client (Merkle proof verification), Chain Signatures (MPC signing)

### Bridge Flows

**Deposit (BTC → nBTC)**
```
1. User sends BTC to deposit address (derived from DepositMsg hash)
2. Relayer: bridge.verify_deposit(tx_proof)
3. Bridge verifies with Light Client → calls nbtc.mint(user, amount)
4. UTXO added to bridge's available set
```

**Withdraw (nBTC → BTC)**
```
1. User: nbtc.ft_transfer(bridge, amount, WithdrawMsg)
→ Tokens TRANSFERRED to bridge (not burned yet!)
2. nBTC: bridge.ft_on_transfer(user, amount, msg) → Bridge returns 0 (keeps tokens)
3. Bridge creates BTC tx, Chain Signatures signs
4. Tx broadcast to Bitcoin network
5. Relayer: bridge.verify_withdraw(tx_proof)
6. Bridge verifies → calls nbtc.burn(user, amount, relayer, fee)
→ Burns from bridge balance (tokens already there!)
```

---

## Security Invariants

### Token Flow (NEP-141)
- **Withdraw tokens already transferred:** By the time `burn()` is called, tokens are in bridge balance via `ft_transfer`
- **burn_account_id is for events only:** Actual burn happens from bridge balance, not from burn_account_id
- **ft_on_transfer return value:** `0` = keep all tokens, `amount` = refund amount
- Only burn after BTC tx is verified on-chain

### Arithmetic Safety
- **overflow-checks = true:** All overflow panics in release mode (fail-safe)
- Use `checked_mul()`, `checked_add()` for explicit error handling
- Prefer panic over silent

### State Management
- Mutate state (mark UTXO used, update balances) BEFORE cross-contract calls
- Create and emit events AFTER all state mutations complete
- **Cross-contract calls are NOT atomic:** Each callback is a separate transaction - must manually rollback state in callback if external call fails

### Zcash-Specific
- **Mutual exclusion:** `actual_received_amounts.len() == 1` ensures EITHER transparent OR Orchard output, never both
- **OVK required:** All Orchard bundles must provide valid Outgoing Viewing Key for decryption
- **Address restrictions:** Transparent addresses CANNOT accept Orchard bundles (panics)
- **Bridge transparency:** Full transaction tracking required, privacy is NOT a design goal
- **Branch IDs hardcoded:** Network upgrades require contract redeployment anyway

---

## Critical Patterns

**NEAR decorators:** `#[private]` for callbacks, `#[access_control_any(roles(...))]` for admin functions, `#[pause(except(roles(...)))]` for pausable functions, `assert_one_yocto()` to prevent batching

**Security checks:** Always use `require!(condition, "message")` for validation, `checked_*` arithmetic for money operations, emit events AFTER state changes

---

## Safe Functions (Omni Bridge Integration)

The bridge provides "safe" versions of deposit/mint functions primarily used by Omni Bridge:

### verify_deposit vs safe_verify_deposit

**verify_deposit (standard):**
- Normal deposit flow with fees
- Charges deposit bridge fee
- Pays for user's token storage
- Requires `safe_deposit: None` in DepositMsg
- Does NOT revert on mint failures (uses lost & found)

**safe_verify_deposit (integration):**
- Primarily used by Omni Bridge
- NO fees charged
- User must attach NEAR for storage (via `#[payable]`)
- **Reverts entire transaction if mint fails** (no lost & found)
- Requires `safe_deposit: Some(SafeDepositMsg)` in DepositMsg
- **post_actions must be None** (not supported in safe mode)
- Safer for integrations - atomic success/failure

### mint vs safe_mint (nbtc contract)

**mint (standard):**
- Mints tokens unconditionally
- If account not registered → panics or creates account

**safe_mint (integration):**
- Checks if account is registered first
- If NOT registered → returns `U128(0)` instead of panicking
- Used by safe_verify_deposit to detect failures

---

## Design Decisions (Non-Issues)

These patterns are intentional. Do not flag or "fix" them:

- **DAO powers are by design:** Governance functions with DAO role are necessary, not a vulnerability
- **Expiry height gap:** Buffer for transaction processing delays (Zcash)
- **No validation for self-serialized data:** Format guaranteed by construction - only validate external inputs
- **Public API vs private callbacks:** If parameter cannot be passed through public API, no vulnerability exists

---

## Git Workflow

**Main branch:** `omni-main` (use for PRs)

**Before committing:** Run `cargo test`, `cargo fmt`, `cargo clippy`. **Only commit if user explicitly requests.**

---

*Version: 2.1*
*Last Updated: 2026-02-16*
Loading
Loading