Skip to content

Contract Architecture

Technical deep dive into how SUDIGITAL smart contracts are designed across both chains.

Hybrid Architecture

SUDIGITAL uses a minimal on-chain approach -- only financial operations that require trustless execution live on-chain. Everything else runs off-chain in PostgreSQL.

┌─────────────────────────────────────────────────────────────┐
│                      ON-CHAIN                                │
│                                                             │
│  Token operations    Escrow deposits    XP accounts          │
│  Staking/vesting     Winner recording   Claim proofs         │
│  NFT minting         Reward claims      Airdrop claims       │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                      OFF-CHAIN                               │
│                                                             │
│  Mission logic       User profiles      Leaderboards         │
│  Game rules          Participant lists   Error messages       │
│  Analytics           Notifications       Complex validation   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

This yields 87% storage cost reduction compared to fully on-chain alternatives.

Claim Proof Pattern

The primary interaction pattern across both chains is the backend-signed claim proof:

  1. User requests an action (mint NFT, claim airdrop, record win)
  2. Backend validates eligibility (checks DB, rules, limits)
  3. Backend signs a ClaimProof with its private key (ECDSA for EVM, Ed25519 for Solana)
  4. User submits the signed proof on-chain
  5. Contract verifies the signature matches the trusted backend_authority
  6. Contract executes the operation and increments the user's nonce

ClaimProof Structure

FieldEVM TypeSolana TypeDescription
claim_iduint64u64Unique operation ID
claim_typeClaimType (0-3)ClaimType (1-4)Token, Nft, Reward, Airdrop
useraddressPubkeyRecipient
amountuint256u64Token amount
nonceuint64u64Monotonically increasing per user
expiryuint256i64Proof expiration timestamp
signaturebytes[u8; 64]Backend authority signature

Signature message: claim_id || claim_type || user || amount || nonce || expiry

  • EVM: keccak256(abi.encodePacked(...)) + ethSignedMessageHash, recovered via ECDSA ecrecover
  • Solana: Ed25519 signature verified via ed25519_program sysvar instruction introspection

ClaimType values differ between chains:

TypeEVMSolana
Token01
Nft12
Reward23
Airdrop34

Anti-replay: Each user maintains a last_nonce counter. Every proof must have nonce > last_nonce.

EVM Architecture

UUPS Proxy Pattern

All EVM contracts use OpenZeppelin's UUPS (Universal Upgradeable Proxy Standard):

User ──> Proxy (storage) ──> Implementation (logic)

                └── delegatecall
  • Proxy holds all state (storage slots)
  • Implementation holds all logic
  • Upgrade deploys new implementation, proxy points to it
  • Storage gaps -- all contracts reserve uint256[48-50] __gap for future slots
  • AUTHORIZED_DEPLOYER -- immutable, set in constructor, prevents unauthorized initialization

Module Versions

ContractVersionPausableReentrancy Guard
SudigitalTokenYes (OZ)No
CoreModule0.6.6Yes (OZ)No
StakingModule0.6.6Yes (OZ)Yes (OZ)
VestingModule0.6.6Yes (OZ)Yes (OZ)
MissionModule0.6.6Yes (OZ)Yes (OZ)
NftModule1.2.0No (checks CoreModule)Yes (OZ)
EconomyModule0.6.6Yes (OZ)No
AirdropModule1.0.0Yes (OZ)Yes (custom)

Library Files (8 libraries)

LibraryLinesPurpose
Constants.sol87Mission limits, NFT constants, XP thresholds/multipliers
Errors.sol235~80 error codes as string constants (6100-7499)
Events.sol388~50 event definitions across all modules
Roles.sol29Role enum + bit flag utilities
Types.sol198All struct definitions (PlatformConfig, ClaimProof, etc.)
MissionTypes.sol68Mission enums (6 statuses, 6 types, 5 difficulties)
MissionValidator.sol114Creation validation, auto-start, state transitions
SignatureVerifier.sol144ECDSA verification for claim/winner/mint proofs
TokenAllocation.sol92Supply allocation constants (6 decimals)
Security.sol191Rate limiting, circuit breaker, timelock structs/logic

Interface Files (6 interfaces)

ICoreModule, IEconomyModule, IMissionModule, INftModule, ISudigitalToken, IVestingModule

Build Configuration

Solidity:  ^0.8.28
Optimizer: enabled, 20 runs (dev) / 1,000,000 runs (production)
via_ir:    true
Deps:      OpenZeppelin Contracts Upgradeable

Solana Architecture

Anchor Program Structure

Each Solana program follows Anchor conventions:

programs/sudigital-{module}/src/
├── lib.rs              — program entrypoint, instruction handlers
├── constants.rs        — module constants
├── errors.rs           — error codes (#[error_code])
├── events.rs           — event definitions (Token only)
├── instructions/       — instruction context structs
├── state/              — account data structures
└── utils/              — helper functions

All 5 programs are version 0.6.6, built with Anchor 0.32.1 + init-if-needed feature.

PDA (Program Derived Address) Seeds

AccountSeedsProgram
PlatformConfig["platform_config"]Core
UserClaimState["user_claim_state", user]Core
TokenConfig["token_config"]Token
TokenStats["token_stats"]Token
StakingConfig["staking_config"]Token
StakeAccount["stake", user]Token
VestingAccount["vesting", beneficiary]Token
AirdropEscrow["airdrop_escrow", airdrop_id_le]Token
EscrowToken["escrow_token", airdrop_id_le]Token
EscrowAuthority["escrow_authority", airdrop_id_le]Token
AirdropClaimState["airdrop_claim_state", user, airdrop_id_le]Token
NftMetadata["nft_metadata", nft_id_le]NFT
HolderShare (NFT)["holder_share", nft_id_le, holder]NFT
MissionEscrow["mission_escrow", mission_id_32]Mission
MissionWinner["mission_winner", mission_id_32, winner]Mission
HolderPool["holder_pool", mission_id_32]Mission
HolderShare (Mission)["holder_share", mission_id_32, holder]Mission
CircuitBreaker["circuit_breaker"]Mission
XpAccount["xp_account", user]Economy

Account Sizes

AccountSize (bytes)Program
PlatformConfig90Core
UserClaimState53Core
XpAccount76Economy
NftMetadata49NFT
HolderShare (NFT)46NFT
MissionEscrow130Mission
MissionWinner92Mission
HolderPool74Mission
HolderShare (Mission)90Mission
CircuitBreaker60Mission

Build Configuration

Anchor:          0.32.1
Package Manager: yarn
Deploy Order:    token -> economy -> core -> nft -> mission

Error Code Ranges

Both chains use consistent error code ranges:

ModuleSource RangeAnchor IDL Range (+6000)
Core100-1996100-6199
Economy200-2996200-6299
NFT300-4996300-6499
Mission500-7996500-6799
Token800-9996800-6999
Staking1000-11997000-7199
Vesting1200-13997200-7399
Airdrop1400-14997400-7499

Error messages are stored off-chain in PostgreSQL for cost optimization. On-chain errors contain only the numeric code as a string (e.g., "6100" for MathOverflow).

Event System

Solana: Only the Token program emits events (11 event types: StakeEvent, UnstakeEvent, RewardsClaimedEvent, VestingCreatedEvent, VestedReleasedEvent, VestingRevokedEvent, StakingConfigUpdatedEvent, MintEvent, BurnEvent, AirdropCreatedEvent, AirdropClaimedEvent). Core, NFT, Mission, and Economy programs emit no events.

EVM: All modules emit events via the shared Events.sol library (~50 event types covering missions, XP, rewards, NFTs, admin, security, platform config, escrow, burns, and more).

Cross-Program Invocations (Solana)

CallerReads FromCPIs Into
Token (claim_airdrop)Core PlatformConfig
NFT (mint_nft)Core PlatformConfig, UserClaimStateCore process_claim
MissionCore PlatformConfig
Economy (award_xp)Core WALLET_CREATOR constant

Core is the root program and does not CPI into anything. All programs use Core's WALLET_CREATOR constant for access control on admin operations.

One backend. Three products. One token.