This repository was archived by the owner on Jul 31, 2026. It is now read-only.
Repository navigation
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ground-up SDK foundations (todo 948, overhaul/ground-up): - errors.ts: PasskeyKitError + ranged code enum; ContractError registry (contract-errors.ts, current 9 codes) + diagnostic decode + bindings-sync test - types.ts: discriminated TransactionResult, CreateWalletResult/ ConnectWalletResult, StoredPasskey/StorageAdapter; rename Mercury-row Signer -> IndexedSigner (kills bindings Signer collision) - utils.ts: compactSignature, extractPublicKeyFromAttestation (SPKI path), generateChallenge (random 32B), deriveContractAddress (+ golden vectors) - validation.ts, base64url.ts shim, events.ts typed emitter, constants.ts, version.ts (+ scripts/sync-version.js) - storage/: Memory/LocalStorage/IndexedDB adapters (passkey-kit/storage subpath) - kit/auth-payload.ts: SDK-backed P27 preimage helpers (ports bun_tests/9) - Packaging: compile to dist/ (ESM+d.ts), exports map (. /storage /server), files whitelist, sideEffects false, NodeNext; stellar-sdk 16.0.1 dep + peer >=16.0.0; relayer-plugin-channels ^0.20; verify-esm.mjs gate; bindings ship compiled JS dist - Vitest (70 tests) replaces bun_tests/; delete bun_tests/, clone-js-sdk.sh pnpm build (+ verify-esm) and vitest green. Old kit.ts/server.ts kept compiling under NodeNext; full rewrites land in B2/B3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…an-sdk 27) - single types source: interface crate exports spec types/events/errors; duplicated smart-wallet/src/types.rs deleted - timestamp-based SignerExpiration (u64 unix seconds, inclusive) - fail-closed SignerLimits (Some(empty) = no permissions); deploy requires unlimited signer; limited signers may always self-remove (typed, boolean) - __check_auth: pass-1 context coverage purely boolean; pass-2 single-point expiration + crypto/policy verification of every signatures entry - WebAuthn: type==webauthn.get + UP flag + authData length checks, typed oversized-clientDataJSON error; challenge equality unchanged - policy install/uninstall lifecycle (hard install, best-effort uninstall); sample-policy demonstrates installed-wallet gating; recursion depth guard - upgrade(new_wasm_hash) + Upgraded event + contractmeta binver; constructor- only init (init flag deleted); get_signer view; #[contractevent] schema (signer_added/signer_updated/signer_removed/upgraded) - errors renumbered to 100+ ranges (legacy 1-9 disjoint for SDK detection) - Makefile: fix wrong-dir wipe/double build, add test target, CLI 27 optimize Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…F7 ownership)
Ground-up rewrite of the signing pipeline into DI managers (todo 949):
- signers.ts: PasskeySigner/Ed25519Signer/PolicySigner replace the
keyId|keypair|policy option trio with explicit signer objects.
- kit/auth-payload.ts: host-order compareScVal + Signatures map read/upsert/sort
(replaces the localeCompare bug) + SignerKey/Signature encoding via bindings Spec.
- kit/webauthn-ops.ts: registration/authentication ceremonies with a RANDOM
challenge (was the static "stellaristhebetterblockchain") and rpId from the
constructor; SPKI public-key extraction via utils.
- kit/deploy-ops.ts: configurable deploySource (default preserves the canonical
"kalepail" deterministic deployer); deterministic address derivation.
- kit/wallet-ops.ts: add/update/remove signer builders + SignerKey/SignerLimits
encoding + getSigner via RPC ledger read (temporary-before-persistent).
- kit/tx-ops.ts: single explicit AssembledTransaction input (kills the lossy
tri-input coercion), calculateExpiration, restoreFootprint helper.
- managers/{credential,signer,submission}-manager.ts: vi.fn-testable DI managers.
- kit.ts: full facade rewrite wiring the managers with late-bound closures;
connectWallet ownership verification (#601 F7) — a keyId MUST resolve to a live
signer on the resolved wallet (closes #598 F3 at the SDK layer), plus opt-in
wasm-hash check; typed events (walletCreated/walletConnected/...). Deleted every
P27 probe shim, dead commented experiment, and the legacy factory-address
fallback.
pnpm build (+ verify-esm) and vitest (90 tests) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…admin/events (62 tests) - test_webauthn: deterministic RFC6979 secp256r1 assertions shaped like real authenticator output; happy path, UP/UV flags, truncated authData, wrong type, challenge mismatch (payload binding), oversized/malformed cdj, corrupted signature, key confusion, fixed challenge vector - test_auth: limits branches (unlimited/empty/granted/co-signer present, missing, expired), expiration boundary (inclusive), signatures-map hygiene (unknown/expired extras fail deterministically), self-remove rule + F5 foreign-contract non-interference, deploy-requires-unlimited, wallet-self admin grant, policy-as-signature vs policy-as-limit granularity (counting policy), cyclic policy limits fail closed via depth guard - test_admin: constructor/add/update/remove/upgrade + full event assertions, durability-flip opposing-entry removal, get_signer raw view, policy install/uninstall lifecycle (rejecting install blocks add; panicking uninstall cannot block removal) - test_base64_url: RFC 4648 vectors, url-safe alphabet, reference cross-check - test_integration: full set_auths flows for ed25519+policy AND the previously-untested secp256r1 passkey path (F1); budget regression print - build.rs builds wasm fixtures (sample-policy + smart-wallet) into OUT_DIR and copies to gitignored fixtures/ — cargo test self-sufficient on clean checkout; wasm contract builds skip the script entirely Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Builds all three contracts from ff7bd22, uploads to testnet (passkey-kit-deployer, friendbot-funded), verifies fetch-back hashes, and live-smokes a deployed wallet instance (constructor + get_signer). Canonical smart-wallet wasm hash: 9e7fad441d6560b31eafbf3b627dbc196cf19df4dcdb91e0aededaf6590d6fbe Manifest records toolchain/build provenance, per-component hashes + upload txs, the NORMATIVE derivation tuple (salt=sha256(keyId), canonical kalepail deployer, F7 connect-verification requirement), and the legacy e45c42b9 hash. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
todo 950. Relayer client + true client/server separation:
- relayer.ts: RelayerClient wraps ChannelsClient ^0.20 — submitSorobanTransaction
({func,auth}) preferred + submitTransaction({xdr}) for envelopes + getTransaction
polling; skipWait/fundRelayerId passthrough; PluginClientError family mapped to
typed RelayerError (or decoded ContractError); typed never-throw TransactionResult.
- server.ts: PasskeyServer rewritten server-only — validated config at construction,
drops PasskeyBase for plain NetworkContext (rpc + passphrase), RelayerClient
integration (smart func/auth vs xdr routing via hasSourceAccountAuth), hardened
Mercury methods (AbortController timeout, typed IndexerError instead of raw JSON
throws). Superseded by the SignerIndexer abstraction in B5.
- Server-only entrypoint: PasskeyServer + RelayerClient removed from the main
`passkey-kit` export; reachable ONLY via `passkey-kit/server` so the relayer
secret can never be bundled into browser code. verify-esm asserts the split.
- sac decision: KEEP the generated sac-sdk client (demo needs full SEP-41 reads:
balance, metadata, transfer) rather than collapsing to a helper; SACClient made
standalone (base.ts deleted). Added buildTokenTransferHostFunction for the
low-level relayer {func,auth} path. (sac-sdk regen from canonical source = B4.)
- constants: DEFAULT_RELAYER_TIMEOUT_MS (6min testnet-retry window),
CHANNELS_MAINNET/TESTNET_URL, DEFAULT_INDEXER_TIMEOUT_MS; TransactionSuccess
gains optional transactionId (skipWait polling).
pnpm build (+ verify-esm) and vitest (96 tests) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
todo 952. One SignerIndexer interface, two interchangeable backends (src/indexer/**):
- types.ts: SignerIndexer { getSigners, findWallets, health }; WalletSigner
{ key, publicKey?, expiration?, limits, storage, status: live|expired|evicted|
removed }; IndexerHealth; FindWalletsHardeningDeps.
- codec.ts: shared SignerKey/SignerVal codec — signerKeyToContractScVal,
signerKeyToIndexerJson (predicate), scValToSignerKey, decodeSignerVal (via
bindings Spec, memoized), deriveStatus (inclusive timestamp expiration).
- mercury.ts: MercuryIndexer — get_signers_by_address / get_addresses_by_signer
via /zephyr/execute; temporary-eviction RPC probe (#598 F6); findWallets
derive-then-confirm hardening (#598 F3) reusing kit/wallet-ops.getSigner.
- stellar-indexer.ts: StellarIndexerBackend over POST /v1/contract-data/ —
contract-scoped enumeration + client-side SignerVal decode; tombstone grouping
(removed = tombstoned with no live counterpart in either durability);
findWallets = derive candidate (Secp256r1) then confirm (<=25/query).
- index.ts: indexerForConfig (null-tolerant seam) + lookupWithRetry (20x2s poll).
- Exports: browser-safe types + lookupWithRetry from `passkey-kit`; token-holding
backends from `passkey-kit/server`.
WIRE NOTE (flagged in-code): exact Mercury row/arg shapes (C1) and Stellar
Indexer request/response encoding (hex vs base64 bytes, deleted_at) are confirmed
live in F2. Unit tests use mocked fixtures; cross-backend equality is an F2 e2e.
pnpm build (+ verify-esm) and vitest (111 tests) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
todo 951 (ungated). Regenerated from the canonical v1 smart-wallet WASM
(9e7fad44…d6fbe, docs/deployments-testnet-2026-07-11.md; Stellar CLI 27.0.0):
- packages/passkey-kit-sdk/src/index.ts regenerated (post-gen: copy generated
spec + README, preserve B1 packaging). New surface: error codes 100-129
(renumbered, disjoint from legacy 1-9); SignerExpiration = Option<u64>
(UNIX-timestamp, was ledger-seq u32); upgrade (renamed from
update_contract_code); get_signer view (-> Option<SignerVal>); add/update/
remove/upgrade now return Result<void>; #[contractevent] structs in the spec
(Upgraded/SignerAdded/SignerRemoved/SignerUpdated).
- SDK integration: kit/wallet-ops WalletTx = AssembledTransaction<Result<void>>;
toSignerExpiration encodes Option<u64> (bigint); new upgrade builder threaded
through SignerManager + PasskeyKit facade; getSigner ledger-read unchanged
(get_signer view now exists but the RPC read is equivalent + wallet-auth-free).
- contract-errors: registry resynced to v1 codes 100-125 (family SmartWallet) +
kept legacy 1-9 (family SmartWalletLegacy) so errors from pre-1.0 deployed
wallets still decode. bindings-sync test asserts the registry matches the
regenerated Errors map.
- scripts/bindings/{build.sh,verify.sh}: build regenerates from the canonical
hash into the package; verify (drift guard) fetches by hash, regenerates to
mktemp (trap EXIT cleanup), and diffs the ContractSpec base64 array vs the
committed bindings. Wired verify:bindings into prepublishOnly (network gate);
verify:esm stays in build (offline).
- sac-sdk: KEPT as-is (B3 decision) — standard network-independent SEP-41 token
interface, packaging already fixed in B1; no regen/drift-guard warranted.
pnpm build (+ verify-esm) green; verify:bindings green (committed bindings match
canonical WASM); vitest 112 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…coverage
Ground-up rebuild of demo/ on Svelte 5 + Vite 6 against the frozen B1-B3 SDK
client API. Kills the 427-line App.svelte and the secrets-in-bundle pattern.
- Zero secrets: purge the committed Stellar seed + relayer key / Mercury JWT
VITE_ inlining. Submission routes through a relayer-proxy worker
({func,auth}|{xdr}); discovery through an indexer proxy with a
Mercury <-> Stellar-Indexer toggle. passkey-kit/server is never imported; the
bundle carries no relayer/Channels client (verified via grep).
- Storage via passkey-kit/storage LocalStorageAdapter (not hand-rolled localStorage).
- Full feature coverage: passkey create / reconnect, add / update / remove for all
three signer kinds, Persistent + Temporary, SignerLimits builder
(unlimited / none / restricted), admin rotation + active-signer selection,
multisig + per-signer transfers, non-native SAC token picker, typed-error
surfacing (PasskeyKitError / ContractError with decoded contract codes).
- Componentized (Header / Wallet / Transfer / Signers / Discovery / ActivityLog +
reusable LimitsBuilder / SignerRow); Svelte 5 runes store + an actions layer so
components stay thin.
- Drop vite-plugin-filter-replace + the VITE_mercuryKey dead type; new public-only
.env.example (rpc, passphrase, wasm hash pinned from the deployments manifest,
native SAC, proxy URLs); fix index.html title + favicon.
- data-testid selectors throughout for the D2 e2e harness.
pnpm build:demo green (svelte-check 0 errors / 0 warnings + vite build).
Refs #602, #597 s1.7, #600 s6; browser<->worker wire shape coordinated on todo 954.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rdening) Addresses the 3-way audit gate (todo 958). Core auth/WebAuthn/storage/ upgrade/derivation were cleared; findings concentrated in the policy lifecycle. 64 tests green. - FIX-1 [HIGH]: remove untrusted uninstall from remove_signer critical path. Removal is now pure wallet state + SignerRemoved event — no policy code runs. try_uninstall could not catch non-recoverable Budget/Storage ExceededLimit, so a budget-exhausting policy could unwind the atomic tx and roll back its own removal. Policies self-clean via the now-permissionless uninstall entrypoint, which verifies get_signer(Policy(self))==None first. New test malicious_uninstall_cannot_block_removal proves removal survives a budget-exhausting policy. - FIX-2 [MED]: policy__ caller authentication. Interface documents the public-callability contract (impls MUST be read-only OR source.require_auth). Confirmed require_auth works inside __check_auth via invoker auth (ed25519_with_policy_limits_full_stack); sample-policy uses it and sample_policy_rejects_spoofed_caller proves external callers are blocked. - FIX-3 [MED]: sample-policy is now deny-by-default — fails closed on arg shape/type, gates only transfer within cap, rejects the wallet's own admin surface and all non-contract contexts. - FIX-4 [MED]: sample-policy renews its instance/code TTL and the Installed(wallet) key on install and every successful policy__. - FIX-5 [MED]: required co-signers are scope-independent approvers. Dropped the recursive enforcement of stored policy-limit-key limits (and the depth guard — no recursion, no cycle). Symmetric across key kinds; Some(empty) disables INDEPENDENT coverage only. Documented in types.rs; tests nonpolicy/policy_co_signer_own_limits_not_enforced. - FIX-6 [LOW]: storage.rs uses saturating_sub for max_ttl - WEEK_OF_LEDGERS. - FIX-7 [LOW]: policy__ documented side-effect-free (or require_auth); UP-only default and UV rationale documented on UserPresenceRequired. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ban-sdk) Full rewrite of the Mercury/Zephyr indexer against the v1 smart wallet and the current (P27) zephyr-sdk. Kills audit findings F1/F3/F4/F5/F7. - F1 (build): drop the smart-wallet-interface dependency (soroban-sdk 27) that created a dual-soroban-sdk tree; decode events via local #[contracttype] mirror types on a SINGLE soroban-sdk, pinned =22.0.7 to match zephyr-sdk and to keep its wasm panic-handler gated off (22.0.8+ duplicates std's panic_impl and fails to build). zephyr-sdk pinned to stellar-main-2 rev 3b83284; rustc pinned 1.93.0. Builds green for wasm32-unknown-unknown, no warnings. - New event schema: consumes signer_added/signer_updated/signer_removed/ upgraded #[contractevent] structs (map data_format) instead of sw_v1 tuples. - F3 (security): WASM-hash allowlist on ingestion. Only wallets whose on-chain instance executable is a known passkey-kit hash (from docs/deployments-testnet-2026-07-11.md) are trusted; a `wallets` table holds the trusted set and latest hash, also refreshed from `upgraded` events. - F4/F5 (expiration): UNIX-timestamp inclusive semantics; both read functions return signers with an `expired` flag instead of filtering inconsistently. - F7: native `bool` active column; explicit val nullability (Void vs Bytes(65)); documented base64-XDR limits encoding. - Drops the unauthenticated debug_signers dump. - README documents the toolchain/F1 story, schemas, function contracts, and the Mercury deploy-tooling status: published mercury-cli 0.2.1 targets removed endpoints (404); current backend moved under /rest (audit F2). Testnet deploy is pending correct tooling and is an orchestrator-gated step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the smart-account-kit 4-layer e2e harness to passkey-kit, under scripts/e2e/, driving the real Svelte demo with data-testid selectors (no React native-setter shims). Wired as pnpm scripts in demo/package.json (root package.json is off-limits). Layers: - agent-browser-webauthn-helper.mjs: attaches a CDP virtual authenticator (ctap2 / internal / residentKey / userVerified / automaticPresenceSimulation) to a live agent-browser session and tears it down in a finally. Generic port. - webauthn-browser-probe.sh: isolated create -> get -> crypto.subtle.verify P-256 sanity, asserts verified:true. Generic port. - testnet-passkey-smoke.sh: orchestration. Idempotent Vite start (lsof guard), EXIT trap that kills only the server it started and closes only its own agent-browser session (no orphaned Chrome-for-Testing). MODE=probe|audit. - browser-full-e2e-audit.sh: the passkey-kit flow — create -> reconnect (discoverable passkey, ownership verified) -> fund -> transfer -> add secp256r1 / Ed25519 / policy signers -> update -> per-signer transfer -> remove -> discovery via BOTH indexer backends (lookupWithRetry poll) -> reverse lookup. Asserts REAL outputs (C... contract id, 64-hex tx hashes), an explicit failure pattern on every wait, and a DISTINCT exit code per layer (10/20/21/30/31/ 40..45/50/51/52). Auto-gates: skips on-chain layers when the demo header shows "no relayer proxy" and discovery when "no indexer proxy" (explicit log, never a silent pass) so it is meaningful before C2/B5 land; full live run is F2. Demo selector hooks (D2 scope: "demo selectors as needed"): scoped log-box testid + full data-hash on tx links; signer-update / signer-update-save / signer-set-active / use-fund-addr testids. Static-validated: node --check (mjs) + bash -n (all sh) clean; pnpm build:demo green (svelte-check 0/0 + vite build). Live browser execution is F2 (orchestrator- gated) per the master plan; the harness is isolation-safe (unique session, lsof guard, session-scoped close) for that run. Refs #600 s6/s14, TOPROMPT verify rules. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e-policy
sol re-verify (comment 2484) confirmed FIX-3's fail-open/admin gaps closed but
kept it STILL-OPEN: Signature::Policy is secretless, so a per-transfer cap is
trivially bypassed by repeating capped transfers to drain the wallet, and
policy__ can't see co-signers — the only in-policy close is a cumulative budget.
- sample-policy upgraded from a 10M per-transfer cap to a genuine cumulative
rolling-window spending allowance: tracks Spend(wallet)={window_start, spent}
in policy storage, resets after WINDOW_SECONDS (24h tumbling window), sums
transfer amounts across the invocation's contexts and rejects once the
window total would exceed WINDOW_ALLOWANCE (100M). checked_add on both the
per-call total and the cumulative to fail closed on overflow.
- Spend(wallet) TTL renewed alongside instance + Installed(wallet) so the
counter can't archive-grief; uninstall also clears Spend.
- Loud docs: interface policy__ gains a 'Value transfers with a SECRETLESS
policy' section — value-moving policies are only safe as a cumulative/rate-
limited allowance and/or paired via SignerLimits with an authenticated
co-signer, never an unbounded per-transfer cap. Sample module doc rewritten.
- New test sample_policy_cumulative_allowance: repeated sub-cap transfers are
rejected once cumulative hits the cap, and the window reset restores spend.
smart-wallet wasm unchanged (84924c53…); FIX-3b is sample-policy behavior +
interface docs only. sample-policy wasm now e74af535….
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…emo Pages config
New top-level relayer-proxy/ Cloudflare Worker, adapted from the smart-account-kit
reference (audit #598/#600 §ADAPT). Fixes passkey-kit's secrets-in-bundle defect.
- Keyless per-IP Relayer API-key minting: GET {RELAYER_BASE_URL}/gen, cached in
KV under api-key:<ip> (CF-Connecting-IP -> X-Forwarded-For -> X-Real-IP), so
the browser submits with ZERO secrets.
- POST / accepts {func,auth} -> submitSorobanTransaction OR {xdr} ->
submitTransaction (plugin ^0.20); exactly one required.
- Error mapping: PluginExecutionError -> 400 (code/details), PluginTransportError
-> its status, malformed JSON -> 400, else -> 500. GET /status, GET / health.
- Testnet Friendbot retry loop for missing channel accounts (5 min); mainnet
never retries. [env.production] mainnet block. Real wrangler.toml (placeholder
KV ids to provision at deploy). NO deploys from this todo.
- vitest suite: 20 tests, all green (IP extraction, mode validation, both submit
paths, 3 error mappings, testnet/mainnet retry, per-IP key lifecycle incl.
legacy-format migration). tsc typecheck clean.
Demo Pages deploy gap (audit #597: "broken-as-scripted, no wrangler.toml"):
- Root wrangler.toml with name=passkey-kit-demo + pages_build_output_dir=demo/dist
so the existing root `deploy:demo` (wrangler pages deploy, run from repo root)
resolves project + assets with no flags. deploy:demo:prod's `--env production`
is invalid for `pages deploy` (no such flag; Pages uses --branch) — that's a
root package.json fix owned by the demo agent; documented in-config + on todo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit gate cleared (all 7 findings confirmed-closed: terra FIX-1/4, sol FIX-2/3/5/6/7). Re-pins the manifest to the final post-remediation build (commit range 067f807..0563520). Final canonical hashes (supersede interim 9e7fad44…/e6d00383…): - smart-wallet: 84924c53a413318df2ce753e30de53ec651404c916d30e861718ad155c94b319 (29,275 B) - sample-policy: e74af5355f933f2c3421845178ca789c9bcf3ea7612a7c9966b9b57f26e59aed (13,064 B) - example-contract: 47a3326360bdce2ca360ed1b226ad636e14ac698f63efc11ab28e0d41f1f76aa (unchanged) Rebuilt clean from 0563520 (Rust 1.94.0 / soroban-sdk 27.0.0 / CLI 27.0.0), uploaded to testnet (smart-wallet tx ceb4ed26…, sample-policy tx 2f36077b…), all three fetched back by hash byte-identical, and live-smoked a fresh wallet instance (CCK7VZBY…, deploy tx 4a9ff09b…, get_signer verified). Legacy hash + normative derivation-tuple sections unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The contracts audit gate + FIX-1..7/FIX-3b re-pinned the canonical smart-wallet WASM hash (docs/deployments-testnet-2026-07-11.md now FINAL post-audit): 9e7fad44… (interim pre-audit) -> 84924c53a413318df2ce753e30de53ec651404c916d30e861718ad155c94b319. Update the F3 ingestion allowlist + README to consume the manifest's final hash. Legacy hash e45c42b9… unchanged. Builds green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-pin canonical smart-wallet WASM 9e7fad44… -> 84924c53a413318df2ce753e30de53ec651404c916d30e861718ad155c94b319 (A4 audit-gate remediation; manifest docs/deployments-testnet-2026-07-11.md, ff06059) in scripts/bindings/{build,verify}.sh. Regenerated passkey-kit-sdk bindings from the new WASM via the pinned build.sh (stellar contract fetch --wasm-hash -> bindings typescript, CLI 27.0.0). The wallet interface is semantically UNCHANGED: every error code (100-125), error name, function signature, and type struct is byte-identical. The only spec delta is two embedded Rust doc-comment strings the audit reworded -- UserPresenceRequired (FIX-7) and SignerLimits (FIX-5) -- which live inside the ScSpec and so shift the base64 spec literals. - src/contract-errors.ts: no resync needed (codes/names identical); contract-errors.test.ts bindings-sync guard green. - build.sh/verify.sh: bindings output dir gen -> pks-gen so the generated README's package name stays stable across regens (name is spec-independent; verify compares only the base64 spec, so the guard is unaffected). - verify:bindings passes against 84924c53…; pnpm build + npx vitest run (132 tests, 17 files) green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes stale-hash drift in demo/.env.example (was pinned to the pre-audit 9e7fad44… despite the re-pin comment). Now matches the canonical smart-wallet hash 84924c53… from docs/deployments-testnet-2026-07-11.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…to-fund
Code-review gate (todo 966, comment 2496) findings in demo/** scope.
H1 [HIGH] — demo submission happy-path was fully broken: the relayer-proxy worker
returns { success, data: { transactionId, hash, status } } but the client read
payload.hash/ledger/transactionId at the TOP level, so payload.hash was always
undefined → the success guard fired on EVERY 200 → every submission (deploy /
transfer / add-update-remove-signer / fund) reported failure. Fix: unwrap the
`data` envelope (payload?.data), treat an explicit success:false as failure, and
drop `ledger` (the worker has none). Error path: the worker returns `error` as a
STRING + optional data:{code,details}; toError now reads the string message +
data.code (numeric → contractErrorFromCode) and scans message/raw for
Error(Contract,#N) so contract-error codes are no longer dropped.
M3 [MED] — register() called fundWallet(100) nested inside run("Create wallet");
fundWallet wraps its body in run(), which early-returns when app.busy is set, so
the post-deploy auto-fund silently no-oped. Factored an unwrapped fundWalletInner()
(fundWallet is now a thin run() wrapper) and register calls it directly, guarded so
a fund failure never fails wallet creation.
LOW — fundWalletInner uses BigInt(Math.round(amountXlm * 10_000_000)) so a
fractional fund input (<input type=number> allows 0.5) no longer throws RangeError.
format.ts toStroops/fromStroops now use exact decimal↔BigInt math (no JS Number
rounding / precision loss); number inputs in scientific notation fall back safely.
pnpm build:demo green (svelte-check 0/0 + vite build). Worker unchanged (client
aligned to worker shape per the finding). Refs todo 966 / comment 2496.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zephyr (M4 + LOW): - M4: fold same-ledger signer events per (address,key) into one pending state, then flush with a single existence-read + single write. Host db writes commit only after on_close, so per-event read+write couldn't see this close's own writes — add+remove left a signer active forever, two updates duplicated rows. Now correct (mirrors the wallets `trusted` in-memory cache). - LOW: read handlers parse caller-supplied address/key fallibly (validate strkey / match base64 Result) and conclude empty instead of panicking (panic=abort). relayer-proxy (M5 + LOWs): - M5: move per-IP key custody from KV (no CAS → N concurrent first-requests mint N keys) into a per-IP Durable Object (`ApiKeyStore`); blockConcurrencyWhile serializes get-or-create so at most ONE /gen key is minted per IP. Drops the KV binding; wrangler.toml gains the DO binding + migration (both envs). - LOW: never log /gen body or candidate key material — log only status/length. - LOW: getClientIP uses CF-Connecting-IP ONLY (unspoofable at the edge); dropped the client-controlled X-Forwarded-For/X-Real-IP fallback. CORS left open with an explicit endgame/mainnet decision note. - Tests rewritten for the DO model (21 pass): worker DO integration + direct ApiKeyStore get-or-create/peek/mint-fail. tsc clean. package.json (LOW): deploy:demo:prod uses `--branch production` (Pages selects prod by branch, not `--env`). Not fixed here (other owners): H1 (demo client envelope unwrap), H2/M1/M2 (SDK), M3 + demo LOWs (demo). No Mercury redeploy (F2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes the SDK findings from the F1b /code-review gate (todo 966, comment 2496).
No contract wasm change; no hash re-pin.
H2 [HIGH] — the eviction probe queried the WRONG ledger key. mercury.ts and
server.ts probed getContractData(wallet, scvBytes(keyId), Temporary), but the
contract keys signer entries by the SignerKey ScVal vec (signer.rs
set::<SignerKey, SignerVal>), so every probe missed -> catch -> every live
temporary signer was reported "evicted". Now probe by
signerKeyToContractScVal(...) — the exact key getSigner uses. getContractData
also collapses not-found AND transport errors into one {code:404}, so eviction
could not be told from a flaky RPC: added src/rpc-data.ts, which reads via
getLedgerEntries (empty entries = genuine not-found; a throw = transport) and
concludes eviction ONLY on an authoritative not-found, never on 429/5xx/timeout.
M1 [MED] — codec limitsOf encoded a limits-nested Ed25519 key as HEX, diverging
from scValToSignerKey / types.ts (a G-address); the hex value threw in
Keypair.fromPublicKey on re-encode. Now StrKey.encodeEd25519PublicKey. Fixing
this surfaced an adjacent bug: spec.scValToNative decodes an ScMap to an ARRAY
of [key,value] pairs (not a JS Map), so limitsOf's `map.entries()` mis-keyed
every entry under a numeric index — limits decoding was broken outright.
Switched to direct `for..of map` (correct for both an array-of-pairs and a real
Map).
M2 [MED] — relayer toResult used a failure DENYLIST, so a non-terminal "pending"
status mapped to success:true and a poll loop would treat an unconfirmed (or
later-reverted) tx as confirmed. Now a terminal-success ALLOWLIST
(/confirm|success/i); a pending status is a distinct non-success result carrying
the new RELAYER_PENDING code + `pending:true` context. The binary
TransactionResult union is preserved deliberately (a structural third arm would
ripple into the out-of-scope demo consumers).
LOW:
- stellar-indexer extractEntries: throw IndexerError on an unrecognized non-empty
envelope instead of degrading to [] (empty-vs-error conflation burned the retry
budget then reported "no signers"). null / {} still map to [].
- wallet-ops getSigner: transport errors propagate instead of being swallowed to
null, so a transient RPC failure during connectWallet's ownership check no
longer throws a false WalletOwnershipError for a valid passkey; connectWallet
clears the half-connected state and rethrows the truthful error.
- tx-ops: `expiration == null` instead of `!expiration` (an explicit 0 is honored).
Tests: +9 (mercury probe-key/evict/transport, relayer pending, codec limits
round-trip, stellar-indexer response shapes, getSigner transport-propagation).
pnpm build + npx vitest run = 141 tests / 17 files green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ning
F2 live testnet verification surfaced two issues:
- SDK product bug (src/managers/submission-manager.ts): signDeploy left the
assembled deploy fee at inclusion+resource. Deploys go through the relayer's
fee-bump ({xdr}) path, and a fee-bumped Soroban inner tx must have
fee == resourceFee (the fee-bump supplies inclusion) — so every
relayer-sponsored wallet deploy failed with RelayerError 7002 "Transaction
fee must be equal to the resource fee". Pin the inner fee to the resource fee
before the deployer signs (surgical envelope edit; TransactionBuilder.cloneFrom
drops SorobanTransactionData → txMalformed). Verified live: testnet deploys
now confirm on-chain (e.g. 3d168e24…, 069c23df…); 141 SDK/worker tests green.
- e2e harness (scripts/e2e/browser-full-e2e-audit.sh): action buttons are
disabled while app.busy, and flows hold busy past their success log
(createWallet through its trailing auto-fund; write layers through a trailing
balance refresh), so the next layer clicked a still-disabled button (silent
no-op → timeout). Add wait_enabled/wait_idle settle gates before the reconnect
and signer-row clicks and inside expect_after.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standard self-serve Zephyr flow is retired on Mercury's current backend: POST /zephyr/execute 404s even with a valid JWT (retroshade/query SQL works with the same token); /zephyr_upload + /zephyr_table_new gone; mercury-cli 0.2.1 dead; the --base CLI is source-built/non-public. Live self-serve model is Retroshades (/rest/retroshade/*). Mercury now hosts a Smart Account Indexer (/rest/smart-account-indexer, no auth) but it indexes OZ smart-account-kit, NOT passkey-kit (real wallet/signer → count:0). Documented the decision tree to get passkey-kit indexed (ask Mercury to add wasm 84924c53… to the hosted indexer, or self-host via the current CLI/dashboard) — all USER/Mercury-gated. Full probe evidence in Solo todo 967. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nly; gate the dead Mercury endpoint
todo 967 (F2b indexer reconciliation). src/indexer/** only; no contract/hash change.
STELLAR INDEXER (src/indexer/stellar-indexer.ts) — the live POST /v1/contract-data
returns each entry's key/val as JSON ScVal (`{"vec":[{"symbol":..},{"bytes":..}]}`),
NOT the base64-XDR the backend decoded (F2, todo 959 c2502).
- Added jsonScValToXdr: JSON ScVal -> xdr.ScVal using the standard Stellar
convention (symbol / bytes=hex / address=strkey / vec / map=[{key,val}] /
u64,i64=string / u32,i32=number / bool / void), then reuse the contract-spec
decoders (scValToSignerKey / decodeSignerVal). Unknown/malformed variants
THROW — never a silent misdecode.
- StellarIndexerEntry updated to the live field set (key/val JSON ScVal;
id/contract_id/last_modified_ledger_seq/tx_meta_version/timestamp/created_at/
updated_at optional); entriesToSigners groups by the raw JSON key.
- Mainnet-only made honest (the indexer returns 0 entries on testnet): added
STELLAR_INDEXER_NETWORK, a networkPassphrase config, a forNetwork() factory
returning null off mainnet (discovery disabled, not "no signers"), and a
constructor guard that throws on a non-mainnet passphrase — testnet no longer
silently returns []. extractEntries still throws on unknown non-empty shapes.
- Live validation needs a mainnet v1 wallet (endgame-gated); unit-tested against
the documented shape now.
MERCURY (src/indexer/mercury.ts) — the assumed POST /zephyr/execute route is
RETIRED (404 even with a valid JWT; F2b todo 959 c2502 + services c2504). The
replacement is a pending USER decision, NOT the SDK's to make:
A. Mercury's hosted Smart Account Indexer (/rest/smart-account-indexer) — but
it currently indexes OZ smart-account-kit events, not passkey-kit (real
passkey wallets return count:0), so it needs Mercury to add passkey-kit's
wasm coverage.
B. Self-deployed zephyr program + Mercury's current (non-public) tooling.
So the client is NOT re-pointed at either surface. Instead the backend is GATED:
unless the new `zephyrExecuteConfirmed` opt-in is set, every query throws a clear
"pending (todo 967)" IndexerError instead of silently 404-ing, and health()
reports { ok: false }. Both options are documented in the module.
Green: pnpm build (bindings + tsc + verify-esm) + npx vitest run = 159 tests /
17 files (+18: JSON-ScVal decode, mainnet gate, Mercury pending-gate).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… releasing Rewrite the docs suite against the FINAL verified v1 API (every table/sample checked against src/ and the contract interface — no aspirational docs). - README.md: rewritten. Config tables (Option|Type|Required|Description) for PasskeyKit/PasskeyServer; method tables inlining the wrapped contract fn; Quick Start before the API reference; client (PasskeyKit) vs server (PasskeyServer) split; dist/exports packaging + three entrypoints; typed Signer pipeline; typed errors + discriminated TransactionResult + renumbered contract error table (100-129); storage adapters; relayer-proxy usage; indexer backends with the pending-decision framing; caveats/footguns (min-signer brick, temp sole-signer eviction, deploy front-running, WebAuthn UP); deterministic derivation. Killed the passkey-factory-sdk ghost, the Protocol-21 framing, the make deploy/init + SMART_WALLET_FACTORY note, the 1.3.x relayer link, and the raw-TS shipping note. - CHANGELOG.md (new): SAK discipline (per-component versions, Compatibility callouts). Version is a placeholder (0.13.0 vs 1.0.0, decided at publish). - docs/migration-v1.md (new): Before/After, Removed exports, Behavior-changes table, and an explicit A/B GAP ANALYSIS (incl. the deferred live Mercury discovery regression). - docs/releasing.md (new): dependency-ordered publish (bindings -> SDK), verify:bindings gate, authed --dry-run, OTP step; folds the load-bearing cheatsheet commands. - Cruft: fold + delete cheatsheet.txt; move PROPOSAL.md -> docs/history/ with a historical banner; regenerate .cursorrules against the final stack. Docs only — no source/test changes; green baseline (tsc --noEmit) unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Minor bump 0.12.1 → 0.13.0 for the ground-up v1 overhaul (soroban-sdk 27 contract, reworked SDK, regenerated bindings, indexer abstraction, keyless relayer-proxy). Canonical v1 contract WASM hash: 84924c53a413318df2ce753e30de53ec651404c916d30e861718ad155c94b319 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A ground-up, forward-only overhaul of passkey-kit — contracts, SDK, bindings, indexing, demo, and services — released as
v0.13.0(tagv0.13.0). No backwards-compatibility layer with0.12.x; every line was re-examined against the current stack.Scope: 30 commits, ~177 files, +23.3k / −5.5k. Canonical v1 contract WASM hash:
84924c53a413318df2ce753e30de53ec651404c916d30e861718ad155c94b319(seedocs/deployments-testnet-2026-07-11.md).Contract (
smart-wallet, soroban-sdk 27)Rebuilt on
soroban-sdk 27/wasm32v1-none, Protocol 27 ("Zipper").__constructor(signer), CAP-0058); the legacyinitflag and the un-authenticated first-add_signerwindow are gone.update_contract_code→upgrade(new_wasm_hash)(emits anUpgradedevent, caches the hash); newget_signer(signer_key) -> Option<SignerVal>view.SignerExpirationis au64UNIX timestamp (inclusive), no longer a ledger sequence (survives ledger-close-time drift).SignerLimits::Some(empty)now means no permissions;Noneis the one unlimited encoding.CreateContract*requires an unlimited signer (deploy permission is no longer implied by a wallet-address limits entry).__check_authcorrectness — pass 1 (coverage) is purely boolean and can't panic-poison a valid auth; pass 2 verifies every signatures-map entry with a single-point expiration check (fixes an order-dependent, skippable-expiration bug).type == "webauthn.get"+ UP-flag checks, boundsclientDataJSON, returns typed errors instead of panicking.install(wallet)/ permissionlessuninstall(wallet); no policy code runs on theremove_signerpath, so a broken/malicious policy can never block its own removal.sample-policynow demonstrates a cumulative rolling-window spending allowance.#[contractevent]events (SignerAdded/SignerUpdated/SignerRemoved/Upgraded, SEP-48 schema in the WASM) replacing the schemaless("sw_v1", …)tuples.100–129(disjoint from legacy1–9); single spec-type source insmart-wallet-interface.SDK
PasskeyKitErrorhierarchy + a contract-error registry decoded from diagnostics, sync-tested against the bindings) and a discriminatedTransactionResult— submission returns, never throws.buildAuthorizationEntryPreimage; host-ordercompareScValScMap sort replacing the oldlocaleCompare(fixes same-type/differing-length key mis-ordering); V2 address credentials (SOROBAN_CREDENTIALS_ADDRESS_V2).kit/*,managers/*) that's unit-testable with fakes; storage adapters (memory / localStorage / IndexedDB,passkey-kit/storagesubpath); typed event emitter..d.tswith anexportsmap (././storage/./server), replacing raw-TSmain;@stellar/stellar-sdkmoved to a>=16peer;verify:bindingsdrift guard +verify-esmbuild gate.vitestsuite replacing the ad-hocbun_testsscripts (141 tests).Indexing
One
SignerIndexerabstraction with two interchangeable backends. See Remaining decisions for live-coverage status — the backends are code-complete and unit-tested; live discovery depends on external-service decisions./v1/contract-data) — decodes the live JSON-ScVal wire format; mainnet-only (network-aware factory returnsnulloff mainnet rather than masquerading an empty result)./zephyr/executeroute; the integration approach is pending (see below).Demo & services
relayer-proxy/— a Cloudflare Worker doing keyless per-IP OZ-Relayer key minting (Durable Object custody),@openzeppelin/relayer-plugin-channels^0.20. Purges the last Launchtube references.scripts/e2e/.Assurance
verify:bindings,verify-esm); browser passkey e2e on testnet with real contract IDs + tx hashes; Relayer verified on testnet and mainnet (mainnet fee-bump97e6fcea…, pubnet ledger 63438464). Caught and fixed a deploy fee-bump bug (RelayerError 7002) that only surfaces against a live relayer.Migration
Breaking, forward-only. Full guide with Before/After, a removed-exports list, and an A/B gap analysis:
docs/migration-v1.md. Pre-1.0 wallets stay live on both networks (same address-derivation tuple) and can be upgraded in place to a v1 WASM via their own auth.Remaining decisions
Deliberately unresolved, flagged rather than guessed:
/zephyr/executeis a dead route). Preferred path: have Mercury add passkey-kit's v1 WASM (84924c53…) to their existing hosted Smart Account Indexer (the same product smart-account-kit uses), so the SDK just queries/api/lookup/*with no self-deploy. Alternative: keep the self-deployed program on Mercury's current tooling.nextcarries two P27-prep commits (Oz #30, [codex] Prepare auth signing for Protocol 27 #31) that this overhaul supersedes; how it lands on the default branch (merge vs. reset) is a maintainer call. npm publish is pending the version go-ahead.🤖 Generated with Claude Code