Skip to content

Commit 3eb7134

Browse files
authored
Merge pull request #47 from lazor-kit/fix/sdk-execute-sysvar-collision
feat: odometer replay protection, Solita SDK, deferred execution, unified API, security tests
2 parents 75116a6 + 2b0a7cf commit 3eb7134

32 files changed

Lines changed: 3874 additions & 1591 deletions

CHANGELOG.md

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

99
### Added
1010

11+
- Unified SDK API with discriminated union signer types (`ed25519()`, `secp256r1()`, `session()` helper constructors)
12+
- `CreateWalletOwner` union type: single `createWallet()` method for both Ed25519 and Secp256r1
13+
- `AdminSigner` union type for admin operations (addAuthority, removeAuthority, transferOwnership, createSession)
14+
- `ExecuteSigner` union type for execute operations (includes session keys)
15+
- `DeferredPayload` interface for clean authorize() -> executeDeferredFromPayload() flow
16+
- Security test suite: 19 new tests across 3 files (permissions, session execution, attack vectors)
17+
- Permission boundary tests: role enforcement for spender/admin/owner (error 3002 verification)
18+
- Session execution tests: session key execute, transferSol via session, wrong key rejection, expiry enforcement
19+
- Security edge case tests: counter increment verification, self-reentrancy prevention (error 3013), cross-wallet authority isolation, accounts hash binding (recipient swap detection)
20+
- High-level `transferSol()` method: transfer SOL with just payer, wallet, signer, recipient, and amount
21+
- High-level `execute()` method: execute arbitrary TransactionInstructions without manual compact encoding
22+
- Auto-derivation of authority PDAs from signer.credentialIdHash (authorityPda now optional in Secp256r1 methods)
1123
- Deferred Execution: 2-transaction flow for large payloads exceeding the ~574-byte limit of a single Secp256r1 Execute tx
1224
- Authorize instruction (disc=6): TX1 signs over instruction/account hashes, creates DeferredExec PDA
1325
- ExecuteDeferred instruction (disc=7): TX2 verifies hashes and executes via CPI with vault signing
1426
- ReclaimDeferred instruction (disc=8): closes expired DeferredExec accounts, refunds rent to original payer
1527
- DeferredExecAccount (176 bytes): stores instruction/account hashes, wallet, authority, payer, expiry
28+
- Devnet smoke test (`tests-sdk/tests/devnet-smoke.ts`): exercises all 9 instructions across Ed25519/Secp256r1/Session auth types and Owner/Admin/Spender roles, reporting CU/TX size/rent
1629
- Deferred execution benchmarks (CU + tx size measurements for TX1/TX2)
1730
- Error codes 3014-3018 for deferred execution (expired, hash mismatch, invalid expiry, unauthorized reclaim)
1831
- SDK builders: `createAuthorizeIx`, `createExecuteDeferredIx`, `createReclaimDeferredIx`
1932
- SDK helpers: `findDeferredExecPda`, `computeInstructionsHash`
20-
- LazorKitClient methods: `authorizeSecp256r1`, `executeDeferredSecp256r1`, `reclaimDeferred`
21-
- 7 new integration tests for deferred execution (tests-sdk, total now 35)
33+
- LazorKitClient methods: `authorize`, `executeDeferredFromPayload`, `reclaimDeferred`
2234
- Odometer counter replay protection for Secp256r1 (monotonic u32 per authority)
2335
- program_id included in challenge hash (cross-program replay prevention)
2436
- rpId stored on authority account at creation (saves ~14 bytes per transaction)
2537
- TypeScript SDK (`sdk/solita-client`) with Solita code generation
26-
- Integration test suite (`tests-sdk/`) with 35 tests across 8 files
38+
- Integration + security test suite (`tests-sdk/`) with 56 tests across 11 files
2739
- Benchmark script for CU and transaction size measurements
2840
- CompactInstructions accounts hash for anti-reordering protection
2941
- Session expiry validation (future check + 30-day max duration)
@@ -37,6 +49,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
3749

3850
### Changed
3951

52+
- SDK API: unified all methods via discriminated unions (breaking: removed `createWalletEd25519`, `createWalletSecp256r1`, `addAuthoritySecp256r1`, `removeAuthoritySecp256r1`, `executeEd25519`, `executeSecp256r1`, `executeSession`, `createSessionSecp256r1`, `transferOwnershipSecp256r1`, `authorizeSecp256r1`)
53+
- SDK API: all methods now return `{ instructions: TransactionInstruction[]; ...extraPdas }` consistently
54+
- SDK API: `createSession` now takes `sessionKey: PublicKey` instead of `Uint8Array`
55+
- SDK architecture: split monolithic wrapper.ts into client.ts, types.ts, signing.ts, compact.ts
4056
- Secp256r1 replay protection: primary mechanism changed from WebAuthn hardware counter to program-controlled odometer
4157
- Auth payload layout: added 4-byte counter field at offset 8 (all subsequent fields shifted)
4258
- Challenge hash: 5 elements -> 7 elements (added counter + program_id)
@@ -48,9 +64,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4864
- Counter size: u64 -> u32 (4 billion operations per authority is sufficient)
4965
- Execute Secp256r1 transaction size: 708 -> 658 bytes (50 bytes saved)
5066
- Execute Secp256r1 accounts: 8 -> 7 (SlotHashes sysvar removed)
67+
- Cost documentation: updated all CU numbers from local validator to devnet-measured actuals, expanded CU table to all 9 instructions across all auth types and roles
68+
- Shank IDL: fixed 4 instructions missing `rent_sysvar` accounts, added 3 missing deferred execution instructions (Authorize, ExecuteDeferred, ReclaimDeferred)
5169

5270
### Fixed
5371

72+
- `PublicKey.default` collision with `SystemProgram.programId` in SDK execute methods: both are 32 zero bytes, causing `buildCompactLayout` to map SystemProgram to the sysvar slot (index 4) instead of adding it as a remaining account. Replaced with `SYSVAR_INSTRUCTIONS_PUBKEY`.
5473
- Synced passkey lockout: WebAuthn hardware counter=0 no longer causes rejection
5574
- 17/17 audit issues resolved (Accretion audit)
5675

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Thank you for your interest in contributing to LazorKit.
4747
All PRs must:
4848

4949
1. Pass `cargo test` (35+ Rust tests)
50-
2. Pass `npm test` in `tests-sdk/` (28+ integration tests)
50+
2. Pass `npm test` in `tests-sdk/` (56+ tests: integration, security, permissions, sessions)
5151
3. Not break the benchmark script (`npm run benchmark`)
5252

5353
## Security

DEVELOPMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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 35 tests
58+
# Terminal 2: Run all 56 tests
5959
cd tests-sdk && npm test
6060
```
6161

README.md

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,23 @@ A high-performance smart wallet program on Solana with passkey (WebAuthn/Secp256
2222

2323
## LazorKit vs Normal SOL Transfer
2424

25-
| Metric | Normal Transfer | LazorKit (Secp256r1) | LazorKit (Session) |
26-
|---|---|---|---|
27-
| Compute Units | 150 | 12,441 | 8,983 |
28-
| Transaction Size | 215 bytes | 658 bytes | 452 bytes |
29-
| Accounts | 2 | 7 | 7 |
30-
| Instructions | 1 | 2 | 1 |
31-
| Transaction Fee | 0.000005 SOL | 0.000005 SOL | 0.000005 SOL |
25+
| Metric | Normal Transfer | LazorKit (Secp256r1) | LazorKit (Ed25519) | LazorKit (Session) |
26+
|---|---|---|---|---|
27+
| Compute Units | 150 | 9,441 | 5,864 | 4,483-5,983 |
28+
| Transaction Size | 215 bytes | 658 bytes | 452 bytes | 452 bytes |
29+
| Accounts | 2 | 7 | 7 | 7 |
30+
| Instructions | 1 | 2 | 1 | 1 |
31+
| Transaction Fee | 0.000005 SOL | 0.000005 SOL | 0.000005 SOL | 0.000005 SOL |
3232

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.
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. All CU measurements are from real devnet transactions.
3434

3535
### Deferred Execution (Large Payloads)
3636

3737
For operations exceeding the ~574 bytes available in a single Secp256r1 Execute tx (e.g., Jupiter swaps):
3838

3939
| Metric | Immediate Execute | Deferred (2 txs) |
4040
|---|---|---|
41-
| Total CU | 12,441 | 18,613 (11,709 + 6,904) |
41+
| Total CU | 9,441 | 15,613 (10,209 + 5,404) |
4242
| Inner Ix Capacity | ~574 bytes | ~1,100 bytes (1.9x) |
4343
| Tx Fee | 0.000005 SOL | 0.00001 SOL |
4444
| Temp Rent | -- | 0.00212 SOL (refunded) |
@@ -101,7 +101,7 @@ program/src/ Rust smart contract (pinocchio, zero-copy)
101101
sdk/solita-client/ TypeScript SDK (Solita-generated + hand-written utils)
102102
src/generated/ Auto-generated instructions, accounts, errors
103103
src/utils/ Instruction builders, PDA helpers, signing utils
104-
tests-sdk/ Integration tests (vitest, 35 tests)
104+
tests-sdk/ Integration tests (vitest, 56 tests)
105105
docs/ Architecture, cost analysis
106106
audits/ Audit reports
107107
```
@@ -122,23 +122,54 @@ cargo build-sbf
122122
npm install @lazorkit/solita-client
123123
```
124124

125-
### Create a Wallet (Ed25519)
125+
### Create a Wallet
126126

127127
```typescript
128-
import { Connection, Keypair } from '@solana/web3.js';
128+
import { Connection } from '@solana/web3.js';
129129
import { LazorKitClient } from '@lazorkit/solita-client';
130130
import * as crypto from 'crypto';
131131

132132
const connection = new Connection('https://api.devnet.solana.com');
133133
const client = new LazorKitClient(connection);
134134

135-
const owner = Keypair.generate();
136-
const userSeed = crypto.randomBytes(32);
135+
const { instructions, walletPda, vaultPda } = client.createWallet({
136+
payer: payer.publicKey,
137+
userSeed: crypto.randomBytes(32),
138+
owner: {
139+
type: 'secp256r1',
140+
credentialIdHash, // 32-byte SHA256 of WebAuthn credential ID
141+
compressedPubkey, // 33-byte compressed Secp256r1 public key
142+
rpId: 'your-app.com',
143+
},
144+
});
145+
```
146+
147+
### Transfer SOL
148+
149+
```typescript
150+
import { secp256r1 } from '@lazorkit/solita-client';
137151

138-
const { ix, walletPda, vaultPda } = client.createWalletEd25519({
152+
// Just payer, wallet, signer, recipient, amount -- nothing else
153+
const { instructions } = await client.transferSol({
154+
payer: payer.publicKey,
155+
walletPda,
156+
signer: secp256r1(mySigner), // or ed25519(kp.publicKey) or session(sessionPda, sessionKp.publicKey)
157+
recipient,
158+
lamports: 1_000_000n,
159+
});
160+
```
161+
162+
### Execute Arbitrary Instructions
163+
164+
```typescript
165+
const [vault] = client.findVault(walletPda);
166+
const { instructions } = await client.execute({
139167
payer: payer.publicKey,
140-
userSeed,
141-
ownerPubkey: owner.publicKey,
168+
walletPda,
169+
signer: secp256r1(mySigner),
170+
instructions: [
171+
SystemProgram.transfer({ fromPubkey: vault, toPubkey: recipient, lamports: 500_000 }),
172+
],
142173
});
143174
```
144175

@@ -152,14 +183,14 @@ See [sdk/solita-client/README.md](sdk/solita-client/README.md) for full API refe
152183
# Start local validator with program loaded
153184
cd tests-sdk && npm run validator:start
154185

155-
# Run all 35 integration tests
186+
# Run all 56 tests (integration + security + permission + session)
156187
npm test
157188

158189
# Run CU benchmarks
159190
npm run benchmark
160191
```
161192

162-
Tests cover: wallet lifecycle, authority management, execute, deferred execution, sessions, replay protection, counter edge cases, and end-to-end workflows.
193+
Tests cover: wallet lifecycle, authority management, execute, deferred execution, sessions, replay protection, counter edge cases, end-to-end workflows, permission boundaries, session-based execution, and security attack vectors (reentrancy, cross-wallet isolation, accounts hash binding).
163194

164195
See [DEVELOPMENT.md](DEVELOPMENT.md) for full development workflow.
165196

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ Reports available in the `audits/` directory.
3939

4040
## Security Features
4141

42-
- Odometer counter replay protection (monotonic u64 per authority)
43-
- SlotHashes liveness window (150 slots)
42+
- Odometer counter replay protection (monotonic u32 per authority)
43+
- Clock-based slot freshness window (150 slots via `Clock::get()`)
4444
- CPI stack_height reentrancy prevention
4545
- Signature binding to payer, accounts, counter, and program_id
4646
- Self-removal and owner removal protection

docs/Architecture.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ For Secp256r1 Execute, the signed payload includes a SHA256 hash of all account
241241

242242
| Path | Inner Ix Capacity | Total CU | Tx Fee |
243243
|---|---|---|---|
244-
| Immediate Execute | ~574 bytes | 12,441 | 0.000005 SOL |
245-
| Deferred (2 txs) | ~1,100 bytes (1.9x) | 18,613 | 0.00001 SOL |
244+
| Immediate Execute | ~574 bytes | 9,441 | 0.000005 SOL |
245+
| Deferred (2 txs) | ~1,100 bytes (1.9x) | 15,613 | 0.00001 SOL |
246246

247247
### Security Properties
248248

@@ -303,10 +303,13 @@ sdk/solita-client/
303303
generated/ Solita-generated instructions, accounts, errors
304304
utils/
305305
instructions.ts Low-level instruction builders
306-
wrapper.ts LazorKitClient high-level API
306+
client.ts LazorKitClient high-level API (unified)
307+
types.ts Discriminated union signer types + helper constructors
308+
signing.ts Secp256r1 signing utilities
309+
compact.ts CompactInstruction layout builder
307310
pdas.ts PDA derivation helpers
308311
secp256r1.ts Challenge hash + auth payload builders
309312
packing.ts CompactInstruction packing
310313
errors.ts Error code mapping
311-
tests-sdk/ Integration tests (vitest, 35 tests)
314+
tests-sdk/ Integration + security tests (vitest, 56 tests)
312315
```

docs/Costs.md

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,65 @@
11
# LazorKit Cost Analysis
22

3-
This document provides comprehensive cost data for the LazorKit smart wallet program on Solana. All compute unit (CU) measurements are from real transactions against a local validator. Rent costs use Solana's standard formula.
3+
This document provides comprehensive cost data for the LazorKit smart wallet program on Solana. All compute unit (CU) measurements are from real transactions on devnet. Rent costs use Solana's standard formula.
44

55
> Program ID: `FLb7fyAtkfA4TSa2uYcAT8QKHd2pkoMHgmqfnXFXo7ao`
66
77
---
88

99
## Compute Units & Transaction Size
1010

11-
| Instruction | CU | Tx Size (bytes) | Ix Data (bytes) | Accounts | Instructions |
12-
|---|---|---|---|---|---|
13-
| Normal SOL Transfer (baseline) | 150 | 215 | 12 | 2 | 1 |
14-
| CreateWallet (Ed25519) | 15,187 | 408 | 73 | 6 | 1 |
15-
| CreateWallet (Secp256r1) | 13,687 | 453 | 118 | 6 | 1 |
16-
| AddAuthority (Ed25519 admin) | 5,846 | 473 | 41 | 7 | 1 |
17-
| Execute Secp256r1 (SOL transfer) | 12,441 | 658 | 254 | 7 | 2 |
18-
| Execute Session (SOL transfer) | 8,983 | 452 | 20 | 7 | 1 |
19-
| CreateSession (Ed25519) | 6,015 | 473 | 41 | 7 | 1 |
11+
| Instruction | CU | Tx Size (bytes) |
12+
|---|---|---|
13+
| Normal SOL Transfer (baseline) | 150 | 215 |
14+
| **CreateWallet** | | |
15+
| CreateWallet (Ed25519 owner) | 16,688 | 408 |
16+
| CreateWallet (Secp256r1 owner) | 13,688 | 454 |
17+
| **AddAuthority** | | |
18+
| AddAuthority (Ed25519 owner → Ed25519 admin) | 7,347 | 473 |
19+
| AddAuthority (Ed25519 admin → Ed25519 spender) | 5,853 | 473 |
20+
| AddAuthority (Secp256r1 owner → Ed25519 admin) | 11,621 | 679 |
21+
| AddAuthority (Secp256r1 owner → Secp256r1 spender) | 11,646 | 726 |
22+
| **Execute (SOL Transfer)** | | |
23+
| Execute (Ed25519 owner) | 5,864 | 452 |
24+
| Execute (Ed25519 spender) | 5,864 | 452 |
25+
| Execute (Secp256r1 owner) | 9,441 | 658 |
26+
| Execute (Secp256r1 spender) | 9,441 | 658 |
27+
| Execute (Session key) | 4,483–5,983 | 452 |
28+
| **CreateSession** | | |
29+
| CreateSession (Ed25519 admin) | 9,015 | 473 |
30+
| CreateSession (Secp256r1 admin) | 13,289 | 679 |
31+
| **RemoveAuthority** | | |
32+
| RemoveAuthority (Ed25519) | 621 | 368 |
33+
| RemoveAuthority (Secp256r1) | 4,691 | 574 |
34+
| **TransferOwnership** | | |
35+
| TransferOwnership (Ed25519 → Ed25519) | 5,872 | 466 |
36+
| TransferOwnership (Secp256r1 → Secp256r1) | 14,669 | 719 |
2037

2138
**Notes:**
22-
- CU values are from real transactions on a local validator
23-
- Secp256r1 Execute requires 2 instructions (precompile verification + execute)
24-
- Session Execute is cheaper -- only 1 instruction, no precompile, no auth payload
39+
- CU values are from real transactions on devnet
40+
- Secp256r1 operations require 2 instructions (precompile verification + program ix), increasing TX size by ~200 bytes
41+
- Session Execute is the cheapest auth path -- only 1 instruction, no precompile, no auth payload
2542
- All operations fit well within Solana's 200,000 CU default budget
2643
- Transaction sizes are well within Solana's 1,232-byte limit
44+
- RemoveAuthority refunds rent to a specified destination
2745

2846
### Deferred Execution (Large Payloads)
2947

3048
For operations exceeding the ~574 bytes available in a single Secp256r1 Execute transaction:
3149

3250
| Instruction | CU | Tx Size (bytes) | Accounts | Instructions |
3351
|---|---|---|---|---|
34-
| Authorize (TX1) | 11,709 | 705 | 7 | 2 |
35-
| ExecuteDeferred (TX2, 1 inner ix) | 6,904 | 356 | 7 | 1 |
52+
| Authorize (TX1) | 10,209 | 705 | 7 | 2 |
53+
| ExecuteDeferred (TX2, 1 inner ix) | 5,404 | 356 | 7 | 1 |
3654

3755
| Metric | Immediate Execute | Deferred (2 txs) |
3856
|---|---|---|
39-
| Total CU | 12,441 | 18,613 (11,709 + 6,904) |
57+
| Total CU | 9,441 | 15,613 (10,209 + 5,404) |
4058
| Inner Ix Capacity | ~574 bytes | ~1,100 bytes (1.9x) |
4159
| Tx Fee | 0.000005 SOL | 0.00001 SOL |
4260
| Temp Rent | -- | 0.002116 SOL (refunded) |
4361

44-
The deferred path trades ~50% more total CU and 2x the tx fee for 1.9x the inner instruction space. The DeferredExec account rent (0.002116 SOL) is temporary -- it is refunded when TX2 executes or when the payer reclaims an expired authorization.
62+
The deferred path trades ~66% more total CU and 2x the tx fee for 1.9x the inner instruction space. The DeferredExec account rent (0.002116 SOL) is temporary -- it is refunded when TX2 executes or when the payer reclaims an expired authorization.
4563

4664
---
4765

@@ -61,21 +79,22 @@ The Secp256r1 Execute transaction was optimized from **708 bytes to 658 bytes**
6179

6280
## LazorKit vs Normal SOL Transfer
6381

64-
| Metric | Normal Transfer | LazorKit Secp256r1 | LazorKit Session | Notes |
65-
|---|---|---|---|---|
66-
| Compute Units | 150 | 12,441 | 8,983 | Session uses Ed25519 signer (no precompile) |
67-
| Transaction Size | 215 bytes | 658 bytes | 452 bytes | Session tx is smaller (no precompile ix) |
68-
| Instruction Data | 12 bytes | 254 bytes | 20 bytes | Session has no auth payload |
69-
| Accounts | 2 | 7 | 7 | Secp256r1 uses sysvar_instructions only |
70-
| Instructions per Tx | 1 | 2 | 1 | Session needs only 1 instruction |
71-
| Transaction Fee | 0.000005 SOL | 0.000005 SOL | 0.000005 SOL | Same base fee |
82+
| Metric | Normal Transfer | LazorKit Secp256r1 | LazorKit Ed25519 | LazorKit Session | Notes |
83+
|---|---|---|---|---|---|
84+
| Compute Units | 150 | 9,441 | 5,864 | 4,483–5,983 | Session is cheapest auth path |
85+
| Transaction Size | 215 bytes | 658 bytes | 452 bytes | 452 bytes | Session tx is same as Ed25519 |
86+
| Instruction Data | 12 bytes | 254 bytes | 20 bytes | 20 bytes | Session has no auth payload |
87+
| Accounts | 2 | 7 | 7 | 7 | Secp256r1 uses sysvar_instructions |
88+
| Instructions per Tx | 1 | 2 | 1 | 1 | Only Secp256r1 needs precompile ix |
89+
| Transaction Fee | 0.000005 SOL | 0.000005 SOL | 0.000005 SOL | 0.000005 SOL | Same base fee |
7290

7391
**Why the overhead is acceptable:**
74-
- 12,441 CU (Secp256r1) is only **6.2%** of the 200,000 CU default budget
75-
- 8,983 CU (Session) is only **4.5%** of the 200,000 CU default budget
92+
- 9,441 CU (Secp256r1) is only **4.7%** of the 200,000 CU default budget
93+
- 5,864 CU (Ed25519) is only **2.9%** of the budget
94+
- 4,483–5,983 CU (Session) is only **2.2–3.0%** of the budget
7695
- 658 bytes (Secp256r1) is **53%** of the 1,232-byte transaction limit, leaving **574 bytes** for inner instructions
7796
- Deferred Execution provides ~1,100 bytes for inner instructions when needed (1.9x)
78-
- 452 bytes (Session) is only **37%** of the 1,232-byte limit
97+
- 452 bytes (Ed25519/Session) is only **37%** of the 1,232-byte limit
7998
- Transaction fee is identical (base fee is per-signature, not per-CU)
8099
- The overhead buys: passkey auth, RBAC, replay protection, session keys, multi-sig
81100

@@ -174,11 +193,8 @@ The compact data sizes are achieved through:
174193
## Reproducing These Numbers
175194

176195
```bash
177-
# Start local validator
178-
cd tests-sdk && npm run validator:start
179-
180-
# Run benchmarks
181-
npm run benchmark
196+
# Run devnet smoke test (all instructions, all auth types)
197+
cd tests-sdk && npx tsx tests/devnet-smoke.ts
182198
```
183199

184-
The benchmark script (`tests-sdk/tests/benchmark.ts`) sends real transactions and extracts CU consumption from transaction metadata.
200+
The devnet smoke test exercises all 9 instructions across all authority types (Ed25519, Secp256r1, Session) and roles (Owner, Admin, Spender), reporting CU consumption, TX size, and rent costs from real devnet transactions.

0 commit comments

Comments
 (0)