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:
- User requests an action (mint NFT, claim airdrop, record win)
- Backend validates eligibility (checks DB, rules, limits)
- Backend signs a
ClaimProofwith its private key (ECDSA for EVM, Ed25519 for Solana) - User submits the signed proof on-chain
- Contract verifies the signature matches the trusted
backend_authority - Contract executes the operation and increments the user's nonce
ClaimProof Structure
| Field | EVM Type | Solana Type | Description |
|---|---|---|---|
claim_id | uint64 | u64 | Unique operation ID |
claim_type | ClaimType (0-3) | ClaimType (1-4) | Token, Nft, Reward, Airdrop |
user | address | Pubkey | Recipient |
amount | uint256 | u64 | Token amount |
nonce | uint64 | u64 | Monotonically increasing per user |
expiry | uint256 | i64 | Proof expiration timestamp |
signature | bytes | [u8; 64] | Backend authority signature |
Signature message: claim_id || claim_type || user || amount || nonce || expiry
- EVM:
keccak256(abi.encodePacked(...))+ethSignedMessageHash, recovered via ECDSAecrecover - Solana: Ed25519 signature verified via
ed25519_programsysvar instruction introspection
ClaimType values differ between chains:
| Type | EVM | Solana |
|---|---|---|
| Token | 0 | 1 |
| Nft | 1 | 2 |
| Reward | 2 | 3 |
| Airdrop | 3 | 4 |
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] __gapfor future slots AUTHORIZED_DEPLOYER-- immutable, set in constructor, prevents unauthorized initialization
Module Versions
| Contract | Version | Pausable | Reentrancy Guard |
|---|---|---|---|
| SudigitalToken | — | Yes (OZ) | No |
| CoreModule | 0.6.6 | Yes (OZ) | No |
| StakingModule | 0.6.6 | Yes (OZ) | Yes (OZ) |
| VestingModule | 0.6.6 | Yes (OZ) | Yes (OZ) |
| MissionModule | 0.6.6 | Yes (OZ) | Yes (OZ) |
| NftModule | 1.2.0 | No (checks CoreModule) | Yes (OZ) |
| EconomyModule | 0.6.6 | Yes (OZ) | No |
| AirdropModule | 1.0.0 | Yes (OZ) | Yes (custom) |
Library Files (8 libraries)
| Library | Lines | Purpose |
|---|---|---|
Constants.sol | 87 | Mission limits, NFT constants, XP thresholds/multipliers |
Errors.sol | 235 | ~80 error codes as string constants (6100-7499) |
Events.sol | 388 | ~50 event definitions across all modules |
Roles.sol | 29 | Role enum + bit flag utilities |
Types.sol | 198 | All struct definitions (PlatformConfig, ClaimProof, etc.) |
MissionTypes.sol | 68 | Mission enums (6 statuses, 6 types, 5 difficulties) |
MissionValidator.sol | 114 | Creation validation, auto-start, state transitions |
SignatureVerifier.sol | 144 | ECDSA verification for claim/winner/mint proofs |
TokenAllocation.sol | 92 | Supply allocation constants (6 decimals) |
Security.sol | 191 | Rate 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 UpgradeableSolana 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 functionsAll 5 programs are version 0.6.6, built with Anchor 0.32.1 + init-if-needed feature.
PDA (Program Derived Address) Seeds
| Account | Seeds | Program |
|---|---|---|
| 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
| Account | Size (bytes) | Program |
|---|---|---|
| PlatformConfig | 90 | Core |
| UserClaimState | 53 | Core |
| XpAccount | 76 | Economy |
| NftMetadata | 49 | NFT |
| HolderShare (NFT) | 46 | NFT |
| MissionEscrow | 130 | Mission |
| MissionWinner | 92 | Mission |
| HolderPool | 74 | Mission |
| HolderShare (Mission) | 90 | Mission |
| CircuitBreaker | 60 | Mission |
Build Configuration
Anchor: 0.32.1
Package Manager: yarn
Deploy Order: token -> economy -> core -> nft -> missionError Code Ranges
Both chains use consistent error code ranges:
| Module | Source Range | Anchor IDL Range (+6000) |
|---|---|---|
| Core | 100-199 | 6100-6199 |
| Economy | 200-299 | 6200-6299 |
| NFT | 300-499 | 6300-6499 |
| Mission | 500-799 | 6500-6799 |
| Token | 800-999 | 6800-6999 |
| Staking | 1000-1199 | 7000-7199 |
| Vesting | 1200-1399 | 7200-7399 |
| Airdrop | 1400-1499 | 7400-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)
| Caller | Reads From | CPIs Into |
|---|---|---|
| Token (claim_airdrop) | Core PlatformConfig | — |
| NFT (mint_nft) | Core PlatformConfig, UserClaimState | Core process_claim |
| Mission | Core 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.
Related
- EVM Contracts -- Base L2 specifics
- Solana Programs -- Anchor program specifics
- Security -- circuit breakers, rate limiting, multisig
- Integration Guide -- TypeScript SDK