Sei Giga Developer Guide
Introduction
Building on Sei Giga opens up possibilities for creating applications that were previously impossible on traditional blockchains. This guide provides everything you need to start developing high-performance dApps that can handle millions of users.
Key advantages for developers:
- Familiar Tools: Full EVM compatibility means you can use existing Ethereum tooling
- Superior Performance: Build apps that feel like Web2 with Web3 guarantees
- Lower Costs: Efficient architecture means cheaper transactions for users
- Advanced Features: Access to unique precompiles and parallel execution
- Future-Proof: Designed for the next generation of blockchain applications
Prerequisites
- Familiarity with Solidity or Vyper
- Basic understanding of EVM and smart contracts
- Node.js 16+ and npm/yarn installed
- Git for version control
Development Environment Setup
Quick Start
# Install Sei CLI
npm install -g @sei/cli
# Create a new project
sei create my-dapp
cd my-dapp
# Install dependencies
npm install
# Start local development node
sei node start
# Deploy your first contract
sei deploy contracts/HelloSei.sol
Quick Setup
# Add Sei network to your Hardhat config
npx hardhat init
// hardhat.config.js - Essential configuration only
module.exports = {
networks: {
seiTestnet: {
url: "https://testnet-rpc.sei.io",
chainId: 1328,
accounts: [process.env.PRIVATE_KEY]
}
},
solidity: "0.8.20"
};
Smart Contract Development
Writing High-Performance Contracts
Sei Giga’s parallel execution engine rewards well-designed contracts. Follow these patterns for optimal performance:
Pattern 1: User-Isolated State
contracts/OptimizedToken.sol
// GOOD: Each user has isolated state
contract OptimizedToken {
mapping(address => uint256) private balances;
mapping(address => mapping(address => uint256)) private allowances;
function transfer(address to, uint256 amount) public {
// Only touches sender and recipient state
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
// BAD: Global state creates conflicts
contract PoorToken {
uint256 public totalTransfers; // Global counter
function transfer(address to, uint256 amount) public {
totalTransfers++; // Every transfer conflicts!
// ... transfer logic
}
}
Pattern 2: Optimistic Updates
contracts/OptimisticDEX.sol
contract OptimisticDEX {
struct Pool {
uint256 reserve0;
uint256 reserve1;
uint256 k; // Constant product
uint256 lastUpdate;
}
mapping(bytes32 => Pool) public pools;
mapping(address => uint256) public userNonces;
event Swap(address indexed user, bytes32 indexed poolId, uint256 amountIn, uint256 amountOut);
event LiquidityAdded(address indexed user, bytes32 indexed poolId, uint256 amount0, uint256 amount1);
function swap(
bytes32 poolId,
uint256 amountIn,
uint256 minAmountOut,
bool zeroForOne
) external {
Pool storage pool = pools[poolId];
require(pool.k > 0, "Pool does not exist");
uint256 amountOut;
if (zeroForOne) {
// Swap token0 for token1
amountOut = (pool.reserve1 * amountIn) / (pool.reserve0 + amountIn);
require(amountOut >= minAmountOut, "Insufficient output");
pool.reserve0 += amountIn;
pool.reserve1 -= amountOut;
} else {
// Swap token1 for token0
amountOut = (pool.reserve0 * amountIn) / (pool.reserve1 + amountIn);
require(amountOut >= minAmountOut, "Insufficient output");
pool.reserve1 += amountIn;
pool.reserve0 -= amountOut;
}
// Update constant product
pool.k = pool.reserve0 * pool.reserve1;
pool.lastUpdate = block.timestamp;
// Increment user nonce for parallel execution
userNonces[msg.sender]++;
emit Swap(msg.sender, poolId, amountIn, amountOut);
}
function addLiquidity(
bytes32 poolId,
uint256 amount0,
uint256 amount1
) external {
Pool storage pool = pools[poolId];
if (pool.k == 0) {
// Initialize pool
pool.reserve0 = amount0;
pool.reserve1 = amount1;
} else {
// Add proportional liquidity
require(
amount0 * pool.reserve1 == amount1 * pool.reserve0,
"Liquidity ratio mismatch"
);
pool.reserve0 += amount0;
pool.reserve1 += amount1;
}
pool.k = pool.reserve0 * pool.reserve1;
pool.lastUpdate = block.timestamp;
emit LiquidityAdded(msg.sender, poolId, amount0, amount1);
}
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256 amountOut) {
require(amountIn > 0, "Insufficient input amount");
require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");
uint256 numerator = reserveOut * amountIn;
uint256 denominator = reserveIn + amountIn;
amountOut = numerator / denominator;
}
function getPoolInfo(bytes32 poolId) external view returns (Pool memory) {
return pools[poolId];
}
function getUserNonce(address user) external view returns (uint256) {
return userNonces[user];
}
}
Pattern 3: Batch Operations
contracts/BatchProcessor.sol
contract BatchProcessor {
mapping(address => uint256) public balances;
event BatchTransfer(address indexed sender, uint256 totalAmount, uint256 recipientCount);
event Transfer(address indexed from, address indexed to, uint256 amount);
// Process multiple operations in one transaction
function batchTransfer(
address[] calldata recipients,
uint256[] calldata amounts
) external {
require(recipients.length == amounts.length, "Length mismatch");
require(recipients.length > 0, "Empty batch");
require(recipients.length <= 100, "Batch too large");
uint256 totalAmount = 0;
// Calculate total amount needed
for (uint256 i = 0; i < amounts.length; i++) {
require(amounts[i] > 0, "Invalid amount");
require(recipients[i] != address(0), "Invalid recipient");
totalAmount += amounts[i];
}
// Single balance check
require(balances[msg.sender] >= totalAmount, "Insufficient balance");
balances[msg.sender] -= totalAmount;
// Parallel-friendly distribution
for (uint256 i = 0; i < recipients.length; i++) {
balances[recipients[i]] += amounts[i];
emit Transfer(msg.sender, recipients[i], amounts[i]);
}
emit BatchTransfer(msg.sender, totalAmount, recipients.length);
}
function batchCall(
address[] calldata targets,
bytes[] calldata data
) external returns (bytes[] memory results) {
require(targets.length == data.length, "Length mismatch");
require(targets.length > 0, "Empty batch");
require(targets.length <= 50, "Batch too large");
results = new bytes[](targets.length);
for (uint256 i = 0; i < targets.length; i++) {
require(targets[i] != address(0), "Invalid target");
(bool success, bytes memory result) = targets[i].call(data[i]);
require(success, "Call failed");
results[i] = result;
}
}
function deposit() external payable {
require(msg.value > 0, "Must deposit positive amount");
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) external {
require(amount > 0, "Must withdraw positive amount");
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
}
Gas Optimization Techniques
Sei Giga Gas Tips:
- Storage operations are expensive - minimize writes
- Use
memory
for temporary data - Pack struct variables efficiently
- Use events for data not needed on-chain
- Leverage view functions for reads
contracts/GasOptimized.sol
contract GasOptimized {
// Pack structs to use fewer storage slots
struct User {
uint128 balance; // Slot 1 (16 bytes)
uint64 lastUpdate; // Slot 1 (8 bytes) - same slot!
uint64 nonce; // Slot 1 (8 bytes) - still same slot!
address referrer; // Slot 2 (20 bytes)
bool isActive; // Slot 2 (1 byte) - same slot as referrer!
}
mapping(address => User) public users;
mapping(address => uint256) public balances;
// Use constants for values that don't change
uint256 private constant PRECISION = 1e18;
uint256 private constant MAX_BATCH_SIZE = 100;
event UserUpdated(address indexed user, uint128 balance, uint64 nonce);
// Cache array lengths in loops
function sumBalances(address[] memory userAddresses) public view returns (uint256 total) {
uint256 length = userAddresses.length; // Cache length
for (uint256 i = 0; i < length; ) {
total += balances[userAddresses[i]];
unchecked { i++; } // Save gas on overflow check
}
}
// Batch operations for gas efficiency
function batchUpdateUsers(
address[] calldata userAddresses,
uint128[] calldata newBalances
) external {
require(userAddresses.length == newBalances.length, "Length mismatch");
require(userAddresses.length <= MAX_BATCH_SIZE, "Batch too large");
uint256 length = userAddresses.length;
for (uint256 i = 0; i < length; ) {
User storage user = users[userAddresses[i]];
user.balance = newBalances[i];
user.lastUpdate = uint64(block.timestamp);
user.nonce++;
emit UserUpdated(userAddresses[i], newBalances[i], user.nonce);
unchecked { i++; }
}
}
// Use memory for temporary calculations
function calculateRewards(address userAddress) external view returns (uint256 reward) {
User memory user = users[userAddress]; // Load to memory once
if (!user.isActive) {
return 0;
}
uint256 timeDiff = block.timestamp - user.lastUpdate;
reward = (uint256(user.balance) * timeDiff * PRECISION) / (365 days * 100); // 1% APY
}
// Efficient storage packing demonstration
function updateUser(
address userAddress,
uint128 newBalance,
address newReferrer,
bool active
) external {
User storage user = users[userAddress];
// Single storage write updates multiple fields
user.balance = newBalance;
user.lastUpdate = uint64(block.timestamp);
user.nonce++;
user.referrer = newReferrer;
user.isActive = active;
emit UserUpdated(userAddress, newBalance, user.nonce);
}
}
Sei-Specific Features
Native Precompiles
Sei Giga includes powerful precompiles for enhanced functionality:
contracts/IOracle.sol
// Oracle Precompile interface
interface IOracle {
struct OracleExchangeRate {
string exchangeRate;
string lastUpdate;
int64 lastUpdateTimestamp;
}
struct DenomOracleExchangeRatePair {
string denom;
OracleExchangeRate oracleExchangeRateVal;
}
struct OracleTwap {
string denom;
IOracle constant oracle = IOracle(0x0000000000000000000000000000000000001008);
mapping(address => uint256) public balances;
uint256 constant PRICE_STALENESS_THRESHOLD = 60; // 60 seconds
event PriceUpdate(string denom, string price, int64 timestamp);
modifier withFreshPrice(string memory denom) {
IOracle.DenomOracleExchangeRatePair[] memory rates = oracle.getExchangeRates();
bool found = false;
for (uint256 i = 0; i < rates.length; i++) {
if (keccak256(bytes(rates[i].denom)) == keccak256(bytes(denom))) {
uint256 age = block.timestamp - uint256(uint64(rates[i].oracleExchangeRateVal.lastUpdateTimestamp));
require(age < PRICE_STALENESS_THRESHOLD, "Price too stale");
emit PriceUpdate(
rates[i].denom,
rates[i].oracleExchangeRateVal.exchangeRate,
rates[i].oracleExchangeRateVal.lastUpdateTimestamp
);
found = true;
break;
}
}
require(found, "Price not found");
_;
}
function priceAwareTransfer(
string memory denom,
address to,
uint256 amount
) external withFreshPrice(denom) {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
}
function isPriceStale(string memory denom, uint256 maxAge) public view returns (bool) {
(, int64 timestamp) = getCurrentPrice(denom);
return block.timestamp - uint256(uint64(timestamp)) > maxAge;
}
}
Parallel Execution Awareness
Design contracts that maximize parallel execution:
contract ParallelFriendlyNFT {
// Each token ID is independent
mapping(uint256 => address) private owners;
mapping(uint256 => string) private tokenURIs;
// Minting doesn't conflict between users
function mint(uint256 tokenId, string memory uri) external {
require(owners[tokenId] == address(0), "Already minted");
owners[tokenId] = msg.sender;
tokenURIs[tokenId] = uri;
emit Transfer(address(0), msg.sender, tokenId);
}
// Transfers only affect specific tokens
function transfer(address to, uint256 tokenId) external {
require(owners[tokenId] == msg.sender, "Not owner");
owners[tokenId] = to;
emit Transfer(msg.sender, to, tokenId);
}
}
Frontend Integration
Web3 Setup
// lib/web3.ts
import { createPublicClient, createWalletClient, custom, http } from 'viem';
import { seiGiga } from './chains';
export const publicClient = createPublicClient({
chain: seiGiga,
transport: http('https://rpc.sei.io')
});
export const walletClient = createWalletClient({
chain: seiGiga,
transport: custom(window.ethereum)
});
// Custom chain configuration
export const seiGiga = {
id: 1329,
name: 'Sei Giga',
network: 'sei-giga',
nativeCurrency: {
decimals: 18,
name: 'Sei',
symbol: 'SEI',
},
rpcUrls: {
public: { http: ['https://rpc.sei.io'] },
default: { http: ['https://rpc.sei.io'] },
},
blockExplorers: {
default: { name: 'SeiScan', url: 'https://seiscan.io' },
},
};
React Hook Examples
// hooks/useContract.ts
import { useContractRead, useContractWrite, usePrepareContractWrite } from 'wagmi';
import { parseEther } from 'viem';
import TokenABI from '../abi/Token.json';
export function useTokenBalance(address: `0x${string}`) {
return useContractRead({
address: TOKEN_ADDRESS,
abi: TokenABI,
functionName: 'balanceOf',
args: [address],
watch: true, // Auto-refresh on new blocks
});
}
export function useTokenTransfer() {
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const { config } = usePrepareContractWrite({
address: TOKEN_ADDRESS,
abi: TokenABI,
functionName: 'transfer',
args: [recipient as `0x${string}`, parseEther(amount || '0')],
enabled: Boolean(recipient && amount),
});
const { write, isLoading, isSuccess } = useContractWrite(config);
return {
transfer: write,
isLoading,
isSuccess,
setRecipient,
setAmount,
};
}
Optimistic UI Updates
// Optimistically update UI before transaction confirms
function TransferButton({ recipient, amount }: TransferProps) {
const queryClient = useQueryClient();
const { transfer } = useTokenTransfer();
const handleTransfer = async () => {
// Optimistic update
queryClient.setQueryData(['balance', account], (old: bigint) =>
old - parseEther(amount)
);
try {
await transfer?.();
} catch (error) {
// Revert on error
queryClient.invalidateQueries(['balance', account]);
}
};
return (
<button onClick={handleTransfer}>
Transfer {amount} SEI
</button>
);
}
Testing & Debugging
Unit Testing
// test/Token.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("Token", function () {
let token;
let owner, addr1, addr2;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("Token");
token = await Token.deploy("SeiToken", "SEI", ethers.parseEther("1000000"));
await token.waitForDeployment();
});
describe("Parallel Execution", function () {
it("Should handle concurrent transfers", async function () {
// Setup
await token.transfer(addr1.address, ethers.parseEther("1000"));
await token.transfer(addr2.address, ethers.parseEther("1000"));
// Execute transfers in parallel
const tx1 = token.connect(addr1).transfer(owner.address, ethers.parseEther("100"));
const tx2 = token.connect(addr2).transfer(owner.address, ethers.parseEther("200"));
// Both should succeed
await expect(tx1).to.not.be.reverted;
await expect(tx2).to.not.be.reverted;
// Verify final state
expect(await token.balanceOf(owner.address)).to.equal(
ethers.parseEther("998300")
);
});
});
});
Performance Testing
// scripts/performance-test.js
async function testThroughput() {
const Token = await ethers.getContractFactory("Token");
const token = await Token.deploy("TestToken", "TEST", ethers.parseEther("1000000"));
const signers = await ethers.getSigners();
const batchSize = 100;
const iterations = 10;
console.log("Starting throughput test...");
const startTime = Date.now();
for (let i = 0; i < iterations; i++) {
const promises = [];
// Create batch of transactions
for (let j = 0; j < batchSize; j++) {
const signer = signers[j % signers.length];
const tx = token.connect(signer).transfer(
signers[(j + 1) % signers.length].address,
ethers.parseEther("1")
);
promises.push(tx);
}
// Execute in parallel
await Promise.all(promises);
}
const endTime = Date.now();
const totalTx = batchSize * iterations;
const duration = (endTime - startTime) / 1000;
const tps = totalTx / duration;
console.log(`Processed ${totalTx} transactions in ${duration}s`);
console.log(`Throughput: ${tps.toFixed(2)} TPS`);
}
Debugging Tools
⚠️
Common Issues & Solutions:
- Transaction Reverts: Use
hardhat console.log
in contracts - Gas Estimation Errors: Increase gas limit by 20%
- Nonce Issues: Use
eth_getTransactionCount
for accurate nonces - RPC Timeouts: Implement retry logic with exponential backoff
Deployment & DevOps
Production Deployment Checklist
# 1. Security audit
npm run audit
# 2. Gas optimization report
npm run gas-report
# 3. Test coverage
npm run coverage
# 4. Deploy to testnet
npm run deploy:testnet
# 5. Verify contracts
npm run verify:testnet
# 6. Integration tests
npm run test:integration
# 7. Deploy to mainnet
npm run deploy:mainnet
# 8. Verify on mainnet
npm run verify:mainnet
Contract Verification
// scripts/verify.js
async function main() {
await hre.run("verify:verify", {
address: CONTRACT_ADDRESS,
constructorArguments: [
"SeiToken",
"SEI",
ethers.parseEther("1000000")
],
});
}
Monitoring & Analytics
// Monitor contract events
import { publicClient } from './web3';
publicClient.watchContractEvent({
address: TOKEN_ADDRESS,
abi: TokenABI,
eventName: 'Transfer',
onLogs: (logs) => {
logs.forEach(log => {
console.log('Transfer:', {
from: log.args.from,
to: log.args.to,
value: formatEther(log.args.value)
});
// Send to analytics
analytics.track('token_transfer', {
from: log.args.from,
to: log.args.to,
value: log.args.value.toString(),
blockNumber: log.blockNumber
});
});
}
});
Best Practices Summary
Architecture Guidelines
- State Isolation: Design contracts where operations on different entities don’t conflict
- Batch Operations: Group multiple operations to amortize gas costs
- Event-Driven: Use events for off-chain data and indexing
- Upgrade Patterns: Implement proxy patterns for upgradability
- Access Control: Use role-based permissions for admin functions
Security Considerations
contracts/SecureContract.sol
contract SecureContract {
// Use OpenZeppelin libraries
using SafeERC20 for IERC20;
// Implement reentrancy guards
uint256 private _status = 1;
modifier nonReentrant() {
require(_status == 1, "Reentrant call");
_status = 2;
_;
_status = 1;
}
// Validate inputs
modifier validAddress(address addr) {
require(addr != address(0), "Invalid address");
_;
}
// Use checks-effects-interactions pattern
function withdraw(uint256 amount) external nonReentrant {
// Checks
require(balances[msg.sender] >= amount, "Insufficient balance");
// Effects
balances[msg.sender] -= amount;
// Interactions
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
Advanced Development Patterns
Designing for Parallel Execution
Consider a typical DeFi scenario with multiple users:
// Traditional sequential processing:
// User A swaps USDC for ETH (100ms)
// User B stakes SEI (100ms)
// User C mints NFT (100ms)
// Total time: 300ms
// Sei's parallel processing:
// All three execute simultaneously
// Total time: 100ms
The key: transactions touching different state execute in parallel.
State Isolation Pattern
contracts/ParallelFriendlyDEX.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract ParallelFriendlyDEX {
// GOOD: State isolated by user
mapping(address => uint256) private balances;
mapping(address => mapping(address => uint256)) private allowances;
// Each user's operations are independent
function deposit() external payable {
balances[msg.sender] += msg.value;
}
// BAD: Global state creates conflicts
uint256 public totalDeposits; // Every deposit must update this
function depositWithGlobalCounter() external payable {
balances[msg.sender] += msg.value;
totalDeposits += msg.value; // Forces sequential execution
}
}
Advanced Oracle Integration
Step 1: Basic Oracle Price Query
contracts/SimpleOracle.sol
contract SimpleOracle {
IOracle constant oracle = IOracle(0x0000000000000000000000000000000000001008);
function getPrice(string memory denom) public view returns (string memory) {
IOracle.DenomOracleExchangeRatePair[] memory rates = oracle.getExchangeRates();
for (uint256 i = 0; i < rates.length; i++) {
if (keccak256(bytes(rates[i].denom)) == keccak256(bytes(denom))) {
return rates[i].oracleExchangeRateVal.exchangeRate;
}
}
revert("Price not found");
}
}
Step 2: Price Staleness Check
contracts/PriceValidator.sol
contract PriceValidator {
IOracle constant oracle = IOracle(0x0000000000000000000000000000000000001008);
uint256 constant MAX_PRICE_AGE = 60; // 60 seconds
function isPriceStale(string memory denom) public view returns (bool) {
IOracle.DenomOracleExchangeRatePair[] memory rates = oracle.getExchangeRates();
for (uint256 i = 0; i < rates.length; i++) {
if (keccak256(bytes(rates[i].denom)) == keccak256(bytes(denom))) {
uint256 age = block.timestamp - uint256(uint64(rates[i].oracleExchangeRateVal.lastUpdateTimestamp));
return age > MAX_PRICE_AGE;
}
}
return true; // Price not found = stale
}
}
Step 3: TWAP Price Query
contracts/TwapOracle.sol
contract TwapOracle {
IOracle constant oracle = IOracle(0x0000000000000000000000000000000000001008);
function getTwapPrice(string memory denom, uint64 lookbackSeconds) public view returns (string memory) {
IOracle.OracleTwap[] memory twaps = oracle.getOracleTwaps(lookbackSeconds);
for (uint256 i = 0; i < twaps.length; i++) {
if (keccak256(bytes(twaps[i].denom)) == keccak256(bytes(denom))) {
return twaps[i].twap;
}
}
revert("TWAP not found");
}
}
Advanced Staking Patterns
contracts/LiquidStaking.sol
contract LiquidStaking {
IStaking constant staking = IStaking(0x0000000000000000000000000000000000001005);
IBank constant bank = IBank(0x0000000000000000000000000000000000001001);
mapping(address => uint256) public shares;
uint256 public totalShares;
string constant VALIDATOR = "seivaloper1...";
event Staked(address indexed user, uint256 amount, uint256 shares);
event Unstaked(address indexed user, uint256 shares, uint256 amount);
function stake() external payable {
require(msg.value > 0, "Must stake positive amount");
uint256 newShares = calculateShares(msg.value);
shares[msg.sender] += newShares;
totalShares += newShares;
// Delegate to validator
bool success = staking.delegate{value: msg.value}(VALIDATOR);
require(success, "Delegation failed");
emit Staked(msg.sender, msg.value, newShares);
}
function unstake(uint256 shareAmount) external {
require(shares[msg.sender] >= shareAmount, "Insufficient shares");
require(shareAmount > 0, "Must unstake positive amount");
uint256 tokenAmount = calculateTokens(shareAmount);
shares[msg.sender] -= shareAmount;
totalShares -= shareAmount;
bool success = staking.undelegate(VALIDATOR, tokenAmount);
require(success, "Undelegation failed");
emit Unstaked(msg.sender, shareAmount, tokenAmount);
}
function calculateShares(uint256 tokenAmount) public view returns (uint256) {
if (totalShares == 0) {
return tokenAmount;
}
uint256 totalTokens = getTotalStaked();
return (tokenAmount * totalShares) / totalTokens;
}
function calculateTokens(uint256 shareAmount) public view returns (uint256) {
if (totalShares == 0) {
return 0;
}
uint256 totalTokens = getTotalStaked();
return (shareAmount * totalTokens) / totalShares;
}
function getTotalStaked() public view returns (uint256) {
IStaking.Delegation memory delegation = staking.delegation(address(this), VALIDATOR);
return delegation.balance.amount;
}
}
Gas Optimization Techniques
Struct Packing
contracts/OptimizedStorage.sol
contract OptimizedStorage {
// BAD: Uses 3 storage slots
struct Inefficient {
uint256 amount; // 256 bits = 1 slot
uint64 timestamp; // 64 bits = 1 slot (wastes 192 bits)
uint64 nonce; // 64 bits = 1 slot (wastes 192 bits)
}
// GOOD: Uses 1 storage slot
struct OptimalPacked {
uint128 amount; // 128 bits
uint64 timestamp; // 64 bits
uint64 nonce; // 64 bits
// Total: 256 bits = 1 slot
}
}
Event-Driven Architecture
contracts/EventDriven.sol
contract EventDrivenAuction {
struct Auction {
uint128 highestBid;
address highestBidder;
uint64 endTime;
}
mapping(uint256 => Auction) public auctions;
// Store bid history in events, not storage
event BidPlaced(uint256 indexed auctionId, address indexed bidder, uint256 amount);
event AuctionWon(uint256 indexed auctionId, address indexed winner, uint256 amount);
function bid(uint256 auctionId) external payable {
Auction storage auction = auctions[auctionId];
require(block.timestamp < auction.endTime, "Auction ended");
require(msg.value > auction.highestBid, "Bid too low");
// Refund previous bidder
if (auction.highestBidder != address(0)) {
payable(auction.highestBidder).transfer(auction.highestBid);
}
auction.highestBid = uint128(msg.value);
auction.highestBidder = msg.sender;
// Emit event instead of storing bid history
emit BidPlaced(auctionId, msg.sender, msg.value);
}
}
Common Pitfalls & Solutions
Avoiding Sequential Bottlenecks
contracts/AvoidBottlenecks.sol
contract AvoidBottlenecks {
// BAD: Global nonce forces sequential execution
uint256 public globalNonce;
mapping(uint256 => address) public nonceToUser;
function badMint() external {
uint256 tokenId = globalNonce++;
nonceToUser[tokenId] = msg.sender;
}
// GOOD: User-specific nonces allow parallelism
mapping(address => uint256) public userNonces;
mapping(address => mapping(uint256 => uint256)) public userTokens;
function goodMint() external {
uint256 userNonce = userNonces[msg.sender]++;
userTokens[msg.sender][userNonce] = block.timestamp;
}
}
Optimistic Execution
contracts/OptimisticExecution.sol
contract OptimisticVault {
mapping(address => uint256) public balances;
uint256 public totalSupply;
// Optimistic deposit - assumes success
function deposit(uint256 amount) external returns (uint256 shares) {
// Calculate shares optimistically
if (totalSupply == 0) {
shares = amount;
} else {
shares = (amount * totalSupply) / address(this).balance;
}
// Update state
balances[msg.sender] += shares;
totalSupply += shares;
// Transfer happens last - if it fails, entire tx reverts
(bool success,) = address(this).call{value: amount}("");
require(success, "Transfer failed");
}
}
Development Checklists
Gas Optimization Checklist
- ✓ Pack structs to minimize storage slots
- ✓ Use events for historical data
- ✓ Cache array lengths in loops
- ✓ Use
unchecked
blocks where safe - ✓ Batch operations when possible
- ✓ Minimize external calls
Parallel Execution Checklist
- ✓ Isolate state by user/entity
- ✓ Avoid global counters
- ✓ Use mapping over arrays for lookups
- ✓ Design for optimistic execution
- ✓ Minimize shared state dependencies
Security Considerations
- ✓ Follow checks-effects-interactions pattern
- ✓ Use custom errors for gas efficiency
- ✓ Implement proper access controls
- ✓ Handle all return values
- ✓ Consider reentrancy guards where needed
Next Steps
Now that you understand Sei Giga development patterns:
- Learn the Fundamentals - Review the Sei Giga Overview for architecture details
- Review Technical Specs - Study the Technical Specifications for detailed specifications
- Join the Community - Connect with other developers on Discord
Last updated on