Status: 🟢 Ready for Execution Date: 2026-02-12 Target Network: Starknet Sepolia Testnet
# Required tools
- starkli (latest version)
- scarb 2.8.4
- snforge 0.33.0
- sncast (for deployments)
# Environment variables
export STARKNET_ACCOUNT=~/.starknet_accounts/deployer-account.json
export STARKNET_KEYSTORE=~/.starknet_accounts/deployer-keystore.json
export STARKNET_RPC=https://starknet-sepolia.public.blastapi.io/rpc/v0_7- Deployer Account: For deploying contracts (needs testnet ETH)
- Owner Account: Master key for SessionAccount
- Session Key Pair: Generated keypair for session key testing
Deploy or use existing Sepolia ERC-20s:
- Mock USDC (6 decimals)
- Mock WETH (18 decimals)
cd contracts/session-account
scarb buildExpected Output:
target/dev/session_account_SessionAccount.contract_class.json- Sierra class hash
- Compiled artifact ready for deployment
starkli declare \
target/dev/session_account_SessionAccount.contract_class.json \
--account $STARKNET_ACCOUNT \
--keystore $STARKNET_KEYSTORE \
--rpc $STARKNET_RPCExpected Output:
Class hash declared: 0x...
Transaction hash: 0x...
Save class hash to DEPLOYED_CONTRACTS.md
# Constructor: owner_pubkey (felt252)
OWNER_PUBKEY=0x123456789abcdef... # Your owner public key
starkli deploy \
<CLASS_HASH> \
$OWNER_PUBKEY \
--account $STARKNET_ACCOUNT \
--keystore $STARKNET_KEYSTORE \
--rpc $STARKNET_RPCExpected Output:
Contract deployed: 0x...
Transaction hash: 0x...
Save contract address to DEPLOYED_CONTRACTS.md
If needed, deploy test tokens with generous supply:
# Mock USDC (6 decimals, 1M supply)
starkli deploy <ERC20_CLASS_HASH> \
str:MockUSDC \
str:MUSDC \
u256:1000000000000 \
<YOUR_ADDRESS> \
--account $STARKNET_ACCOUNT
# Mock WETH (18 decimals, 1K supply)
starkli deploy <ERC20_CLASS_HASH> \
str:MockWETH \
str:MWETH \
u256:1000000000000000000000 \
<YOUR_ADDRESS> \
--account $STARKNET_ACCOUNTSave token addresses to DEPLOYED_CONTRACTS.md
# Generate session keypair
starkli signer gen-keypair
# Save private key securely
# PUBLIC_KEY: 0x...
# Add session key to account
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
add_or_update_session_key \
<SESSION_PUBLIC_KEY> \
u64:$(date -d '+7 days' +%s) \
u32:100 \
array:1:0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e \
--account $STARKNET_ACCOUNTExpected Result: ✅ Transaction succeeds
Verify: Call get_session_key_status(<SESSION_PUBLIC_KEY>)
# Policy: 1000 USDC per call, 5000 USDC per 24h window
# USDC has 6 decimals: 1000 = 1000000000
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
set_spending_policy \
<SESSION_PUBLIC_KEY> \
<MOCK_USDC_ADDRESS> \
u256:1000000000 \
u256:5000000000 \
u64:86400 \
--account $STARKNET_ACCOUNTExpected Result: ✅ Transaction succeeds, SpendingPolicySet event emitted
Verify: Call get_spending_policy(<SESSION_PUBLIC_KEY>, <USDC_ADDRESS>)
# Transfer 500 USDC (within 1000 per-call limit)
# Recipient: any address
RECIPIENT=0x...
# Sign with SESSION_KEY using session key signature format
# Signature: [session_pubkey, r_low, r_high, valid_until]
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
__execute__ \
array:1 \
struct:<MOCK_USDC_ADDRESS>:0x83afd...12e:array:3:<RECIPIENT>:500000000:0 \
--account <SESSION_KEY_ACCOUNT> \
--keystore <SESSION_KEY_KEYSTORE>Expected Result: ✅ Transfer succeeds Verify:
- USDC balance decreased by 500000000
get_spending_policy()showsspent_in_window = 500000000
# Transfer 1: 500 USDC
# Transfer 2: 1000 USDC
# Transfer 3: 2000 USDC
# Total: 3500 USDC < 5000 window limit ✅
# Execute transfers sequentially (use script for automation)Expected Result: ✅ All 3 transfers succeed
Verify: spent_in_window = 3500000000
# Advance time by 24h + 1 second
# In testnet, either wait or use block timestamp tricks
# After 24h+1s, execute transfer
# Transfer 4: 1000 USDC
starkli invoke <SESSION_ACCOUNT_ADDRESS> __execute__ ...Expected Result: ✅ Transfer succeeds after window reset
Verify: spent_in_window = 1000000000 (reset to only this transfer)
# Try to transfer 1500 USDC (> 1000 limit)
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
__execute__ \
array:1 \
struct:<USDC>:0x83afd...:array:3:<RECIPIENT>:1500000000:0 \
--account <SESSION_KEY_ACCOUNT>Expected Result: ❌ Transaction fails with "Spending: exceeds per-call"
Verify: Balance unchanged, spent_in_window unchanged
# After Test 2.4 (spent = 3500), try to transfer 2000 more
# 3500 + 2000 = 5500 > 5000 window limit
starkli invoke <SESSION_ACCOUNT_ADDRESS> __execute__ ...Expected Result: ❌ Transaction fails with "Spending: exceeds window limit"
Verify: spent_in_window still 3500000000
# Try to call set_spending_policy from session key
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
set_spending_policy \
<SESSION_PUBLIC_KEY> \
<USDC> \
u256:9999999 \
u256:9999999 \
u64:1 \
--account <SESSION_KEY_ACCOUNT>Expected Result: ❌ Transaction fails (blocklist rejection) Verify: Policy unchanged
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
remove_spending_policy \
<SESSION_PUBLIC_KEY> \
<USDC> \
--account <SESSION_KEY_ACCOUNT>Expected Result: ❌ Transaction fails (blocklist rejection) Verify: Policy still active
# Scenario: Spend at exact window_start + 86400 seconds
# 1. Note current window_start from get_spending_policy
# 2. Wait until exactly window_start + 86400
# 3. Transfer max_per_window (5000 USDC)
# 4. Try to transfer again at same timestamp
# Expected: First succeeds, second fails (window NOT reset yet)Expected Result:
- ✅ First transfer at boundary succeeds
- ❌ Second transfer at boundary fails (window not reset)
- ✅ Third transfer at boundary+1s succeeds (window resets)
Verifies: Critical fix V1 (>= changed to >)
# Execute multicall with 5 transfers of 500 each
# Total: 2500 USDC in single transaction
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
__execute__ \
array:5 \
struct:<USDC>:0x83afd...:array:3:<ADDR1>:500000000:0 \
struct:<USDC>:0x83afd...:array:3:<ADDR2>:500000000:0 \
struct:<USDC>:0x83afd...:array:3:<ADDR3>:500000000:0 \
struct:<USDC>:0x83afd...:array:3:<ADDR4>:500000000:0 \
struct:<USDC>:0x83afd...:array:3:<ADDR5>:500000000:0 \
--account <SESSION_KEY_ACCOUNT>Expected Result: ✅ All 5 transfers succeed, spent_in_window = 2500000000
Verifies: Multicall cumulative tracking works
# Transfer exactly 1000 USDC (max_per_call)
# Transfer exactly 5000 USDC total (max_per_window)
starkli invoke <SESSION_ACCOUNT_ADDRESS> __execute__ ...Expected Result: ✅ Succeeds (boundary inclusive: amount <= limit) Verifies: Exact limit transfers allowed
# Call balanceOf on USDC (non-spending selector)
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
__execute__ \
array:1 \
struct:<USDC>:0x2e4263afad...8dc:array:1:<SESSION_ACCOUNT> \
--account <SESSION_KEY_ACCOUNT>Expected Result: ✅ Succeeds without affecting spent_in_window
Verifies: Non-spending selectors ignored
# Call approve(spender, amount) on USDC
# approve selector: 0x219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
__execute__ \
array:1 \
struct:<USDC>:0x219209e...:array:3:<SPENDER>:1000000000:0 \
--account <SESSION_KEY_ACCOUNT>Expected Result: ✅ Succeeds, spent_in_window increases by 1000000000
Verifies: Approve tracked as spending
# Owner removes spending policy
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
remove_spending_policy \
<SESSION_PUBLIC_KEY> \
<USDC> \
--account $STARKNET_ACCOUNTExpected Result: ✅ Policy removed, SpendingPolicyRemoved event
Verify: get_spending_policy() returns all zeros
# Transfer large amount (>5000 USDC) after policy removed
starkli invoke <SESSION_ACCOUNT_ADDRESS> __execute__ \
array:1 \
struct:<USDC>:0x83afd...:array:3:<RECIPIENT>:10000000000:0 \
--account <SESSION_KEY_ACCOUNT>Expected Result: ✅ Large transfer succeeds (no policy enforcement) Verifies: Policy removal works correctly
# Owner updates policy with higher limits
# New: 2000 per call, 10000 per window
starkli invoke \
<SESSION_ACCOUNT_ADDRESS> \
set_spending_policy \
<SESSION_PUBLIC_KEY> \
<USDC> \
u256:2000000000 \
u256:10000000000 \
u64:86400 \
--account $STARKNET_ACCOUNTExpected Result: ✅ Policy updated
Verify: get_spending_policy() reflects new limits
# Set separate policies for USDC and WETH
# USDC: 1000/5000/24h
# WETH: 0.5/2/24h
starkli invoke <SESSION_ACCOUNT_ADDRESS> \
set_spending_policy <SESSION_KEY> <USDC> ...
starkli invoke <SESSION_ACCOUNT_ADDRESS> \
set_spending_policy <SESSION_KEY> <WETH> ...Expected Result: ✅ Both policies set independently Verify: Transfers of each token tracked separately
# Execute 100 transactions over 1 hour
# Each transfer: 50 USDC (well within limits)
# Target: ~1.67 tx/minute sustained
# Use automation script
./scripts/load_test.sh \
--account <SESSION_ACCOUNT> \
--token <USDC> \
--amount 50000000 \
--count 100 \
--duration 3600Expected Results:
- ✅ All 100 transactions succeed
- ✅ Cumulative tracking accurate (5000000000 total)
- ✅ No state corruption
- ✅ Gas costs consistent
Metrics to Track:
- Transaction success rate
- Average confirmation time
- Gas usage per transaction
- Policy state consistency
On-Chain State:
# Query spending state every 5 minutes
watch -n 300 'starkli call <SESSION_ACCOUNT> get_spending_policy <SESSION_KEY> <USDC>'Event Monitoring:
# Monitor SpendingPolicySet and SpendingPolicyRemoved events
starkli events <SESSION_ACCOUNT> --from-block <START_BLOCK>Balance Tracking:
# Track USDC balance changes
starkli call <USDC> balanceOf <SESSION_ACCOUNT>- Total spending per token
- Spending rate (tokens/hour)
- Time until window reset
- Policy update history
- Window boundary double-spend → BLOCKED ✅
- Same-block spending bypass → BLOCKED ✅
- Reentrancy attack → PROTECTED ✅
- Overflow attack → PREVENTED ✅
- Admin function bypass → BLOCKED ✅
-
transferFromnot tracked (requires approval first) → DOCUMENTED ✅ - Failed calls count against limit → FAIL-CLOSED ✅
- Zero policy disables enforcement → BY DESIGN ✅
If issues found during E2E:
- Stop all testing immediately
- Document exact reproduction steps
- Analyze root cause in code
- Fix and re-run unit tests
- Re-deploy and re-test affected scenarios
- Update security audit with findings
- All 18 happy path tests succeed
- All 4 failure path tests correctly reject
- All 5 edge case tests behave as expected
- All 4 policy management tests work
- Load test completes with 100% success rate
- No bypasses found in attack simulations
- Known limitations verified and documented
- State consistency maintained under load
- All test results documented in
E2E_TEST_RESULTS.md - Deployment addresses saved in
DEPLOYED_CONTRACTS.md - Gas usage metrics recorded
- Known issues (if any) documented with mitigations
- Review Results with security team
- Final Security Sign-Off from all stakeholders
- Mainnet Deployment Planning
- User Documentation (guides, examples, best practices)
- MCP Tools Integration (spending policy management via MCP)
Document Version: 1.0 Last Updated: 2026-02-12 Status: Ready for Execution