Skip to content

Commit 8c9b92b

Browse files
committed
docs: update documentation for deferred execution and sync devnet program ID
Update all documentation to cover the new deferred execution feature (Authorize, ExecuteDeferred, ReclaimDeferred) and sync program ID to devnet address FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao across 22 files including SDK, tests, docs, and build scripts.
1 parent 8861b16 commit 8c9b92b

22 files changed

Lines changed: 250 additions & 44 deletions

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88

99
### Added
1010

11+
- Deferred Execution: 2-transaction flow for large payloads exceeding the ~574-byte limit of a single Secp256r1 Execute tx
12+
- Authorize instruction (disc=6): TX1 signs over instruction/account hashes, creates DeferredExec PDA
13+
- ExecuteDeferred instruction (disc=7): TX2 verifies hashes and executes via CPI with vault signing
14+
- ReclaimDeferred instruction (disc=8): closes expired DeferredExec accounts, refunds rent to original payer
15+
- DeferredExecAccount (176 bytes): stores instruction/account hashes, wallet, authority, payer, expiry
16+
- Deferred execution benchmarks (CU + tx size measurements for TX1/TX2)
17+
- Error codes 3014-3018 for deferred execution (expired, hash mismatch, invalid expiry, unauthorized reclaim)
18+
- SDK builders: `createAuthorizeIx`, `createExecuteDeferredIx`, `createReclaimDeferredIx`
19+
- SDK helpers: `findDeferredExecPda`, `computeInstructionsHash`
20+
- LazorKitClient methods: `authorizeSecp256r1`, `executeDeferredSecp256r1`, `reclaimDeferred`
21+
- 7 new integration tests for deferred execution (tests-sdk, total now 35)
1122
- Odometer counter replay protection for Secp256r1 (monotonic u32 per authority)
1223
- program_id included in challenge hash (cross-program replay prevention)
1324
- rpId stored on authority account at creation (saves ~14 bytes per transaction)
1425
- TypeScript SDK (`sdk/solita-client`) with Solita code generation
15-
- Integration test suite (`tests-sdk/`) with 28 tests across 7 files
26+
- Integration test suite (`tests-sdk/`) with 35 tests across 8 files
1627
- Benchmark script for CU and transaction size measurements
1728
- CompactInstructions accounts hash for anti-reordering protection
1829
- Session expiry validation (future check + 30-day max duration)

DEVELOPMENT.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ cargo test
3838
### C. IDL Generation (using Shank)
3939

4040
```bash
41-
cd program && shank idl -o . --out-filename idl.json -p 2m47smrvCRpuqAyX2dLqPxpAC1658n1BAQga1wRCsQiT
41+
cd program && shank idl -o . --out-filename idl.json -p FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao
4242
```
4343

4444
### D. SDK Generation (using Solita)
@@ -55,7 +55,7 @@ The generate.mjs script reads the Shank IDL, enriches it with accounts/errors/ty
5555
# Terminal 1: Start local validator with program loaded
5656
cd tests-sdk && npm run validator:start
5757

58-
# Terminal 2: Run all 28 tests
58+
# Terminal 2: Run all 35 tests
5959
cd tests-sdk && npm test
6060
```
6161

@@ -65,7 +65,7 @@ cd tests-sdk && npm test
6565
cd tests-sdk && npm run benchmark
6666
```
6767

68-
Measures CU usage and transaction sizes for all instructions.
68+
Measures CU usage and transaction sizes for all instructions, including deferred execution (Authorize TX1 + ExecuteDeferred TX2).
6969

7070
### G. Program ID Sync
7171

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A high-performance smart wallet program on Solana with passkey (WebAuthn/Secp256r1) authentication, role-based access control, session keys, and replay-safe odometer counters. Built with [pinocchio](https://github.com/febo/pinocchio) for zero-copy serialization.
44

5-
**Program ID**: `2m47smrvCRpuqAyX2dLqPxpAC1658n1BAQga1wRCsQiT`
5+
**Program ID**: `FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao`
66

77
---
88

@@ -15,6 +15,7 @@ A high-performance smart wallet program on Solana with passkey (WebAuthn/Secp256
1515
- **Clock-Based Slot Freshness**: 150-slot window via `Clock::get()` — no SlotHashes sysvar needed
1616
- **Zero-Copy Serialization**: Raw byte casting via pinocchio, no Borsh overhead
1717
- **CompactInstructions**: Index-based instruction packing for multi-call payloads within Solana's 1,232-byte tx limit
18+
- **Deferred Execution**: 2-transaction flow for payloads exceeding the tx limit (e.g., Jupiter swaps) -- TX1 authorizes via signature, TX2 executes with full inner instruction space (~1,100 bytes)
1819
- **CPI Reentrancy Protection**: stack_height check prevents cross-program authentication attacks
1920

2021
---
@@ -23,13 +24,24 @@ A high-performance smart wallet program on Solana with passkey (WebAuthn/Secp256
2324

2425
| Metric | Normal Transfer | LazorKit (Secp256r1) | LazorKit (Session) |
2526
|---|---|---|---|
26-
| Compute Units | 150 | 10,941 | 4,483 |
27+
| Compute Units | 150 | 12,441 | 8,983 |
2728
| Transaction Size | 215 bytes | 658 bytes | 452 bytes |
2829
| Accounts | 2 | 7 | 7 |
2930
| Instructions | 1 | 2 | 1 |
3031
| Transaction Fee | 0.000005 SOL | 0.000005 SOL | 0.000005 SOL |
3132

32-
Session keys are ideal for frequent transactions — they skip the Secp256r1 precompile and use a simple Ed25519 signer, resulting in lower CU and smaller transactions.
33+
Session keys are ideal for frequent transactions -- they skip the Secp256r1 precompile and use a simple Ed25519 signer, resulting in lower CU and smaller transactions.
34+
35+
### Deferred Execution (Large Payloads)
36+
37+
For operations exceeding the ~574 bytes available in a single Secp256r1 Execute tx (e.g., Jupiter swaps):
38+
39+
| Metric | Immediate Execute | Deferred (2 txs) |
40+
|---|---|---|
41+
| Total CU | 12,441 | 18,613 (11,709 + 6,904) |
42+
| Inner Ix Capacity | ~574 bytes | ~1,100 bytes (1.9x) |
43+
| Tx Fee | 0.000005 SOL | 0.00001 SOL |
44+
| Temp Rent | -- | 0.00212 SOL (refunded) |
3345

3446
See [docs/Costs.md](docs/Costs.md) for full cost analysis, session key costs, and CU benchmarks for all instructions.
3547

@@ -45,6 +57,7 @@ See [docs/Costs.md](docs/Costs.md) for full cost analysis, session key costs, an
4557
| Authority (Ed25519) | 80 bytes | 0.001448 |
4658
| Authority (Secp256r1) | ~125 bytes | 0.001761 |
4759
| Session | 80 bytes | 0.001448 |
60+
| DeferredExec | 176 bytes | 0.002116 (temporary, refunded) |
4861

4962
### Total Wallet Creation
5063

@@ -72,6 +85,7 @@ Session rent is refundable after expiry. Ongoing Execute transactions cost only
7285
| Vault PDA | `["vault", wallet]` | Holds SOL/tokens, program signs via PDA |
7386
| Authority PDA | `["authority", wallet, id_hash]` | Per-key auth with role + counter |
7487
| Session PDA | `["session", wallet, session_key]` | Ephemeral sub-key with expiry |
88+
| DeferredExec PDA | `["deferred", wallet, authority, counter]` | Temporary pre-authorized execution (176 bytes) |
7589

7690
See [docs/Architecture.md](docs/Architecture.md) for struct definitions, security mechanisms, and instruction reference.
7791

@@ -82,12 +96,12 @@ See [docs/Architecture.md](docs/Architecture.md) for struct definitions, securit
8296
```
8397
program/src/ Rust smart contract (pinocchio, zero-copy)
8498
auth/ Ed25519 + Secp256r1/WebAuthn authentication
85-
processor/ 6 instruction handlers
99+
processor/ 9 instruction handlers
86100
state/ Account data structures (NoPadding)
87101
sdk/solita-client/ TypeScript SDK (Solita-generated + hand-written utils)
88102
src/generated/ Auto-generated instructions, accounts, errors
89103
src/utils/ Instruction builders, PDA helpers, signing utils
90-
tests-sdk/ Integration tests (vitest, 28 tests)
104+
tests-sdk/ Integration tests (vitest, 35 tests)
91105
docs/ Architecture, cost analysis
92106
audits/ Audit reports
93107
```
@@ -138,14 +152,14 @@ See [sdk/solita-client/README.md](sdk/solita-client/README.md) for full API refe
138152
# Start local validator with program loaded
139153
cd tests-sdk && npm run validator:start
140154

141-
# Run all 28 integration tests
155+
# Run all 35 integration tests
142156
npm test
143157

144158
# Run CU benchmarks
145159
npm run benchmark
146160
```
147161

148-
Tests cover: wallet lifecycle, authority management, execute, sessions, replay protection, counter edge cases, and end-to-end workflows.
162+
Tests cover: wallet lifecycle, authority management, execute, deferred execution, sessions, replay protection, counter edge cases, and end-to-end workflows.
149163

150164
See [DEVELOPMENT.md](DEVELOPMENT.md) for full development workflow.
151165

assertions/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use pinocchio_pubkey::declare_id;
1111
use pinocchio_system::ID as SYSTEM_ID;
1212

1313
// LazorKit Program ID
14-
declare_id!("2m47smrvCRpuqAyX2dLqPxpAC1658n1BAQga1wRCsQiT");
14+
declare_id!("FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao");
1515

1616
#[allow(unused_imports)]
1717
use std::mem::MaybeUninit;

docs/Architecture.md

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub enum AccountDiscriminator {
4848
Wallet = 1,
4949
Authority = 2,
5050
Session = 3,
51+
DeferredExec = 4,
5152
}
5253
```
5354

@@ -109,13 +110,36 @@ pub struct SessionAccount {
109110
// Total: 1+1+1+5+32+32+8 = 80 bytes
110111
```
111112

112-
### D. Vault PDA
113+
### D. DeferredExecAccount (176 bytes)
114+
115+
Seeds: `["deferred", wallet_pubkey, authority_pubkey, counter_le(4)]`
116+
117+
```rust
118+
#[repr(C, align(8))]
119+
pub struct DeferredExecAccount {
120+
pub discriminator: u8, // 4 = DeferredExec
121+
pub version: u8,
122+
pub bump: u8,
123+
pub _padding: [u8; 5],
124+
pub instructions_hash: [u8; 32], // SHA256 of serialized compact instructions
125+
pub accounts_hash: [u8; 32], // SHA256 of all account pubkeys referenced
126+
pub wallet: Pubkey, // 32 bytes
127+
pub authority: Pubkey, // 32 bytes — the authority that authorized
128+
pub payer: Pubkey, // 32 bytes — receives rent refund on close
129+
pub expires_at: u64, // Absolute slot at which this expires
130+
}
131+
// Total: 1+1+1+5+32+32+32+32+32+8 = 176 bytes
132+
```
133+
134+
Temporary account created during `Authorize` (tx1) and closed during `ExecuteDeferred` (tx2). Uses the authority's odometer counter as a seed nonce, ensuring unique PDAs per authorization. Expired accounts can be reclaimed via `ReclaimDeferred`.
135+
136+
### E. Vault PDA
113137

114138
Seeds: `["vault", wallet_pubkey]`
115139

116140
No data allocated. Holds SOL. Program signs for it via PDA seeds during Execute.
117141

118-
## 5. Instructions (6 total)
142+
## 5. Instructions (9 total)
119143

120144
### CreateWallet (discriminator: 0)
121145

@@ -155,6 +179,35 @@ No data allocated. Holds SOL. Program signs for it via PDA seeds during Execute.
155179
- Validates expires_at: must be in future, max ~30 days.
156180
- Accounts: payer, wallet, authorizer, session, system_program, rent_sysvar.
157181

182+
### Authorize (discriminator: 6) — Deferred Execution TX1
183+
184+
- Creates a DeferredExec PDA storing pre-authorized instruction/account hashes.
185+
- Only Secp256r1 Owner/Admin can authorize (not Ed25519, not Spender).
186+
- Signed payload: `instructions_hash || accounts_hash` (64 bytes).
187+
- Expiry offset bounded to 10-9,000 slots (~4 seconds to ~1 hour).
188+
- Uses the authority's odometer counter (post-increment) as PDA seed nonce.
189+
- Instruction data: `[instructions_hash(32)][accounts_hash(32)][expiry_offset(2)][auth_payload(variable)]`.
190+
- Accounts: payer, wallet, authority, deferred_exec, system_program, rent_sysvar, sysvar_instructions.
191+
192+
### ExecuteDeferred (discriminator: 7) — Deferred Execution TX2
193+
194+
- Verifies compact instructions against stored hashes, executes via CPI with vault PDA signing.
195+
- Closes the DeferredExec account before CPI (close-before-execute pattern).
196+
- Verifies both instructions_hash and accounts_hash match stored values.
197+
- Checks expiry (must not be past `expires_at` slot).
198+
- Refunds rent to the original payer (stored in DeferredExec).
199+
- Self-reentrancy protection: rejects CPI back into this program.
200+
- Instruction data: `[compact_instructions(variable)]`.
201+
- Accounts: payer, wallet, vault, deferred_exec, refund_destination, [remaining accounts...].
202+
203+
### ReclaimDeferred (discriminator: 8)
204+
205+
- Closes an expired DeferredExec account and refunds rent to the original payer.
206+
- Only the original payer (stored in `deferred.payer`) can reclaim.
207+
- Can only be called after `expires_at` has passed.
208+
- No instruction data (discriminator only).
209+
- Accounts: payer, deferred_exec, refund_destination.
210+
158211
## 6. CompactInstructions Format
159212

160213
Binary format for packing multiple instructions into Execute:
@@ -175,7 +228,32 @@ Overhead per instruction: 4 bytes + num_accounts. Replaces 32-byte pubkeys with
175228

176229
For Secp256r1 Execute, the signed payload includes a SHA256 hash of all account pubkeys referenced by the compact instructions. This prevents account reordering attacks where an attacker could swap recipient addresses while keeping the signature valid.
177230

178-
## 7. Auth Payload Layout (Secp256r1)
231+
## 7. Deferred Execution
232+
233+
2-transaction flow for payloads exceeding the ~574 bytes available in a single Secp256r1 Execute transaction (e.g., Jupiter swaps with complex routing).
234+
235+
### Flow
236+
237+
1. **TX1 (Authorize)**: Client computes `instructions_hash = SHA256(packed_compact_instructions)` and `accounts_hash = SHA256(all_referenced_pubkeys)`. These hashes are signed via Secp256r1 and stored in a DeferredExec PDA. The authority's odometer counter is incremented.
238+
2. **TX2 (ExecuteDeferred)**: Any signer submits the full compact instructions. The program verifies both hashes match, checks expiry, closes the DeferredExec account, and executes via CPI with vault signing.
239+
240+
### Capacity
241+
242+
| Path | Inner Ix Capacity | Total CU | Tx Fee |
243+
|---|---|---|---|
244+
| Immediate Execute | ~574 bytes | 12,441 | 0.000005 SOL |
245+
| Deferred (2 txs) | ~1,100 bytes (1.9x) | 18,613 | 0.00001 SOL |
246+
247+
### Security Properties
248+
249+
- **Hash binding**: Both instruction content and account ordering are hash-verified.
250+
- **Replay protection**: Odometer counter used as PDA seed nonce — each authorization gets a unique PDA.
251+
- **Expiry**: 10-9,000 slot window (~4s to ~1h). Prevents stale authorizations.
252+
- **Role gating**: Only Secp256r1 Owner/Admin can authorize.
253+
- **Close-before-CPI**: DeferredExec account is closed before CPI execution, avoiding stale-pointer issues with `invoke_signed_unchecked`. Transaction reverts atomically if any CPI fails.
254+
- **Rent recovery**: `ReclaimDeferred` allows original payer to reclaim rent from expired, unexecuted authorizations.
255+
256+
## 8. Auth Payload Layout (Secp256r1)
179257

180258
```
181259
[slot: u64 LE] // 8 bytes -- Clock-based slot freshness
@@ -190,7 +268,7 @@ Compared to the previous layout, 3 optimizations reduce the per-transaction payl
190268
- **SlotHashes index**: removed (slot freshness via `Clock::get()` instead of sysvar lookup)
191269
- **rpId**: stored on the authority account at creation, not sent per-tx (saves ~12 bytes)
192270

193-
## 8. Project Structure
271+
## 9. Project Structure
194272

195273
```
196274
program/
@@ -205,16 +283,20 @@ program/
205283
processor/
206284
create_wallet.rs
207285
manage_authority.rs AddAuthority + RemoveAuthority
208-
execute.rs CompactInstruction execution
286+
execute.rs CompactInstruction execution (immediate)
287+
authorize.rs Deferred execution TX1 (creates DeferredExec PDA)
288+
execute_deferred.rs Deferred execution TX2 (verifies + executes)
289+
reclaim_deferred.rs Closes expired DeferredExec accounts
209290
create_session.rs
210291
transfer_ownership.rs
211292
state/
212293
wallet.rs WalletAccount (8 bytes)
213294
authority.rs AuthorityAccountHeader (48 bytes)
214295
session.rs SessionAccount (80 bytes)
296+
deferred.rs DeferredExecAccount (176 bytes)
215297
compact.rs CompactInstruction serialization
216298
utils.rs PDA initialization, stack_height check
217-
error.rs AuthError enum (3001-3013)
299+
error.rs AuthError enum (3001-3018)
218300
entrypoint.rs Instruction routing
219301
sdk/solita-client/
220302
src/
@@ -226,5 +308,5 @@ sdk/solita-client/
226308
secp256r1.ts Challenge hash + auth payload builders
227309
packing.ts CompactInstruction packing
228310
errors.ts Error code mapping
229-
tests-sdk/ Integration tests (vitest, 28+ tests)
311+
tests-sdk/ Integration tests (vitest, 35 tests)
230312
```

0 commit comments

Comments
 (0)