Reference
JavaScript engine
The same ES modules run in Node, in tests and in the browser.
The engine is three files under src/engine/. No dependencies; it uses WebCrypto (SHA-256 and Ed25519), which Node 20+ and current browsers provide.
platforms.mjs
parseTarget(input, hint?) -> { platform, kind, key, url } | null
lookup(target, { fetch?, youtubeKey? }) -> Profile // throws LookupError
PLATFORMS, ORDER // names, brand colours, where the proof goes
Profile = { platform, id, name, handle, url, avatar,
proofText, live, viewers, followers, note }
fetch is injectable, which is how the tests feed real response shapes without a network.
proof.mjs
challengeCode({ mint, platform, id, wallet }) -> "up1-XXXX-XXXX-XXXX"
findCode(text, code) -> boolean
newKey(seed?) -> { publicKey, seed, sign(msg) }
verifySig(publicKey, message, signature) -> boolean
attest({ attester, mint, target, wallet, epoch, now, ctx })
-> { status, profile, code, attestation? }
checkAttestations(list, { trusted, threshold, expect, now, ttlMs })
-> { ok, valid, threshold, reasons }
b58encode(bytes), b58decode(string), isWallet(string), sha256(data)
escrow.mjs
new Vault(config, now)
.accrue(amountLamports, now) // BigInt, returns { streamer, launcher, burn }
.claim({ attestations, wallet, now })
.rotate({ attestations, wallet, now })
.cancelRotation({ signature, now })
.tick(now) // applies due rotations and expiries
.snapshot(now)
.code(wallet) // the code to paste for this wallet
.conserved() // accrued == pending + paid + burned
cancelMessage({ mint, wallet, effectiveAt })
lamports(sol), toSol(lamports), DAY, DEFAULTS
Example
import { Vault, lamports } from './src/engine/escrow.mjs';
import { attest, newKey } from './src/engine/proof.mjs';
const vault = new Vault({ mint, target: { platform: 'twitch', id: '12826', name: 'twitch' },
launcher, trusted: [pkA, pkB, pkC], threshold: 2 });
vault.accrue(lamports(0.4));
const code = await vault.code(wallet); // the owner pastes this in their bio
const a = await attest({ attester: keyA, mint, target: 'twitch.tv/twitch', wallet });
const b = await attest({ attester: keyB, mint, target: 'twitch.tv/twitch', wallet });
if (a.status === 'verified' && b.status === 'verified')
await vault.claim({ attestations: [a.attestation, b.attestation], wallet });
attester/handler.mjs and client.mjs
createAttester({ key, name, fetch, now, limit, timeoutMs, youtubeKey }) -> { handle({method,path,body,ip}), publicKey }
toFetchHandler(attester) -> async (Request) => Response // Workers, Netlify, Deno, Bun
collect({ attesters, mint, target, wallet, epoch, expectId, threshold, fetch, timeoutMs })
-> { code, attestations, results, ok }
See Attester network.
chain/client.mjs (Node)
vaultAddress(programId, mint, launcher) -> { address, bump }
decodeVault(bytes) -> the 512-byte account as an object
effectiveState(state, nowSecs) -> what the next instruction will see (due rotation applied, epoch raised)
ixInitialize / ixCrank / ixClaim / ixRotate / ixCancelRotation
ed25519Ixs(attestations) -> the precompile instructions that must precede claim and rotate
programError(e) -> 'not_enough_proof', 'already_bound', ...
Needs @solana/web3.js. See On-chain program.
Time is an argument
Every method that depends on time takes now (milliseconds). Nothing reads the clock behind your back, which is why the tests can run 300 days in a few milliseconds and why the app's "wait 30 days" buttons work.
Money is BigInt
Amounts are lamports as BigInt. accrue refuses anything that is not a positive BigInt.