# Contract reference

Every public function, type, error and event of the proof-registry Soroban contract, with storage lifetime, fees and Testnet addresses.

`contracts/proof-registry` is the Soroban contract that holds an institution's records. Use this reference to read a record yourself, check a holder's inclusion or subscribe to new proofs.

The contract holds the verifying key, runs UltraHonk verification over BN254 with the native host functions, stores each verified proof, and serves the Poseidon2 inclusion check. It makes no cross-contract calls. Each institution has its own instance, its [registry](/docs/overview/what-is-solva).

Addresses on this page are Stellar strkeys as defined in SEP-23: contracts start with `C`, accounts with `G`.

## Functions
### `__constructor`
```rust
fn __constructor(env: Env, owner: Address, vk: Bytes) -> Result<(), Error>
```

Runs once, at deploy. It stores `owner`, the account or smart wallet allowed to publish, and `vk`, the circuit's verifying key, and sets the latest proof id to `0`. The key cannot be changed afterwards. Returns error 1 if the contract already has an owner.

Build the contract and deploy a registry with the committed verifying key, from the repository root:

```bash
stellar contract build
stellar contract deploy \
  --wasm target/wasm32v1-none/release/proof_registry.wasm \
  --source tester --network testnet -- \
  --owner tester \
  --vk-file-path contracts/proof-registry/src/testdata/solvency_vk.bin
```

### `publish_proof`
```rust
fn publish_proof(env: Env, proof: Bytes, pub_inputs: PubInputs) -> Result<u64, Error>
```

Verifies a proof and records it. Only the owner can call it. In order, it:

1. Requires the owner's authorization with `owner.require_auth()`.
2. Loads the verifying key. Error 2 if it is missing.
3. Verifies `proof` against the key, with the public inputs rebuilt from `pub_inputs` in the order `R, root_h, L, R_prev`. A proof that is not exactly 14,592 bytes fails at once. Error 3 if verification fails.
4. Checks `reserves_total >= liabilities_total`. Error 4 otherwise.
5. Checks the growth baseline. On the first proof, `prev_reserves` must equal `reserves_total`. After that, it must equal `r` of the latest stored proof. Error 6 otherwise.
6. Stores a `ProofMeta` under the next id, with the ledger timestamp, and sets it as the latest.
7. Extends the lifetime of the new proof entry and of the contract instance.
8. Emits `proof_published_event`.

It returns the new proof id. Ids start at `1` and increase by one per proof.

### `get_latest_proof`
```rust
fn get_latest_proof(env: Env) -> Result<ProofMeta, Error>
```

Returns the latest proof. Before the first publish, the latest id is `0` and the call returns error 5. Anyone can call it; a read needs no authorization.

### `get_proof`
```rust
fn get_proof(env: Env, id: u64) -> Result<ProofMeta, Error>
```

Returns the proof with that id, or error 5 if there is none. When the call is sent as a transaction, it also extends the entry's lifetime. A simulated read changes nothing.

### `verify_inclusion`
```rust
fn verify_inclusion(
    env: Env,
    id: u64,
    id_hash: BytesN<32>,
    balance: u128,
    path: Vec<PathNode>,
) -> Result<bool, Error>
```

Checks that a holder's leaf is in the tree committed by proof `id`. `id` is the on-chain proof id, not the orchestrator's proof UUID. It returns error 5 if the proof does not exist.

The contract hashes the leaf as `hash4([id_hash, balance, 0, 0])`, then walks `path` from the leaf to the root. At each step it orders the current node and the sibling by `sibling_is_left` and hashes `hash4([left.hash, left.sum, right.hash, right.sum])`, adding the sibling's sum to the running sum. It returns `true` only if the final hash equals the stored `root_h` and the final sum equals the stored `l`. The sum uses saturating addition, so an oversized path returns `false` instead of failing.

## Types
```rust
pub struct PubInputs {
    pub reserves_total: u128,
    pub liabilities_total: u128,
    pub root_hash: BytesN<32>,
    pub prev_reserves: u128,
}

pub struct ProofMeta {
    pub root_h: BytesN<32>,
    pub r: u128,
    pub l: u128,
    pub timestamp: u64,
}

pub struct PathNode {
    pub hash: BytesN<32>,
    pub sum: u128,
    pub sibling_is_left: bool,
}
```

| Field                           | Meaning                                                                   |
| ------------------------------- | ------------------------------------------------------------------------- |
| `ProofMeta.root_h`              | Root hash of the Merkle sum tree, 32 bytes.                               |
| `ProofMeta.r`, `ProofMeta.l`    | Total reserves and total liabilities, integer minor units.                |
| `ProofMeta.timestamp`           | Ledger close time of the publish, Unix seconds.                           |
| `PathNode.hash`, `PathNode.sum` | The sibling's hash and the total of the sibling's subtree.                |
| `PathNode.sibling_is_left`      | `true` when the sibling is the left child and the current node the right. |

## Errors
| Code | Name                   | Meaning                                                                                                        |
| ---- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| 1    | `AlreadyInitialized`   | The constructor ran on a contract that already has an owner.                                                   |
| 2    | `NotInitialized`       | The owner, the verifying key or the latest id is missing.                                                      |
| 3    | `ProofInvalid`         | The proof is the wrong length or does not verify against the stored key and the given public inputs.           |
| 4    | `InsolventBound`       | `reserves_total` is below `liabilities_total`.                                                                 |
| 5    | `ProofNotFound`        | No proof with that id. Also returned by `get_latest_proof` before the first publish.                           |
| 6    | `PrevReservesMismatch` | `prev_reserves` does not match the registry's history: the latest `r`, or `reserves_total` on the first proof. |

The Stellar CLI and RPC report these as `Error(Contract, #<code>)`.

## Event
Each successful publish emits one event, defined in `storage.rs`:

```rust title="contracts/proof-registry/src/storage.rs"
#[contractevent]
pub struct ProofPublishedEvent {
    pub reserves_total: u128,
    #[topic]
    pub liabilities_total: u128,
    #[topic]
    pub ts: u64,
}
```

| Part    | Value                                        |
| ------- | -------------------------------------------- |
| Topic 0 | The symbol `proof_published_event`           |
| Topic 1 | `liabilities_total`, `u128`                  |
| Topic 2 | `ts`, the ledger timestamp, `u64`            |
| Data    | A map with one key, `reserves_total`, `u128` |

The event carries no proof id and no root. To get them, call `get_latest_proof` after you see the event.

## Storage and lifetime
| Storage    | Key          | Value                            |
| ---------- | ------------ | -------------------------------- |
| Instance   | `Owner`      | `Address`                        |
| Instance   | `Vk`         | `Bytes`                          |
| Instance   | `LatestId`   | `u64`                            |
| Persistent | `Proof(u64)` | `ProofMeta`, one entry per proof |

Soroban entries have a time to live (TTL). The contract counts 17,280 ledgers per day, about 5 seconds a ledger. When a proof is published, and when `get_proof` runs in a transaction, the contract extends the entry to 30 days (518,400 ledgers) if fewer than 7 days (120,960 ledgers) remain. A publish extends the instance the same way. An entry nobody extends is archived when its TTL runs out and must be restored before it can be read again.

## Read a registry
Read the latest proof with the Stellar CLI. `--send=no` simulates the call and submits nothing, so it costs nothing. Any funded Testnet identity works as the source account.

```bash
stellar contract invoke \
  --id CAFUK25F56VW442VAOHDKGMPYF2BZ3ERVLVAM4H6JTN2WGZPDUQLRTPN \
  --source-account tester --network testnet --send=no \
  -- get_latest_proof
```

The result looks like this. The values change after each cycle.

```json
{
  "l": "9000000",
  "r": "16000000",
  "root_h": "0c5c65b8b72f420da5a035470aa35ce6bce46a8309bd605cb10b9392d82c922e",
  "timestamp": 1793685990
}
```

Print the full interface of a deployed registry:

```bash
stellar contract info interface \
  --id CAFUK25F56VW442VAOHDKGMPYF2BZ3ERVLVAM4H6JTN2WGZPDUQLRTPN --network testnet
```

From TypeScript, use the SDK's [`getOnChainLatestProof` and `verifyInclusion`](/docs/build/sdk).

## Fees
Measured on Testnet and recorded in `contracts/proof-registry/README.md`:

| Transaction                                                              | Fee                                   | How often               |
| ------------------------------------------------------------------------ | ------------------------------------- | ----------------------- |
| `publish_proof`                                                          | about 240,693 stroops (0.0240693 XLM) | once per cycle          |
| UltraHonk verification alone (`verify_proof` on the standalone verifier) | 122,514 stroops (0.0122514 XLM)       | per verification        |
| Contract deploy and constructor                                          | 210,822 stroops (0.0210822 XLM)       | once per registry       |
| Wasm upload                                                              | 16,567,513 stroops (1.6567513 XLM)    | once per contract build |

The account that submits `publish_proof` pays its fee. In the demo that is the orchestrator's signer. Reads by simulation are free.

## Deployed contracts on Testnet
Kestrel Anchor, Solstice Exchange and Harbor Wallet are fictional example institutions used by the demo. All three registries run the current contract build, with errors 1 to 6.

| Contract                   | Address                                                    |
| -------------------------- | ---------------------------------------------------------- |
| Kestrel Anchor registry    | `CAFUK25F56VW442VAOHDKGMPYF2BZ3ERVLVAM4H6JTN2WGZPDUQLRTPN` |
| Solstice Exchange registry | `CDSQ35U2Y3VC44PJC6RRUJVCES2DLBWELDN32AIAEY4J35H4WBEKEUQW` |
| Harbor Wallet registry     | `CBOA5AJEMTQ5CSMHE6WFWWBIYYMNSUQGKWE6FCFB2KKNJSJPHBWSCSJK` |
| Standalone verifier        | `CA3KQYYAGUEIURJSB5CSWVMNIY2FF2EG3YTM7SBBRSN7DQWZXCCXWXN3` |

The standalone verifier is not a registry. It is the reference UltraHonk verifier, deployed on its own to measure the verification cost. A known-good proof verified on it at ledger 3191042, on protocol 27.

| Network    | Value                                     |
| ---------- | ----------------------------------------- |
| Passphrase | `Test SDF Network ; September 2015`       |
| RPC        | `https://soroban-testnet.stellar.org`     |
| Explorer   | `https://stellar.expert/explorer/testnet` |

The contract and the vendored verifier are unaudited. Mainnet <ComingSoon /> and an independent security audit <ComingSoon /> are listed on [Security](/docs/overview/security).

## `is_covered`
`is_covered` <ComingSoon /> is a read for other contracts, such as a lending pool that accepts a token as collateral. Without it, a contract reads `get_latest_proof` and compares `r` and `l` itself.

The interface is read only and follows the pattern of SEP-40, the Stellar oracle interface:

* `is_covered(issuer, asset)` returns the asset's status: covered, short or stale.
* Every answer carries the ledger the asset was checked at, so the caller can decide how old a record it accepts.

SEP-40 itself covers price feeds only. Solva follows its read-only design; it does not implement SEP-40.

**Next:** [REST API](/docs/build/rest-api).
