Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"contracts/game_contract",
"contracts/emergency_circuit_breaker",
"contracts/ai_nft",
"contracts/gasless_relayer",
]
resolver = "2"

Expand Down
1 change: 1 addition & 0 deletions contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"game_contract",
"emergency_circuit_breaker",
"ai_nft",
"gasless_relayer",
"title_badge",
"referral_splitter",
"model_attestation",
Expand Down
26 changes: 26 additions & 0 deletions contracts/gasless_relayer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "gasless_relayer"
version = "0.1.0"
edition = "2021"
publish = false

[features]
testutils = ["soroban-sdk/testutils"]

[dependencies]
soroban-sdk = "21.0.0"

[dev-dependencies]
soroban-sdk = { version = "21.0.0", features = ["testutils"] }
ed25519-dalek = { version = "2", features = ["rand_core"] }
rand = "0.8"

[lib]
crate-type = ["cdylib"]
doctest = false

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
overflow-checks = true
82 changes: 82 additions & 0 deletions contracts/gasless_relayer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Gasless Meta-Transaction Relayer Contract (#1148 SC-48)

A Soroban smart contract providing a secure, high-throughput gasless meta-transaction forwarder and match staking relayer for KnightVerse.

## Overview

Web2 chess players often do not hold native XLM for transaction fees. The `GaslessRelayer` contract allows players to sign match stakes, moves, and contract calls off-chain with an Ed25519 keypair. Sponsored relayers submit these transactions on-chain, paying network gas fees on behalf of the player.

## Key Features

1. **Nonce-Based Replay Protection**:
- Monotonic sequential nonces tracked per user address (`DataKey::UserNonce(Address)`).
- Prevents duplicate execution of captured or intercepted transactions.
- `bump_nonce(user)` allows players to revoke/invalidate any pending off-chain signed meta-transactions.

2. **EIP-712 / SEP Style Structured Typed Data Hashing**:
- Domain separator incorporates contract address and network passphrase hash (`\x19\x01` prefix).
- Protects against cross-contract and cross-network replay attacks.
- Cryptographic verification via Stellar native `env.crypto().ed25519_verify(...)`.

3. **Gasless Match Staking & Escrow**:
- Web2 players sign off-chain match creation (`is_creator = true`) and match joining (`is_creator = false`).
- Tokens are pulled from the player's approved allowance into contract escrow.
- Match lifecycle: `Created` → `Active` → `Settled` (or `Cancelled`).
- Settle match disburses prize pot to winner (or 50/50 split on draw).

4. **Generic Meta-Transaction Forwarding & Batching**:
- `execute_meta_transaction` forwards arbitrary contract calls.
- `execute_meta_tx_batch` executes multiple meta-transactions in a single invocation.

5. **Relayer Access & Governance**:
- Supports permissionless (`open_relayers = true`) and whitelisted relayer policies.
- Emergency circuit breaker pausing (`pause` / `unpause`).

## Contract Interface

### Gasless Match Staking
```rust
pub fn gasless_stake_match(
env: Env,
relayer: Address,
request: GaslessMatchStakeRequest,
signer_pubkey: BytesN<32>,
signature: BytesN<64>,
) -> Result<(), RelayerError>;
```

### Generic Meta-Transaction Forwarding
```rust
pub fn execute_meta_transaction(
env: Env,
relayer: Address,
request: ForwardRequest,
signer_pubkey: BytesN<32>,
signature: BytesN<64>,
) -> Result<Val, RelayerError>;
```

### Batch Meta-Transactions
```rust
pub fn execute_meta_tx_batch(
env: Env,
relayer: Address,
requests: Vec<ForwardRequest>,
signer_pubkeys: Vec<BytesN<32>>,
signatures: Vec<BytesN<64>>,
) -> Result<Vec<Val>, RelayerError>;
```

### Nonce & Key Management
```rust
pub fn get_nonce(env: Env, user: Address) -> u64;
pub fn bump_nonce(env: Env, user: Address) -> Result<u64, RelayerError>;
pub fn register_signer_key(env: Env, player: Address, signer_pubkey: BytesN<32>) -> Result<(), RelayerError>;
pub fn get_signer_key(env: Env, player: Address) -> Option<BytesN<32>>;
```

## Running Tests

```bash
cargo test -p gasless_relayer
```
74 changes: 74 additions & 0 deletions contracts/gasless_relayer/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
use soroban_sdk::contracterror;

/// Centralized error codes for the Gasless Meta-Transaction Relayer Contract (#1148 SC-48).
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum RelayerError {
// ── Authorization & Governance Errors (1–9) ──
/// Contract is already initialized
AlreadyInitialized = 1,
/// Contract has not been initialized yet
NotInitialized = 2,
/// Caller is not authorized for this operation
Unauthorized = 3,
/// Caller is not the contract admin
NotAdmin = 4,
/// Contract is paused for emergency stop
ContractPaused = 5,
/// Contract is not paused
NotPaused = 6,
/// The submitting relayer address is not authorized
RelayerNotAuthorized = 7,

// ── Cryptographic & Signature Verification Errors (10–19) ──
/// Cryptographic signature verification failed
InvalidSignature = 10,
/// Public key or signer address format is invalid
InvalidSigner = 11,
/// Recovered signer does not match expected sender/player
SignerMismatch = 12,
/// Domain separator mismatch (wrong chain or contract)
InvalidDomain = 13,
/// Meta-transaction validity window has expired
ExpiredTransaction = 14,

// ── Nonce & Replay Protection Errors (20–29) ──
/// The specified nonce is invalid or mismatched
InvalidNonce = 20,
/// The nonce has already been consumed (replay attempt)
NonceAlreadyUsed = 21,
/// The provided nonce does not match the expected sequential nonce
NonceMismatch = 22,

// ── Match Staking & Execution Errors (30–49) ──
/// Staking or fee amount must be positive
InvalidAmount = 30,
/// Player has insufficient token balance
InsufficientFunds = 31,
/// Insufficient allowance granted to the forwarder contract
InsufficientAllowance = 32,
/// Match / game escrow record not found
MatchNotFound = 33,
/// Match with this ID already exists
MatchAlreadyExists = 34,
/// Match is already full (both players joined)
MatchAlreadyFull = 35,
/// Match is not in an active or joinable state
MatchNotInProgress = 36,
/// Match has already been settled
MatchAlreadySettled = 37,
/// Player 2 cannot be the same as Player 1
SamePlayerJoining = 38,
/// Downstream target contract invocation failed
TargetCallFailed = 39,
/// Batch request contains no transactions
EmptyBatch = 40,
/// Batch request exceeds maximum allowable size
BatchTooLarge = 41,
/// Length of batch requests, keys, and signatures mismatch
InvalidBatchLengths = 42,
/// Relayer gas fee compensation transfer failed
FeeTransferFailed = 43,
/// Reentrancy guard triggered
ReentrantCall = 44,
}
Loading