docs: refresh markdown for current codebase (dual-cluster IDs, action permissions, current test counts) - #53
Merged
Merged
Conversation
Port the 8 session action types (SolLimit, SolRecurringLimit, SolMaxPerTx, TokenLimit, TokenRecurringLimit, TokenMaxPerTx, ProgramWhitelist, ProgramBlacklist) plus parser, validator, and 30 unit tests verbatim from lazorkit-protocol. Type discriminators (1, 2, 3, 4, 5, 6, 10, 11) and the 11-byte header layout are preserved so the unified SDK can encode actions identically for both programs. Foundation only — does not yet wire actions into CreateSession or enforce them at Execute. Those land in follow-up commits (P1b, P1c). Notes: - action.rs is byte-identical to lazorkit-protocol@HEAD for audit comparability; existing harmless `unused_mut` warning ported as-is. - AuthError numeric codes 3020-3029 mirror lazorkit-protocol so the unified SDK can decode action errors uniformly.
…reation Port from lazorkit-protocol the variable-size SessionAccount and the ParsedCreateSessionArgs flow that lets a CreateSession instruction carry an optional 8-action permission buffer. state/session.rs is now byte-identical to upstream: - exposes SESSION_HEADER_SIZE (80) constant - adds has_actions() and actions_slice() helpers - documents that optional actions follow the fixed header processor/create_session.rs: - replaces CreateSessionArgs with ParsedCreateSessionArgs that parses [session_key(32)][expires_at(8)][actions_len(2)?][actions(N)?] - caps actions_len at 2048 to prevent BPF heap exhaustion - runs validate_actions_buffer() at creation time - session PDA size is now SESSION_HEADER_SIZE + actions_bytes.len() - ed25519 + secp256r1 signed payloads include actions_bytes so the permission set is bound to the signature Wire-up only: actions are stored on-chain but not yet enforced at Execute time. P1c will port execute/actions.rs to add enforcement. Backwards compatibility note: clients still sending exactly 40 bytes (no actions_len header) continue to work via the legacy parser branch, matching upstream behavior.
…ream
Adds CompactInstructionRef::{from_bytes, decompress}, DecompressedInstructionRef,
and parse_compact_instructions_ref_with_len from lazorkit-protocol. Lets the
Execute hot path parse and decompress without per-instruction Vec<u8> allocs
for account-index bytes or instruction data.
Existing parse_compact_instructions / serialize_compact_instructions remain
for callers (execute_deferred) that still need owned copies. File is now
byte-identical to upstream compact.rs for audit comparability.
Wires the 8 session action types (ported in earlier commits) into the
Execute instruction. Sessions with attached actions now have spending
limits, recurring caps, per-tx maxes, and program whitelist/blacklist
rules enforced around the CPI loop.
Files:
- processor/execute_actions.rs (NEW, 1644 lines, byte-identical to upstream
processor/execute/actions.rs): the enforcement engine — pre-CPI program
whitelist/blacklist checks, token-balance + token-authority snapshots,
post-CPI delta computation, SOL/token cap enforcement with saturating
arithmetic, recurring window resets aligned to slot boundaries.
- processor/execute.rs (REPLACED with upstream processor/execute/immediate.rs):
integrates pre/post action evaluation around the CPI loop, snapshots
vault metadata + token authorities for invariant checks, tracks gross
SOL outflow per CPI for SolMaxPerTx. Adds L5 anti-CPI guard for
session-authenticated Execute (stack height must be 1).
- error.rs: adds SessionVaultOwnerChanged (3030), SessionVaultDataLenChanged
(3031), SessionTokenAuthorityChanged (3032) for the vault-invariant
defenses against System::Assign and SetAuthority escapes.
- processor/mod.rs: wires execute_actions module.
Verification:
- 111 unit tests pass (action validator + execute_actions helpers)
- 18 litesvm integration tests pass (wallet_lifecycle, sessions, etc.)
- Existing devnet program ID FLb7… still works for backward compatibility
Note on file structure: upstream organizes execute as a subdir
(processor/execute/{actions,immediate,...}.rs); program-v2 keeps the flat
layout for now, putting the helpers in processor/execute_actions.rs and
the immediate-execute logic in processor/execute.rs. Future P2 may
restructure to subdir for closer upstream alignment.
Apply Pattern D from lazorkit-protocol PR #9: the embedded program ID is now chosen by `--features mainnet` or `--features devnet`, with a `compile_error!` if neither (or both) is set. Prevents accidental cross-cluster deploys — a binary compiled with one ID malfunctions if deployed to the other cluster's slot. Mainnet feature embeds LazorjRFNavitUaBu5m3WaNPjU1maipvSW2rZfAFAKi — the SAME program ID as lazorkit-protocol. program-v2 (foundation, no-fee build) occupies that mainnet slot for the duration of the foundation contract; at contract end the upgrade authority swaps the binary at the same slot to lazorkit-protocol's commercial build. dApp integrators keep one stable program ID through the transition. Devnet feature keeps program-v2's existing FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao. Build verification: cargo build-sbf --features mainnet → sha differs from devnet cargo build-sbf --features devnet → sha differs from mainnet cargo build-sbf → fails with compile_error cargo build-sbf --features mainnet --features devnet → fails
Add solana-security-txt + default-env deps and embed a `security_txt!` block in program/src/lib.rs so on-chain inspectors (and security researchers) get a self-described pointer to the SECURITY.md, contact endpoints, source repo, and audit report. Identifies the binary as the "Foundation Build" of LazorKit Smart Wallet to distinguish it from the lazorkit-protocol commercial binary that may later occupy the same mainnet slot. source_revision and source_release are populated from GITHUB_SHA / GITHUB_REF_NAME at CI build time. Audit pointer is the existing Accretion Labs report shipped under audits/.
- scripts/build-all.sh now takes a cluster argument (mainnet|devnet) and passes it through to cargo build-sbf. The program ID for IDL generation is derived from the resulting keypair instead of being passed in. - tests-sdk/package.json validator:start now builds with --features devnet before launching solana-test-validator and reads the program ID via solana-keygen pubkey on the keypair file (matching upstream pattern). - DEVELOPMENT.md updated with the new build invocations, --features devnet for cargo test, and a "Mainnet Deploy Strategy" section documenting the slot-sharing arrangement with lazorkit-protocol and the binary swap at contract end. - Cargo.lock regenerated for the security-txt deps added in the previous commit.
Verifies the dual-cluster mechanism stays intact:
1. cargo build-sbf --features mainnet succeeds
2. cargo build-sbf --features devnet succeeds
3. The two .so binaries differ (catches a refactor that neutralises
the cfg gate on declare_id!)
4. cargo build-sbf with no feature fails with the expected
"pick exactly one cluster" compile_error
5. cargo build-sbf with both features fails the same way
Workflow is byte-identical to upstream's. Triggers on PRs touching
program/ or assertions/, and on pushes to main.
Captures everything landed since the last audit cycle: - 8 session action permission types (state + parser + validator + tests) - SessionAccount variable-size + CreateSession action buffer support - Pre-CPI program whitelist/blacklist + post-CPI spending caps - Vault + token-account invariant defenses (3030-3032 errors) - Anti-CPI guard for session-authenticated Execute - Zero-copy CompactInstructionRef parser - Dual-cluster Cargo features (mainnet/devnet) with compile_error guard - security.txt embedded via solana-security-txt - Cherry-pick guardrails (fee-paths.txt + scripts + CI workflow) - sbf-cluster-check CI workflow - build-all.sh refactored for feature-flagged builds; sync-program-id.sh removed
Two operational documents covering the path from audit submission to production deploy: docs/MAINNET_DEPLOY.md — runbook for the foundation deploy and the binary swap to lazorkit-protocol at contract end. Both binaries occupy the same mainnet slot (LazorjRF…) at different times. Covers initial deploy, routine upgrades, binary swap, rollback, pre-existing wallet account behavior across the swap, and final upgrade-authority lock-down. docs/AUDIT_PREP.md — pre-tag checklist run before submitting a revision to Accretion. Covers code state hygiene, dual-feature build verification, test suite, fee-surface invariant (check-no-fee), documentation alignment, diff bounding, and the audit-packet contents to deliver. Also defines the audit-frozen-vN tag naming convention and the post-audit fix-up flow.
Triggered by audit-frozen-v* and v*.*.* tags. Builds both mainnet and devnet SBF binaries, records SHA-256 hashes, asserts they differ (catches a regression of the dual-cluster mechanism), and uploads the binaries + IDL + a manifest as GitHub Release assets. Manifest captures the build environment (Solana CLI version, rustc version, commit SHA) and the reproduction commands so anyone can locally rebuild and confirm the published hashes match. audit-frozen-v* tags create draft + prerelease releases (the artifact is for the auditor, not for end-users); v*.*.* tags create normal releases. Production deploys must consume binaries from a release — see docs/MAINNET_DEPLOY.md. GITHUB_SHA + GITHUB_REF_NAME are passed through to cargo build-sbf so the embedded security.txt advertises the exact source revision.
Switches the integration test imports from the in-repo Solita-generated
client to @lazorkit/sdk-legacy as a file: dependency, on the way to
deleting sdk/solita-client entirely.
Path changes:
- '../../sdk/solita-client/src{,/utils/*,/generated/accounts}'
→ '@lazorkit/sdk-legacy'
- PROGRAM_ID is now imported from ./common (which already hardcodes the
foundation-devnet ID FLb7…) rather than from the SDK, since the SDK's
exports differ between solita-client (single PROGRAM_ID) and
sdk-legacy (PROGRAM_ID_MAINNET / _DEVNET / _FOUNDATION_DEVNET).
API differences absorbed:
- await added on async client.createWallet calls (sdk-legacy probes
ProtocolConfig before building the tx; solita-client returned sync).
API differences NOT yet absorbed (~100 type errors remaining; tracked
as follow-up):
- Standalone instruction builders (createCreateWalletIx, createExecuteIx,
etc.) and PDA finders (findWalletPda, findAuthorityPda, etc.) now
require an explicit `programId` arg in sdk-legacy (post lazorkit-protocol
PR #9). Previously solita-client used an ambient PROGRAM_ID. Each call
site needs PROGRAM_ID threaded through; that's a separate mechanical
pass.
The vitest suite will not pass until those ~100 call sites are updated.
The sdk/solita-client directory deletion is gated on that completion.
…256r1 mocks
Final pass of the migration to @lazorkit/sdk-legacy. tsc now passes (0 errors).
Three classes of fixes:
1. PDA finders (find{Wallet,Vault,Authority,Session,DeferredExec}Pda):
sdk-legacy requires explicit programId after lazorkit-protocol PR #9.
Threaded `PROGRAM_ID` (from ./common) through all call sites.
2. Instruction builders (createCreateWalletIx, createExecuteIx, etc.):
Same — added `programId: PROGRAM_ID` to the object args of every call.
3. secp256r1 mock signer:
sdk-legacy's WebAuthn signing flow embeds clientDataJson directly
into the auth payload (vs solita-client's older typeAndFlags shortcut).
Replaced secp256r1Utils.ts with the version from lazorkit-protocol's
tests-sdk and imported the helpers from @lazorkit/sdk-legacy. Added
backwards-compat aliases (createMockSigner = createMockRawSigner,
signSecp256r1 = signSecp256r1Raw) so existing test code is unchanged.
Other touch-ups:
- tests-sdk/package.json description updated.
- benchmark.ts: PROGRAM_ID moved to ./common import.
- devnet-smoke.ts: missing await on client.executeDeferredFromPayload.
The sdk/solita-client directory can now be deleted in a follow-up commit
once vitest is run end-to-end against the migrated tests (requires a
local-validator + the program-v2 SBF binary).
The Solita-generated client is no longer needed. tests-sdk now depends on @lazorkit/sdk-legacy (file: link to ../../lazorkit-protocol/sdk/sdk-legacy during local dev; npm-published version after release). Anyone targeting program-v2 from TypeScript should: npm install @lazorkit/sdk-legacy The SDK probes the on-chain ProtocolConfig PDA on first use: - foundation binary at the slot → no PDA → no fee accounts → no fee - commercial binary at the slot → PDA present → fee accounts appended - same SDK code works against either binary, transparently scripts/build-all.sh and DEVELOPMENT.md still reference solita-client in places — separate cleanup commit follows.
Followup to deleting sdk/solita-client. Updated: - README.md: install/usage examples + project structure now reference @lazorkit/sdk-legacy. Added a one-liner explaining the SDK's flavor-blind probing behavior so foundation + commercial users see the same DX. - DEVELOPMENT.md: removed solita-client from project structure, removed the SDK regeneration workflow (the SDK is hand-written upstream), added a note on local file: link setup. - SECURITY.md: in-scope SDK reference updated. - CHANGELOG.md: replaced the v0.1.0 line claiming Solita codegen with a pointer to the published @lazorkit/sdk-legacy. - docs/Architecture.md: removed the in-tree SDK module tree from the layout diagram, added a pointer to the sibling repo. - scripts/build-all.sh: removed step 3 (Solita SDK regeneration). Build is now Rust + IDL only. Only remaining "solita-client" mention is in the CHANGELOG entry that documents this removal — intentional.
9 vitest cases dogfooding @lazorkit/sdk-legacy's Actions builder against
program-v2's session enforcement engine. All pass against
solana-test-validator with the foundation binary loaded.
Coverage:
- session without actions = unrestricted (baseline)
- ProgramWhitelist: allow whitelisted, reject non-whitelisted (3021)
- ProgramBlacklist: allow non-blacklisted, reject blacklisted (3022)
- SolMaxPerTx: allow at-cap, reject over-cap (3023)
- SolLimit (lifetime): allow within budget, reject when exhausted (3024),
then accept exact remaining
- Combined ProgramWhitelist + SolMaxPerTx: both rules enforced
Asserts vault-balance delta rather than recipient balance (recipient is
the test payer, an existing funded account, to sidestep the rent-exempt
minimum that fresh accounts hit on a tiny SOL transfer). Action checks
fire before the inner CPI, so this doesn't weaken what's being tested.
Also fixes tests/common.ts to resolve PROGRAM_ID dynamically:
1. PROGRAM_ID env var (CI override)
2. Pubkey of target/deploy/lazorkit_program-keypair.json (matches what
`npm run validator:start` loads the program at)
3. FLb7… fallback (typecheck-only)
Previously hardcoded to FLb7…, which broke for any locally built binary
because cargo build-sbf generates a fresh keypair on first build.
LazorKitClient is constructed with `new LazorKitClient(connection,
PROGRAM_ID)` to override its URL-based auto-inference (which defaults
localhost to the commercial 4h3X… ID).
Verified end-to-end: built program-v2 SBF binary, ran solana-test-validator,
ran `npx vitest run tests/12-actions.test.ts` → 9/9 pass in ~9 seconds.
sdk-legacy's LazorKitClient infers programId from the RPC URL when the second arg is omitted, defaulting localhost to the commercial devnet ID (4h3X…). Tests target the program-v2 binary loaded at the keypair pubkey, so the inference returns the wrong ID and all txs fail with "Attempt to load a program that does not exist". Pass PROGRAM_ID explicitly across all 9 test files. Also added the PROGRAM_ID import to 02-authority, 03-execute, 04-session, 07-e2e, 09-permissions, 10-session-execute (the others already had it). After this fix, vitest results against a live local validator: 35 passed | 28 failed | 2 skipped (65 total) The 28 remaining failures are ALL Secp256r1 paths. Root cause: program-v2's on-chain auth code still uses the OLD typeAndFlags format (extracts a single byte from auth_payload[13] and reconstructs clientDataJson on-chain), while sdk-legacy's mock signer uses the NEW format (embeds full clientDataJson directly in the payload). lazorkit-protocol's auth was upgraded to the new format; program-v2's wasn't ported. Fixing this requires porting lazorkit-protocol/program/src/auth/secp256r1/ to program-v2 — substantial change with audit attention. Tracked as follow-up. Ed25519 paths all pass. The 9 new E2E action tests (12-actions.test.ts) all pass since they use Ed25519 admin signers.
Byte-identical with lazorkit-protocol/program/src/auth/secp256r1/. Replaces the older typeAndFlags format (which reconstructed clientDataJSON server-side from a single byte at auth_payload[13]) with the format that embeds the full raw clientDataJSON in the auth payload. Required for slot-share strategy: a wallet created on either binary (commercial or foundation) must remain verifiable after binary swap. Both binaries now share the same auth verification logic + on-chain authority account layout. Verification: - 58/65 vitest E2E tests pass against live validator (up from 12/65 before port). The 7 remaining failures are in 08-deferred.test.ts and reflect a test-side bug (missing expiryBuf in signedPayload), addressed in P5.3.
…m upstream
Byte-identical with lazorkit-protocol/program/src/processor/{wallet/create,
authority/manage, authority/transfer_ownership, execute/deferred,
session/revoke}.rs.
Brings the on-chain authority data layout into alignment with upstream:
Secp256r1 authority = header(48) + cred_hash(32) + pubkey(33) + rpIdHash(32) = 145B
Previously program-v2 stored variable-length raw rpId; the new layout stores
a precomputed SHA256 digest at offset 113. Saves one sol_sha256 syscall per
Execute. Critical for slot-share: existing wallets created on lazorkit-protocol
must remain readable after binary swap.
File names stay flat (program-v2 keeps `processor/create_wallet.rs` rather
than upstream's `processor/wallet/create.rs`); content identical.
…yload The Authorize instruction binds expiry_offset to the Secp256r1 signature hash via signed_payload = instructions_hash || accounts_hash || expiry_offset (u16 LE). Test code was building signed_payload without the expiryBuf, causing all 7 deferred tests to fail with InvalidMessageHash (3005). Add expiryBuf at all 6 sign sites; values match the corresponding createAuthorizeIx expiryOffset arg (4 × 300, 1 × 9000, 1 × 10). Verification: 65/65 vitest pass against live validator (was 58/65 before).
…fee ix) instruction.rs's ProgramIx enum declarations (account metadata: writable modifiers, positions, descriptions) had drifted from lazorkit-protocol. Runtime not affected — sdk-legacy uses hand-written instruction builders, not the generated IDL. Resync now to keep IDL output (program/idl.json) faithful to actual on-chain account expectations. Strip 5 protocol-mgmt instruction variants (disc 10-14): InitializeProtocol, UpdateProtocol, RegisterPayer, WithdrawTreasury, InitializeTreasuryShard. program-v2 keeps disc 0-9 only (matches entrypoint dispatch). Verification: - cargo build --features devnet → clean - bash scripts/check-no-fee.sh → clean
Prepares the consolidated state at audit-pending-v1 for Accretion's delta-audit review. Local-only artifacts; not published, not pushed. Deliverables under docs/audit/: - DELTA_BRIEF.md: structured summary by phase (P0-P5), audit asks per phase, byte-identity claims vs upstream lazorkit-protocol, slot-share strategy context, contact + reproducibility info - program-src.diff: full unified diff of program/ between audit-baseline-2026-02-accretion (d1eaaeb, the prior audited state) and audit-pending-v1 (9c97fe2, the new state) - program-src.diff.stat: per-file changed-line summary - upstream-parity.txt: byte-identity report — 13/19 changed files byte-identical with already-audited lazorkit-protocol; 6 differ only for fee-strip / cosmetic reasons (URLs, layout) Local git tags created (not pushed): - audit-baseline-2026-02-accretion → d1eaaeb (prior audit baseline) - audit-pending-v1 → 9c97fe2 (current consolidated state) Audit ask is explicit per phase: - P1 action enforcement engine: confirm no new vulnerabilities - P5 auth/processor port: confirm Accretion's prior review of byte-identical upstream files extends to program-v2 - Slot-share compatibility: confirm state account layouts forward-compatible for binary swap at LazorjRF… mainnet slot Per user direction, NOT publishing or pushing yet — awaiting explicit permission for those operational steps.
Cite the audit firm as 'Accretion' / 'Accretion Labs' only — README, SECURITY policy, on-chain security_txt, and the audit delta brief. Audit PDF filename retained (already an immutable artifact).
feat(P1+P2+P4): session action permissions + dual-cluster + release infra
feat(P3+P5+P6): SDK consolidation + auth/processor port + audit prep
…am IDs Updates README, DEVELOPMENT, Architecture, and Costs docs: - README: explicit dual-cluster program ID table (mainnet LazorjRF…, devnet FLb7…), slot-share note pointing at lazorkit-protocol; build commands now show --features mainnet/devnet; test count 56 → 65; instruction count 9 → 10; authority size 125 → 145 bytes (rpIdHash); session description expanded to cover action permissions - DEVELOPMENT: test count 56 → 65 in run-tests command - Architecture: SessionAccount section now covers actions buffer + table of all 8 action types; Secp256r1 authority data layout updated to rpIdHash 32 bytes (total 145B); WebAuthn description updated to embedded raw clientDataJSON (no longer 'reconstructs from packed flags'); processor tree updated for execute_actions.rs + revoke_session.rs; AuthError range 3001-3018 → 3001-3032; project structure mentions action.rs + zero-copy compact ref variants; entrypoint mentions disc 0–9 - Costs: program ID line now lists both mainnet and devnet; Secp256r1 authority size updated to 145 bytes / rpIdHash; smoke test exercises 10 instructions
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Summary
Stale-reference cleanup pass over the project's markdown after the recent
P0–P6 work landed on `chore/cherry-pick-guardrails`. Brings docs into
agreement with the codebase as it stands.
Files updated
Test plan
Documentation only — no code changes. Reviewer needs to verify: