A next-generation singleton smart contract protocol for onchain event management with comprehensive social coordination, location tracking, and multi-currency support.
Built with ERC-6909 multi-token standard, EIP-1153 transient storage, soulbound credentials, and advanced venue management.
- Multi-Tier Ticketing - Configurable pricing, capacity limits, and payment splits
- Location Integration - GPS coordinate storage with efficient data packing
- Venue Credential System - Soulbound tokens for organizers with venue experience
- Multi-Currency Support - ETH and ERC20 token payments (USDC, DAI, etc.)
- Advanced Refund System - Automatic refunds for cancelled events with 90-day claim window
- Private Events - Invite-only events with curated guest lists
- Flexible Visibility - Public, private, and invite-only event types
- Social Graph - Friends, RSVPs, and social discovery with onchain coordination
- Group Check-ins - Delegate check-in functionality for group ticket purchases
- Platform Fees - Optional 0-5% referral fees to incentivize ecosystem growth
- ERC20 Payments - Support for stablecoins and major tokens
- Payment Splits - Automatic revenue distribution to multiple recipients
- Pull Payment Pattern - Secure fund distribution with withdrawal mechanism
- GPS Coordinate Storage - Precise location data with efficient packing
- Venue Hash System - Efficient venue identification and tracking
- Venue Credentials - Soulbound reputation tokens for experienced organizers
- Location-Based Discovery - Events searchable by geographic location
- ERC-6909 Multi-Token - Efficient batch operations for tickets and badges
- Transferrable Tickets - Standard event tickets with resale capability
- Soulbound Badges - Non-transferable attendance and organizer credentials
- Venue Credentials - Permanent reputation tokens for venue organizers
- Comment System - Threaded event discussions with moderation controls
- RSVP Tracking - Social commitment and attendance prediction
- Friend Network - Social connections for event discovery
- Social Verification - Reputation building through attendance history
Singleton Design - Single contract manages all events, users, and social interactions
Multi-Token Standard - ERC-6909 enables efficient batch operations for tickets and badges
Gas Optimization - EIP-1153 transient storage reduces gas costs for complex operations
Soulbound Credentials - Non-transferable tokens for attendance proof and organizer reputation
Location Integration - GPS coordinates packed efficiently in 128-bit storage
Multi-Currency - Native ETH and ERC20 token support with unified payment handling
EVENT_TICKET- Transferrable tickets for event accessATTENDANCE_BADGE- Soulbound proof of attendance (ERC-5192)ORGANIZER_CRED- Soulbound reputation tokens for event organizersVENUE_CRED- Soulbound venue management credentials
PUBLIC- Open to all users with full visibilityPRIVATE- Limited visibility, discoverable but restrictedINVITE_ONLY- Curated guest list with strict access control
- ETH - Native Ethereum payments
- ERC20 Tokens - USDC, DAI, and other standard tokens
- Mixed Payments - Tips in ETH, tickets in ERC20 (or vice versa)
forge install# Run all tests (196 tests)
forge test
# Run with gas reporting
forge test --gas-report
# Run specific test suites
forge test --match-contract "LocationSystem"
forge test --match-contract "ERC20Payment"
forge test --match-contract "VenueSystem"# Start local testnet
anvil
# Deploy contracts for local testing (see test files for examples)
forge test// Create event with location data
function createEvent(
EventParams calldata params,
TicketTier[] calldata tiers,
PaymentSplit[] calldata splits
) external returns (uint256 eventId)// ETH payments
function purchaseTickets(uint256 eventId, uint256 tierId, uint256 quantity) external payable
function purchaseTickets(uint256 eventId, uint256 tierId, uint256 quantity, address referrer, uint256 platformFeeBps) external payable
// ERC20 payments
function purchaseTicketsERC20(uint256 eventId, uint256 tierId, uint256 quantity, address token) external
function purchaseTicketsERC20(uint256 eventId, uint256 tierId, uint256 quantity, address token, address referrer, uint256 platformFeeBps) external// Tip events with ERC20 tokens
function tipEventERC20(uint256 eventId, address token, uint256 amount) external
function tipEventERC20(uint256 eventId, address token, uint256 amount, address referrer, uint256 platformFeeBps) external
// Withdraw ERC20 earnings
function claimERC20Funds(address token) external
// Check token support
function setSupportedToken(address token, bool supported) externalfunction addFriend(address friend) external
function updateRSVP(uint256 eventId, RSVPStatus status) external
function postComment(uint256 eventId, string calldata content, uint256 parentId) externalfunction inviteToEvent(uint256 eventId, address[] calldata invitees) external
function isInvited(uint256 eventId, address user) external view returns (bool)┌─────────────┬─────────────┬─────────────┬─────────────┬─────────────┐
│ TokenType │ EventId │ TierId │ SerialNum │ Metadata │
│ (8 bits) │ (64 bits) │ (32 bits) │ (64 bits) │ (88 bits) │
└─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘
Single uint256 encodes token type, event reference, tier information, and unique serial number.
Precise GPS coordinate storage with efficient data packing:
// Create event with NYC coordinates (40.7431° N, 73.9597° W)
int64 latitude = 40743100; // 40.7431 * 1e6
int64 longitude = -73959700; // -73.9597 * 1e6
uint256 eventId = assemble.createEvent{value: PLATFORM_FEE}(
params, // includes latitude/longitude
tiers,
splits
);
// Location data is automatically packed and stored efficientlyKey Features:
- Precision: 6 decimal places (±1 meter accuracy)
- Efficient Storage: 128-bit packed coordinates
- Global Coverage: Full latitude/longitude range support
- Integration Ready: Compatible with mapping APIs and services
Organizers earn soulbound venue credentials for hosting events:
// Create event at specific venue
uint256 eventId = assemble.createEvent{value: PLATFORM_FEE}(
params, // includes venueName: "The Fillmore"
tiers,
splits
);
// Organizer automatically receives venue credential token (soulbound, non-transferable)
// Venue credentials accumulate over time and build reputationBenefits:
- Reputation Building - Permanent record of venue management experience
- Trust Signals - Visible proof of successful event history
- Venue Partnerships - Credentials can unlock venue-specific benefits
- Anti-Fraud - Prevents false venue claims and credential farming
Support for ETH and major ERC20 tokens:
// USDC ticket purchase
IERC20(USDC).approve(address(assemble), ticketPrice);
assemble.purchaseTicketsERC20(eventId, tierId, quantity, USDC);
// DAI event tip with platform fee
IERC20(DAI).approve(address(assemble), tipAmount);
assemble.tipEventERC20(eventId, DAI, tipAmount, platformAddress, 200);
// Mixed payments - ETH tips, USDC tickets
assemble.tipEvent{value: 0.01 ether}(eventId);
assemble.purchaseTicketsERC20(eventId, 0, 1, USDC);Supported Tokens:
- ETH - Native Ethereum
- USDC - USD Coin stablecoin
- DAI - Dai stablecoin
- Any ERC20 - Standard token interface support
Features:
- Pull Payment Pattern - Secure withdrawal mechanism
- Mixed Currency Events - Accept both ETH and ERC20
- Platform Fee Support - Platform fees work with all currencies
- Automatic Splitting - Revenue splits work across all payment types
Perfect for exclusive gatherings, private parties, corporate events, and curated experiences:
// Create invite-only event with location
EventParams memory params = EventParams({
organizer: msg.sender,
startTime: block.timestamp + 3600,
venueName: "Private Residence",
visibility: EventVisibility.INVITE_ONLY,
// ... other params including GPS coordinates
});
uint256 eventId = assemble.createEvent{value: PLATFORM_FEE}(
params, tiers, splits
);
// Invite specific guests
address[] memory guests = [alice, bob, charlie];
assemble.inviteToEvent(eventId, guests);
// Only invited users can see and purchase ticketsUse Cases:
- Wedding Celebrations - Guest list management with location sharing
- Exclusive Art Openings - Curated audience with venue credentials
- Corporate Retreats - Private events with expense tracking
- VIP Product Launches - Controlled access with social verification
- Community Gatherings - Intimate events with reputation requirements
Platform fees (0-5%) enable sustainable ecosystem growth:
// Music venue gets 2% for hosting (ETH)
assemble.purchaseTickets{value: ticketPrice}(eventId, 0, 1, venueAddress, 200);
// Event platform gets 1.5% for promotion (USDC)
assemble.purchaseTicketsERC20(eventId, 0, 1, USDC, platformAddress, 150);
// Cross-currency platform fees work seamlessly
assemble.tipEventERC20(eventId, DAI, tipAmount, influencerAddress, 100);Ecosystem Participants:
- Music Venues - Earn fees for hosting and promotion
- Event Platforms - Monetize discovery and booking services
- Influencer Partners - Revenue sharing for audience development
- Corporate Sponsors - Track ROI on event investments
- Community Builders - Sustain long-term community operations
- Venue Partners - Leverage credentials for preferential rates
Consolidated error handling for better UX:
// Consolidated error system
error SocialError(); // Social interaction failures
error PaymentError(); // Payment and financial failures
error EventError(); // Event management failures
error AccessError(); // Permission and access failures
error ValidationError(); // Input validation failuresBenefits:
- Smaller Contract Size - Consolidated errors reduce bytecode
- Better DX - Consistent error patterns across functions
- Gas Efficiency - Reduced deployment and execution costs
- Frontend Integration - Easier error handling in applications
- Core Functionality - 40+ tests covering all primary features
- Security Tests - 17 tests for attack vectors and edge cases
- Edge Case Tests - 22 tests for boundary conditions
- Fuzz Tests - 15 property-based tests (1000 runs each)
- Invariant Tests - 8 stateful tests (256 runs each)
- Scenario Tests - 25 real-world usage patterns
- Private Event Tests - 12 access control and privacy tests
- Location Tests - 10 GPS and venue system tests
- ERC20 Payment Tests - 15 multi-currency transaction tests
- Static Analysis Clean - Zero issues with Slither
- EIP-1153 Reentrancy Protection - Transient storage guards
- Pull Payment Pattern - Secure fund distribution
- Soulbound Token Enforcement - Prevents credential transfer
- Access Control - Role-based permissions throughout
- Input Validation - Comprehensive parameter checking
- Integer Overflow Protection - SafeMath patterns where needed
- Contract Size: 24,214 bytes (under 24,576 limit)
- EIP-1153 Transient Storage - Reduced state storage costs
- Efficient Data Packing - Optimized struct layouts
- Batch Operations - ERC-6909 multi-token efficiency
- View Function Optimization - Direct mapping access patterns
This protocol has not been audited. Use at your own risk.
| Operation | ETH Gas | ERC20 Gas |
|---|---|---|
| Create Event | 391,143 | N/A |
| Create Event + Location | 398,180 | N/A |
| Purchase Tickets | 153,928 | 195,440 |
| Purchase with Platform Fee | ~165,000 | ~210,000 |
| Private Event Invitations | 71,758 | N/A |
| Check-in Operations | 69,378 - 82,124 | N/A |
| Social Operations (RSVP/Friends) | 24,243 - 78,604 | N/A |
| ERC20 Tips | N/A | 117,041 |
| ERC20 Withdrawals | N/A | 53,918 |
| Location Queries | 12,545 | N/A |
Note: Gas usage measured on latest protocol version. Actual usage may vary based on transaction complexity, network conditions, and specific parameters used.
import { keccak256, toBytes } from 'viem';
import { publicClient, walletClient } from './config'; // your viem client setup
// Create event (NO ETH VALUE REQUIRED - creation is free)
const tx = await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'createEvent',
args: [params, ticketTiers, paymentSplits] // params includes latitude/longitude
// NO value parameter - event creation is free!
});
// Purchase tickets with USDC (platform fee charged here, not creation)
await walletClient.writeContract({
address: USDC_ADDRESS,
abi: erc20Abi,
functionName: 'approve',
args: [ASSEMBLE_ADDRESS, ticketPrice]
});
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'purchaseTicketsERC20',
args: [eventId, tierId, quantity, USDC_ADDRESS]
});
// Purchase with platform fee (2% to venue partner)
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'purchaseTickets',
args: [eventId, tierId, quantity, venuePartnerAddress, 200], // 200 = 2%
value: ticketPrice
});
// Check venue credentials
const venueHash = keccak256(toBytes(venueName));
const credTokenId = await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'generateTokenId',
args: [4, 0, venueHash, 0] // TokenType.VENUE_CRED = 4
});
const hasCredential = await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'balanceOf',
args: [organizer, credTokenId]
});import { keccak256, toBytes } from 'viem';
import { publicClient } from './config';
// Location-based event discovery
const events = await fetchEventsNearLocation(latitude, longitude, radiusKm);
// Multi-currency price display
const prices = {
eth: await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'calculatePrice',
args: [eventId, tierId, quantity]
}),
usdc: await getUSDCPrice(eventId, tierId, quantity),
dai: await getDAIPrice(eventId, tierId, quantity)
};
// Venue reputation display
const venueHash = keccak256(toBytes(venueName));
const venueStats = {
eventCount: await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'venueEventCount',
args: [venueHash]
}),
hasCredential: await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'balanceOf',
args: [organizer, credTokenId]
}) > 0
};- Rate: 0.5% (50 basis points) by default, maximum 10%
- Charged On: All ticket purchases and tips
- Goes To: Protocol treasury (
feeToaddress) - Purpose: Protocol maintenance and development
- Rate: 0-5% (0-500 basis points) - set per transaction
- Charged On: Ticket purchases and tips (when specified)
- Goes To: Referrer/platform address specified in transaction
- Purpose: Ecosystem growth, venue partnerships, influencer rewards
// User buys 1 ticket for 0.1 ETH - NO platform fee
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'purchaseTickets',
args: [eventId, 0, 1], // No referrer, no platform fee
value: parseEther('0.1') // 0.1 ETH
});
// Fee calculation:
// Ticket price: 0.1 ETH
// Protocol fee: 0.1 ETH × 0.5% = 0.0005 ETH (goes to protocol)
// Net to organizer: 0.1 ETH - 0.0005 ETH = 0.0995 ETH// User buys 1 ticket for 0.1 ETH with 2% platform fee to venue partner
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'purchaseTickets',
args: [eventId, 0, 1, venuePartnerAddress, 200], // 200 = 2% platform fee
value: parseEther('0.1')
});
// Fee calculation (order: Platform fee → Protocol fee → Event splits):
// 1. Ticket price: 0.1 ETH
// 2. Platform fee: 0.1 ETH × 2% = 0.002 ETH (goes to venue partner)
// 3. Remaining: 0.1 ETH - 0.002 ETH = 0.098 ETH
// 4. Protocol fee: 0.098 ETH × 0.5% = 0.00049 ETH (goes to protocol)
// 5. Net to organizer: 0.098 ETH - 0.00049 ETH = 0.09751 ETH// User tips 0.05 ETH with 1.5% platform fee to influencer
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'tipEvent',
args: [eventId, influencerAddress, 150], // 150 = 1.5% platform fee
value: parseEther('0.05')
});
// Fee calculation:
// 1. Tip amount: 0.05 ETH
// 2. Platform fee: 0.05 ETH × 1.5% = 0.00075 ETH (goes to influencer)
// 3. Remaining: 0.05 ETH - 0.00075 ETH = 0.04925 ETH
// 4. Protocol fee: 0.04925 ETH × 0.5% = 0.00024625 ETH (goes to protocol)
// 5. Net to event splits: 0.04925 ETH - 0.00024625 ETH = 0.04900375 ETH// User buys ticket with 100 USDC + 3% platform fee to venue
await erc20Contract.writeContract({
functionName: 'approve',
args: [ASSEMBLE_ADDRESS, parseUnits('100', 6)] // USDC has 6 decimals
});
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'purchaseTicketsERC20',
args: [eventId, 0, 1, USDC_ADDRESS, venueAddress, 300] // 300 = 3%
});
// Fee calculation (USDC amounts):
// 1. Ticket price: 100 USDC
// 2. Platform fee: 100 USDC × 3% = 3 USDC (goes to venue)
// 3. Remaining: 100 USDC - 3 USDC = 97 USDC
// 4. Protocol fee: 97 USDC × 0.5% = 0.485 USDC (goes to protocol)
// 5. Net to organizer: 97 USDC - 0.485 USDC = 96.515 USDC// Event with payment splits: 60% organizer, 30% artist, 10% venue
const paymentSplits = [
{ recipient: organizerAddress, bps: 6000 }, // 60%
{ recipient: artistAddress, bps: 3000 }, // 30%
{ recipient: venueAddress, bps: 1000 } // 10%
];
// User tips 1 ETH with 2% platform fee to promoter
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'tipEvent',
args: [eventId, promoterAddress, 200], // 200 = 2%
value: parseEther('1.0')
});
// Fee calculation & distribution:
// 1. Tip amount: 1.0 ETH
// 2. Platform fee: 1.0 ETH × 2% = 0.02 ETH → promoterAddress
// 3. Remaining: 1.0 ETH - 0.02 ETH = 0.98 ETH
// 4. Protocol fee: 0.98 ETH × 0.5% = 0.0049 ETH → protocol treasury
// 5. Net for splits: 0.98 ETH - 0.0049 ETH = 0.9751 ETH
// 6. Distribution:
// - Organizer: 0.9751 ETH × 60% = 0.58506 ETH
// - Artist: 0.9751 ETH × 30% = 0.29253 ETH
// - Venue: 0.9751 ETH × 10% = 0.09751 ETH// Music venue gets 2% for hosting/promoting
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'purchaseTickets',
args: [eventId, 0, 2, musicVenueAddress, 200], // 2 tickets, 2% to venue
value: parseEther('0.2') // 0.1 ETH per ticket
});
// Event discovery platform gets 1% for driving traffic
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'tipEvent',
args: [eventId, eventPlatformAddress, 100], // 1% to platform
value: parseEther('0.1')
});
// Social media influencer gets 2.5% for promotion
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'purchaseTickets',
args: [eventId, 1, 1, influencerAddress, 250], // 2.5% to influencer
value: parseEther('0.15')
});// Check current protocol fee rate
const protocolFeeBps = await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'protocolFeeBps'
});
console.log(`Protocol fee: ${protocolFeeBps / 100}%`); // e.g., "Protocol fee: 0.5%"
// Check pending withdrawals (includes fees received)
const pendingFees = await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'pendingWithdrawals',
args: [feeRecipientAddress]
});
// Check ERC20 fees pending
const pendingUSDC = await publicClient.readContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'pendingERC20Withdrawals',
args: [USDC_ADDRESS, feeRecipientAddress]
});- Creation Cost: ✅ FREE - No fees to create events
- Cancellation: Events can be cancelled before start time
- Refund Window: 90 days after cancellation
- Full Refunds: Users get back exactly what they paid for tickets/tips
- Protocol Fees: Not refunded (already used for infrastructure)
- Platform Fees: Not refunded (already paid to referrers)
// Example: Event cancelled, user gets refund
// User originally paid: 0.1 ETH for ticket
// Platform fee: 0.002 ETH (already paid to venue partner - not refunded)
// Protocol fee: 0.00049 ETH (already paid to protocol - not refunded)
// User refund: 0.1 ETH (full original ticket price returned)
await walletClient.writeContract({
address: ASSEMBLE_ADDRESS,
abi: assembleAbi,
functionName: 'claimTicketRefund',
args: [cancelledEventId]
});
// User receives back: 0.1 ETH (their full ticket payment)Assemble Protocol is deployed with identical vanity addresses across 8 networks:
Mainnet (Chain ID: 1):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Verified - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Verified
Sepolia Testnet (Chain ID: 11155111):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Verified - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Verified
Base Mainnet (Chain ID: 8453):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Verified - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Verified
Base Sepolia (Chain ID: 84532):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Verified - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Verified
Optimism (Chain ID: 10):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Verified - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Verified
Arbitrum One (Chain ID: 42161):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Verified - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Verified
Polygon (Chain ID: 137):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Deployed * - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Deployed *
Zora (Chain ID: 7777777):
- Assemble:
0x000000000a020d45fFc5cfcF7B28B5020ddd6a85✅ Verified - SocialLibrary:
0xebE033f26d5CAb84F5C174C882e2e036F59FAD55✅ Verified
* Verification failed due to foundry bug #3507 with via_ir compiler setting on PolygonScan
# Clone repository
git clone https://github.com/your-org/assemble-protocol
cd assemble-protocol
# Install dependencies
forge install
# Run tests
forge test
# Run coverage
forge coverage
# Deploy locally
anvil &
forge testMIT License - see LICENSE for details.
Built with love for the onchain event ecosystem
Assemble Protocol - Where events meet the future of web3