feat(P3+P5+P6): SDK consolidation + auth/processor port + audit prep - #52
Merged
Merged
Conversation
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).
onspeedhp
force-pushed
the
feat/session-action-permissions
branch
from
May 6, 2026 11:52
b68a5eb to
9f77dcd
Compare
onspeedhp
force-pushed
the
feat/use-sdk-legacy
branch
from
May 6, 2026 11:52
31fbb2f to
1abb98c
Compare
Base automatically changed from
feat/session-action-permissions
to
chore/cherry-pick-guardrails
May 6, 2026 14:38
3 tasks
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
Stacks P3, P5, and P6 of the slot-share migration plan. Top of the 3-PR stack.
Base: `feat/session-action-permissions` (PR #51). Merge that first.
P3 — SDK consolidation (no audit)
Removes `sdk/solita-client` entirely; the unified `@lazorkit/sdk-legacy` (shared with `lazorkit-protocol`) is now the single SDK for both binaries.
5 commits
P5 — Consolidate (auth + processor port + IDL sync)
Aligns the remaining processor + auth files with upstream so the slot-share strategy works end-to-end. Most files in this PR are byte-identical with the already-audited `lazorkit-protocol` repo — see `docs/audit/upstream-parity.txt`.
5 commits
P6 — Audit prep (delta-audit)
1 commit
Local git tags created (not pushed): `audit-baseline-2026-02-accretion` (d1eaaeb) and `audit-pending-v1` (this branch's HEAD).
Test plan
Verified locally with `solana-test-validator`:
Audit ask
Per `docs/audit/DELTA_BRIEF.md`:
🤖 Generated with Claude Code