From 849d94363cef953740e7ae1d0a691a39ecd3f8f7 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Tue, 17 Dec 2024 18:31:38 +0000 Subject: [PATCH 1/2] feat: refactored back the paymaster to a hook --- frontend/src/components/NewPlayer.tsx | 70 ++------------ frontend/src/components/modals/BuySeeds.tsx | 62 ++---------- .../src/components/modals/HarvestModal.tsx | 60 ++---------- frontend/src/components/modals/PlantModal.tsx | 60 ++---------- frontend/src/components/modals/SellItem.tsx | 60 ++---------- frontend/src/hooks/usePaymaster.ts | 95 +++++++++++++++++++ 6 files changed, 133 insertions(+), 274 deletions(-) create mode 100644 frontend/src/hooks/usePaymaster.ts diff --git a/frontend/src/components/NewPlayer.tsx b/frontend/src/components/NewPlayer.tsx index 7b0ceed..b3a310a 100644 --- a/frontend/src/components/NewPlayer.tsx +++ b/frontend/src/components/NewPlayer.tsx @@ -10,8 +10,8 @@ import type { FarmContract } from "../sway-api"; import Loading from "./Loading"; import { PlayerOutput } from "../sway-api/contracts/FarmContract"; -import { Address, BN, type Coin, Provider, bn } from "fuels"; -import axios from "axios"; +import { Address, BN, Provider } from "fuels"; +import { usePaymaster } from "../hooks/usePaymaster"; interface NewPlayerProps { contract: FarmContract | null; @@ -83,43 +83,10 @@ export default function NewPlayer({ 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), - }; - console.log("gasCoin", gasCoin); - // const address = Address.fromRandom(); + + const paymaster = usePaymaster(); + const { maxValuePerCoin } = await paymaster.metadata(); + const { coin: gasCoin, jobId } = await paymaster.allocate(); const addressIdentityInput = { Address: { bits: Address.fromAddressOrString(wallet.address.toString()).toB256() }, @@ -146,29 +113,8 @@ export default function NewPlayer({ request.gasLimit = gasUsed; 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"); - } - - 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; + const { signature } = await paymaster.fetchSignature(request, jobId); + request.updateWitnessByOwner(gasCoin.owner, signature); // await wallet.fund(request, txCost); diff --git a/frontend/src/components/modals/BuySeeds.tsx b/frontend/src/components/modals/BuySeeds.tsx index 2870bd8..1b60996 100644 --- a/frontend/src/components/modals/BuySeeds.tsx +++ b/frontend/src/components/modals/BuySeeds.tsx @@ -19,6 +19,7 @@ import { useNetwork, useBalance, } from "@fuels/react"; +import { usePaymaster } from "../../hooks/usePaymaster"; interface BuySeedsProps { contract: FarmContract | null; @@ -43,42 +44,11 @@ export default function BuySeeds({ throw new Error("No wallet found"); } 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), - }; - console.log("gasCoin", gasCoin); + const paymaster = usePaymaster(); + const { maxValuePerCoin } = await paymaster.metadata(); + const { coin: gasCoin, jobId } = await paymaster.allocate(); + const amount = 10; const realAmount = amount / 1_000_000_000; const inputAmount = bn.parseUnits(realAmount.toFixed(9).toString()); @@ -148,26 +118,8 @@ export default function BuySeeds({ ); request.addChangeOutput(gasCoin.owner, provider.getBaseAssetId()); - 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"); - } - console.log("gasInput", gasInput); - const wi = request.getCoinInputWitnessIndexByOwner(gasCoin.owner); - request.witnesses[wi as number] = response.data.signature; - console.log("request manually after coin", request.toJSON()); + const {signature} = await paymaster.fetchSignature(request, jobId); + request.updateWitnessByOwner(gasCoin.owner, signature); const tx = await wallet.sendTransaction(request, { estimateTxDependencies: false, diff --git a/frontend/src/components/modals/HarvestModal.tsx b/frontend/src/components/modals/HarvestModal.tsx index aec5ec0..8d5c62b 100644 --- a/frontend/src/components/modals/HarvestModal.tsx +++ b/frontend/src/components/modals/HarvestModal.tsx @@ -59,42 +59,11 @@ export default function HarvestModal({ 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), - }; - console.log("gasCoin", gasCoin); + const paymaster = usePaymaster(); + const { maxValuePerCoin } = await paymaster.metadata(); + const { coin: gasCoin, jobId } = await paymaster.allocate(); + const addressIdentityInput = { Address: { bits: Address.fromAddressOrString( @@ -122,23 +91,10 @@ export default function HarvestModal({ request.gasLimit = gasUsed; request.maxFee = maxFee; console.log(`Harvest Cost gasLimit: ${gasUsed}, 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; + + const { signature } = await paymaster.fetchSignature(request, jobId); + request.updateWitnessByOwner(gasCoin.owner, signature); + console.log("harvest request manually", request.toJSON()); const tx = await wallet.sendTransaction(request); if (tx) { diff --git a/frontend/src/components/modals/PlantModal.tsx b/frontend/src/components/modals/PlantModal.tsx index a756a52..f218321 100644 --- a/frontend/src/components/modals/PlantModal.tsx +++ b/frontend/src/components/modals/PlantModal.tsx @@ -9,6 +9,7 @@ import Loading from "../Loading"; import { Address, type Coin, Provider, bn } from "fuels"; import { useWallet } from "@fuels/react"; import axios from "axios"; +import { usePaymaster } from "../../hooks/usePaymaster"; interface PlantModalProps { contract: FarmContract | null; @@ -65,42 +66,11 @@ export default function PlantModal({ 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), - }; - console.log("gasCoin", gasCoin); + const paymaster = usePaymaster(); + const { maxValuePerCoin } = await paymaster.metadata(); + const { coin: gasCoin, jobId } = await paymaster.allocate(); + const addressIdentityInput = { Address: { bits: Address.fromAddressOrString( @@ -129,26 +99,10 @@ export default function PlantModal({ request.gasLimit = gasUsed; request.maxFee = maxFee; console.log(`Plant Cost gasLimit: ${gasUsed}, 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"); - } - 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 { signature } = await paymaster.fetchSignature(request, jobId); + request.updateWitnessByOwner(gasCoin.owner, signature); - request.witnesses[gasInput.witnessIndex] = response.data.signature; const tx = await wallet.sendTransaction(request); if (tx) { console.log("tx", tx); diff --git a/frontend/src/components/modals/SellItem.tsx b/frontend/src/components/modals/SellItem.tsx index 400ee1b..f2b1345 100644 --- a/frontend/src/components/modals/SellItem.tsx +++ b/frontend/src/components/modals/SellItem.tsx @@ -8,6 +8,7 @@ import type { FarmContract } from "../../sway-api/contracts"; import Loading from "../Loading"; import { useWallet } from "@fuels/react"; import axios from "axios"; +import { usePaymaster } from "../../hooks/usePaymaster"; interface SellItemProps { contract: FarmContract | null; @@ -42,42 +43,10 @@ export default function SellItem({ ).toB256(), }, }; - 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), - }; - console.log("gasCoin", gasCoin); + const paymaster = usePaymaster(); + const { maxValuePerCoin } = await paymaster.metadata(); + const { coin: gasCoin, jobId } = await paymaster.allocate(); const scope = contract.functions.sell_item( seedType, @@ -110,23 +79,10 @@ export default function SellItem({ provider.getBaseAssetId() ); request.addChangeOutput(gasCoin.owner, provider.getBaseAssetId()); - 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; + + const { signature } = await paymaster.fetchSignature(request, jobId); + request.updateWitnessByOwner(gasCoin.owner, signature); + const tx = await wallet.sendTransaction(request); console.log("tx", tx); } catch (err) { diff --git a/frontend/src/hooks/usePaymaster.ts b/frontend/src/hooks/usePaymaster.ts new file mode 100644 index 0000000..996972b --- /dev/null +++ b/frontend/src/hooks/usePaymaster.ts @@ -0,0 +1,95 @@ +import axios from "axios"; +import { Address, type Coin, TransactionRequest, bn } from "fuels"; + +type PaymasterMetadata = { + maxValuePerCoin: string; +} + +type PaymasterAllocateResponse = { + coin: { + id: string; + amount: string; + assetId: string; + owner: string; + blockCreated: string; + txCreatedIdx: string; + }; + jobId: string; + utxoId: string; +} + +type PaymasterAllocate = { + coin: Coin; + jobId: string; + utxoId: string; +} + +export const usePaymaster = () => { + const baseUrl = 'http://167.71.42.88:3000'; + const metadataUrl = `${baseUrl}/metadata`; + const allocateUrl = `${baseUrl}/allocate-coin`; + const signUrl = `${baseUrl}/sign`; + + + const metadata = async (): Promise => { + const { data: MetaDataResponse } = await axios.get(metadataUrl); + const { maxValuePerCoin } = MetaDataResponse; + if (!maxValuePerCoin) { + throw new Error("No maxValuePerCoin found"); + } + return { maxValuePerCoin}; + } + + + const allocate = async (): Promise => { + const { data } = await axios.post(allocateUrl); + const { jobId, utxoId, coin } = data; + + if (!coin) { + throw new Error("No coin found"); + } + if (!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), + }; + + return { coin: gasCoin, jobId, utxoId } + } + + const fetchSignature = async (request: TransactionRequest, jobId: string) => { + // return; + const response = await axios.post(signUrl, { + request: request.toJSON(), + 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"); + } + + return { signature: response.data.signature, gasInput, request } + } + + return { + allocate, + metadata, + fetchSignature, + } +} \ No newline at end of file From 345aba0b3210e1e23adc2128715b1b7ba7ceca16 Mon Sep 17 00:00:00 2001 From: Peter Smith Date: Tue, 17 Dec 2024 18:34:12 +0000 Subject: [PATCH 2/2] chore: missing import --- frontend/src/components/modals/HarvestModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/modals/HarvestModal.tsx b/frontend/src/components/modals/HarvestModal.tsx index 8d5c62b..21fd39e 100644 --- a/frontend/src/components/modals/HarvestModal.tsx +++ b/frontend/src/components/modals/HarvestModal.tsx @@ -6,8 +6,8 @@ import { buttonStyle, FUEL_PROVIDER_URL } from "../../constants"; import type { FarmContract } from "../../sway-api"; import type { Modals } from "../../constants"; import { useWallet } from "@fuels/react"; -import { Address, type Coin, Provider, bn } from "fuels"; -import axios from "axios"; +import { Address, Provider } from "fuels"; +import { usePaymaster } from "../../hooks/usePaymaster"; interface HarvestProps { contract: FarmContract | null; tileArray: number[];