diff --git a/frontend/src/components/NewPlayer.tsx b/frontend/src/components/NewPlayer.tsx index ae09a7f..e66aada 100644 --- a/frontend/src/components/NewPlayer.tsx +++ b/frontend/src/components/NewPlayer.tsx @@ -1,5 +1,5 @@ import { cssObj } from "@fuel-ui/css"; -import { Button } from "@fuel-ui/react"; +import { BoxCentered, Button } from "@fuel-ui/react"; import { useWallet } from "@fuels/react"; import { useState, useEffect } from "react"; import type { Dispatch, SetStateAction } from "react"; @@ -26,7 +26,7 @@ export default function NewPlayer({ setPlayer, setModal, }: NewPlayerProps) { - const [status, setStatus] = useState<"error" | "loading" | "none">("none"); + const [status, setStatus] = useState<"error" | "loading" | "retrying" | "none">("none"); const [hasFunds, setHasFunds] = useState(false); const { wallet } = useWallet(); @@ -47,124 +47,159 @@ export default function NewPlayer({ } } + async function createPlayerWithoutGasStation() { + if (!wallet || !contract) throw new Error("Wallet or contract not found"); + + const addressIdentityInput = { + Address: { bits: Address.fromAddressOrString(wallet.address.toString()).toB256() }, + }; + + const tx = await contract.functions + .new_player(addressIdentityInput) + .txParams({ + variableOutputs: 1, + }) + .call(); + + if (tx) { + setPlayer({ + farming_skill: new BN(1), + total_value_sold: new BN(0), + } as PlayerOutput); + setModal("none"); + updatePageNum(); + } + return tx; + } + async function handleNewPlayer() { - const provider = await Provider.create(FUEL_PROVIDER_URL); + if (!wallet) { + throw new Error("No wallet found"); + } if (contract !== null) { - if (!wallet) { - throw new Error("No wallet found"); - } try { setStatus("loading"); - const { data: MetaDataResponse } = await axios.get<{ - maxValuePerCoin: string; - }>(`http://167.71.42.88:3000/metadata`); - const { maxValuePerCoin } = MetaDataResponse; - console.log("maxValuePerCoin", maxValuePerCoin); - if (!maxValuePerCoin) { - throw new Error("No maxValuePerCoin found"); - } - const { data } = await axios.post<{ - coin: { - id: string; - amount: string; - assetId: string; - owner: string; - blockCreated: string; - txCreatedIdx: string; + + try { + // Try with gas station first + const provider = await Provider.create(FUEL_PROVIDER_URL); + const { data: MetaDataResponse } = await axios.get<{ + maxValuePerCoin: string; + }>(`http://167.71.42.88:3000/metadata`); + const { maxValuePerCoin } = MetaDataResponse; + console.log("maxValuePerCoin", maxValuePerCoin); + if (!maxValuePerCoin) { + throw new Error("No maxValuePerCoin found"); + } + const { data } = await axios.post<{ + coin: { + id: string; + amount: string; + assetId: string; + owner: string; + blockCreated: string; + txCreatedIdx: string; + }; + jobId: string; + utxoId: string; + }>(`http://167.71.42.88:3000/allocate-coin`); + if (!data?.coin) { + throw new Error("No coin found"); + } + + if (!data.jobId) { + throw new Error("No jobId found"); + } + const gasCoin: Coin = { + id: data.coin.id, + amount: bn(data.coin.amount), + assetId: data.coin.assetId, + owner: Address.fromAddressOrString(data.coin.owner), + blockCreated: bn(data.coin.blockCreated), + txCreatedIdx: bn(data.coin.txCreatedIdx), }; - jobId: string; - utxoId: string; - }>(`http://167.71.42.88:3000/allocate-coin`); - if (!data?.coin) { - throw new Error("No coin found"); - } + console.log("gasCoin", gasCoin); + // const address = Address.fromRandom(); - if (!data.jobId) { - throw new Error("No jobId found"); - } - const gasCoin: Coin = { - id: data.coin.id, - amount: bn(data.coin.amount), - assetId: data.coin.assetId, - owner: Address.fromAddressOrString(data.coin.owner), - blockCreated: bn(data.coin.blockCreated), - txCreatedIdx: bn(data.coin.txCreatedIdx), - }; - console.log("gasCoin", gasCoin); - // const address = Address.fromRandom(); - - const addressIdentityInput = { - Address: { bits: Address.fromAddressOrString(wallet.address.toString()).toB256() }, - }; - - const scope = contract.functions - .new_player(addressIdentityInput) - .txParams({ - variableOutputs: 1, + const addressIdentityInput = { + Address: { bits: Address.fromAddressOrString(wallet.address.toString()).toB256() }, + }; + + const scope = contract.functions + .new_player(addressIdentityInput) + .txParams({ + variableOutputs: 1, + }); + const request = await scope.getTransactionRequest(); + // const txCost = await wallet.getTransactionCost(request); + // console.log("txCost", txCost); + request.addCoinInput(gasCoin); + request.addCoinOutput( + gasCoin.owner, + gasCoin.amount.sub(maxValuePerCoin), + provider.getBaseAssetId() + ); + + request.addChangeOutput(gasCoin.owner, provider.getBaseAssetId()); + const { gasLimit, maxFee } = await provider.estimateTxGasAndFee({ + transactionRequest: request, }); - const request = await scope.getTransactionRequest(); - // const txCost = await wallet.getTransactionCost(request); - // console.log("txCost", txCost); - request.addCoinInput(gasCoin); - request.addCoinOutput( - gasCoin.owner, - gasCoin.amount.sub(maxValuePerCoin), - provider.getBaseAssetId() - ); - - request.addChangeOutput(gasCoin.owner, provider.getBaseAssetId()); - const { gasLimit, maxFee } = await provider.estimateTxGasAndFee({ - transactionRequest: request, - }); - console.log(`New Player Cost gasLimit: ${gasLimit}, Maxfee: ${maxFee}`); - request.gasLimit = gasLimit; - request.maxFee = maxFee; - - // return; - const response = await axios.post(`http://167.71.42.88:3000/sign`, { - request: request.toJSON(), - jobId: data.jobId, - }); - if (response.status !== 200) { - throw new Error("Failed to sign transaction"); - } + console.log(`New Player Cost gasLimit: ${gasLimit}, Maxfee: ${maxFee}`); + request.gasLimit = gasLimit; + request.maxFee = maxFee; - if (!response.data.signature) { - throw new Error("No signature found"); - } - console.log("response.data", response.data); - const gasInput = request.inputs.find((coin) => { - return coin.type === 0; - }); - if (!gasInput) { - throw new Error("Gas coin not found"); - } + // return; + const response = await axios.post(`http://167.71.42.88:3000/sign`, { + request: request.toJSON(), + jobId: data.jobId, + }); + if (response.status !== 200) { + throw new Error("Failed to sign transaction"); + } + + if (!response.data.signature) { + throw new Error("No signature found"); + } + console.log("response.data", response.data); + const gasInput = request.inputs.find((coin) => { + return coin.type === 0; + }); + if (!gasInput) { + throw new Error("Gas coin not found"); + } + + const wi = request.getCoinInputWitnessIndexByOwner(gasCoin.owner); + console.log("wi", wi); + request.witnesses[wi as number] = response.data.signature; + + // await wallet.fund(request, txCost); - const wi = request.getCoinInputWitnessIndexByOwner(gasCoin.owner); - console.log("wi", wi); - request.witnesses[wi as number] = response.data.signature; - - // await wallet.fund(request, txCost); - - const tx = await (await wallet.sendTransaction(request)).wait(); - console.log("tx", tx); - // const tx = await scope.call(); - // .txParams({ - // variableOutputs: 1, - // }) - // .call(); - - // setStatus('none'); - if (tx) { - // Immediately update player state with initial values - setPlayer({ - farming_skill: new BN(1), - total_value_sold: new BN(0), - } as PlayerOutput); - setModal("none"); - - updatePageNum(); + const tx = await (await wallet.sendTransaction(request)).wait(); + console.log("tx", tx); + // const tx = await scope.call(); + // .txParams({ + // variableOutputs: 1, + // }) + // .call(); + + // setStatus('none'); + if (tx) { + // Immediately update player state with initial values + setPlayer({ + farming_skill: new BN(1), + total_value_sold: new BN(0), + } as PlayerOutput); + setModal("none"); + + updatePageNum(); + } + } catch (error) { + console.log("Gas station failed, trying direct transaction...", error); + setStatus("retrying"); + await createPlayerWithoutGasStation(); } + + setStatus("none"); } catch (err) { console.log("Error in NewPlayer:", err); setStatus("error"); @@ -177,41 +212,37 @@ export default function NewPlayer({ return ( <> - {console.log("status", status)}
{status === "none" && ( )} - {/* {status === "none" && !hasFunds && ( - - You need some ETH to play: - - - - + {status === "loading" && ( + + +

Creating new player...

- )} */} + )} + {status === "retrying" && ( + + +

Retrying with alternate method...

+
+ )} {status === "error" && (
-

Something went wrong!

+

Failed to create player! Please try again.

)} - {status === "loading" && }
); diff --git a/frontend/src/components/modals/BuySeeds.tsx b/frontend/src/components/modals/BuySeeds.tsx index 03db27d..2870bd8 100644 --- a/frontend/src/components/modals/BuySeeds.tsx +++ b/frontend/src/components/modals/BuySeeds.tsx @@ -33,7 +33,9 @@ export default function BuySeeds({ setCanMove, farmCoinAssetID, }: BuySeedsProps) { - const [status, setStatus] = useState<"error" | "none" | `loading`>("none"); + const [status, setStatus] = useState<"error" | "none" | "loading" | "retrying">("none"); + const [retryCount, setRetryCount] = useState(0); + const MAX_RETRIES = 3; const { wallet } = useWallet(); async function getCoins() { @@ -163,11 +165,8 @@ export default function BuySeeds({ throw new Error("Gas coin not found"); } console.log("gasInput", gasInput); - // request.witnesses.push(response.data.signature); const wi = request.getCoinInputWitnessIndexByOwner(gasCoin.owner); - console.log("wi", wi); request.witnesses[wi as number] = response.data.signature; - // request.witnesses = [request.witnesses[0]]; console.log("request manually after coin", request.toJSON()); const tx = await wallet.sendTransaction(request, { @@ -177,6 +176,30 @@ export default function BuySeeds({ console.log("tx", tx); } } + + async function buyWithoutGasStation() { + if (!wallet || !contract) throw new Error("Wallet or contract not found"); + + const amount = 10; + const realAmount = amount / 1_000_000_000; + const inputAmount = bn.parseUnits(realAmount.toFixed(9).toString()); + const seedType: FoodTypeInput = FoodTypeInput.Tomatoes; + const price = 750_000 * amount; + + const addressIdentityInput = { + Address: { bits: Address.fromAddressOrString(wallet.address.toString()).toB256() }, + }; + + const tx = await contract.functions + .buy_seeds(seedType, inputAmount, addressIdentityInput) + .callParams({ + forward: [price, farmCoinAssetID], + }) + .call(); + + return tx; + } + async function buySeeds() { if (!wallet) { throw new Error("No wallet found"); @@ -185,38 +208,23 @@ export default function BuySeeds({ try { setStatus("loading"); setCanMove(false); - const amount = 10; - const realAmount = amount / 1_000_000_000; - const inputAmount = bn.parseUnits(realAmount.toFixed(9).toString()); - const seedType: FoodTypeInput = FoodTypeInput.Tomatoes; - const price = 750_000 * amount; - await getCoins(); - // const addressIdentityInput = { - // Address: { - // bits: Address.fromAddressOrString( - // wallet.address.toString() - // ).toB256(), - // }, - // }; - // const txRequest = await contract.functions - // .buy_seeds(seedType, inputAmount, addressIdentityInput) - // .callParams({ - // forward: [price, farmCoinAssetID], - // }) - // .call(); - // if (wallet) { - // const txResult = await ( - // await wallet.sendTransaction(txRequest) - // ).wait(); - // console.log("txResult", txResult); - // } + + try { + await getCoins(); + } catch (error) { + console.log("Gas station failed, trying direct transaction...",error); + setStatus("retrying"); + await buyWithoutGasStation(); + } + setStatus("none"); updatePageNum(); } catch (err) { console.log("Error", err); - setStatus("error"); + setStatus("none"); + } finally { + setCanMove(true); } - setCanMove(true); } else { console.log("ERROR: contract missing"); setStatus("error"); @@ -228,19 +236,26 @@ export default function BuySeeds({ {status === "loading" && ( +

Processing transaction...

+
+ )} + {status === "retrying" && ( + + +

Retrying with alternate method...

)} {status === "error" && (
-

Something went wrong!

+

Transaction failed! Please try again.

)} diff --git a/frontend/src/components/modals/HarvestModal.tsx b/frontend/src/components/modals/HarvestModal.tsx index 0992c20..ffb6cf8 100644 --- a/frontend/src/components/modals/HarvestModal.tsx +++ b/frontend/src/components/modals/HarvestModal.tsx @@ -25,111 +25,143 @@ export default function HarvestModal({ setModal, onHarvestSuccess, }: HarvestProps) { - const [status, setStatus] = useState<"error" | "none" | "loading">("none"); + const [status, setStatus] = useState<"error" | "none" | "loading" | "retrying">("none"); const { wallet } = useWallet(); - async function harvestItem() { - const provider = await Provider.create(FUEL_PROVIDER_URL); + async function harvestWithoutGasStation() { + if (!wallet || !contract) throw new Error("Wallet or contract not found"); + + const addressIdentityInput = { + Address: { bits: Address.fromAddressOrString(wallet.address.toString()).toB256() }, + }; + + const tx = await contract.functions + .harvest(tileArray, addressIdentityInput) + .call(); + + if (tx) { + onHarvestSuccess(tileArray[0]); + setModal("plant"); + updatePageNum(); + } + return tx; + } - if (contract !== null && wallet) { + async function harvestItem() { + if (!wallet) { + throw new Error("No wallet found"); + } + if (contract !== null) { try { setStatus("loading"); setCanMove(false); - const { data: MetaDataResponse } = await axios.get<{ - maxValuePerCoin: string; - }>(`http://167.71.42.88:3000/metadata`); - const { maxValuePerCoin } = MetaDataResponse; - console.log("maxValuePerCoin", maxValuePerCoin); - if (!maxValuePerCoin) { - throw new Error("No maxValuePerCoin found"); - } - const { data } = await axios.post<{ - coin: { - id: string; - amount: string; - assetId: string; - owner: string; - blockCreated: string; - txCreatedIdx: string; + + try { + // Try with gas station first + const provider = await Provider.create(FUEL_PROVIDER_URL); + const { data: MetaDataResponse } = await axios.get<{ + maxValuePerCoin: string; + }>(`http://167.71.42.88:3000/metadata`); + const { maxValuePerCoin } = MetaDataResponse; + console.log("maxValuePerCoin", maxValuePerCoin); + if (!maxValuePerCoin) { + throw new Error("No maxValuePerCoin found"); + } + const { data } = await axios.post<{ + coin: { + id: string; + amount: string; + assetId: string; + owner: string; + blockCreated: string; + txCreatedIdx: string; + }; + jobId: string; + utxoId: string; + }>(`http://167.71.42.88:3000/allocate-coin`); + if (!data?.coin) { + throw new Error("No coin found"); + } + + if (!data.jobId) { + throw new Error("No jobId found"); + } + const gasCoin: Coin = { + id: data.coin.id, + amount: bn(data.coin.amount), + assetId: data.coin.assetId, + owner: Address.fromAddressOrString(data.coin.owner), + blockCreated: bn(data.coin.blockCreated), + txCreatedIdx: bn(data.coin.txCreatedIdx), }; - jobId: string; - utxoId: string; - }>(`http://167.71.42.88:3000/allocate-coin`); - if (!data?.coin) { - throw new Error("No coin found"); + console.log("gasCoin", gasCoin); + const addressIdentityInput = { + Address: { + bits: Address.fromAddressOrString( + wallet.address.toString() + ).toB256(), + }, + }; + // const result = await contract.functions + // .harvest(tileArray, addressIdentityInput) + // .call(); + const scope = contract.functions.harvest( + tileArray, + addressIdentityInput + ); + const request = await scope.getTransactionRequest(); + request.addCoinInput(gasCoin); + request.addCoinOutput( + gasCoin.owner, + gasCoin.amount.sub(maxValuePerCoin), + provider.getBaseAssetId() + ); + request.addChangeOutput(gasCoin.owner, provider.getBaseAssetId()); + const { gasLimit, maxFee } = await provider.estimateTxGasAndFee({ + transactionRequest: request, + }); + request.gasLimit = gasLimit; + request.maxFee = maxFee; + console.log(`Harvest Cost gasLimit: ${gasLimit}, Maxfee: ${maxFee}`); + const response = await axios.post(`http://167.71.42.88:3000/sign`, { + request: request.toJSON(), + jobId: data.jobId, + }); + if (response.status !== 200) { + throw new Error("Failed to sign transaction"); + } + if (!response.data.signature) { + throw new Error("No signature found"); + } + const gasInput = request.inputs.find((coin) => { + return coin.type === 0; + }); + if (!gasInput) { + throw new Error("Gas coin not found"); + } + request.witnesses[gasInput.witnessIndex] = response.data.signature; + console.log("harvest request manually", request.toJSON()); + const tx = await wallet.sendTransaction(request); + if (tx) { + console.log("tx", tx); + onHarvestSuccess(tileArray[0]); // Update tile state + setModal("plant"); // Close modal + updatePageNum(); // Update other state + } + // setStatus('none'); + } catch (error) { + console.log("Gas station failed, trying direct transaction...", error); + setStatus("retrying"); + await harvestWithoutGasStation(); } - if (!data.jobId) { - throw new Error("No jobId found"); - } - const gasCoin: Coin = { - id: data.coin.id, - amount: bn(data.coin.amount), - assetId: data.coin.assetId, - owner: Address.fromAddressOrString(data.coin.owner), - blockCreated: bn(data.coin.blockCreated), - txCreatedIdx: bn(data.coin.txCreatedIdx), - }; - console.log("gasCoin", gasCoin); - const addressIdentityInput = { - Address: { - bits: Address.fromAddressOrString( - wallet.address.toString() - ).toB256(), - }, - }; - // const result = await contract.functions - // .harvest(tileArray, addressIdentityInput) - // .call(); - const scope = contract.functions.harvest( - tileArray, - addressIdentityInput - ); - const request = await scope.getTransactionRequest(); - request.addCoinInput(gasCoin); - request.addCoinOutput( - gasCoin.owner, - gasCoin.amount.sub(maxValuePerCoin), - provider.getBaseAssetId() - ); - request.addChangeOutput(gasCoin.owner, provider.getBaseAssetId()); - const { gasLimit, maxFee } = await provider.estimateTxGasAndFee({ - transactionRequest: request, - }); - request.gasLimit = gasLimit; - request.maxFee = maxFee; - console.log(`Harvest Cost gasLimit: ${gasLimit}, Maxfee: ${maxFee}`); - const response = await axios.post(`http://167.71.42.88:3000/sign`, { - request: request.toJSON(), - jobId: data.jobId, - }); - if (response.status !== 200) { - throw new Error("Failed to sign transaction"); - } - if (!response.data.signature) { - throw new Error("No signature found"); - } - const gasInput = request.inputs.find((coin) => { - return coin.type === 0; - }); - if (!gasInput) { - throw new Error("Gas coin not found"); - } - request.witnesses[gasInput.witnessIndex] = response.data.signature; - console.log("harvest request manually", request.toJSON()); - const tx = await wallet.sendTransaction(request); - if (tx) { - console.log("tx", tx); - onHarvestSuccess(tileArray[0]); // Update tile state - setModal("plant"); // Close modal - updatePageNum(); // Update other state - } - // setStatus('none'); + setStatus("none"); } catch (err) { console.log("Error in HarvestModal:", err); setStatus("error"); + } finally { + setCanMove(true); } - setCanMove(true); } else { console.log("ERROR: contract missing"); setStatus("error"); @@ -141,16 +173,22 @@ export default function HarvestModal({ {status === "loading" && ( +

Processing harvest...

+
+ )} + {status === "retrying" && ( + + +

Retrying with alternate method...

)} {status === "error" && (
-

Something went wrong!

+

Harvest failed! Please try again.

); }