Stellar Community Fund — Build Award · Integration Track

Excalibur Soroban PvP Settlement
Technical Architecture

Project Excalibur — Competitive PvP Arena
Submitter Chanoirs ASBL (Belgium)
Live product launch.excalibur.game (currently on Tezos with USDC)
Migration target Stellar / Soroban mainnet, aligned with Korea Blockchain Week (Oct 1, 2026)
Document version v1.0 — for SCF reviewer evaluation

Excalibur is a competitive PvP gaming arena where two players stake USDC or EURC and fight in real-time matches. Every kill resolves into an atomic transfer from loser to winner. Outcomes are deterministic and skill-based. This document specifies how the existing Tezos implementation migrates to Stellar/Soroban, what gets built, what gets integrated, and how on-chain activity is made verifiable to reviewers.

Why Stellar specifically. A single PvP match generates 10 to 30 micro-payouts (kills + final settlement). Sub-cent fees + 5-second finality + native USDC/EURC make this unit-positive. EVM gas would destroy the unit economics; centralized card rails impose 3-7% fees and payout trust risk; Tezos works but has no MiCA-compliant EURC for the European audience. Stellar is the only chain where the constraints align.

1System overview

The architecture combines an offchain low-latency match server (preserves in-game UX) with an on-chain Soroban settlement layer (preserves trust and verifiability). Kill events are signed offchain by the game server during the match and batched to Soroban every 5 seconds.

┌───────────────────────────────────────────────────────────────────────────────┐ │ EXCALIBUR CLIENT (UNITY) │ │ │ │ ┌─────────────────────────┐ ┌─────────────────────────────────────────┐ │ │ │ Gameplay Loop │ │ Wallet Layer │ │ │ │ (PvP combat, kills) │ │ ├ Stellar Wallets Kit │ │ │ └─────────────────────────┘ │ │ (Freighter, Albedo, LOBSTR, xBull) │ │ │ │ └ Privy embedded wallets │ │ │ │ (email / Google → custodial Stellar) │ │ │ └─────────────────────────────────────────┘ │ └──────────┬───────────────────────────────────────────────┬────────────────────┘ │ kill events │ deposit / withdraw │ (signed match state) │ transactions ▼ ▼ ┌──────────────────────────────┐ ┌──────────────────────────────┐ │ EXCALIBUR GAME SERVER │ │ STELLAR / SOROBAN │ │ (offchain, low-latency) │ │ │ │ │ │ ┌───────────────────────┐ │ │ ├ match orchestration │ │ │ MatchEscrow.rs │ │ │ ├ anti-cheat │ signed kill │ │ (Soroban contract) │ │ │ ├ kill receipt signing │───batches────►│ │ │ │ │ │ (Ed25519, every 5s) │ (Horizon) │ │ ├ init() │ │ │ └ dispute window manager │ │ │ ├ deposit() │ │ │ │◄─verification─│ │ ├ resolve_kill() │ │ │ │ results │ │ ├ finalize_match() │ │ └──────────────────────────────┘ │ │ └ timeout_refund() │ │ │ └──────────┬────────────┘ │ │ │ Atomic Splits │ │ ▼ │ │ ┌───────────────────────┐ │ │ │ Stellar Asset Contract│ │ │ │ USDC · EURC │ │ │ └───────────────────────┘ │ └────────────┬─────────────────┘ │ ┌─────────────────────────────┴────────────────┐ │ │ ┌─────────▼──────────┐ ┌───────────▼──────────┐ │ Stellar Broker │ │ Anchor Platform │ │ (multi-asset │ │ (fiat → USDC ramp, │ │ funding layer) │ │ EU and KR rails) │ └────────────────────┘ └──────────────────────┘

2What we build vs what we integrate

Reviewers consistently ask for this split, so we make it explicit upfront. The core innovation is on the BUILD side. The INTEGRATE side extends the user-facing surface using existing Stellar building blocks.

LayerComponentModeOwner
SettlementMatchEscrow.rs Soroban contractBUILD (Rust, MIT)Thomas + Briac
SettlementGame server kill batching serviceBUILD (Rust/Go)Briac
SettlementUnity Stellar SDKBUILD (C#)Sam + Thomas
WalletsStellar Wallets Kit (Freighter, Albedo, LOBSTR, xBull)INTEGRATESam
WalletsPrivy embedded wallets (email / Google)INTEGRATESam
AssetsUSDC + EURC via Stellar Asset ContractINTEGRATEThomas
FundingStellar Broker (multi-asset funding)INTEGRATEThomas
FundingAnchor Platform (fiat → USDC ramp)INTEGRATEThomas + Titou

3MatchEscrow.rs — Soroban contract

The core innovation. A single contract holds both players' stakes for the duration of a match, resolves kills as atomic transfers between participants, and finalizes the match with a 5% protocol fee. Open-source under MIT license; reusable by any other PvP gaming project on Stellar.

3.1 Contract storage

// MatchEscrow.rs — storage layout (simplified)

pub struct Match {
    pub match_id: BytesN<32>,
    pub player_a: Address,
    pub player_b: Address,
    pub asset: Address,           // USDC SAC or EURC SAC
    pub stake_per_player: i128,   // e.g. 10_000_000 = 10 USDC (7 decimals)
    pub server_pubkey: BytesN<32>,// Ed25519 pubkey of authorized game server
    pub state: MatchState,        // Pending | Active | Settling | Closed
    pub balance_a: i128,
    pub balance_b: i128,
    pub created_at: u64,
    pub last_activity: u64,
    pub timeout_seconds: u64,     // default 600s
}

pub enum MatchState {
    Pending,    // contract created, awaiting deposits
    Active,     // both players deposited, match in progress
    Settling,   // server submitted final state, dispute window open
    Closed,     // funds released to winner + protocol
}

3.2 Public API

impl MatchEscrow {

    // Create a new match. Anyone can call; both players must subsequently deposit.
    pub fn init(
        env: Env,
        match_id: BytesN<32>,
        player_a: Address,
        player_b: Address,
        asset: Address,
        stake_per_player: i128,
        server_pubkey: BytesN<32>,
    ) -> Match;

    // Player deposits their stake. Requires player.require_auth().
    pub fn deposit(env: Env, match_id: BytesN<32>, player: Address);

    // Server submits a batch of signed kill events.
    // Each event: (loser, winner, amount, nonce, signature).
    // Verified on-chain against server_pubkey (Ed25519).
    pub fn submit_kill_batch(
        env: Env,
        match_id: BytesN<32>,
        events: Vec<KillEvent>,
    );

    // Server signals match end. Opens a dispute window of N seconds.
    pub fn finalize_match(
        env: Env,
        match_id: BytesN<32>,
        final_state_signature: BytesN<64>,
    );

    // After dispute window: protocol fee (5%) sent to treasury,
    // remaining balance to winner. Atomic Splits primitive used.
    pub fn settle(env: Env, match_id: BytesN<32>);

    // Either player can claim if server abandons match (after timeout).
    // Refunds via Claimable Balance to avoid contract holding stale funds.
    pub fn timeout_refund(env: Env, match_id: BytesN<32>);

    // Player disputes a specific kill event during the dispute window.
    // Requires proof of out-of-order nonce or invalid signature.
    pub fn dispute(env: Env, match_id: BytesN<32>, evidence: Bytes);
}

3.3 Why Soroban primitives, specifically

Atomic Splits

Native multi-recipient atomic payout. One settle() call pushes funds to winner + protocol treasury in one tx, with one fee. No two-step pattern, no failure recovery code needed.

Claimable Balance

Used for timeout_refund(). If the server abandons a match, players can claim their stake back without the contract holding stale state. The Claimable Balance is the refund vehicle.

Stellar Asset Contract

USDC and EURC both expose a SAC interface on Stellar. MatchEscrow.rs treats them identically — same contract handles both assets via the asset address parameter.

require_auth() + Ed25519 verification

Player authorizations use require_auth() (native Soroban). Server signatures use BytesN<64> Ed25519 verified onchain via env.crypto().ed25519_verify().

4Kill batching: hybrid optimistic UI + on-chain settlement

The most-asked question: how can kills be onchain without breaking gameplay latency? Answer: kills are signed offchain by the game server in <5ms (Ed25519), shown to the player immediately (optimistic UI), then batched to Soroban every 5 seconds. From the player's perspective the kill is instant. From the chain's perspective the kill is final within 10 seconds.

4.1 Sequence — single kill

Player A Player B Game Server Soroban MatchEscrow │ │ │ │ │── shoots ──►│ │ │ │ X dies │ │ │ │── kill event ──►│ │ │ │ │ validate │ │ │ │ (hitreg, anti-cheat)│ │ │ │ │ │◄── XP, sound, instant feedback (optimistic UI) │ │ │ │ │ │ │ │ buffer kill │ │ │ │ (batch of N) │ │ │ │ │ │ │ │ every 5s, sign & │ │ │ │ submit batch ─────►│ │ │ │ │ │ │ │ │ verify Ed25519 │ │ │ │ verify nonces strict-monotonic │ │ │ │ apply atomic transfer │ │ │ │ update balance_a / balance_b │ │ │ │ │ │ │◄──── batch ack ─────│ │ │ │ │

4.2 Why this preserves trust

5End-to-end match flow

Reviewers requested an explicit progression from prototype to live mainnet. Here it is.

[1] Player A and Player B agree to a match │ via lobby UI (existing Excalibur client) ▼ [2] Client calls MatchEscrow.init() │ specifies asset (USDC or EURC), stake amount, server_pubkey │ contract emits MatchCreated event ▼ [3] Both players call deposit() with require_auth() │ wallet signing via Stellar Wallets Kit OR Privy embedded │ contract state → Active ▼ [4] Match begins (gameplay runs in Unity client) │ kills tracked by game server, signed Ed25519, shown to players │ every 5s: server batches signed kills → submit_kill_batch() ▼ [5] Match ends (one player out of stake OR time limit) │ server calls finalize_match() with final state signature │ contract state → Settling, dispute window opens (60s) ▼ [6] After dispute window: │ anyone calls settle() │ contract performs Atomic Split: │ - 95% of winner's balance → winner │ - 5% → protocol treasury │ contract state → Closed ▼ [7] Winner withdraws to wallet (or starts next match with rolled stake)

6Wallet integration paths

Two paths cover the two distinct audiences: crypto-native players (already hold a Stellar wallet) and mainstream gamers (no crypto experience).

6.1 Crypto-native — Stellar Wallets Kit

// Unity-side, C# wrapper over @creit.tech/stellar-wallets-kit (Web)
//   exposed to Unity via a WebView bridge.

var kit = new StellarWalletsKit({
  network: WalletNetwork.PUBLIC,
  selectedWalletId: XBULL_ID,   // or Freighter, Albedo, LOBSTR
  modules: allowAllModules()
});

await kit.openModal({
  onWalletSelected: async (option) => {
    await kit.setWallet(option.id);
    var { address } = await kit.getAddress();
    excaliburClient.setPlayerWallet(address);
  }
});

6.2 Mainstream gamers — Privy embedded wallets

// Sign-in with email or Google → Privy provisions custodial Stellar wallet
//   transparently. Player never sees a seed phrase.

import { PrivyProvider } from '@privy-io/react-auth';

<PrivyProvider
  appId="excalibur"
  config={{
    embeddedWallets: { createOnLogin: 'users-without-wallets' },
    supportedChains: [stellarMainnet],
    loginMethods: ['email', 'google']
  }}>
  <ExcaliburApp />
</PrivyProvider>

For Privy users, the deposit() call still goes through the embedded wallet — the user authorizes the spend via their Privy session, and the SDK signs and submits the Soroban transaction.

7Funding flows

Three funding paths cover all entry points.

PathUser profileStellar building block
Direct USDC depositCrypto-native with existing USDC on StellarStellar Asset Contract (USDC SAC)
Multi-asset depositCrypto-native with XLM or other Stellar assetsStellar Broker (converts on-the-fly to USDC/EURC; contract still receives the settlement asset)
Fiat to USDCMainstream gamer paying with card or SEPAAnchor Platform (EUR/KRW → USDC); for EU users, EURC variant for MiCA compliance

Critically, the MatchEscrow contract logic does not care which path funded the deposit. The settlement asset is always USDC or EURC. The funding layer is decoupled from the contract layer.

8Verifiability checklist for reviewers

Each tranche delivers a specific set of reviewer-verifiable artifacts. We list them here so reviewers know exactly what to look at when they evaluate each milestone.

TrancheVerifiable artifacts
MVP (T1) • Public GitHub repo (MIT license)
• MatchEscrow.rs source + Rust unit test suite passing
• Contract compiled and deployed to local Soroban testnet
• Unity SDK published on GitHub
• Server batching service code public
Testnet (T2) • Public testnet contract address
• Explorer transaction links for: init, deposit, kill batches, finalize, settle
• Video demo of full match flow on testnet
• Stellar Wallets Kit integration screen recordings
• Privy onboarding flow screen recording
Mainnet (T3) • Public mainnet contract address
• Mainnet transaction links for USDC and EURC matches
• Stellar Broker funding transaction links
• Anchor Platform fiat ramp transaction links
• Mainnet activity dashboard (live during KBW)
• Post-launch technical report (volume, gas, latency)

9Stack summary

LayerTech
Smart contractRust + Soroban SDK
Game serverRust (axum) or Go — Ed25519 signing, kill batching, dispute manager
IndexerSoroban RPC + custom event consumer (PostgreSQL)
Unity clientUnity 2022 LTS + custom Stellar SDK (C#) + WebView bridge for SWK
Wallet (web)Stellar Wallets Kit (Freighter, Albedo, LOBSTR, xBull)
Wallet (embedded)Privy with Stellar support
AssetsUSDC (Stellar Asset Contract), EURC (Stellar Asset Contract)
FundingStellar Broker (multi-asset) · Anchor Platform (fiat ramp)
InfraCloudflare for static, AWS for game server cluster, Stellar RPC (Validation Cloud or self-hosted)
Open-source licenseMIT (contract + SDK + batching service)

10Why this matters for Stellar