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
28 changes: 26 additions & 2 deletions .github/workflows/snippets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,15 @@ jobs:
uses: actions/checkout@v4

# -----------------------------------------------------------------------
# Rust — pinned stable toolchain for reproducibility.
# Rust — pinned toolchain for reproducibility. 1.89 is the floor for the
# Solana workspace's dependency tree (anchor-lang 0.32 pulls crates that
# require edition 2024 and rustc 1.87+); rustfmt gates formatting.
# -----------------------------------------------------------------------
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: "1.78.0"
toolchain: "1.89.0"
components: rustfmt

# -----------------------------------------------------------------------
# Python — pinned minor version; patch resolved by setup-python.
Expand Down Expand Up @@ -147,6 +150,27 @@ jobs:
# 1 – content regression → always a hard failure
# 2 – provider outage → warning annotation, does NOT block merge
# ---------------------------------------------------------------------------
# -----------------------------------------------------------------------
# Solana Anchor workspace — the structural reference behind
# contracts/solana.mdx. `cargo check` type-checks every documented
# instruction, account context, event, account layout, and error against
# the pinned anchor-lang; the TS check compares each fence against the
# workspace and compares program IDs, layouts, and discriminators with
# the canonical program sources and the chain-generated deployment
# record in wraith-protocol/contracts. These are structural checks —
# they do not execute or deploy the programs.
# -----------------------------------------------------------------------
- name: Check Solana program workspace
working-directory: snippets/solana-contracts
run: cargo check --workspace --locked

- name: Check Solana workspace formatting
working-directory: snippets/solana-contracts
run: cargo fmt --check --all

- name: Compare Solana program IDs and account layouts
run: pnpm run check:solana-contracts

stellar-testnet-snippets:
name: Stellar snippet testnet validation
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ node_modules/
.npm/
test-results/
playwright-report/

# Rust build artifacts
snippets/*/target/
132 changes: 113 additions & 19 deletions contracts/solana.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ description: "Anchor program specs and deployment"

Solana programs (smart contracts) for stealth address operations, written in Rust with the Anchor framework.

Every Rust block on this page is a verbatim excerpt from the reference
workspace in [`snippets/solana-contracts`](https://github.com/wraith-protocol/docs/tree/develop/snippets/solana-contracts),
which CI type-checks with `cargo check --workspace` against the pinned
`anchor-lang` release and gates with `cargo fmt --check`. A second CI step
compares the workspace's program IDs, instruction signatures, account
layouts, events, and error codes against the canonical program sources and
the chain-generated deployment record in the `wraith-protocol/contracts`
repository.

Those checks are **structural**. They confirm that the documented API shape
matches the canonical program and that the program IDs match the deployment
record. They do not execute the program, deploy it, or verify runtime
on-chain behaviour; the workspace is a documentation fixture, not a deployment
artifact.

Two things are intentionally abbreviated here:

- Instruction fences show the public signature only; the transfer, CPI, and
event-emission bodies live in the workspace crates.
- The bare PDA `seeds = ...` fragment under [wraith-names](#pda-derivation) is
an Anchor account-attribute fragment, not a standalone Rust item, so it is
marked `no-check`.

## Program Set

| Program | Purpose |
Expand All @@ -21,7 +44,7 @@ Emits announcement events via Anchor's `emit!()` macro. Stateless — no on-chai

### Instruction

```rust no-check
```rust
pub fn announce(
ctx: Context<Announce>,
scheme_id: u32,
Expand All @@ -33,7 +56,7 @@ pub fn announce(

### Accounts

```rust no-check
```rust
#[derive(Accounts)]
pub struct Announce<'info> {
#[account(mut)]
Expand All @@ -43,7 +66,7 @@ pub struct Announce<'info> {

### Event

```rust no-check
```rust
#[event]
pub struct AnnouncementEvent {
pub scheme_id: u32,
Expand Down Expand Up @@ -85,7 +108,7 @@ Atomic SOL or SPL token transfer + announcement in one instruction. Sends funds

Transfer SOL to a stealth address and emit an announcement.

```rust no-check
```rust
pub fn send_sol(
ctx: Context<SendSol>,
amount: u64,
Expand All @@ -98,7 +121,7 @@ pub fn send_sol(

**Accounts:**

```rust no-check
```rust
#[derive(Accounts)]
pub struct SendSol<'info> {
#[account(mut)]
Expand All @@ -114,7 +137,7 @@ pub struct SendSol<'info> {

Transfer SPL tokens to a stealth address's associated token account and emit an announcement.

```rust no-check
```rust
pub fn send_spl(
ctx: Context<SendSpl>,
amount: u64,
Expand All @@ -127,7 +150,7 @@ pub fn send_spl(

**Accounts:**

```rust no-check
```rust
#[derive(Accounts)]
pub struct SendSpl<'info> {
#[account(mut)]
Expand All @@ -140,6 +163,23 @@ pub struct SendSpl<'info> {
}
```

### Event

Both instructions emit the same `AnnouncementEvent` that the announcer
program emits, so indexers can subscribe to either program. The reference
crate re-declares it rather than importing across programs:

```rust
#[event]
pub struct AnnouncementEvent {
pub scheme_id: u32,
pub stealth_address: Pubkey,
pub caller: Pubkey,
pub ephemeral_pub_key: [u8; 32],
pub metadata: Vec<u8>,
}
```

### Usage

```typescript
Expand Down Expand Up @@ -178,7 +218,7 @@ PDA-based name to meta-address mapping. Names are stored in Program Derived Addr

Register a new `.wraith` name.

```rust no-check
```rust
pub fn register(
ctx: Context<Register>,
name: String,
Expand All @@ -190,32 +230,78 @@ pub fn register(

Update the meta-address for a name you own.

```rust no-check
pub fn update(
ctx: Context<Update>,
new_meta_address: [u8; 64],
) -> Result<()>
```rust
pub fn update(ctx: Context<Update>, new_meta_address: [u8; 64]) -> Result<()>
```

#### `release`

Release a name. Closes the PDA account and returns rent to the owner.

```rust no-check
```rust
pub fn release(ctx: Context<Release>) -> Result<()>
```

#### `resolve`

Look up a name's meta-address. Read-only.

```rust no-check
```rust
pub fn resolve(ctx: Context<Resolve>) -> Result<[u8; 64]>
```

### Account Contexts

The account layouts each instruction validates against. Only `Register`
creates the PDA; the other three reference it through the same seeds:

```rust
#[derive(Accounts)]
#[instruction(name: String)]
pub struct Register<'info> {
#[account(
init,
payer = owner,
space = 8 + 4 + MAX_NAME_LEN + 64 + 32 + 8,
seeds = [b"name", name.as_bytes()],
bump,
)]
pub name_record: Account<'info, NameRecord>,
#[account(mut)]
pub owner: Signer<'info>,
pub system_program: Program<'info, System>,
}
```

```rust
#[derive(Accounts)]
pub struct Update<'info> {
#[account(mut)]
pub name_record: Account<'info, NameRecord>,
pub owner: Signer<'info>,
}
```

```rust
#[derive(Accounts)]
pub struct Release<'info> {
#[account(mut, close = owner)]
pub name_record: Account<'info, NameRecord>,
#[account(mut)]
pub owner: Signer<'info>,
}
```

```rust
#[derive(Accounts)]
pub struct Resolve<'info> {
pub name_record: Account<'info, NameRecord>,
}
```

### Account Structure

```rust no-check
```rust
#[account]
pub struct NameRecord {
pub name: String, // max 32 bytes
Expand All @@ -230,6 +316,9 @@ pub struct NameRecord {
Name records are stored at PDAs derived from the name:

```rust no-check
// Anchor account-attribute fragment, not a standalone Rust item, so it is
// not compiled on its own. The full `Register` account context above shows
// this seed in place.
seeds = [b"name", name.as_bytes()]
```

Expand All @@ -241,9 +330,14 @@ This means names are globally unique and can be resolved without knowing the own
- Meta-address: must be exactly 64 bytes (two 32-byte ed25519 public keys)
- Only the owner can update or release a name

The meta-address is stored exactly as supplied. The program does not verify a
spending-key signature over it: registration binds the record to the
registrant's Solana wallet, and later updates are gated on that wallet being
the record owner.

### Error Codes

```rust no-check
```rust
#[error_code]
pub enum WraithError {
#[msg("Name must be 3-32 characters")]
Expand Down Expand Up @@ -329,7 +423,7 @@ anchor deploy --provider.cluster devnet
anchor test
```

Tests cover:
The canonical program's `anchor test` suite covers:
- **wraith-announcer** — event emission, multiple callers, metadata preservation
- **wraith-sender** — `send_sol` transfers + emits, `send_spl` token transfer + emits, insufficient funds rejection
- **wraith-names** — register/resolve, name validation (too short, invalid chars), update by owner, update by non-owner (rejected), release and re-register
Expand All @@ -343,7 +437,7 @@ Tests cover:
| Language | Solidity | Rust | Rust (Anchor) |
| Contract model | Bytecode deployment | WASM deployment | BPF program deployment |
| Name storage | Contract storage | Contract storage | PDAs (Program Derived Addresses) |
| Name sig verification | On-chain ECDSA recovery | Caller auth | Signer constraint |
| Name sig verification | On-chain ECDSA recovery | Caller auth | Wallet signer constraint (no meta-address signature check) |
| Event indexing | Subgraph / The Graph | Soroban RPC `getEvents` | Program transaction logs |
| Account model | Address always exists | Must `createAccount` first | Address always valid, no deployment |
| Token transfers | `msg.value` / `safeTransferFrom` | Soroban token contract | SystemProgram / SPL Token |
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"check:snippets": "tsx scripts/check-snippets.ts && tsx scripts/check-api-examples.ts",
"check:api-examples": "tsx scripts/check-api-examples.ts",
"check:integration-guides": "tsx scripts/check-integration-guides.ts",
"check:solana-contracts": "tsx scripts/check-solana-contracts.ts",
"check:snippets-extended": "tsx scripts/check-snippets-extended.ts && npm run check:python-quickstart",
"check:python-quickstart": "tsx scripts/python-quickstart/generate-vectors.ts --check && python3 scripts/python-quickstart/run_quickstart.py --bootstrap",
"generate:python-quickstart-vectors": "tsx scripts/python-quickstart/generate-vectors.ts",
Expand Down
Loading
Loading