Integration Guide
Developer guide for integrating with SUDIGITAL smart contracts using the TypeScript SDK (@sudi/evm).
TypeScript SDK
The @sudi/evm package (v0.1.0) provides typed client factories for all EVM contracts, built on viem and zod.
Installation
bun add @sudi/evmPackage Structure
packages/evm/src/
├── index.ts — barrel exports (everything below)
├── client/
│ ├── index.ts — re-exports client + transaction
│ ├── client.ts — createEvmClient, createReadOnlyClient, rpcWithRetry
│ └── transaction.ts — createTransactionBuilder, encodeCallData, waitForTransaction
├── contracts/
│ ├── index.ts — re-exports all contract clients + ABIs
│ ├── abi/ — 8 ABI JSON files
│ ├── airdrop.ts — createAirdropClient
│ ├── airdrop.types.ts
│ ├── core.ts — createCoreClient
│ ├── core.types.ts
│ ├── economy.ts — createEconomyClient
│ ├── economy.types.ts
│ ├── mission.ts — createMissionClient
│ ├── mission.types.ts
│ ├── nft.ts — createNftClient
│ ├── nft.types.ts — NftRole enum (Explorer variant)
│ ├── staking.ts — createStakingClient
│ ├── staking.types.ts
│ ├── token.ts — createTokenClient
│ ├── vesting.ts — createVestingClient
│ └── vesting.types.ts
├── types/
│ ├── index.ts — re-exports chains + contracts
│ ├── chains.ts — EvmChainId, CHAIN_CONFIG, chain utilities
│ └── contracts.ts — ClaimType, GenericClaimProof, XP_LEVELS, NftRole (Memer variant)
└── utils/
├── index.ts — re-exports address + signature + signing
├── address.ts — isValidAddress, shortenAddress, etc.
├── signature.ts — EIP-712 helpers, proof type definitions
└── signing.ts — backend proof signing (signEvmClaimProof, etc.)Client Creation
Full Client (with wallet)
import { createEvmClient } from "@sudi/evm";
import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const { publicClient, walletClient, chain, chainId } = createEvmClient({
chain: 8453, // EvmChainId (Base mainnet)
rpcUrl: "https://mainnet.base.org",
account,
transport: "http", // "http" | "websocket" (default: "http")
pollingInterval: 4000, // optional (default: 4000ms)
batch: { multicall: true }, // optional
});Read-Only Client
import { createReadOnlyClient } from "@sudi/evm";
const publicClient = createReadOnlyClient(
8453, // EvmChainId
"https://mainnet.base.org", // optional rpcUrl
);RPC with Retry
import { rpcWithRetry } from "@sudi/evm";
const balance = await rpcWithRetry(
() => publicClient.getBalance({ address }),
{ maxRetries: 3, retryDelayMs: 1000 }, // optional (these are defaults)
);Contract Clients
Each contract module has a dedicated factory function. All follow the same pattern:
import {
createStakingClient,
createNftClient,
createMissionClient,
createVestingClient,
createAirdropClient,
createCoreClient,
createEconomyClient,
createTokenClient, // Note: 8 clients, not 7
} from "@sudi/evm";Staking Example
const staking = createStakingClient({
contractAddress: "0x...",
publicClient,
walletClient,
});
// Read operations
const config = await staking.getStakingConfig();
const pending = await staking.getPendingRewards(userAddress);
const apy = await staking.getApyForLockDuration(7776000); // 90 days
// Write operations
await staking.stake(amount, lockDuration);
await staking.unstake(amount);
await staking.claimRewards();Mission Example
const mission = createMissionClient({
contractAddress: "0x...",
publicClient,
walletClient,
});
// Creator deposits escrow (ETH)
await mission.depositEscrow(missionId, "0x0000000000000000000000000000000000000000", amount, expiresAt);
// Creator deposits escrow (ERC-20)
await mission.depositEscrow(missionId, tokenAddress, amount, expiresAt);
// Backend records winners
await mission.recordWinner(winnerProof);
await mission.batchRecordWinners(proofs); // max 100
// Winner claims reward (80/15/5 split)
await mission.claimReward(missionId);
// NFT holders claim their 15% share
await mission.claimHolderReward(missionId);
// Failed ETH transfers — pull-payment fallback
await mission.withdrawPending();Token Example
const token = createTokenClient({
contractAddress: "0x4E13A52764aFC8AE4C23C58e67fDe8bf5F6C100b",
publicClient,
walletClient,
});
// Approve staking contract before staking
await token.approve(stakingAddress, amount);
// Check frozen status
const frozen = await token.isFrozen(userAddress);Backend Proof Signing
The SDK provides server-side utilities for signing claim proofs. These require EVM_BACKEND_PRIVATE_KEY in the environment.
Claim Types (EVM)
import { EvmClaimType } from "@sudi/evm";
// EVM uses 0-based values (Solana uses 1-based)
EvmClaimType.Token; // 0
EvmClaimType.Nft; // 1
EvmClaimType.Reward; // 2
EvmClaimType.Airdrop; // 3Signing Functions
import {
signEvmClaimProof,
signEvmWinnerProof,
signEvmMintProof,
generateEvmClaimParams,
getEvmBackendAccount,
getEvmBackendAuthorityAddress,
} from "@sudi/evm";
// Generate nonce + expiry (5-minute window)
const { nonce, expiry } = generateEvmClaimParams();
// Sign a claim proof
const signature = await signEvmClaimProof({
claimId: 1n,
claimType: 0, // EvmClaimType.Token
user: "0x...",
amount: 1_000_000n, // 1 token (6 decimals)
nonce,
expiry,
});
// Sign a winner proof (for missions)
const winnerSig = await signEvmWinnerProof({
missionId: "0x...", // bytes32
winner: "0x...",
rank: 1,
rewardAmount: 5_000_000n,
nonce,
expiry,
});
// Sign a mint proof (for NFTs)
const mintSig = await signEvmMintProof({
nftId: 1,
role: 6, // Owner
minter: "0x...", // Note: "minter", not "user"
price: 100_000_000n, // Note: "price", not "maxShares"
nonce,
expiry,
});EIP-712 Typed Data
The SDK defines EIP-712 type schemas for structured proof signing:
import {
WINNER_PROOF_TYPES, // { WinnerProof: [...] }
MINT_PROOF_TYPES, // { MintProof: [...] }
createSudigitalDomain, // (chainId, contractAddress) => Eip712Domain
} from "@sudi/evm";Generating Full Proof Data
import { generateEvmProof } from "@sudi/evm";
const { signature, proofData } = await generateEvmProof({
claimId: "123",
walletAddress: "0x...",
claimAmount: 1_000_000n,
nonce: 1n,
expiry: BigInt(Math.floor(Date.now() / 1000) + 300),
tokenAddress: "0x4E13A52764aFC8AE4C23C58e67fDe8bf5F6C100b",
chain: "base",
network: "mainnet",
});Address Utilities
import {
isValidAddress,
toAddress, // checksummed (throws if invalid)
tryToAddress, // checksummed (returns null if invalid)
shortenAddress,
addressEquals, // case-insensitive comparison
isZeroAddress,
ZERO_ADDRESS,
} from "@sudi/evm";Signature Utilities
import {
signMessage,
signTypedData,
verifySignedMessage,
recoverSigner,
hashSignedMessage,
hashEip712TypedData,
verifyTypedDataSignature,
} from "@sudi/evm";Transaction Builder
For batching multiple contract calls:
import { createTransactionBuilder, waitForTransaction } from "@sudi/evm";
const builder = createTransactionBuilder(publicClient, walletClient);
builder.addContractCall(/* ... */);
const gasEstimate = await builder.estimateGas();
const gasPrices = await builder.getGasPrices();
const nonce = await builder.getNonce();
// Wait for a transaction to confirm
const result = await waitForTransaction(publicClient, txHash, 1); // 1 confirmationABIs
All 8 contract ABIs are available as JSON files:
packages/evm/src/contracts/abi/
├── AirdropModule.json
├── CoreModule.json
├── EconomyModule.json
├── MissionModule.json
├── NftModule.json
├── StakingModule.json
├── SudigitalToken.json
└── VestingModule.jsonimport {
AIRDROP_MODULE_ABI,
CORE_MODULE_ABI,
ECONOMY_MODULE_ABI,
MISSION_MODULE_ABI,
NFT_MODULE_ABI,
STAKING_MODULE_ABI,
SUDIGITAL_TOKEN_ABI,
VESTING_MODULE_ABI,
CONTRACT_ABIS, // Record<ContractName, ABI>
} from "@sudi/evm";Chain Configuration
import {
CHAIN_CONFIG,
CHAIN_ID_MAP,
DEFAULT_RPC_ENDPOINTS,
TESTNET_CHAINS,
MAINNET_CHAINS,
isTestnetChain,
getChainById,
getViemChain,
type EvmChainId,
} from "@sudi/evm";Supported chain IDs: 8453 (Base), 1 (Ethereum), 42161 (Arbitrum), 10 (Optimism), 137 (Polygon), 11155111 (Sepolia), 84532 (Base Sepolia).
XP and Level Utilities
import { XP_LEVELS, XP_LEVEL_NAMES, calculateLevel, ClaimType } from "@sudi/evm";
const level = calculateLevel(5000n); // returns 3 (Whale)Token Decimals
SUDIGITAL uses 6 decimals on both chains:
// 1 SUDIGITAL = 1_000_000 raw units
const ONE_TOKEN = 1_000_000n;
const amount = 100n * ONE_TOKEN; // 100 tokens = 100_000_000 rawKnown SDK Quirks
Duplicate NftRole Enum
The SDK exports two conflicting NftRole enums:
From contracts/nft.types.ts (package-level export):
enum NftRole {
Explorer = 0,
Adventurer = 1,
Warrior = 2,
Champion = 3,
Legend = 4,
Titan = 5,
Mythic = 6,
}From types/contracts.ts:
enum NftRole {
Memer = 1,
Worker = 2,
Player = 3,
Trader = 4,
Builder = 5,
Owner = 6,
}The types/contracts.ts variant matches the on-chain Roles.sol enum. The nft.types.ts variant uses placeholder names and starts at 0. The package-level NftRole export resolves to the nft.types.ts variant. Use the numeric values directly when interacting with contracts.
unstake() Takes Amount
EVM unstake(amount) takes a specific amount to withdraw. Solana unstake_tokens(amount) also takes an amount. Neither unstakes everything automatically.
Common Integration Patterns
Claim Flow (EVM)
// 1. Backend validates eligibility (off-chain)
// 2. Backend signs proof
const { nonce, expiry } = generateEvmClaimParams();
const signature = await signEvmClaimProof({
claimId: 1n,
claimType: 0,
user: "0x...",
amount: 1_000_000n,
nonce,
expiry,
});
// 3. User submits on-chain
const core = createCoreClient({ contractAddress: "0x...", publicClient, walletClient });
await core.processClaim({ claimId: 1n, claimType: 0, user: "0x...", amount: 1_000_000n, nonce, expiry, signature });
// 4. Contract verifies ECDSA signature + nonce
// 5. Event emitted, nonce incrementedStaking Flow
const staking = createStakingClient({ contractAddress: "0x...", publicClient, walletClient });
const token = createTokenClient({
contractAddress: "0x4E13A52764aFC8AE4C23C58e67fDe8bf5F6C100b",
publicClient,
walletClient,
});
// 1. Approve staking contract to spend tokens
await token.approve(stakingAddress, amount);
// 2. Stake with lock duration (90 days = 7,776,000 seconds)
await staking.stake(amount, 7_776_000); // 8% APY
// 3. Check rewards anytime
const rewards = await staking.getPendingRewards(userAddress);
// 4. Claim rewards (no unstake needed)
await staking.claimRewards();
// 5. Unstake after lock expires
await staking.unstake(amount);NFT Mint Flow
const nft = createNftClient({ contractAddress: "0x...", publicClient, walletClient });
// 1. Backend signs mint proof
const mintSig = await signEvmMintProof({
nftId: 1,
role: 6,
minter: "0x...",
price: 100_000_000n,
nonce,
expiry,
});
// 2. User approves SUDIGITAL token spending
await token.approve(nftContractAddress, price);
// 3. User mints (proof verified on-chain)
await nft.mintNft(mintProof, maxShares);
// 4. First minter becomes founder (1% on future mints)Contract Addresses
Base Mainnet (Chain ID: 8453)
| Contract | Address |
|---|---|
| SUDIGITAL Token | 0x4E13A52764aFC8AE4C23C58e67fDe8bf5F6C100b |
| Gnosis Safe | 0xb0599ce3595Ed3768B08B9B4e7d96AF309a7f49a |
Solana Devnet
| Program | ID |
|---|---|
| sudigital-core | CARHonCaZPoHNQhmxVhtGjgx3BKaJB7RsbHPyAyd21di |
| sudigital-token | 4kTLHjrng6e6rmu8R1dGx1e9pHZjdV7mztax7tN3VsdP |
| sudigital-nft | 4azFkFofcXHZh1tLfKXYXemWepBu2tTgyX1WFeXXErUN |
| sudigital-mission | 6zso2TEKGZr21AkNjX9doAXr7GEHVdkYSD3p8v75huB |
| sudigital-economy | CympDTHAJrBeMk8SC84iEVyFS5neUcACkKssbaJk6yvG |
Related
- Contract Architecture -- design patterns, PDAs
- EVM Contracts -- full function reference
- Solana Programs -- Anchor program reference
- Security -- security considerations