This document explains how to use the contract service layer to interact with the Soroban raffle smart contract.
The contract service layer provides a clean interface for interacting with the raffle smart contract, including:
- Configuration: Contract address and network settings
- Read Operations: Fetching raffle data, active raffles, user participation
- Write Operations: Creating raffles, buying tickets
- Error Handling: User-friendly error messages and proper error types
- React Integration: Custom hooks for state management
First, configure your environment variables in .env:
# Contract Configuration
VITE_RAFFLE_CONTRACT_ADDRESS=CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM
VITE_CONTRACT_DEPLOYMENT_HASH=abc123...
# Network Configuration
VITE_STELLAR_NETWORK=testnet
VITE_SOROBAN_RPC_URL=https://soroban-testnet.stellar.orgimport { useContract } from '../hooks/useContract';
function RaffleComponent() {
const {
getRaffleData,
getActiveRaffleIds,
createRaffle,
buyTicket,
isLoading,
error,
isConfigured
} = useContract();
// Check if contract is configured
if (!isConfigured) {
return <div>Contract not configured. Please deploy contract first.</div>;
}
// Fetch active raffles
const loadActiveRaffles = async () => {
const activeIds = await getActiveRaffleIds();
console.log('Active raffles:', activeIds);
};
// Create a new raffle
const handleCreateRaffle = async () => {
const params = {
metadataId: "supabase-record-id",
ticketPrice: "1000000", // 1 XLM in stroops
totalTickets: 100,
durationInSeconds: 86400 // 24 hours
};
const txHash = await createRaffle(params);
if (txHash) {
console.log('Raffle created! Transaction:', txHash);
}
};
return (
<div>
{isLoading && <div>Loading...</div>}
{error && <div>Error: {error}</div>}
<button onClick={loadActiveRaffles}>
Load Active Raffles
</button>
<button onClick={handleCreateRaffle}>
Create Raffle
</button>
</div>
);
}The main service class for contract interactions.
// Get raffle data by ID
const result = await ContractService.getRaffleData(raffleId);
if (result.success) {
console.log('Raffle data:', result.data);
}
// Get active raffle IDs
const activeResult = await ContractService.getActiveRaffleIds();
if (activeResult.success) {
console.log('Active raffles:', activeResult.data);
}
// Get user participation
const participation = await ContractService.getUserParticipation(userAddress, raffleId);
if (participation.success && participation.data) {
console.log('User has', participation.data.ticketsPurchased, 'tickets');
}// Create a raffle
const createParams = {
metadataId: "metadata-record-id",
ticketPrice: "1000000", // 1 XLM in stroops
totalTickets: 100,
durationInSeconds: 86400
};
const createResult = await ContractService.createRaffle(createParams);
if (createResult.success) {
console.log('Transaction hash:', createResult.transactionHash);
}
// Buy tickets
const buyParams = {
raffleId: 1,
ticketCount: 5,
maxPricePerTicket: "1000000" // Slippage protection
};
const buyResult = await ContractService.buyTicket(buyParams);
if (buyResult.success) {
console.log('Tickets purchased! TX:', buyResult.transactionHash);
}React hook for contract interactions with state management.
const {
// State
isLoading,
error,
isConfigured,
// Read functions
getRaffleData,
getActiveRaffleIds,
getAllRaffleIds,
getUserParticipation,
// Write functions
createRaffle,
buyTicket,
// Transaction status
transactionStatus,
clearTransactionStatus,
// Utilities
refresh,
clearError
} = useContract();Hook for efficiently fetching multiple raffles:
const { raffles, isLoading, error, refresh } = useRaffles([1, 2, 3, 4]);
// raffles will contain ContractRaffleData[] for the specified IDsThe contract service provides comprehensive error handling with user-friendly messages:
const result = await ContractService.getRaffleData(999);
if (!result.success) {
switch (result.error) {
case "Network connection failed":
// Handle network error
break;
case "Raffle not found or has been removed":
// Handle not found error
break;
case "Wallet operation failed":
// Handle wallet error
break;
default:
// Handle unknown error
}
}NETWORK_ERROR: Connection or RPC issuesCONTRACT_ERROR: Smart contract execution errorsWALLET_ERROR: Wallet connection or signing issuesVALIDATION_ERROR: Invalid parametersINSUFFICIENT_FUNDS: Not enough balanceRAFFLE_NOT_FOUND: Raffle doesn't existRAFFLE_ENDED: Raffle has endedRAFFLE_FULL: All tickets soldUNAUTHORIZED: Permission deniedUNKNOWN_ERROR: Unexpected errors
Monitor transaction status with the useContract hook:
const { transactionStatus, clearTransactionStatus } = useContract();
useEffect(() => {
if (transactionStatus) {
switch (transactionStatus.status) {
case "pending":
console.log("Transaction pending...");
break;
case "success":
console.log("Transaction successful:", transactionStatus.hash);
break;
case "failed":
console.log("Transaction failed:", transactionStatus.error?.message);
break;
}
}
}, [transactionStatus]);The service includes validation constants:
CONTRACT_CONFIG.constants = {
minTicketPrice: 1000000, // 0.1 XLM minimum
maxTickets: 10000, // Maximum tickets per raffle
minDuration: 3600, // 1 hour minimum
maxDuration: 2592000, // 30 days maximum
}Automatically configured based on environment:
CONTRACT_CONFIG = {
address: "CAAAA...", // From VITE_RAFFLE_CONTRACT_ADDRESS
network: "testnet", // From VITE_STELLAR_NETWORK
networkPassphrase: "Test SDF...", // Auto-selected
rpcUrl: "https://soroban-testnet...", // From VITE_SOROBAN_RPC_URL
}// 1. Upload metadata to Supabase
const metadataId = await MetadataService.uploadRaffleMetadata({
title: "Amazing Prize Raffle",
description: "Win an amazing prize!",
// ... other metadata
});
// 2. Create raffle with metadata ID
const txHash = await createRaffle({
metadataId,
ticketPrice: "1000000",
totalTickets: 100,
durationInSeconds: 86400
});
// 3. Link contract raffle ID to metadata (after transaction confirms)
await MetadataService.linkToContract(metadataId, contractRaffleId);The contract service automatically uses the connected wallet:
// Wallet must be connected before contract operations
const { isConnected } = useWalletContext();
if (isConnected) {
// Contract operations will use the connected wallet
const result = await createRaffle(params);
}When VITE_RAFFLE_CONTRACT_ADDRESS is not set, the service will warn but not crash:
if (!ContractService.isConfigured()) {
console.log("Contract not configured - using demo data");
// Fall back to demo data or mock responses
}Test error handling by providing invalid parameters:
// This will trigger validation error
const result = await ContractService.createRaffle({
metadataId: "", // Invalid: empty string
ticketPrice: "100", // Invalid: below minimum
totalTickets: 50000, // Invalid: above maximum
durationInSeconds: 60 // Invalid: below minimum
});
console.log(result.error); // "Metadata ID is required"- Always check
isConfigured()before using contract functions - Handle errors gracefully with user-friendly messages
- Use loading states to provide feedback during operations
- Validate parameters before calling contract functions
- Monitor transaction status for write operations
- Clear errors after user actions to reset state
- Use the React hooks instead of calling service directly in components
-
"Contract address not configured"
- Set
VITE_RAFFLE_CONTRACT_ADDRESSin your.envfile - Deploy the contract first if not done
- Set
-
"Network connection failed"
- Check
VITE_SOROBAN_RPC_URLis correct - Verify network connectivity
- Try a different RPC endpoint
- Check
-
"Wallet not connected"
- Ensure wallet is connected before contract operations
- Check wallet provider is installed
-
"Simulation failed"
- Check contract parameters are valid
- Ensure contract is deployed to the correct network
- Verify contract address is correct
-
"Insufficient funds"
- Check wallet has enough XLM for transaction fees
- For ticket purchases, ensure enough balance for tickets + fees