UNYKORN WHITEPAPER
v1.0 · 2026-08-08
UNYKORN Vault · Technical whitepaper · v1.0 · 2026-08-08

A wallet-side rail for licensed issuers of real-world assets.

Kevan Burns, UnyKorn LLC · [email protected]

Custody delegated to BitGo Bank & Trust, N.A. (OCC-chartered)

Abstract. UNYKORN Vault is custody, policy, recovery and issuance infrastructure for real-world assets, operated by UnyKorn LLC. The operator does not issue instruments — that refusal is compiled in, not documented in policy — and every dollar-denominated instrument must land through IssuerAuthority::LicensedIssuer, the only constructor that admits a chartered bank or a permitted stablecoin issuer. We add a namespace to EIP-1193 for what custody actually does (multi-approver movements, timelocks, policy refusals, recovery preconditions) and an agent-safe capability layer (x402) that neither BitGo nor MetaMask has. This paper explains the shape, the guardrails, and the parts that are deliberately not built.

01 Executive summary

Three positions define the system:

  1. The operator is never the issuer. Instrument::new() refuses any name resolving to Unykorn. The client brings the licence and is the issuer of record.
  2. Every negative state is representable. Standard EIP-1193 can say YES or NO. Real custody needs to say refused for reason X, needs two more approvers, timelocked until T, recovery not proven. We add those four with error codes in the 4900 application-defined range.
  3. Agents get their own primitive. The x402 capability token is a narrow, expiring, revocable, budget-bounded grant. It can never sign, never approve, and cannot raise its own budget. Fully compromised, its blast radius is remaining budget → allowlist.

What ships: real policy engine, recovery ceremony with negative-control proof, x402 tokens, EIP-1193 + EIP-6963 provider, the issuance-register legal types, an offline-verifiable ML-DSA-65 receipt chain, and — as of fbacc68issuance gated by the same policy engine that governs movements, with three receipts per action and a register-versus- supply reconciliation gate that refuses commits which break the invariant. What does not: gated custodian submission (allowed means policy passed, not moved), live-attestation feed, threshold signing backends. What is deliberately absent: thin any smart contract in this repo — the on-chain leg is client-deployed under the client's own licence.

271 tests pass (235 Rust + 17 x402 + 19 vault-sdk). make ci is green for the first time as of fbacc68 — pre-existing clippy failures in uny-recovery, uny-bitgo, uny-store, uny-pq, and uny-api were cleaned up in the same patch.

02 The problem with custody-blind wallets

A wallet built for a person clicking Confirm can only say two things about any request: you signed it or you didn't. That model has no vocabulary for the four things a custody platform does constantly.

Every one of those is not user rejection. Every one of those, an EIP-1193 wallet must currently return as an error. Every dapp receiving that error teaches its user to retry, and every retry is wrong — the retry runs into the same policy the previous attempt did.

The industry's response has been to hide policy behind institutional middleware — BitGo Console, Fireblocks, Copper. That works for the operator UI but not for dapps: a dapp still sees only the standard provider, so agentic treasury tooling either doesn't exist or lives outside the wallet's guardrails entirely.

UNYKORN Vault adds the missing vocabulary at the provider layer, so a dapp can render "waiting on 1 more approver" or "unlocks in 24h" directly, without the operator needing to open a second surface.

03 The rail vs the issuer

Every dollar-denominated instrument on this platform lands through exactly one constructor:

/// Issued by a chartered bank or a permitted payment-stablecoin issuer under
/// its own licence. **This is the only route by which a dollar-denominated
/// instrument exists on this platform**, and the licence belongs to the
/// issuer, never to the operator.
LicensedIssuer { institution: String, licence: String },

And every attempted Instrument::new() whose issuer resolves to the platform operator is rejected at compile time:

const OPERATOR_NAMES: &[&str] = &[
    "unykorn", "uny korn", "unykorn llc", "unykorn 7777",
];
// "The operator builds and runs this infrastructure; it does not issue on it."

This is not a policy statement. It is a compilation-refusing constant. An engineer who tries to remove it discovers that Instrument::new() is the only public constructor and the check runs unconditionally. The alternative — a platform that could quietly become an issuer if someone changed a config — is not one this codebase can be reconfigured into.

The BitGo→SoFi shape: BitGo sells the qualified-custody rail; SoFi and its peers use it to hold their customers' assets. BitGo never touches the customer relationship, the licence, or the balance-sheet liability. That shape survives regulatory diligence in a way "we run a stablecoin platform" does not.

04 The three planes

The policy engine evaluates independently on three planes:

PlaneLocationPurpose
Controluny-api on 127.0.0.1:7331The operator UI's backend. Loopback-only; a non-loopback bind is refused, not merely discouraged.
SignerExtension service workerWhere the wallet key is held. Re-evaluates policy from its own state; does not trust the control plane's decision.
VerificationAnywhere with the signer's public keyAnyone can pull a copy of the receipt chain and verify it independently.

The engine itself is pure: no I/O, no clock reads, no database. That's what lets the same evaluation run three different places and arrive at the same answer. Injecting a repository into it — a common "convenience" refactor — destroys the security property. It is one of ten rules in BUILD-PROMPT.md that the CI's posture job enforces.

05 Recovery is a precondition, not a procedure

create_wallet() takes a ProvenRecoveryPlan, which has no public constructor, no Default, and no Deserialize. The only path that produces one is a rehearsal that:

  1. Reconstructs the wallet key from t of n shares.
  2. Proves that t-1 shares FAILS to reconstruct.

A ceremony that only proves reconstruction succeeds has not proven the threshold. If the negative control were ever silently dropped — a builder in a hurry, a test helper that shortcuts to a valid ProvenRecoveryPlan — the wallet would be created against a threshold nobody has actually rehearsed. The consequence would be first-noticed on the day it mattered, which is the day you cannot recover from.

Printable custodian cards are produced as part of the ceremony. Each card records the wallet id, custodian name, share index (1 of 3), the rehearsal timestamp, and a secret digest that can be re-verified out-of-band before unsealing.

06 The x402 capability layer

Neither BitGo nor MetaMask answers the question an agentic treasury actually asks: how do I let a machine spend, continuously, without giving it the keys or a human babysitter?

BitGo's answer is API keys — which are broad, hard to revoke fast, and have no ambient budget bounds beyond what the operator remembers to set. MetaMask's answer is you don't. Neither shape is what a payables agent, a payroll bot, or an inventory-triggered rebalancer needs to be safe.

x402 is a signed envelope of the shape:

{
  header:    { typ: "uny-cap", v: 1, iss: "<issuer pubkey hex>" },
  claims:    { sub, enterprise, wallet, asset,
                budget, perTxCeiling, destinations,
                notBefore, expiresAt, nonce },
  signature: "<Ed25519 over SHA-256(domain || canonical(header) || canonical(claims))>",
}

Domain separator: unykorn.vault.capability.v1. One algorithm. One domain. No JWT. Algorithm agility is the vulnerability — alg:none and RS256/HS256 confusion have each produced real custody incidents. There is no negotiation surface.

Guardrails:

07 The issuance register and the compliance engine

Even though no instrument is issued by the operator, the operator holds the register — the record of who owns how much, and under what basis. This is what a transfer agent and a broker-dealer back office actually run on.

The register is typed:

IssuerAuthorityWhat it admits
RegD506bPrivate placement, up to 35 non-accredited, no general solicitation.
RegD506cPrivate placement with general solicitation, accredited-only, verified.
RegSOffshore-only.
RegAMini-IPO, up to $75M/year, accredited + retail.
RegisteredSEC-registered (S-1 / F-1 / etc.).
LicensedIssuerThe dollar door. Chartered bank or permitted stablecoin issuer under its own licence.
NotASecurityCounsel-memo-gated. Refuses to construct without a memo hash on file.

Settlement is typed:

SettlementVenueWhat it means
BookEntryRegistrar of record, nothing touches a chain. This is what a transfer agent runs on — holders don't transfer, the intermediary does. Register authoritative; chain optional.
AlternativeTradingSystemATS-based venue-of-record for the trade; register still authoritative.
OnChainPermissionedERC-3643 shaped. contract field is Option<String>defaults to None because no smart contract is deployed by this repo. The chain leg is client-chosen and client-deployed.
OmnibusCustodyPositions held omnibus by a custodian; the register maps beneficial owners under a single wallet.

Compliance is enforced per rule:

ComplianceRuleEnforcement
verified_recipientRecipient identity must be verified on file before receipt.
jurisdiction_allow / blockCountry / state / OFAC-level allow-list or block-list.
holding_periodReg D / Reg S resale restrictions.
max_holders_of_recordSection 12(g) 2,000-holder ceiling / 500 non-accredited ceiling.
max_position_per_holderBeneficial-owner-level concentration limits.
agent_only_transfersThe broker-dealer back-office primitive. Holders cannot transfer; the intermediary transfers on their behalf.
attestation_ceilingSupply hard-capped at the median of a signed verifier quorum. Recomputed per mint, never cached. Staleness halts minting automatically.

08 What is deliberately not built

A component labelled real is running and verified. A component labelled gated is built to a boundary that isn't wired past. A component labelled thin is a surface that exists so the UI reads honestly. None of the three claims audit status; only 7 of 271 tests verify external truth, and the rest verify internal consistency. An internal adversarial review dated 2026-08-08 named 1 critical, 5 high, 6 medium, 4 low and set two operational restrictions (no recovery ceremony for value-bearing wallets; loopback-only control plane exposure). External security review from a named firm is required before any wallet holds client money — see SECURITY-REVIEW-2026-08-08.md.

The current gated boundaries:

The current thin surfaces:

Recently promoted to REAL. The issuance ↔ policy pipe was thin at v1.0 and landed as uny-issuance-flow in commit fbacc68: mint, burn and position transfer project into the same TransferIntent the movement engine already evaluates, producing three receipts per action (request, decision, effect — including refusals). One set of rules governs both paths.

09 Roadmap

Ordered by dependency, not calendar. No dates until the previous item ships.

Prerequisite for every item below. Close the 1 critical and 5 high findings from the 2026-08-08 internal review (14–19 engineer-days). Two operational restrictions are active until then: no recovery ceremony for wallets intended to hold value; no exposure of the control plane beyond loopback on any machine another party can reach. Full report at SECURITY-REVIEW-2026-08-08.md.
  1. Remediation. Ten actions closing C1 + the 5 high findings (14–19 days), then 6 medium + 4 low (20–29 days total). Re-run the sabotage tests after each fix.
  2. External audit. From a named firm (Trail of Bits / OpenZeppelin / Cure53 / NCC Group). Extend coverage to uny-issuance, uny-issuance-flow, x402, vault-sdk — not reviewed at depth internally.
  3. Wire custodian submission. Step 7 of create_intent in services/uny-api/src/routes.rs to uny-bitgo, against the TEST environment only. On Decision::Allow, submit via BitGo Express using the intent id as sequenceId. A pending approval returned by BitGo is not a broadcast — record it distinctly. If Express is unreachable, fail loudly. Never report an unsent transfer as sent. This moves allowed from "policy passed" to "policy passed AND submitted for settlement" — with a separate Broadcast receipt for the settled state.
  4. Live attestation feed → compute_ceiling. Pipe a real reserve-balance signal (chain adapter + off-chain attester) into the ceiling calculation. Staleness detection already exists; only the pipe is missing.
  5. /admin/reload. Re-read state file without a restart. Re-verify receipt chain on reload; refuse to swap in state that fails verification.
  6. Issuance ↔ policy pipe. Route mints through the same evaluator + receipt-chain writer that movements use. ✓ Done in fbacc68.
  7. vault-sdk instrument surface. uny_listInstruments, uny_transferVerdict. Dapps see instruments, holders, verdicts — same shape as movements today. Sits directly on top of the pipe above.
  8. Client statement. Per-wallet PDF + JSON that a client's auditor can verify without contacting us — balances, every movement with its decision and reasons, receipt range with Merkle root, signer's public key, recovery-rehearsal date. Every figure traces to a receipt.

10 Appendix A · Error codes

All in the 4900 application-defined range — never conflicts with EIP-1193 standard errors.

CodeNameMeaning
4001USER_REJECTEDStandard EIP-1193. Operator clicked Reject.
4100UNAUTHORIZEDStandard. Session locked or origin not permitted.
4200UNSUPPORTED_METHODStandard. Use to probe for vault-native features.
4900DISCONNECTEDStandard. Extension not connected to control plane.
4901CHAIN_DISCONNECTEDStandard.
4910POLICY_DENIEDVault. Rule refusal with named reason.
4911PENDING_APPROVALSVault. Intent created, needs more signatures.
4912TIMELOCKEDVault. Approved, execution held until notBefore.
4913RECOVERY_NOT_PROVENVault. Wallet exists but ceremony not rehearsed. No funds can enter.

11 Appendix B · Receipt format

Each entry on the append-only ledger is a canonical-JSON envelope, signed with ML-DSA-65:

{
  idx:      <u64 monotonically increasing>,
  ts:       <ISO 8601 UTC timestamp>,
  actor:    <enterprise::wallet id triple>,
  decision: "allowed" | "pending_approvals" | "policy_denied" | "timelocked",
  payload:  <the exact intent as it was evaluated, byte-for-byte>,
  prev:     <hex sha-256 of the previous canonical envelope>,
  signature: <ML-DSA-65 detached signature over sha-256(this record)>,
  signer:    <hex fingerprint of the ledger's public key>,
}

An auditor with the signer's public key can verify the entire chain independently. If any record is altered, the prev chain breaks; if a signature is forged, the ML-DSA-65 verification fails. Both are detectable offline.

The signer's public key fingerprint is recorded at ceremony time and printed on the recovery cards. An auditor should record it out of band; a chain whose signer nobody wrote down is only self-consistent.