WesternShootout is a feature-rich smart contract that enables 1vs1 wagered gameplay with automated prize distribution, dispute resolution, and comprehensive player statistics tracking. The contract manages multiple concurrent game sessions, player matchmaking, and secure prize distribution while maintaining a complete history of games and player statistics on-chain.
Contract Address (Polygon AMOY): 0x17B6f309E57F684F643C4C3F8A91C6a8219C53f0
- ๐ฎ Multi-game Support: Run multiple concurrent games simultaneously
- ๐ฐ Customizable Wagers: Each game can have its own wager amount
- โฑ๏ธ Timeout Mechanism: Fair handling of abandoned or stalled games
- โ๏ธ Dispute Resolution: Contest results with arbiter voting system
- ๐ Gas Optimized: Efficient data structures and operations
- ๐ On-chain Statistics: Comprehensive player performance tracking
- ๐ Game Discovery: Find games matching specific criteria
- ๐ Automatic Matchmaking: Join existing games or create new ones
- Waiting: New game waiting for players to join
- Ready: Two players have joined, game is in progress
- Completed: Game has ended with a winner declared
- TimedOut: Game was abandoned and resolved via timeout
- Disputed: Game outcome is being contested by a player
Each game contains:
- Unique game ID
- Player addresses (player1 & player2)
- Winner address
- Wager amount
- Fee amount
- Current state
- Timestamps (creation, ready state, dispute)
- Dispute information and votes
Tracks for each player:
- Total wins
- Total losses
- Games played
- Timeouts
- Disputes initiated
For dispute resolution:
- List of approved arbiters
- Voting mechanism
- Dispute tracking
- Resolution based on majority vote
joinGame(uint256 gameId)
: Join a specific game by IDfindAndJoinGame()
: Automatically find or create a game matching your wager
getPlayerGames(address player)
: Get all games a player is participating ingetGameDetails(uint256 gameId)
: Get complete information about a gamefindAvailableGames(uint256 wagerAmount, uint256 limit)
: Find games with specific wager
initiateDispute(uint256 gameId)
: Contest the outcome of a completed gamegetDisputeInfo(uint256 gameId)
: Get information about a dispute
createGame(uint256 wagerAmount)
: Create a new game with specified wagercreateGames(uint256 count, uint256 wagerAmount)
: Create multiple games at oncedeclareWinner(uint256 gameId, address winner)
: Declare the winner of a gamecancelGame(uint256 gameId)
: Cancel an incomplete game and refund players
setDefaultWager(uint256 newDefaultWager)
: Set the default wager amountsetGameTimeout(uint256 newTimeout)
: Set the timeout durationsetDisputeWindow(uint256 newWindow)
: Set the dispute window duration
resolveTimedOutGame(uint256 gameId)
: Resolve a game that has timed outgetTimedOutGames(uint256 limit)
: Get a list of games that have timed outisGameTimedOut(uint256 gameId)
: Check if a specific game has timed out
voteOnDispute(uint256 gameId, address votedWinner, string calldata reason)
: Vote on a disputed gamegetVoteDetails(uint256 gameId, uint256 voteIndex)
: Get details of a specific vote
addArbiter(address arbiter)
: Add a new arbiter (owner only)removeArbiter(address arbiter)
: Remove an arbiter (owner only)getArbiters(uint256 offset, uint256 limit)
: Get the list of arbiters
The contract emits the following events:
GameCreated
: When a new game is createdPlayerJoined
: When a player joins a gameWinnerDeclared
: When a winner is declaredGameTimedOut
: When a game times outDisputeInitiated
: When a player initiates a disputeDisputeVoteCast
: When an arbiter votes on a disputeDisputeResolved
: When a dispute is resolvedArbiterAdded
: When a new arbiter is addedArbiterRemoved
: When an arbiter is removed
- Connect to the Contract
// Using ethers.js
const contractAddress = "0x17B6f309E57F684F643C4C3F8A91C6a8219C53f0";
const westernShootout = new ethers.Contract(contractAddress, WesternShootoutABI, provider);
- Create or Join a Game
// Create a new game with 0.01 POL wager
const tx = await westernShootout.createGame(ethers.utils.parseEther("0.01"));
await tx.wait();
// Or find and join a game with 0.01 POL wager
const tx = await westernShootout.findAndJoinGame({
value: ethers.utils.parseEther("0.01")
});
await tx.wait();
- Monitor Game Events
// Listen for PlayerJoined events
westernShootout.on("PlayerJoined", (gameId, player) => {
console.log(`Player ${player} joined game ${gameId}`);
});
// Listen for WinnerDeclared events
westernShootout.on("WinnerDeclared", (gameId, winner, payout, fee) => {
console.log(`Game ${gameId} won by ${winner} with payout ${payout}`);
});
- Declare Winner
// Only callable by contract owner
const tx = await westernShootout.declareWinner(gameId, winnerAddress);
await tx.wait();
- Handle Timeouts
// Check if a game has timed out
const isTimedOut = await westernShootout.isGameTimedOut(gameId);
// Get time remaining before timeout
const timeRemaining = await westernShootout.getTimeRemaining(gameId);
// Resolve a timed out game (owner only)
if (isTimedOut) {
const tx = await westernShootout.resolveTimedOutGame(gameId);
await tx.wait();
}
- Dispute Resolution
// Initiate a dispute (must be a player in the game)
const tx = await westernShootout.initiateDispute(gameId);
await tx.wait();
// Vote on a dispute (must be an arbiter)
const tx = await westernShootout.voteOnDispute(gameId, playerAddress, "Evidence shows this player won");
await tx.wait();
// Get dispute information
const disputeInfo = await westernShootout.getDisputeInfo(gameId);
- Query Player Statistics
const playerStats = await westernShootout.playerStats(playerAddress);
console.log(`Wins: ${playerStats.wins}`);
console.log(`Losses: ${playerStats.losses}`);
console.log(`Games Played: ${playerStats.gamesPlayed}`);
console.log(`Timeouts: ${playerStats.timeouts}`);
console.log(`Disputes: ${playerStats.disputes}`);
WesternShootout is designed to integrate seamlessly with Unreal Engine 5 games using ThirdWeb Engine.
- Initialize ThirdWeb Engine
// In your game initialization
void AMyGameMode::InitializeBlockchain()
{
// Initialize ThirdWeb with Polygon Amoy network
ThirdWebSubsystem = GEngine->GetEngineSubsystem<UThirdWebSubsystem>();
ThirdWebSubsystem->Initialize(EChain::PolygonAmoy);
// Connect to WesternShootout contract
ContractAddress = TEXT("0x17B6f309E57F684F643C4C3F8A91C6a8219C53f0");
ThirdWebSubsystem->ConnectToContract(ContractAddress, WesternShootoutABI);
}
- Create Blueprint Functions
// Create a game with wager
UFUNCTION(BlueprintCallable, Category = "Blockchain")
void CreateGame(float WagerAmount)
{
FString FunctionName = TEXT("createGame");
TArray<FString> Params;
Params.Add(FString::Printf(TEXT("%f"), WagerAmount * 1e18)); // Convert to wei
ThirdWebSubsystem->CallContractFunction(ContractAddress, FunctionName, Params, true);
}
// Join a game
UFUNCTION(BlueprintCallable, Category = "Blockchain")
void JoinGame(int32 GameId, float WagerAmount)
{
FString FunctionName = TEXT("joinGame");
TArray<FString> Params;
Params.Add(FString::Printf(TEXT("%d"), GameId));
ThirdWebSubsystem->CallContractFunction(
ContractAddress,
FunctionName,
Params,
true,
WagerAmount * 1e18 // Value in wei
);
}
- Listen for Events
// Set up event listeners
void AMyGameMode::SetupEventListeners()
{
ThirdWebSubsystem->ListenForContractEvent(
ContractAddress,
TEXT("PlayerJoined"),
FOnContractEventReceived::CreateUObject(this, &AMyGameMode::OnPlayerJoined)
);
ThirdWebSubsystem->ListenForContractEvent(
ContractAddress,
TEXT("WinnerDeclared"),
FOnContractEventReceived::CreateUObject(this, &AMyGameMode::OnWinnerDeclared)
);
}
// Event handler
void AMyGameMode::OnPlayerJoined(const FString& EventData)
{
// Parse event data and update game state
// ...
// Update UI
if (GameLobbyWidget)
{
GameLobbyWidget->UpdatePlayerList();
}
}
- Game Flow
Player enters lobby โ Connects wallet โ Creates/joins game โ
Waits for opponent โ Plays duel โ Winner determined โ
Result submitted to blockchain โ Prizes distributed
- Timeout Handling
Game starts โ Timer begins counting down โ
If player disconnects โ Warning displayed โ
If timeout occurs โ resolveTimedOutGame called โ
Players receive partial refunds
- Dispute Resolution
Game ends โ Result declared โ Losing player can dispute โ
Dispute evidence collected โ Arbiters vote โ
Majority decision enforced โ Winner receives prize
- โ Secure fund handling with proper balance checks
- โ State validation to prevent invalid operations
- โ Owner-only sensitive operations
- โ Protected winner declaration
- โ Automated fee calculation and distribution
- โ Timeout protection against abandoned games
- โ Dispute resolution for contested outcomes
- โ Gas-optimized operations
To interact with the contract on Polygon Amoy testnet:
-
633A
Get Test POL
- Visit the Polygon Amoy Faucet
- Request test POL for your wallet
-
Connect to Amoy Network
- Network Name: Polygon Amoy
- RPC URL: https://rpc-amoy.polygon.technology/
- Chain ID: 80002
- Currency Symbol: POL
- Block Explorer: https://amoy.polygonscan.com/
-
Test Transactions
- Start with small wager amounts
- Monitor transaction status on the explorer
- Check game state after each operation
- Contract Source Code
- API Documentation
- ThirdWeb Engine Documentation
- Unreal Engine Integration Examples
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
For questions or support, please open an issue on this repository or contact us at:
- Twitter: @Arkadia_Park
- Discord: Join our server
Built with โค๏ธ by the Arkadia Park team