generated from ton-community/twa-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
226 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
const int OP_JOIN_RACE = 1; | ||
const int OP_SUBMIT_TIME = 2; | ||
const int OP_FINALIZE_RACE = 3; | ||
const int OP_DISTRIBUTE_PRIZE = 5; | ||
const int OP_REFUND_ENTRY_FEE = 6; | ||
const int OP_WITHDRAW_FUNDS = 7; | ||
|
||
() recv_internal(int my_balance, cell in_msg, slice in_msg_body) impure { | ||
slice cs = in_msg_body; | ||
int op = cs~load_uint(32); | ||
|
||
;; Contract state variables | ||
int race_start_time; | ||
map participants; ;; Map to store participant addresses, join times, and their race times | ||
slice owner_address; | ||
bool race_finalized = false; | ||
int total_entry_fees = 0; ;; Track total entry fees collected | ||
int participant_entry_fee = 1000000000; ;; 1 TON = 1000000000 nanoton | ||
|
||
;; Load contract state from persistent storage | ||
slice data = get_data(); | ||
if (data.slice_refs() > 0) { | ||
(race_start_time, participants, owner_address, race_finalized, total_entry_fees) = data~load_tuple(); | ||
} | ||
|
||
;; Join Race | ||
if (op == OP_JOIN_RACE) { | ||
slice participant_address = cs~load_msg_addr(); | ||
int current_time = now(); | ||
|
||
if (race_start_time == 0) { | ||
race_start_time = current_time; | ||
} | ||
|
||
tuple participant_info = (current_time, []); | ||
participants = participants.set(participant_address, participant_info); | ||
|
||
total_entry_fees += participant_entry_fee; ;; Add entry fee to total | ||
send_raw_message(build_internal_message(participant_address, participant_entry_fee, false, null()), 128); | ||
} | ||
|
||
;; Submit Time | ||
if (op == OP_SUBMIT_TIME) { | ||
slice participant_address = cs~load_msg_addr(); | ||
int time = cs~load_uint(64); | ||
int current_time = now(); | ||
|
||
tuple participant_info = participants[participant_address]; | ||
int join_time = participant_info.get(0); | ||
cell times = participant_info.get(1); | ||
|
||
if (current_time <= join_time + 20 * 60) { ;; 20 minutes after join time | ||
if (times.size() < 3) { | ||
times = times.push(time); | ||
participants = participants.set(participant_address, (join_time, times)); | ||
} else { | ||
throw(101); ;; "You can only submit up to 3 times." | ||
} | ||
|
||
send_raw_message(build_internal_message(participant_address, 0.002 ton, false, null()), 128); | ||
} else { | ||
throw(102); ;; "Time to submit results has expired." | ||
} | ||
} | ||
|
||
;; Finalize Race | ||
if (op == OP_FINALIZE_RACE) { | ||
int current_time = now(); | ||
int finalization_time = race_start_time + 3600; ;; 1 hour after race start | ||
|
||
if (current_time > finalization_time) { | ||
race_finalized = true; | ||
} else { | ||
throw(103); ;; "Race finalization time has not ended yet." | ||
} | ||
} | ||
|
||
;; Distribute Prize | ||
if (op == OP_DISTRIBUTE_PRIZE) { | ||
if (!race_finalized) { | ||
throw(104); ;; "Race has not been finalized yet." | ||
} | ||
|
||
slice participant_address = cs~load_msg_addr(); | ||
slice winner_address; | ||
int best_time = 9223372036854775807; ;; Maximum 64-bit signed integer value | ||
int winner_entry_fee = 0; | ||
|
||
participants.each { (slice addr, tuple participant_info) => | ||
cell times = participant_info.get(1); | ||
int best_participant_time = times.min(); | ||
if (best_participant_time < best_time) { | ||
best_time = best_participant_time; | ||
winner_address = addr; | ||
winner_entry_fee = participant_entry_fee; | ||
} | ||
} | ||
|
||
if (participant_address == winner_address) { | ||
;; Calculate the prize: 50% of winner's entry fee + 95% of others' entry fees | ||
int prize_amount = (winner_entry_fee / 2) + ((95 * (total_entry_fees - winner_entry_fee)) / 100); | ||
|
||
;; Add a small portion (gas fee) to be left in the contract | ||
int gas_fee = 2000000; | ||
int final_amount = prize_amount - gas_fee; | ||
|
||
send_raw_message(build_internal_message(winner_address, final_amount, false, null()), 128); | ||
} else { | ||
throw(105); ;; "Only the winner can claim the prize." | ||
} | ||
} | ||
|
||
;; Refund Entry Fee | ||
if (op == OP_REFUND_ENTRY_FEE) { | ||
slice participant_address = cs~load_msg_addr(); | ||
int current_time = now(); | ||
int join_end_time = race_start_time + 40 * 60; ;; 40 minutes after race start | ||
|
||
if (current_time > join_end_time && participants.size() == 1) { | ||
;; Return entry fee (1 TON) | ||
int entry_fee = participant_entry_fee; | ||
send_raw_message(build_internal_message(participant_address, entry_fee, false, null()), 128); | ||
} else { | ||
throw(106); ;; "Refund is not allowed." | ||
} | ||
} | ||
|
||
;; Withdraw Funds by Owner | ||
if (op == OP_WITHDRAW_FUNDS) { | ||
slice sender_address = in_msg~load_msg_addr(); | ||
if (sender_address != owner_address) { | ||
throw(107); ;; "Only the owner can withdraw funds." | ||
} | ||
|
||
;; Subtract the gas fee from the current balance to calculate withdrawal amount | ||
int gas_fee = 2000000; | ||
int withdrawal_amount = my_balance - gas_fee; | ||
|
||
;; Ensure the withdrawal amount is non-negative | ||
if (withdrawal_amount < 0) { | ||
throw(108); ;; "Insufficient balance for withdrawal." | ||
} | ||
|
||
;; Send the remaining balance to the owner | ||
send_raw_message(build_internal_message(owner_address, withdrawal_amount, false, null()), 128); | ||
} | ||
|
||
;; Save contract state | ||
set_data(store_tuple((race_start_time, participants, owner_address, race_finalized, total_entry_fees))); | ||
} | ||
|
||
() recv_external(int my_balance, cell in_msg, slice in_msg_body) impure { | ||
throw(100); ;; External messages not supported | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { useState, useEffect } from "react"; | ||
import Race from "../contracts/race"; | ||
import { useTonClient } from "./useTonClient"; | ||
import { useAsyncInitialize } from "./useAsyncInitialize"; | ||
import { useTonConnect } from "./useTonConnect"; | ||
import { Address, OpenedContract } from "ton-core"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
import { CHAIN } from "@tonconnect/protocol"; | ||
|
||
export function useRaceContract() { | ||
const { client } = useTonClient(); | ||
const { sender, network } = useTonConnect(); | ||
|
||
const raceContract = useAsyncInitialize(async () => { | ||
if (!client) return; | ||
const owner = Address.parse("YourOwnerAddressHere"); // Replace with the owner's address | ||
const contract = new Race( | ||
Address.parse( | ||
network === CHAIN.MAINNET | ||
? "EQBpRaceMainNetAddressHere" // Replace with mainnet address | ||
: "EQBraceTestNetAddressHere" // Replace with testnet address | ||
), | ||
owner | ||
); | ||
return client.open(contract) as OpenedContract<Race>; | ||
}, [client]); | ||
|
||
const { data: raceStartTime, refetch: refetchRaceStartTime } = useQuery( | ||
["raceStartTime"], | ||
async () => { | ||
if (!raceContract) return null; | ||
return raceContract.getRaceStartTime(); | ||
}, | ||
{ refetchInterval: 3000 } | ||
); | ||
|
||
const { data: participants, refetch: refetchParticipants } = useQuery( | ||
["participants"], | ||
async () => { | ||
if (!raceContract) return null; | ||
// Fetch and return participants (you may need to implement this method in Race contract) | ||
}, | ||
{ refetchInterval: 3000 } | ||
); | ||
|
||
const { data: winner, refetch: refetchWinner } = useQuery( | ||
["winner"], | ||
async () => { | ||
if (!raceContract) return null; | ||
return raceContract.getWinner(); | ||
}, | ||
{ refetchInterval: 3000 } | ||
); | ||
|
||
return { | ||
raceStartTime, | ||
participants, | ||
winner, | ||
joinRace: () => raceContract?.joinRace(sender), | ||
submitTime: (time: number) => raceContract?.submitTime(sender, time), | ||
finalizeRace: () => raceContract?.finalizeRace(sender), | ||
distributePrize: () => raceContract?.distributePrize(sender), | ||
refundEntryFee: () => raceContract?.refundEntryFee(sender), | ||
refetchRaceStartTime, | ||
refetchParticipants, | ||
refetchWinner, | ||
}; | ||
} |