custody.unykorn.ai · native multisig custody
Two signer processes, a control plane and a command line. Every movement is an intent that policy evaluates, a named person signs, and each signer rebuilds from the inputs before it will put a signature on it. No threshold-signing ceremony, no custodian API in the loop, no key that can leave the machine it was made on. Proven end to end on XRPL, Stellar and an EVM Safe on 12 September 2026 — every figure on this page resolves to a transaction hash or a receipt chain you can check.
Proof
Intent → policy → counsel's signed approval → signer-a rebuilds and signs → signer-b rebuilds and signs → assemble → submit → validated → receipt. Testnets and a local chain, on purpose: the mainnet path is gated behind a typed phrase and a per-signature cap, described under Keys.
| Rail | Control | Movement | Result |
|---|---|---|---|
| XRPL testnet | Signer list K1×1 + K3×2, quorum 3. Clawback, default-ripple and require-auth set in the ledger's required order.rpP4bKgADf2d4JtsCYegPB1i9q98NBosyu | 1.5 XRP | tesSUCCESS, validated in 6.5 sC4CEB09DF5F84A698BA3911FCBFF53105FCB85B8F4D56FABE85E04E9A4821532 |
| Stellar testnet | Master weight 0, thresholds 3/3/3, K1×1 + K3×2.GBXCB5ZZIK7FBB5H2KNEMNJ2FDJ42IRX3FLOHZZQEVCSOACFUOIN25OX | 2.5 XLM | Ledger 4634955ffe8159c34c0a06799989ac353ebc910842725038f1b32c8eeb64f4bbda3de61 |
| EVM · Safe v1.4.1 | Real Safe bytecode at the canonical addresses on a local chain, 2-of-2 owners, relayer paid gas.0x62d36a993DE9cea424b7b6c7E209290c6C1D13b2 | 250 test tokens via execTransaction |
Receipt success, reconciliation clean0x3f723c5af0a711f35db90be0ae1251bc9bcef7071c5b4db41b961dd8caa9b24e (local chain — no public explorer) |
The XRPL testnet account's master key is still enabled, so the signer list is not yet its only control. The check reported that as drift rather than hiding it. Disabling the master key is one more AccountSet and is part of the mainnet ceremony, not the testnet one.
The encoders were checked against ripple-binary-codec, @stellar/stellar-sdk and ethers on 20 reference cases (11 XRPL, 7 Stellar, 2 EVM — the Safe case covering the transaction hash, packed signatures and the execTransaction, setup and createProxyWithNonce calldata) before any of the above was submitted. Byte-identical, or the test fails.
A stablecoin's whole lifecycle went through the pipeline on both testnets: issuer flags, holder authorisation, mint, clawback, and on XRPL an MPT issuance created and minted. Each row is one intent, one signed approval, two signer rebuilds, one validated transaction.
| Operation | Rail | What the ledger did | Transaction |
|---|---|---|---|
| authorize_holder | XRPL testnet | Issuer-side TrustSet with tfSetfAuth for the holder's UNYD line | EA0658E9…8766E0D |
| issue | XRPL testnet | 1,000 UNYD minted to the holder (a Payment from the issuer) | 88F1B907…8CD94F6 |
| clawback | XRPL testnet | 250 UNYD clawed back from the holder | 6F0A745A…BD9BAAE8 |
| mpt_create | XRPL testnet | MPTokenIssuanceCreate, scale 6, cap 1,000,000, RequireAuth · CanTransfer · CanClawback. Issuance id 013BB7940F334D03C35934FFC86FCB5124BA1B00FBDB7D65 | AC3BF038…B614737D6 |
| authorize_holder | XRPL testnet | MPTokenAuthorize for the holder | 42E4FED0…8E43BCAF |
| issue | XRPL testnet | 500 units of the MPT minted to the holder | E50D67F5…690C12B8 |
| account_flags | Stellar testnet | SetOptions: AUTH_REQUIRED · AUTH_REVOCABLE · AUTH_CLAWBACK_ENABLED on the issuer, through the multisig | 2b9322d9…ef5cd428a |
| authorize_holder | Stellar testnet | SetTrustLineFlags AUTHORIZED on the holder's UNYD line | ab1908af…fbb7a721 |
| issue | Stellar testnet | 1,000 UNYD minted to the holder | 84b7e59b…52aeabc9 |
| clawback | Stellar testnet | 250 UNYD clawed back; holder ends at 750, authorised, clawback-enabled | 42b7299a…27608b0211 |
Issuing on a wallet whose policy did not name issue — denied at intent. Issuing an asset whose issuer is somebody else's account — refused before any signer was asked. A self-issued “USDC” — blocked as counterfeit. A Stellar clawback before the issuer had the clawback flag — the ledger said op_not_clawback_enabled, the pipeline recorded the failure, the flag was set through account_flags, and a fresh trust line clawed back cleanly.
Twelve issuer operations validated in all. The two not in the table — the first Stellar holder's authorisation and mint, on a trust line that predates the clawback flag — are in the transcript. Full record: docs/e2e-2026-09-12/ISSUANCE-TRANSCRIPT.md.
Pipeline
Each step writes a receipt. The control plane keeps one chain; each signer keeps its own, so a control plane that lies has two witnesses that do not.
Wallet, destination, amount, asset, memo. Content-addressed: the same intent twice returns the original.
Counterfeit tickers, address-poisoning look-alikes, burned keys, EIP-7702 delegation on a signer, mainnet/testnet mixing. Findings are recorded whether or not they block.
Ceilings, whitelists, approver weights, operating windows. Deny by default. The decision and the policy digest are written to the receipt chain.
A named approver signs (approver, intent id, verdict, time) with an ed25519 key. The initiator may reject their own intent; they may not approve it.
Verifies the intent id, the policy digest and pin, every approval signature against its own registry; runs policy and protections again; rebuilds the transaction; compares digests; scope-checks the key; signs; self-verifies; writes a receipt.
The same, independently, with its own key set, its own burned list and its own receipt chain.
Signatures sorted the way each ledger requires — by account id on XRPL, by hint on Stellar, by owner address on Safe — and packed into the final transaction.
Submitted, then polled until validated. Rate-limited public nodes are retried with backoff; a non-JSON answer is treated as “not validated”, never as success.
Transaction hash, ledger index and the assembled bytes, hash-chained onto the record. /reconcile later finds the transaction on chain again and says so.
Invariants
The control plane sends inputs and a claimed digest. The signer recomputes the transaction from the inputs with the same deterministic builder and refuses on mismatch. There is no code path that signs an arbitrary 32 bytes.
An approval is ed25519 over a domain-separated hash. The signer verifies it against a registry pinned at its own startup. A signed rejection outranks any number of approvals, from anyone, forever.
Control plane, signer-a, signer-b. Each signer uses only the approval weight it verified itself. Editing the control plane's state to say “allowed” changes nothing the signers believe.
Counterfeit assets, poisoned addresses, burned keys, 7702-delegated owners and testnet-to-mainnet mixing are refused in the signer with its own lists merged in — not only at the edge where a compromised edge could skip them.
Refused, live
Through the HTTP surface, against the same signers that produced the hashes above. Each refusal is in the receipt chains.
rHuGNhqTG32mfmAvWA8hUyWRLV3tCSwKQt; nothing else gets to use the ticker. A local EVM token calling itself USDC — blocked, because there is no canonical USDC on that chain.state.json was then edited by hand to flip the intent back to “allowed”. Both signers still refused: rejected by ["kevan"]./reconcile reported the delegation as critical drift.State
The same convention used across unykorn.ai. A component is not described as operating until it is.
Address codecs, binary/XDR/RLP encoding, native-multisig signing data and assembly, RPC clients, the canonical stablecoin registry, the pre-sign protections. 20 reference cases, byte-identical to the reference libraries.
Argon2id → XChaCha20-Poly1305, secrets zeroized. Testnet-only by default; mainnet scope needs a typed phrase, an asset key and a per-signature cap.
The decision sequence under Pipeline, one process per key set, loopback only, constant-time token auth, its own receipt chain.
Intents, signed approvals, execute, protect, estate, reconcile, canonical assets, receipt chain. HTTP on loopback; the desktop shell and the command line both drive it.
ed25519 approvals, domain-separated, verified in each signer against a pinned registry. Rejection outranks approval.
uny keys, uny approver, uny native; a desktop shell that spawns the built binaries on loopback and hosts the cockpit. The command line is the surface that has run the proof; the desktop shell is built and has not yet been run on the Windows box.
Non-exportable keys, exercised against the mock HSM. Gated on hardware: plug-in is a configuration change.
Nothing technical stops it once a key is mainnet-scoped. The gate is the typed phrase, the cap, and the recommendation that mainnet keys live in the HSM.
Deploy works anywhere the canonical 1.4.1 factory exists. Run locally so far; gated on a funded relayer on a public testnet.
A backend where a chartered custodian holds the signing key and this system is the policy co-signer and record. Needs their API credentials to build against.
/reconcile verifies control (signers, thresholds, flags, delegation) and history (every confirmed intent found on chain). Balances against an accounting ledger need the ledger to exist first.
An operation on every intent, refused by policy unless the wallet's policy names it, rebuilt and checked by each signer like any transfer. Proven on XRPL and Stellar testnets on 12 September; EVM mint via the Safe is unit-tested against the mint(address,uint256) selector and not yet run on a chain.
Architecture, every HTTP endpoint, operations per ledger, policy rules, keys, CLI, runbook, integration guide and an OpenAPI 3.1 document, generated from the repository: custody.unykorn.ai/docs.
Deferred on purpose. The maintained CGGMP21 crate carried a published advisory and its successor is pre-release; native on-ledger multisig gives the same “no single party” property with the ledger as the referee.
Keys
Their own key, on their own hardware. Signs under policy, never alone.
Two signer processes that re-run policy on the byte-identical intent and sign only on match. Mandatory on every movement by weight configuration: a human and the policy, never two humans alone.
Counsel, a director, a partner. Held by a different person than K1.
Quorum 3 on XRPL (SignerListSet), thresholds 3/3/3 on Stellar, an owner threshold on
Safe. K1 + K2 alone is 2 and fails. K3 alone is 2 and fails. Nobody, including the operator of the
co-signer, can act alone.
Every software key is TestnetOnly unless it was created with the exact phrase uny keys phrase prints — “I understand this key lives on this computer and can move real funds” — plus an asset key and a per-signature cap. A signer refuses a mainnet payload from a testnet key, refuses a mainnet payload above the cap, and refuses a different asset. HSM keys carry the scope in hardware.
No wallet is created without a recovery plan that has been rehearsed: the key is reconstructed from real shares and one share short is confirmed to fail. If the rehearsal does not pass, no wallet exists and no funds can reach it.
Run it
Rust workspace, builds on Windows and Linux. Secrets are read from the environment or typed once into the unlock screen; nothing here takes a passphrase as a command-line argument, and keystores, seeds and state are never tracked.
# build once cargo build --release # keys, approvers, XRPL + Stellar testnet accounts, wallets, policies $env:UNY_KEYSTORE_PASSPHRASE = "<long passphrase>" .\run\bootstrap-testnet.ps1 # two signers on 7333 / 7334, control plane on 7331 — loopback only $env:UNY_OPERATOR_PASSPHRASE = "<operator passphrase>" .\run\start.ps1 # a movement: intent → policy → approval → two signers → ledger .\run\move.ps1 -Wallet wal_treasury-xrp -Destination r... -Amount 1500000 .\run\stop.ps1 # or the desktop shell, after bootstrap cd desktop; npm install; npm start
Three receipt chains — control plane, signer-a, signer-b — each hash-chained, each verifiable with a public key, offline, without contacting anyone. GET /ledger/verify walks the control plane's chain; each signer answers /receipts for its own. On 12 September: 68, 11 and 11 receipts, all three chains verified.
Access
There is no sales process. The control architecture, the receipt chains and the transcript of the proof above are the file. We will walk any control through with your counsel alongside ours.
The invariants, the signer's decision sequence, the key-scope rule and the receipt format, against your own diligence standard.
Request the fileYour keys stay yours. K3 sits on your signer list at weight 2 and signs only what your own policy permits, with the record to prove it.
Start a conversationPer-deal workspace: custody statements, attestation history, and the append-only record of every sign-off on your file.
portal.unykorn.aiEvery endpoint, operation, rule and command, with the OpenAPI document. Built from the repository, so it cannot describe something that does not exist.
custody.unykorn.ai/docsCustody is one layer. The gate, securitization, settlement and credit — how an asset becomes financeable — are on the main site.
unykorn.ai