Tap Battle - Mobile-First Competitive Tapping Game

Other Transitional
authsession-authecs

Transitional example: This example uses an older Cougr pattern and is preserved for compatibility reference. For the current recommended approach, see snake.

Build Status Tests Stellar

A competitive tapping game implemented as a Soroban smart contract using cougr-core's ECS framework with passkey authentication (secp256r1/WebAuthn) on the Stellar blockchain.

feat/session-manager-fresh

Related example: For the minimal session-only flow without passkeys or game mechanics, see session_arena.

🔐 Why Passkeys?

Why Passkeys?

Traditional blockchain games require seed phrases and per-transaction wallet prompts. Tap Battle eliminates both:

TraditionalWith Passkeys
Write down 24-word seed phraseTap Face ID / Touch ID
Approve every transaction in walletSession key handles gameplay
Complex key managementBrowser/OS manages credentials
Crypto-native onlyAnyone can play

Passkeys use the secp256r1 curve (the same as WebAuthn/FIDO2), enabling biometric authentication natively on mobile devices without any crypto knowledge.


Mobile-First Authentication Flow

┌─────────────────────────────────────────────────────────┐
│ 1. REGISTRATION                                         │
│    Player registers passkey (secp256r1 public key)       │
│    → Secp256r1Storage persists the key on-chain          │
│    → No seed phrases, no mnemonics                       │
├─────────────────────────────────────────────────────────┤
│ 2. AUTHENTICATION + SESSION                              │
│    Player authenticates via passkey (Face ID / Touch ID)  │
│    → verify_secp256r1() validates the signature           │
│    → SessionManager::approve creates a gameplay session   │
│    → Session scoped to: tap, use_power_up                 │
├─────────────────────────────────────────────────────────┤
│ 3. GAMEPLAY (gasless via session key)                     │
│    Rapid tapping → tap(session_key) increments counter    │
│    Combos: consecutive taps within time window = multi    │
│    Power-ups: spend combo charges for burst effects       │
│    Round ends after N ledger sequences                    │
├─────────────────────────────────────────────────────────┤
│ 4. RENEWAL / FALLBACK                                     │
│    session_status → UI prompts when needs_renewal         │
│    renew_session → owner re-approves to extend play       │
│    fallback_tap → direct owner auth when session expires  │
├─────────────────────────────────────────────────────────┤
│ 5. RESULT                                                │
│    Compare scores → winner determined                    │
│    Stats recorded on-chain in PlayerProfile               │
└─────────────────────────────────────────────────────────┘

Cougr-Core Integration

This example showcases two key cougr-core features:

Passkey Authentication (secp256r1_auth)

#![allow(unused)]
fn main() {
use cougr_core::auth::{Secp256r1Key, Secp256r1Storage, verify_secp256r1};

// Register: store public key on-chain
Secp256r1Storage::store(&env, &player, &key);

// Authenticate: verify biometric signature
verify_secp256r1(&env, &pubkey, &message, &signature)?;
}

SessionManager Lifecycle

#![allow(unused)]
fn main() {
use cougr_core::accounts::SessionBuilder;
use cougr_core::session::SessionManager;

// 1. Approve once (after passkey auth)
let scope = SessionBuilder::new(&env)
    .allow_action(symbol_short!("tap"))
    .allow_action(symbol_short!("use_power"))
    .max_operations(500)
    .expires_in(duration)  // seconds from ledger timestamp
    .build_scope();
SessionManager::approve(&env, &player, scope)?;

// 2. Play many times without wallet prompts
SessionManager::execute_action(&env, &player, &session, action, intent_expires)?;

// 3. Poll health for renewal UI
let status = SessionManager::status(&env, &player, &key_id)?;

// 4. Renew before expiry (owner must require_auth)
SessionManager::renew(&env, &owner, &key_id, new_expires_at)?;

// 5. Fallback when session is stale
SessionManager::fallback_execute(&env, &session_intent, &direct_intent)?;
}

Sessions are managed by cougr_core::session::SessionManager - the same pattern as session_arena.

Duration semantics: Session expires_in / expires_at use ledger timestamps (seconds), not ledger sequence numbers. Round duration (start_round) still uses ledger sequences for combo timing.

ECS Components

ComponentFieldsPurpose
PasskeyIdentitypubkey, registered_atPlayer's passkey credential
TapCountercount, combo, multiplier, last_tap_ledgerTap tracking per round
PowerUpcharges, kindCharged abilities from combos
RoundStatestarted_at, duration, scores, finishedMatch state
PlayerProfiletotal_wins, total_taps, best_comboPersistent stats

Session state lives in SessionManager / SessionStorage - not as a custom ECS component.


Contract API

Authentication

FunctionParametersReturnsDescription
register_passkeyplayer, pubkey: BytesN<65>-Register a secp256r1 passkey
authenticate_and_start_sessionplayer, signature, challenge, durationAddressAuth + create session

Session Management

FunctionParametersReturnsDescription
session_statusowner, key_idSessionStatusRemaining ops, expiry, renewal hint
renew_sessionowner, key_id, expires_inActiveSessionExtend session (owner re-approves)
fallback_tapowner, key_idTapResultTap via session or direct auth fallback
get_latest_session_keyplayerBytesN<32>Latest session key ID for a player

Gameplay

FunctionParametersReturnsDescription
tapsession_keyTapResultSubmit a tap (gasless)
use_power_upsession_key, power_up: u32-Activate a power-up
start_roundplayer_a, player_b, duration-Start a match
get_round-RoundStateGet round state (auto-finalizes)
get_profileplayerPlayerProfileGet persistent stats

Power-Up Types

IDNameEffect
0DoubleTap+10 bonus points
1ShieldDefensive (blocks opponent effects)
2Burst+25 bonus points

Game Mechanics

Combo System

Taps within 5 ledger sequences of each other maintain a combo streak:

ComboMultiplierEffect
11xNormal tap
2-42-4xBuilding combo
55xEarns a power-up charge
10+10x (max)Maximum multiplier

Game Constants

ConstantValueDescription
COMBO_WINDOW5 ledgersTime window for combo
MAX_MULTIPLIER10Maximum score multiplier
COMBO_CHARGE_THRESHOLD5Combos to earn a power-up charge
DOUBLE_TAP_BONUS10DoubleTap power-up bonus
BURST_BONUS25Burst power-up bonus
DEFAULT_MAX_OPS500Default session operations

️ Quick Start

Prerequisites

ToolVersionInstallation
Rust1.88.0+rustup.rs
Stellar CLILatestStellar Docs
WASM Target-rustup target add wasm32v1-none

Build

# Standard Rust build
cargo build

# Build WASM for Soroban deployment
stellar contract build

Test

cargo test

Test Results: 17 tests passing ✅

TestDescription
test_register_passkeyPasskey registration stores key on-chain
test_register_multiple_playersMultiple players register independently
test_session_creationSession created and validated
test_session_ops_decrementOperations decremented per action
test_session_ops_exhaustedSession stops after ops limit
test_tap_incrementsTap counter increments correctly
test_combo_streakCombo streak with multiplier
test_multiplier_capMultiplier capped at MAX_MULTIPLIER
test_power_up_charge_and_useCombo charges power-ups
test_power_up_no_chargesNo activation without charges
test_start_roundRound initializes correctly
test_start_round_no_passkeyRound requires registered passkeys
test_round_scoring_and_winnerScores tracked per player
test_profile_statsProfile tracks taps and combos
test_default_profileDefault profile for new players
test_renew_session_extends_play_windowRenew extends absolute expiry
test_fallback_tap_uses_direct_auth_after_session_expiresFallback auth after expiry

Project Structure

examples/tap_battle/
├── Cargo.toml          # Dependencies: cougr-core 1.1.0 + soroban-sdk
├── README.md           # This documentation
└── src/
    ├── lib.rs          # Contract entry points
    ├── types.rs        # ECS Components (PasskeyIdentity, TapCounter, etc.)
    ├── auth.rs         # AuthSystem + SessionSystem (secp256r1 + SessionBuilder)
    ├── game.rs         # TapSystem, ComboSystem, PowerUpSystem, RoundSystem
    └── test.rs         # Unit tests (17 tests)

License

MIT OR Apache-2.0