Skip to content

pactnetwork/pact-umbra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pact-umbra

Confidential underwriting pools for Pact Network, built on Umbra Privacy. Submission for the Superteam Earn Umbra Side Track.

Pact is insurance/refund rails for agentic (x402) payments — its capital comes from underwriting pools where LPs deposit USDC, coverage sales pay premiums in, and breaches pay refunds out. Today that's all public on-chain: who underwrites, how much, which pool, premium income, payouts. pact-umbra holds the pool's capital as an Umbra encrypted balance so amounts and cashflows are hidden — on-chain you see a few Umbra deposit/withdraw transactions with encrypted amounts; off-chain the pool keeps a tamper-evident signed share ledger. An observer can't tell who underwrites, how much, which endpoint they back, the loss ratio, or the pool's P&L. The LPs and the operator can. And it runs Pact's actual coverage flow on top of that encrypted balance — the classifier (ok / server_error / latency_breach / client_error / …), computeCoverage pricing, settle_batch semantics (premium-in agent→pool, Treasury fee fan-out, breach refund pool→agent), and optional registration against a real Pact facilitator.

This is a standalone reference prototype — it does not modify Pact's on-chain program. See DESIGN.md for the architecture, exactly which Umbra primitives are used, exactly how the Pact flow is implemented, and an honest list of what's real vs prototype-grade (per-LP share accounting is off-chain here — production puts it on-chain as encrypted state; the settle_batch instruction is replaced by Umbra deposit/withdraw legs; the x402 payment leg is stubbed, as it's pay.sh's job in real Pact).

Quick start (mock mode — runs anywhere)

npm install
npm run smoke      # full lifecycle: deposits → premium → breach → withdrawals → views
# or, narrated:
./demo.sh

Mock mode runs entirely in-memory: the ledger math is real; the Umbra/Solana calls are stubbed and printed as [MOCK].

You can also drive it step by step:

npx tsx src/cli.ts init --force                       # writes pact-umbra.config.json (mock) + pool/treasury/agent/LP keypairs
npx tsx src/cli.ts deposit alice 1000
npx tsx src/cli.ts deposit bob 3000
# an agent buys coverage on paid x402 calls — each runs Pact's classifier + computeCoverage + settle_batch semantics:
npx tsx src/cli.ts cover 0.05 --status 200 --id call-1   # ok            → premium accrues to LPs, Treasury takes its bps
npx tsx src/cli.ts cover 0.05 --status 503 --id call-2   # server_error  → breach: agent refunded the $0.05 it paid
npx tsx src/cli.ts cover 0.05 --slow      --id call-3   # latency_breach → breach
npx tsx src/cli.ts cover 0.05 --status 404 --id call-4   # client_error  → uncovered: pool does nothing
npx tsx src/cli.ts public                             # what an on-chain observer sees (≈ nothing)
npx tsx src/cli.ts view alice                         # what alice can compute (full P&L)
npx tsx src/cli.ts withdraw alice                     # exits to a fresh, unlinkable address
npx tsx src/cli.ts state
npx tsx src/cli.ts verify                             # Ed25519 + hash-chain integrity check

Add --facilitator https://facilitator.pact.network (or a staging/localhost URL) to init to also POST a real /v1/coverage/register envelope on every cover (best-effort — an outage is ignored, exactly like pact pay).

Running for real on devnet

You need three funded devnet keypairs (SOL for fees + devnet USDC for capital) and an RPC endpoint with WebSocket support:

export PACT_UMBRA_RPC_URL="https://api.devnet.solana.com"          # or your provider
export PACT_UMBRA_WS_URL="wss://api.devnet.solana.com"
export PACT_UMBRA_POOL_KEYPAIR=./keys/pool.json                    # solana-keygen new -o keys/pool.json
export PACT_UMBRA_LP_ALICE_KEYPAIR=./keys/alice.json
export PACT_UMBRA_LP_BOB_KEYPAIR=./keys/bob.json
export PACT_UMBRA_AGENT_KEYPAIR=./keys/agent.json                  # the covered agent (sources premiums, receives refunds)
# optional:
export PACT_UMBRA_NETWORK=devnet                                   # default
export PACT_UMBRA_USDC_MINT=4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU  # devnet USDC (default)
export PACT_UMBRA_INDEXER=https://utxo-indexer.api-devnet.umbraprivacy.com  # default for devnet
export PACT_FACILITATOR_URL=https://facilitator.pact.network       # optional: best-effort POST /v1/coverage/register per call

npm run smoke    # runs against devnet — registers the pool's Umbra user, shields LP capital, runs Pact coverage settlements, unshields

Fund devnet SOL with solana airdrop 2 <pubkey> --url devnet; get devnet USDC from a faucet for that mint, or mint your own and pass it via PACT_UMBRA_USDC_MINT.

The same env vars work for the CLI: npx tsx src/cli.ts init --real --rpc "$PACT_UMBRA_RPC_URL" --ws "$PACT_UMBRA_WS_URL" writes a real-mode config (you'll then point its poolSeed/lps at your funded keypairs, or just use npm run smoke which reads the env vars directly).

Scripts

command what
npm run smoke full lifecycle demo (mock unless real-mode env vars are set)
./demo.sh narrated mock run, screen-record-friendly
npm test ledger math + tamper-detection unit tests
npm run typecheck tsc --noEmit
npm run build compile to dist/
npx tsx src/cli.ts help CLI usage

Library API

import { UnderwritingPool, parseUsdc } from "pact-umbra"; // (after build; or import from "./src/index.ts" in-repo)

const pool = await UnderwritingPool.create({
  network: "devnet",
  rpcUrl, rpcSubscriptionsUrl,
  poolSecret: "./keys/pool.json",          // path | 32-byte seed | 64-byte secret
  usdcMint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
  // mock: true,                            // omit to auto-detect; true = in-memory
});

await pool.deposit({ label: "alice", secret: "./keys/alice.json" }, parseUsdc("1000"));

// Run Pact's coverage flow for a paid x402 call (classify → computeCoverage → settle_batch semantics):
await pool.coverPaidCall({
  callId: "call-1",
  agentSecret: "./keys/agent.json",
  amountPaidBaseUnits: parseUsdc("0.05"),     // what the agent paid the upstream (the at-risk amount)
  result: { paymentOk: true, httpStatus: 503, latencyMs: 40 }, // → server_error → breach: agent refunded $0.05
  latencyBudgetMs: 800,
  resource: "https://api.example/data",
});

await pool.withdraw({ label: "alice", secret: "./keys/alice.json" }); // → fresh address
pool.getPublicView();    // what an on-chain observer can see
pool.getLpView("alice"); // full position (only the LP / operator can compute this)
pool.getPoolState();
pool.verifyLedger();     // throws on tampering

settleCoverage is the lower-level form (you supply the verdict); recordPremium / payBreach are the raw encrypted-balance ops if you want them.

Status

Prototype. Ledger math, tamper-evident signing, and Pact's coverage flow (classifier, computeCoverage, exposure/imputed-cost caps, settle_batch semantics, deterministic coverage id, /v1/coverage/register envelope) are real and unit-tested (19 tests). The Umbra SDK wiring is written against the documented @umbra-privacy/sdk@4.0.0 surface and runs against devnet with funded keypairs, but isn't exercised in CI. Per-LP share accounting is off-chain in this prototype (tamper-evident, but trusts the operator not to withhold/fork) — the production path is on-chain encrypted share state. The settle_batch instruction is replaced by Umbra deposit/withdraw legs; the x402 payment leg is stubbed (it's pay.sh's job in real Pact). Full details in DESIGN.md.

License

MIT.

About

Pact × Umbra — confidential underwriting pools: pool capital held as Umbra Privacy encrypted balances, running Pact's coverage flow (classifier → computeCoverage → settle_batch semantics). Superteam Earn — Umbra Side Track.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors