Skip to content

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

bash
bun add @sudi/evm

Package 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)

typescript
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

typescript
import { createReadOnlyClient } from "@sudi/evm";

const publicClient = createReadOnlyClient(
  8453, // EvmChainId
  "https://mainnet.base.org", // optional rpcUrl
);

RPC with Retry

typescript
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:

typescript
import {
  createStakingClient,
  createNftClient,
  createMissionClient,
  createVestingClient,
  createAirdropClient,
  createCoreClient,
  createEconomyClient,
  createTokenClient, // Note: 8 clients, not 7
} from "@sudi/evm";

Staking Example

typescript
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

typescript
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

typescript
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)

typescript
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; // 3

Signing Functions

typescript
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:

typescript
import {
  WINNER_PROOF_TYPES, // { WinnerProof: [...] }
  MINT_PROOF_TYPES, // { MintProof: [...] }
  createSudigitalDomain, // (chainId, contractAddress) => Eip712Domain
} from "@sudi/evm";

Generating Full Proof Data

typescript
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

typescript
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

typescript
import {
  signMessage,
  signTypedData,
  verifySignedMessage,
  recoverSigner,
  hashSignedMessage,
  hashEip712TypedData,
  verifyTypedDataSignature,
} from "@sudi/evm";

Transaction Builder

For batching multiple contract calls:

typescript
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 confirmation

ABIs

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.json
typescript
import {
  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

typescript
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

typescript
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:

typescript
// 1 SUDIGITAL = 1_000_000 raw units
const ONE_TOKEN = 1_000_000n;
const amount = 100n * ONE_TOKEN; // 100 tokens = 100_000_000 raw

Known SDK Quirks

Duplicate NftRole Enum

The SDK exports two conflicting NftRole enums:

From contracts/nft.types.ts (package-level export):

typescript
enum NftRole {
  Explorer = 0,
  Adventurer = 1,
  Warrior = 2,
  Champion = 3,
  Legend = 4,
  Titan = 5,
  Mythic = 6,
}

From types/contracts.ts:

typescript
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)

typescript
// 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 incremented

Staking Flow

typescript
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

typescript
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)

ContractAddress
SUDIGITAL Token0x4E13A52764aFC8AE4C23C58e67fDe8bf5F6C100b
Gnosis Safe0xb0599ce3595Ed3768B08B9B4e7d96AF309a7f49a

Solana Devnet

ProgramID
sudigital-coreCARHonCaZPoHNQhmxVhtGjgx3BKaJB7RsbHPyAyd21di
sudigital-token4kTLHjrng6e6rmu8R1dGx1e9pHZjdV7mztax7tN3VsdP
sudigital-nft4azFkFofcXHZh1tLfKXYXemWepBu2tTgyX1WFeXXErUN
sudigital-mission6zso2TEKGZr21AkNjX9doAXr7GEHVdkYSD3p8v75huB
sudigital-economyCympDTHAJrBeMk8SC84iEVyFS5neUcACkKssbaJk6yvG

One backend. Three products. One token.