Skip to content

Commit

Permalink
new contract(leaderboardPage, getData, onChainRaceService, onChainRace)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xjaqbek committed Aug 30, 2024
1 parent fc57795 commit 30a6fbb
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 98 deletions.
8 changes: 4 additions & 4 deletions src/LeaderboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { StyledButton } from './StyledButton';
import { TonConnectButton, useTonConnectUI } from "@tonconnect/ui-react";
import { useTonAddress } from "@tonconnect/ui-react";
import { getLeaderboard, updateLeaderboard, LeaderboardEntry } from './gistService';
import { sendTransactionToOnChainRace } from './race/scripts/onChainRaceService';
import { getDataFromOnChainRace } from './race/scripts/getData';
import { sendRecordTimeToRace } from './race/scripts/onChainRaceService';
import { getDataFromRace } from './race/scripts/getData';
import { TonClient, Address } from "@ton/ton";
import { getHttpEndpoint } from "@orbs-network/ton-access";

Expand Down Expand Up @@ -230,7 +230,7 @@ const LeaderboardPage: React.FC<LeaderboardPageProps> = ({ elapsedTime, onClose,
console.log(`User Name: ${userName}`);
console.log(`Elapsed Time: ${elapsedTime.toFixed(3)} seconds`);

await sendTransactionToOnChainRace(rawAddress, elapsedTime, tonConnectUI);
await sendRecordTimeToRace(rawAddress, elapsedTime, tonConnectUI);

} catch (error) {
console.error("Error during OnChain Race:", error);
Expand All @@ -249,7 +249,7 @@ const LeaderboardPage: React.FC<LeaderboardPageProps> = ({ elapsedTime, onClose,

const onChainRaceAddress = Address.parse("kQDW1VLFvS3FJW5rl2tyNfQ-mOfN5nPYGPAHh1vueJsRywwm");

await getDataFromOnChainRace(client, onChainRaceAddress);
await getDataFromRace(client, onChainRaceAddress);

} catch (error) {
console.error("Error during OnChain Race Data retrieval:", error);
Expand Down
47 changes: 23 additions & 24 deletions src/race/scripts/getData.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
import { getHttpEndpoint } from "@orbs-network/ton-access";
import { TonClient, Address } from "@ton/ton";
import { OnChainRace } from "../wrappers/OnChainRace"; // Adjust the import path as necessary
import { Race } from "../wrappers/OnChainRace"; // Adjust the import path as necessary
import axios from "axios";

export async function getDataFromOnChainRace(client: TonClient, onChainRaceAddress: Address) {
export async function getDataFromRace(client: TonClient, raceAddress: Address) {
try {
// Open OnChainRace instance by address
const onChainRace = OnChainRace.createFromAddress(onChainRaceAddress);
const contractAddress = onChainRaceAddress.toString();
console.log(`OnChainRace contract address: ${contractAddress}`);
// Open Race instance by address
const raceContract = Race.createFromAddress(raceAddress);
const contractAddress = raceAddress.toString();
console.log(`Race contract address: ${contractAddress}`);

// Create a ContractProvider for OnChainRace contract
const onChainRaceProvider = client.provider(onChainRaceAddress);
// Create a ContractProvider for Race contract
const raceProvider = client.provider(raceAddress);

// Retrieve and log run time and user address from the smart contract
const runTimeMilliseconds = await onChainRace.getRunTime(onChainRaceProvider);
const runTimeSeconds = runTimeMilliseconds / 1000;
const runTimeMessage = `Run Time: ${runTimeSeconds.toFixed(3)}`; // Show the run time with millisecond precision
console.log(runTimeMessage);

const userAddressObj = await onChainRace.getUserAddress(onChainRaceProvider);
const userAddressMessage = `User Address: ${userAddressObj.toString()}`;
console.log(userAddressMessage);

// Combine all messages into a single alert
const combinedMessage = `
OnChainRace contract address: ${contractAddress}
${runTimeMessage}
${userAddressMessage}
// Retrieve and log race information from the smart contract
const raceInfo = await raceContract.getInfo(raceProvider);
const bestTimeSeconds = raceInfo.bestTime / 1000;
const raceInfoMessage = `
Best Time: ${bestTimeSeconds.toFixed(3)} seconds
Total Fees: ${raceInfo.totalFees.toString()} nanoTON
Current Tournament Number: ${raceInfo.currentTournamentNumber}
Best Player Address: ${raceInfo.bestPlayer.toString()}
`;
alert(combinedMessage.trim());
console.log(raceInfoMessage);

// Alert the retrieved information
alert(`
Race contract address: ${contractAddress}
${raceInfoMessage.trim()}
`.trim());

} catch (error) {
if (axios.isAxiosError(error)) {
Expand Down
68 changes: 27 additions & 41 deletions src/race/scripts/onChainRaceService.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { getHttpEndpoint } from "@orbs-network/ton-access";
import { TonClient, WalletContractV4, Address, Sender, ContractProvider } from "@ton/ton";
import { OnChainRace } from "../wrappers/OnChainRace"; // Adjust the import path as necessary
import { TonConnectUI, SendTransactionRequest } from "@tonconnect/ui-react"; // Use TonConnectUI instead of TonConnect
import { Buffer } from "buffer"; // Add this import for Buffer
import { TonClient, WalletContractV4, Address, Sender, ContractProvider, toNano } from "@ton/ton";
import { Race } from "../wrappers/OnChainRace"; // Adjust the import path as necessary
import { TonConnectUI, SendTransactionRequest } from "@tonconnect/ui-react";
import { Buffer } from "buffer";
import axios from "axios";

export async function sendTransactionToOnChainRace(userAddress: string, elapsedTime: number, tonConnectUI: TonConnectUI) {
export async function sendRecordTimeToRace(userAddress: string, elapsedTime: number, tonConnectUI: TonConnectUI) {
try {
console.log("Starting interaction with OnChainRace contract...");
console.log("Starting interaction with Race contract...");

// Initialize TON RPC client on testnet
const endpoint = await getHttpEndpoint({ network: "testnet" });
console.log(`Using endpoint: ${endpoint}`);
const client = new TonClient({ endpoint });

// Ensure wallet is connected
Expand All @@ -26,30 +25,18 @@ export async function sendTransactionToOnChainRace(userAddress: string, elapsedT
}

const publicKeyBuffer = Buffer.from(publicKeyHex, "hex");
const walletAddress = connectedWallet.account.address;
console.log(`Connected wallet address: ${walletAddress}`);

// Create a wallet contract instance using the connected wallet's public key
const wallet = WalletContractV4.create({ publicKey: publicKeyBuffer, workchain: 0 });
const isDeployed = await client.isContractDeployed(wallet.address);
console.log(`Wallet is deployed: ${isDeployed}`);
if (!isDeployed) {
return console.log("Wallet is not deployed");
}

// Open wallet and read the current seqno of the wallet
const walletContract = client.open(wallet);

// Adapted for proper signing
const walletSender: Sender = {
send: async (message) => {
const transactionRequest: SendTransactionRequest = {
validUntil: Math.floor(Date.now() / 1000) + 60, // 1 minute validity
validUntil: Math.floor(Date.now() / 1000) + 60,
messages: [
{
address: message.to.toString(), // Ensuring the correct address is used
amount: message.value.toString(), // Ensure amount is a string
payload: message.body?.toBoc().toString('base64'), // Convert the payload to base64
address: message.to.toString(),
amount: message.value.toString(),
payload: message.body?.toBoc().toString('base64'),
},
],
};
Expand All @@ -60,21 +47,20 @@ export async function sendTransactionToOnChainRace(userAddress: string, elapsedT
const seqno = await walletContract.getSeqno();
console.log("Seqno: ", seqno);

// Open OnChainRace instance by address
const onChainRaceAddress = Address.parse("kQDW1VLFvS3FJW5rl2tyNfQ-mOfN5nPYGPAHh1vueJsRywwm"); // Replace with your contract address
const onChainRace = OnChainRace.createFromAddress(onChainRaceAddress);
console.log(`OnChainRace contract address: ${onChainRaceAddress.toString()}`);

// Create a ContractProvider for OnChainRace contract
const onChainRaceProvider = client.provider(onChainRaceAddress);
// Open Race instance by address
const raceAddress = Address.parse("kQC_8HjVICJndonC88WPdsekT072YY0vvcj3I2oXgmEcDWT1"); // Replace with your contract address
const raceContract = Race.createFromAddress(raceAddress);
console.log(`Race contract address: ${raceAddress.toString()}`);

// Ensure elapsedTime is an integer in milliseconds
const elapsedTimeMilliseconds = Math.floor(elapsedTime * 1000);
console.log(`Sending transaction with elapsedTime (ms): ${elapsedTimeMilliseconds}, userAddress: ${userAddress}`);
// Create a ContractProvider for Race contract
const raceProvider = client.provider(raceAddress);

// Send the set data transaction
// Send the recorded time
const userAddr = Address.parse(userAddress); // Use the provided user address
await onChainRace.sendSetData(onChainRaceProvider, walletSender, BigInt(0.1 * 1e9), elapsedTimeMilliseconds, userAddr);
await raceContract.sendRecordTime(raceProvider, walletSender, {
time: Math.floor(elapsedTime * 1000), // Ensure time is in milliseconds
value: toNano('1.1'), // Adjust value as necessary
});

// Wait until confirmed
let currentSeqno = seqno;
Expand All @@ -85,18 +71,18 @@ export async function sendTransactionToOnChainRace(userAddress: string, elapsedT
}
console.log("Transaction confirmed!");

// Retrieve and log run time from the smart contract
const runTimeMilliseconds = await onChainRace.getRunTime(onChainRaceProvider);
const runTimeSeconds = runTimeMilliseconds / 1000;
console.log("Run Time:", runTimeSeconds.toFixed(3)); // Show the run time with millisecond precision
// Retrieve and log the updated race info
const raceInfo = await raceContract.getInfo(raceProvider);
console.log("Best Time:", raceInfo.bestTime);
console.log("Total Fees:", raceInfo.totalFees.toString());
console.log("Current Tournament Number:", raceInfo.currentTournamentNumber);
console.log("Best Player Address:", raceInfo.bestPlayer.toString());

} catch (error) {
if (axios.isAxiosError(error)) {
// Handle AxiosError
console.error('Axios error:', error.message);
console.error('Response data:', error.response?.data);
} else {
// Handle other errors
console.error('Unexpected error:', error);
}
}
Expand Down
151 changes: 122 additions & 29 deletions src/race/wrappers/OnChainRace.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,147 @@
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
import {
Address,
beginCell,
Cell,
Contract,
contractAddress,
ContractProvider,
Sender,
SendMode,
toNano
} from '@ton/core';

export type OnChainRaceConfig = {};
export type RaceEntry = {
time: number;
address: Address;
};

export function onChainRaceConfigToCell(config: OnChainRaceConfig): Cell {
return beginCell().endCell();
}
export const Opcodes = {
recordTime: 0x9fd3,
distributePrize: 0xf8a7,
withdrawFees: 0x0cab
};

export class OnChainRace implements Contract {
export class Race implements Contract {
constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {}

static createFromAddress(address: Address) {
return new OnChainRace(address);
return new Race(address);
}

static createFromConfig(config: OnChainRaceConfig, code: Cell, workchain = 0) {
const data = onChainRaceConfigToCell(config);
static createFromConfig(owner: Address, code: Cell, workchain = 0) {
const data = Race.createDataCell(owner);
const init = { code, data };
return new OnChainRace(contractAddress(workchain, init), init);
return new Race(contractAddress(workchain, init), init);
}

static createDataCell(owner: Address): Cell {
return beginCell()
.storeAddress(owner)
.storeUint(0, 64) // lastPayout
.storeUint(0, 120) // totalFees
.storeUint(0, 32) // currentTournamentNumber
.storeUint(0, 64) // bestTime
.storeAddress(Address.parse('0:0000000000000000000000000000000000000000000000000000000000000000')) // bestPlayer
.storeDict(null) // playerEntries
.endCell();
}

async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
body: beginCell().storeUint(0x44, 32).endCell(),
});
}

async sendSetData(provider: ContractProvider, via: Sender, value: bigint, time: number, userAddress: Address) {
console.log(`sendSetData called with time: ${time}, userAddress: ${userAddress.toString()}`);
const body = beginCell()
.storeUint(1, 32) // op code
.storeUint(time, 64)
.storeRef(beginCell().storeAddress(userAddress).endCell())
.endCell();

async sendRecordTime(
provider: ContractProvider,
via: Sender,
opts: {
time: number;
value?: bigint;
}
) {
await provider.internal(via, {
value,
body,
value: opts.value ?? toNano('1.1'),
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(Opcodes.recordTime, 32)
.storeUint(opts.time, 64)
.endCell(),
});
}


async getRunTime(provider: ContractProvider): Promise<number> {
const { stack } = await provider.get('get_run_time', []); // No arguments needed
return Number(stack.readBigNumber()); // Convert BigNumber to number
async sendDistributePrize(
provider: ContractProvider,
via: Sender,
opts: {
tournamentNumber: number;
value?: bigint;
}
) {
await provider.internal(via, {
value: opts.value ?? toNano('0.1'),
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(Opcodes.distributePrize, 32)
.storeUint(opts.tournamentNumber, 32)
.endCell(),
});
}

async getUserAddress(provider: ContractProvider): Promise<Address> {
const { stack } = await provider.get('get_user_address', []); // No arguments needed
return stack.readAddress(); // Read Address from stack
async sendWithdrawFees(
provider: ContractProvider,
via: Sender,
opts: {
value?: bigint;
}
) {
await provider.internal(via, {
value: opts.value ?? toNano('0.1'),
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(Opcodes.withdrawFees, 32)
.endCell(),
});
}

async getInfo(provider: ContractProvider): Promise<{
bestTime: number;
totalFees: bigint;
currentTournamentNumber: number;
bestPlayer: Address;
}> {
const result = await provider.get('get_info', []);
return {
bestTime: result.stack.readNumber(),
totalFees: result.stack.readBigNumber(),
currentTournamentNumber: result.stack.readNumber(),
bestPlayer: result.stack.readAddress(),
};
}

async getPlayerEntry(provider: ContractProvider, address: Address): Promise<{
time: number;
timestamp: number;
tournamentNumber: number;
} | null> {
const result = await provider.get('get_player_entry', [
{ type: 'slice', cell: beginCell().storeAddress(address).endCell() },
]);

const time = result.stack.readNumber();
const timestamp = result.stack.readNumber();
const tournamentNumber = result.stack.readNumber();

if (time === 0 && timestamp === 0 && tournamentNumber === 0) {
return null;
}

return {
time,
timestamp,
tournamentNumber,
};
}
}
}

0 comments on commit 30a6fbb

Please sign in to comment.