Sub Rosa does not require users to come to the Sub Rosa demo app. The demo app is a showcase. The intended product surface is a Soroban contract plus TypeScript packages that auction and competitive-bid apps can embed.
npm install @sub-rosa/sdk @sub-rosa/tlock@sub-rosa/sdk is already present in this monorepo as packages/sdk. Publishing
to npm is a release step, not a protocol requirement.
An integrating app usually needs four pieces:
| Piece | Role |
|---|---|
| Round contract | Stores commitments, ciphertext, escrow, deadlines, Drand R, reveal state |
@sub-rosa/sdk |
Creates rounds and submits contract calls from app backend/frontend |
@sub-rosa/tlock |
Seals values to Drand R and opens ciphertext after R |
| Keeper | Opens reveal and settles when Drand R is live; permissionless by design |
import { SubRosaClient } from "@sub-rosa/sdk";
import { generateNonce, quicknet, sealBid } from "@sub-rosa/tlock";
const drand = quicknet();
const client = new SubRosaClient({
rpcUrl,
networkPassphrase,
contractId,
secretKey,
});
const sealed = await sealBid({
value,
nonce: generateNonce(),
round: revealRound,
client: drand,
identity,
auditorPublicKey,
});
await client.commit({
roundId,
sealed,
escrow,
});After Drand round R is published, any keeper or participant can submit the
Drand signature, reveal valid bids, clear the auction, pay the operator from
winner escrow, and refund losing escrow.
Before signing and submitting a state-changing call, integrators can simulate the transaction against Soroban RPC to see whether it is likely to succeed:
const preflight = await client.preflightCommit({
roundId,
sealed,
escrow,
});
if (!preflight.ok) {
if (preflight.error.kind === "contract_error") {
console.error(
"Contract rejected commit:",
preflight.error.contractErrorMessage,
);
} else {
console.error("Preflight failed:", preflight.error.message);
}
return;
}
console.log("Estimated fee (stroops):", preflight.fee.transactionFee);
console.log("Min resource fee:", preflight.fee.minResourceFee?.toString());
await client.commit({ roundId, sealed, escrow });Each mutating SubRosaClient method has a matching preflight* helper:
| Submit | Preflight |
|---|---|
createRound |
preflightCreateRound |
commit |
preflightCommit |
openReveal |
preflightOpenReveal |
reveal |
preflightReveal |
clear |
preflightClear |
settle |
preflightSettle |
void |
preflightVoid |
Preflight results include:
ok— whether simulation indicates the call would succeedfee— estimated transaction and minimum resource fees when availableresources— CPU/memory footprint estimates when availableerror— typedSubRosaPreflightErrorfor RPC failures, simulation errors, expired contract state, or decoded Round contract error codes
Existing submit methods are unchanged; preflight is optional and does not
require live signing credentials beyond a source publicKey (or secretKey).
For pilots that need machine-readable selective-disclosure evidence, recover bidder identities from auditor blobs with:
pnpm --filter @sub-rosa/tlock recover:identities -- \
--auditor-secret-hex <32-byte-hex> \
--input-json '{"auditor":{"blobs":{"agent-alpha":"<blob-hex>"}}}'Hex-only input (single blob):
pnpm --filter @sub-rosa/tlock recover:identities -- \
--auditor-secret-hex <32-byte-hex> \
--blob-hex <blob-hex> \
--label agent-alphaCanonical trace JSON is supported as well, including shapes like
{"trace":{"auditor":{"blobs":{...}}}} and
{"auditor":{"blobs":{...}}} exported from lifecycle/agent fixtures.
Output is JSON and always includes per-blob rows with either recovered identity
or an error. Invalid required inputs return { "ok": false, ... } and exit
non-zero.
The focused integration target is an escrow-backed sealed auction:
- bids remain unreadable before close;
- the winning bid is paid from escrow;
- losers are refunded deterministically;
- the operator cannot read bids early or choose who settles;
- the final receipt is public and verifiable.
Future templates can adapt the same primitive to grants, judging, RFPs, DAO polls, or allocation workflows, but those do not lead the current SCF resubmission.
| Mode | Who uses it | Notes |
|---|---|---|
| Embedded SDK | Stellar app developers | App owns UI and user flow |
| Hosted keeper | Apps that want liveness without running ops | Keeper cannot read early values; it only opens after R |
| Demo frontend | Reviewers, pilots, onboarding | Shows the primitive working end-to-end |
Sub Rosa does not ask integrators to trust a reveal operator. Before Drand R, values are timelock-encrypted. After R, the Drand BLS signature is public and the Soroban contract verifies it before opening reveal.
Every failure mode from the round contract is returned (or reserved) as a
defined code with no silent fallbacks. When a transaction surfaces a
soroban_sdk::Error::Contract(code), the canonical mapping — variant name,
trigger condition, user-facing message, and suggested next action — lives in:
UI layers, receipt exporters, and keeper triage logic should consult that
table to translate on-chain failures into actionable messages. The contract
test suite (cargo test -p sub-rosa-round ::error_codes) keeps the table in
lock-step with the exported Error enum, so a divergent code is a test
failure, not a silent docs bug.