# descro A decentralized escrow protocol on Solana for physical goods trading. Fully permissionless and resolver-agnostic — any wallet or program that implements the resolver interface can adjudicate disputes. ## Overview ``` APPLICATION LAYER Central Platform (own resolver, KYC, normal UX) Decentralized Clients (raw protocol access, free resolver choice) │ PROTOCOL LAYER ┌─────────────────┐ ┌──────────────────────┐ │ Escrow Program │────▶│ Resolver Registry │ │ (descro) │ CPI │ (descro_ext_res.) │ └─────────────────┘ └──────────────────────┘ ``` The protocol only defines the interface. Any resolver that implements it is compatible — from a single authority wallet to a jury DAO voting contract. ## Programs | Program | ID | |---|---| | `descro` | `DjVR4EuYV6USMJFfsGZwhZ3y8rtWsmG8EvDY96GTqqi3` | | `descro_ext_resolvers` | `GwUPAKs3HHzCpj8uhet4NAnxk9GWNwfrYbpihu5DyFp` | ### Escrow Program (`descro`) Holds trade state and SOL. Two PDAs per escrow: - **EscrowAccount** `["escrow", seller_pubkey, escrow_id_le_bytes]` — trade state - **Vault** `["vault", escrow_account_pubkey]` — system-owned, holds SOL only **Instruction flow:** ``` create_escrow (seller) └─▶ deposit (buyer) ├─▶ complete (buyer) → SOL released to seller └─▶ dispute (buyer | seller) └─▶ resolve (resolver) → SOL released to winner cancel (seller, only from AwaitingDeposit) ``` The `dispute_resolver` field in the escrow can be any pubkey — a wallet signing directly or a program calling `resolve()` via CPI. The check is identical either way. **Emergency resolve:** After 14 days in `Disputed` state with no resolution, either party can call `emergency_resolve` to unlock funds. ### Resolver Registry (`descro_ext_resolvers`) An on-chain directory for resolvers. Resolvers register with metadata (name, type, fee, URI); resolution stats are auto-incremented by the escrow program via CPI and cannot be manipulated externally. `update_stats` is callable only by the escrow program — enforced via an `["escrow_authority"]` PDA that only the escrow program can sign for. Resolver types: `CentralAuthority`, `JuryDAO`, `MAD`, `Algorithmic`, `Multisig` ## Repository Structure ``` programs/ ├── descro/ — Escrow Program (Rust/Anchor) └── descro_ext_resolvers/ — Resolver Registry (Rust/Anchor) sdk/ — @descro/sdk TypeScript client ├── src/ │ ├── idl/ — Anchor-generated IDL JSON │ ├── escrow.ts — EscrowClient (instruction builders + fetchers) │ ├── registry.ts — RegistryClient │ ├── listener.ts — WebSocket subscriptions │ ├── pda.ts — PDA derivation helpers │ ├── types.ts — TypeScript types matching on-chain state │ └── index.ts — Re-exports + DescroSdk convenience class app/ — Next.js + Tamagui playground ├── src/app/ — App Router pages + providers ├── src/components/ — EscrowDetail, ResolverPanel, … └── src/hooks/ — useEscrows, useEscrowDetail ``` ## Getting Started ### Prerequisites - Rust + `cargo-build-sbf` - Solana CLI - Node.js + Yarn ### Build Programs must be built in order — the escrow program depends on the registry: ```bash cargo build-sbf --manifest-path programs/descro_ext_resolvers/Cargo.toml cargo build-sbf --manifest-path programs/descro/Cargo.toml ``` Install JS dependencies from the repo root: ```bash yarn install ``` ### Test Tests load the compiled `.so` files via `include_bytes!`, so programs must be built first: ```bash cargo test --manifest-path programs/descro/Cargo.toml cargo test --manifest-path programs/descro_ext_resolvers/Cargo.toml ``` Run a single test: ```bash cargo test --manifest-path programs/descro/Cargo.toml -- test_create_escrow cargo test --manifest-path programs/descro/Cargo.toml --test test_resolve ``` ### App (dev server) ```bash yarn workspace descro-app run dev ``` ## SDK Usage The SDK is consumed as raw TypeScript via `transpilePackages` in the Next.js config — no build step needed. ```ts import { DescroSdk } from "@descro/sdk"; const sdk = new DescroSdk(provider); // Create escrow await sdk.escrow.createEscrow({ amount, disputeResolver, escrowId }); // Fetch all escrows for a seller const escrows = await sdk.escrow.fetchEscrowsBySeller(sellerPubkey); // Subscribe to state changes sdk.listener.onEscrowUpdate(escrowPubkey, (account) => { ... }); ``` ## Security Properties | Property | Mechanism | |---|---| | Nobody can take SOL without consent | Network enforces signature requirement at protocol level | | Vault can only be drained by the escrow program | Vault owner = system_program, only accessible via `invoke_signed` with correct seeds | | Wrong resolver cannot call `resolve()` | `require!(signer.key() == escrow.dispute_resolver)` | | PDAs cannot be spoofed | Program verifies seeds on-chain for every instruction | | Registry stats cannot be manipulated | `update_stats` only callable via CPI from the escrow program | ## Architecture Notes - **Anchor 1.0.x:** `UncheckedAccount<'info>` with `/// CHECK:` doc comments; `#[derive(InitSpace)]` + `#[max_len(N)]` for strings. - **`@coral-xyz/anchor@0.32.x`:** Use `new Program(idl, provider)` — the IDL's `address` field provides the program ID. The 3-argument form is broken in 0.32.x. - **Yarn workspaces:** `nodeLinker: node-modules` (not PnP) — required for Next.js Turbopack compatibility. - **LiteSVM tests:** each `tests/common/mod.rs` provides `setup()`, PDA helpers, `send()`/`try_send()`, `ix_*` builders, and `read_*` deserializers. `test_resolve_with_registry.rs` loads both `.so` files to test the full CPI flow.