diff --git a/package.json b/package.json index 29fbb6d..8a982ec 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "coverage": "vitest run --coverage", "build-only": "vite build", "type-check": "vue-tsc --skipLibCheck --noEmit", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore", + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore --fix", "lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" }, "dependencies": { @@ -25,9 +25,8 @@ "crc": "^3.8.0", "ethers": "^6.13.4", "marked": "^4.2.12", - "pinia": "^2.0.23", "qrcode": "^1.5.1", - "viem": "2.x", + "viem": "2.19.0", "vite-svg-loader": "^5.1.0", "vue": "^3.2.41", "vue-markdown": "^2.2.4", @@ -36,7 +35,6 @@ "devDependencies": { "@babel/preset-env": "^7.20.2", "@babel/preset-typescript": "^7.18.6", - "@pinia/testing": "^0.0.14", "@rushstack/eslint-patch": "^1.1.4", "@types/crc": "^3.8.0", "@types/jest": "^27.0.0", diff --git a/src/App.vue b/src/App.vue index 02ab545..b9816b9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -2,13 +2,16 @@ import { useRoute } from "vue-router"; import TopBar from "@/components/TopBar/TopBar.vue"; import SpinnerComponent from "@/components/SpinnerComponent.vue"; +import ToasterComponent from "@/components/ToasterComponent.vue"; import { init, useOnboard } from "@web3-onboard/vue"; import injectedModule from "@web3-onboard/injected-wallets"; import { Networks } from "./model/Networks"; import { NetworkEnum } from "./model/NetworkEnum"; +import { ref } from "vue"; const route = useRoute(); const injected = injectedModule(); +const targetNetwork = ref(NetworkEnum.sepolia); const web3Onboard = init({ wallets: [injected], @@ -58,5 +61,6 @@ if (!connectedWallet) { + diff --git a/src/assets/main.css b/src/assets/main.css index 3e1fbc9..16eec84 100644 --- a/src/assets/main.css +++ b/src/assets/main.css @@ -28,3 +28,9 @@ a, .main-container { @apply flex w-full md:max-w-lg flex-col justify-center items-center px-4 sm:px-8 py-4 sm:py-6 gap-4 rounded-lg border border-gray-500 backdrop-blur-md drop-shadow-lg shadow-lg mt-10; } + +input[type="number"] { + appearance: textfield; + -webkit-appearance: textfield; + -moz-appearance: textfield; +} \ No newline at end of file diff --git a/src/blockchain/__tests__/addresses.spec.ts b/src/blockchain/__tests__/addresses.spec.ts index 450417d..8ea10ec 100644 --- a/src/blockchain/__tests__/addresses.spec.ts +++ b/src/blockchain/__tests__/addresses.spec.ts @@ -8,7 +8,7 @@ import { import { setActivePinia, createPinia } from "pinia"; import { NetworkEnum, TokenEnum } from "@/model/NetworkEnum"; -import { useEtherStore } from "@/store/ether"; +import { useUser } from "@/composables/useUser"; describe("addresses.ts types", () => { it("My addresses.ts types work properly", () => { @@ -25,16 +25,16 @@ describe("addresses.ts functions", () => { }); it("getTokenAddress Ethereum", () => { - const etherStore = useEtherStore(); - etherStore.setNetworkId(NetworkEnum.sepolia); + const user = useUser(); + user.setNetworkId(NetworkEnum.sepolia); expect(getTokenAddress(TokenEnum.BRZ)).toBe( "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00" ); }); it("getTokenAddress Rootstock", () => { - const etherStore = useEtherStore(); - etherStore.setNetworkId(NetworkEnum.rootstock); + const user = useUser(); + user.setNetworkId(NetworkEnum.rootstock); expect(getTokenAddress(TokenEnum.BRZ)).toBe( "0xfE841c74250e57640390f46d914C88d22C51e82e" ); @@ -47,16 +47,16 @@ describe("addresses.ts functions", () => { }); it("getP2PixAddress Ethereum", () => { - const etherStore = useEtherStore(); - etherStore.setNetworkId(NetworkEnum.sepolia); + const user = useUser(); + user.setNetworkId(NetworkEnum.sepolia); expect(getP2PixAddress()).toBe( "0x2414817FF64A114d91eCFA16a834d3fCf69103d4" ); }); it("getP2PixAddress Rootstock", () => { - const etherStore = useEtherStore(); - etherStore.setNetworkId(NetworkEnum.rootstock); + const user = useUser(); + user.setNetworkId(NetworkEnum.rootstock); expect(getP2PixAddress()).toBe( "0x98ba35eb14b38D6Aa709338283af3e922476dE34" ); @@ -69,14 +69,14 @@ describe("addresses.ts functions", () => { }); it("getProviderUrl Ethereum", () => { - const etherStore = useEtherStore(); - etherStore.setNetworkId(NetworkEnum.sepolia); + const user = useUser(); + user.setNetworkId(NetworkEnum.sepolia); expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL); }); it("getProviderUrl Rootstock", () => { - const etherStore = useEtherStore(); - etherStore.setNetworkId(NetworkEnum.rootstock); + const user = useUser(); + user.setNetworkId(NetworkEnum.rootstock); expect(getProviderUrl()).toBe(import.meta.env.VITE_ROOTSTOCK_API_URL); }); @@ -85,8 +85,8 @@ describe("addresses.ts functions", () => { }); it("isPossibleNetwork Returns", () => { - const etherStore = useEtherStore(); - etherStore.setNetworkId(NetworkEnum.sepolia); + const user = useUser(); + user.setNetworkId(NetworkEnum.sepolia); expect(isPossibleNetwork(0x5 as NetworkEnum)).toBe(true); expect(isPossibleNetwork(5 as NetworkEnum)).toBe(true); expect(isPossibleNetwork(0x13881 as NetworkEnum)).toBe(true); diff --git a/src/blockchain/addresses.ts b/src/blockchain/addresses.ts index f957138..6eb790f 100644 --- a/src/blockchain/addresses.ts +++ b/src/blockchain/addresses.ts @@ -1,6 +1,7 @@ -import { useEtherStore } from "@/store/ether"; +import { useUser } from "@/composables/useUser"; import { NetworkEnum, TokenEnum } from "@/model/NetworkEnum"; -import { JsonRpcProvider } from "ethers"; +import { createPublicClient, http } from "viem"; +import { sepolia, rootstock } from "viem/chains"; const Tokens: { [key in NetworkEnum]: { [key in TokenEnum]: string } } = { [NetworkEnum.sepolia]: { @@ -14,57 +15,53 @@ const Tokens: { [key in NetworkEnum]: { [key in TokenEnum]: string } } = { }; export const getTokenByAddress = (address: string) => { - for (const network of Object.values(NetworkEnum).filter( - (v) => !isNaN(Number(v)) - )) { - for (const token of Object.keys(Tokens[network as NetworkEnum])) { - if (address === Tokens[network as NetworkEnum][token as TokenEnum]) { - return token as TokenEnum; - } + const user = useUser(); + const networksTokens = Tokens[user.networkName.value]; + for (const [token, tokenAddress] of Object.entries(networksTokens)) { + if (tokenAddress.toLowerCase() === address.toLowerCase()) { + return token; } } - return null; }; -const getTokenAddress = (token: TokenEnum, network?: NetworkEnum): string => { - const etherStore = useEtherStore(); - return Tokens[network ? network : etherStore.networkName][token]; +export const getTokenAddress = ( + token: TokenEnum, + network?: NetworkEnum +): string => { + const user = useUser(); + return Tokens[network ? network : user.networkName.value][token]; }; -const getP2PixAddress = (network?: NetworkEnum): string => { - const etherStore = useEtherStore(); +export const getP2PixAddress = (network?: NetworkEnum): string => { + const user = useUser(); const possibleP2PixAddresses: { [key in NetworkEnum]: string } = { - [NetworkEnum.sepolia]: "0xb7cD135F5eFD9760981e02E2a898790b688939fe", + [NetworkEnum.sepolia]: "0x2414817FF64A114d91eCFA16a834d3fCf69103d4", [NetworkEnum.rootstock]: "0x98ba35eb14b38D6Aa709338283af3e922476dE34", }; - return possibleP2PixAddresses[network ? network : etherStore.networkName]; + return possibleP2PixAddresses[network ? network : user.networkName.value]; }; -const getProviderUrl = (network?: NetworkEnum): string => { - const etherStore = useEtherStore(); +export const getProviderUrl = (network?: NetworkEnum): string => { + const user = useUser(); const possibleProvidersUrls: { [key in NetworkEnum]: string } = { [NetworkEnum.sepolia]: import.meta.env.VITE_SEPOLIA_API_URL, [NetworkEnum.rootstock]: import.meta.env.VITE_RSK_API_URL, }; - return possibleProvidersUrls[network || etherStore.networkName]; + return possibleProvidersUrls[network || user.networkName.value]; }; -const getProviderByNetwork = (network: NetworkEnum): JsonRpcProvider => { +export const getProviderByNetwork = (network: NetworkEnum) => { console.log("network", network); - return new JsonRpcProvider(getProviderUrl(network), network); + const chain = network === NetworkEnum.sepolia ? sepolia : rootstock; + return createPublicClient({ + chain, + transport: http(getProviderUrl(network)), + }); }; -const isPossibleNetwork = (networkChain: NetworkEnum): boolean => { +export const isPossibleNetwork = (networkChain: NetworkEnum): boolean => { return Number(networkChain) in NetworkEnum; }; - -export { - getTokenAddress, - getProviderUrl, - isPossibleNetwork, - getP2PixAddress, - getProviderByNetwork, -}; diff --git a/src/blockchain/buyerMethods.ts b/src/blockchain/buyerMethods.ts index 3abc13d..991fcaa 100644 --- a/src/blockchain/buyerMethods.ts +++ b/src/blockchain/buyerMethods.ts @@ -1,94 +1,61 @@ -import { getContract, getProvider } from "./provider"; -import { getP2PixAddress, getTokenAddress } from "./addresses"; -import { encodeBytes32String, Signature, Contract, parseEther } from "ethers"; - -import p2pix from "@/utils/smart_contract_files/P2PIX.json"; - +import { getContract } from "./provider"; +import { getTokenAddress } from "./addresses"; +import { parseEther } from "viem"; import type { TokenEnum } from "@/model/NetworkEnum"; -import { createSolicitation } from "../utils/bbPay"; -import type { Offer } from "../utils/bbPay"; -const addLock = async ( - sellerId: string, - token: string, +export const addLock = async ( + sellerAddress: string, + tokenAddress: string, amount: number ): Promise => { - const p2pContract = await getContract(); + const { address, abi, client } = await getContract(); - const lock = await p2pContract.lock( - sellerId, - token, - parseEther(String(amount)), // BigNumber - [], - [] - ); + const parsedAmount = parseEther(amount.toString()); - const lock_rec = await lock.wait(); - const [t] = lock_rec.events; + const { request } = await client.simulateContract({ + address, + abi, + functionName: "addLock", + args: [sellerAddress, tokenAddress, parsedAmount], + }); - const offer: Offer = { - amount, - lockId: String(t.args.lockID), - sellerId: sellerId, - }; - const solicitation = await createSolicitation(offer); + const hash = await client.writeContract(request); + const receipt = await client.waitForTransactionReceipt({ hash }); - return; + return receipt.status ? receipt.logs[0].topics[2] : ""; }; -const releaseLock = async (solicitation: any): Promise => { - // const mockBacenSigner = new Wallet( - // "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" - // ); - - // const messageToSign = solidityPackedKeccak256( - // ["bytes32", "uint256", "bytes32"], - // [sellerId, parseEther(String(amount)), encodeBytes32String(signature)] - // ); - - // const messageHashBytes = getBytes(messageToSign); - // const flatSig = await mockBacenSigner.signMessage(messageHashBytes); - - const provider = getProvider(); - - const sig = Signature.from(flatSig); - console.log(sig); - const signer = await provider.getSigner(); - const p2pContract = new Contract(getP2PixAddress(), p2pix.abi, signer); - - const release = await p2pContract.release( - BigInt(lockId), - encodeBytes32String(e2eId), - flatSig - ); - await release.wait(); - - return release; -}; - -const cancelDeposit = async (depositId: bigint): Promise => { - const contract = await getContract(); - - const cancel = await contract.cancelDeposit(depositId); - await cancel.wait(); - - return cancel; -}; - -const withdrawDeposit = async ( +export const withdrawDeposit = async ( amount: string, token: TokenEnum -): Promise => { - const contract = await getContract(); +): Promise => { + const { address, abi, client } = await getContract(); - const withdraw = await contract.withdraw( - getTokenAddress(token), - parseEther(String(amount)), - [] - ); - await withdraw.wait(); + const tokenAddress = getTokenAddress(token); - return withdraw; + const { request } = await client.simulateContract({ + address, + abi, + functionName: "withdrawDeposit", + args: [tokenAddress, parseEther(amount)], + }); + + const hash = await client.writeContract(request); + const receipt = await client.waitForTransactionReceipt({ hash }); + + return receipt.status; }; -export { cancelDeposit, withdrawDeposit, addLock, releaseLock }; +export const releaseLock = async (solicitation: any): Promise => { + const { address, abi, client } = await getContract(); + + const { request } = await client.simulateContract({ + address, + abi, + functionName: "releaseLock", + args: [solicitation.lockId, solicitation.e2eId], + }); + + const hash = await client.writeContract(request); + return client.waitForTransactionReceipt({ hash }); +}; diff --git a/src/blockchain/events.ts b/src/blockchain/events.ts index b9d98cf..95c795c 100644 --- a/src/blockchain/events.ts +++ b/src/blockchain/events.ts @@ -1,21 +1,23 @@ -import { useEtherStore } from "@/store/ether"; -import { Contract, formatEther, Interface } from "ethers"; +import { useUser } from "@/composables/useUser"; +import { + formatEther, + decodeEventLog, + parseAbi, + toHex, + type PublicClient, +} from "viem"; import p2pix from "@/utils/smart_contract_files/P2PIX.json"; import { getContract } from "./provider"; import type { ValidDeposit } from "@/model/ValidDeposit"; -import { - getP2PixAddress, - getProviderByNetwork, - getTokenAddress, -} from "./addresses"; +import { getTokenAddress } from "./addresses"; import { NetworkEnum } from "@/model/NetworkEnum"; import type { UnreleasedLock } from "@/model/UnreleasedLock"; import type { Pix } from "@/model/Pix"; const getNetworksLiquidity = async (): Promise => { - const etherStore = useEtherStore(); - etherStore.setLoadingNetworkLiquidity(true); + const user = useUser(); + user.setLoadingNetworkLiquidity(true); const depositLists: ValidDeposit[][] = []; @@ -23,31 +25,35 @@ const getNetworksLiquidity = async (): Promise => { (v) => !isNaN(Number(v)) )) { console.log("getNetworksLiquidity", network); - const p2pContract = new Contract( - getP2PixAddress(network as NetworkEnum), - p2pix.abi, - getProviderByNetwork(network as NetworkEnum) - ); - - depositLists.push( - await getValidDeposits( - getTokenAddress(etherStore.selectedToken, network as NetworkEnum), - network as NetworkEnum, - p2pContract - ) + const deposits = await getValidDeposits( + getTokenAddress(user.selectedToken.value), + Number(network) ); + if (deposits) depositLists.push(deposits); } - etherStore.setDepositsValidList(depositLists.flat()); - etherStore.setLoadingNetworkLiquidity(false); + const allDeposits = depositLists.flat(); + user.setDepositsValidList(allDeposits); + user.setLoadingNetworkLiquidity(false); }; const getPixKey = async (seller: string, token: string): Promise => { - const p2pContract = await getContract(); - const pixKeyHex = await p2pContract.getPixTarget(seller, token); + const { address, abi, client } = await getContract(); + + const pixKeyHex = await client.readContract({ + address, + abi, + functionName: "getPixTarget", + args: [seller, token], + }); + // Remove '0x' prefix and convert hex to UTF-8 string + const hexString = + typeof pixKeyHex === "string" ? pixKeyHex : toHex(pixKeyHex); + if (!hexString) throw new Error("PixKey not found"); const bytes = new Uint8Array( - pixKeyHex + // @ts-ignore + hexString .slice(2) .match(/.{1,2}/g) .map((byte: string) => parseInt(byte, 16)) @@ -59,50 +65,67 @@ const getPixKey = async (seller: string, token: string): Promise => { const getValidDeposits = async ( token: string, network: NetworkEnum, - contract?: Contract + contractInfo?: { client: any; address: string } ): Promise => { - let p2pContract: Contract; + let client: PublicClient, address, abi; - if (contract) { - p2pContract = contract; + if (contractInfo) { + ({ client, address } = contractInfo); + abi = p2pix.abi; } else { - p2pContract = await getContract(true); + ({ address, abi, client } = await getContract(true)); + } + + const depositLogs = await client.getLogs({ + address, + event: parseAbi([ + "event DepositAdded(address indexed seller, address token, uint256 amount)", + ])[0], + fromBlock: 0n, + toBlock: "latest", + }); + + if (!contractInfo) { + // Get metamask provider contract + ({ address, abi, client } = await getContract()); } - const filterDeposits = p2pContract.filters.DepositAdded(null); - const eventsDeposits = await p2pContract.queryFilter( - filterDeposits - // 0, - // "latest" - ); - if (!contract) p2pContract = await getContract(); // get metamask provider contract const depositList: { [key: string]: ValidDeposit } = {}; - for (const deposit of eventsDeposits) { - const IPix2Pix = new Interface(p2pix.abi); - const decoded = IPix2Pix.parseLog({ - topics: deposit.topics, - data: deposit.data, - }); - // Get liquidity only for the selected token - if (decoded?.args.token != token) continue; - const mappedBalance = await p2pContract.getBalance( - decoded.args.seller, - token - ); - let validDeposit: ValidDeposit | null = null; + for (const log of depositLogs) { + try { + const decoded = decodeEventLog({ + abi, + data: log.data, + topics: log.topics, + }); - if (mappedBalance) { - validDeposit = { - token: token, - blockNumber: deposit.blockNumber, - remaining: Number(formatEther(mappedBalance)), - seller: decoded.args.seller, - network, - pixKey: "", - }; + // Get liquidity only for the selected token + if (decoded?.args.token.toLowerCase() !== token.toLowerCase()) continue; + + const mappedBalance = await client.readContract({ + address, + abi, + functionName: "getBalance", + args: [decoded.args.seller, token], + }); + + let validDeposit: ValidDeposit | null = null; + + if (mappedBalance) { + validDeposit = { + token: token, + blockNumber: Number(log.blockNumber), + remaining: Number(formatEther(mappedBalance)), + seller: decoded.args.seller, + network, + pixKey: "", + }; + } + if (validDeposit) depositList[decoded.args.seller + token] = validDeposit; + } catch (error) { + console.error("Error decoding log", error); } - if (validDeposit) depositList[decoded.args.seller + token] = validDeposit; } return Object.values(depositList); @@ -111,15 +134,20 @@ const getValidDeposits = async ( const getUnreleasedLockById = async ( lockID: string ): Promise => { - const p2pContract = await getContract(); + const { address, abi, client } = await getContract(); const pixData: Pix = { pixKey: "", }; - const lock = await p2pContract.mapLocks(lockID); + const lock = await client.readContract({ + address, + abi, + functionName: "mapLocks", + args: [BigInt(lockID)], + }); const pixTarget = lock.pixTarget; - const amount = formatEther(lock?.amount); + const amount = formatEther(lock.amount); pixData.pixKey = pixTarget; pixData.value = Number(amount); diff --git a/src/blockchain/provider.ts b/src/blockchain/provider.ts index 19c77f8..5f14eac 100644 --- a/src/blockchain/provider.ts +++ b/src/blockchain/provider.ts @@ -1,27 +1,55 @@ import p2pix from "@/utils/smart_contract_files/P2PIX.json"; import { updateWalletStatus } from "./wallet"; import { getProviderUrl, getP2PixAddress } from "./addresses"; -import { BrowserProvider, JsonRpcProvider, Contract } from "ethers"; +import { createPublicClient, createWalletClient, custom, http } from "viem"; +import { sepolia, rootstock } from "viem/chains"; +import { useUser } from "@/composables/useUser"; -let provider: BrowserProvider | JsonRpcProvider | null = null; +let publicClient = null; +let walletClient = null; -const getProvider = (onlyAlchemyProvider: boolean = false) => { - if (onlyAlchemyProvider) return new JsonRpcProvider(getProviderUrl()); // alchemy provider - return provider; +const getPublicClient = (onlyRpcProvider = false) => { + if (onlyRpcProvider) { + const user = useUser(); + const rpcUrl = getProviderUrl(); + return createPublicClient({ + chain: + Number(user.networkName.value) === sepolia.id ? sepolia : rootstock, + transport: http(rpcUrl), + }); + } + return publicClient; }; -const getContract = async (onlyAlchemyProvider: boolean = false) => { - const p = getProvider(onlyAlchemyProvider); - try { - const signer = await p?.getSigner(); - return new Contract(getP2PixAddress(), p2pix.abi, signer); - } catch (err) { - return new Contract(getP2PixAddress(), p2pix.abi, p); - } +const getWalletClient = () => { + return walletClient; +}; + +const getContract = async (onlyRpcProvider = false) => { + const client = getPublicClient(onlyRpcProvider); + const address = getP2PixAddress(); + const abi = p2pix.abi; + + return { address, abi, client }; }; const connectProvider = async (p: any): Promise => { - provider = new BrowserProvider(p, "any"); + console.log("Connecting to provider..."); + const user = useUser(); + const chain = + Number(user.networkName.value) === sepolia.id ? sepolia : rootstock; + + publicClient = createPublicClient({ + chain, + transport: custom(p), + }); + + walletClient = createWalletClient({ + chain, + transport: custom(p), + }); + await updateWalletStatus(); }; -export { getProvider, getContract, connectProvider }; + +export { getPublicClient, getWalletClient, getContract, connectProvider }; diff --git a/src/blockchain/sellerMethods.ts b/src/blockchain/sellerMethods.ts index 203ff4f..a41ce6e 100644 --- a/src/blockchain/sellerMethods.ts +++ b/src/blockchain/sellerMethods.ts @@ -1,60 +1,81 @@ -import { getContract, getProvider } from "./provider"; +import { getContract, getPublicClient, getWalletClient } from "./provider"; import { getTokenAddress, getP2PixAddress } from "./addresses"; - -import { encodeBytes32String, Contract, parseEther } from "ethers"; +import { parseEther, toHex } from "viem"; import mockToken from "../utils/smart_contract_files/MockToken.json"; -import { useEtherStore } from "@/store/ether"; +import { useUser } from "@/composables/useUser"; import { createParticipant } from "@/utils/bbPay"; import type { Participant } from "@/utils/bbPay"; const approveTokens = async (participant: Participant): Promise => { - const provider = getProvider(); - const signer = await provider?.getSigner(); - const etherStore = useEtherStore(); + const user = useUser(); + const publicClient = getPublicClient(); + const walletClient = getWalletClient(); - etherStore.setSeller(participant); - const tokenContract = new Contract( - getTokenAddress(etherStore.selectedToken), - mockToken.abi, - signer - ); + if (!publicClient || !walletClient) { + throw new Error("Clients not initialized"); + } + + user.setSeller(participant); + const [account] = await walletClient.getAddresses(); + + // Get token address + const tokenAddress = getTokenAddress(user.selectedToken.value); // Check if the token is already approved - const approved = await tokenContract.allowance( - await signer?.getAddress(), - getP2PixAddress() - ); - if (approved < parseEther(participant.offer)) { + const allowance = await publicClient.readContract({ + address: tokenAddress, + abi: mockToken.abi, + functionName: "allowance", + args: [account, getP2PixAddress()], + }); + + if (allowance < parseEther(participant.offer.toString())) { // Approve tokens - const apprv = await tokenContract.approve( - getP2PixAddress(), - parseEther(participant.offer) - ); - await apprv.wait(); + const hash = await walletClient.writeContract({ + address: tokenAddress, + abi: mockToken.abi, + functionName: "approve", + args: [getP2PixAddress(), parseEther(participant.offer.toString())], + account, + }); + + await publicClient.waitForTransactionReceipt({ hash }); return true; } return true; }; const addDeposit = async (): Promise => { - const p2pContract = await getContract(); - const etherStore = useEtherStore(); + const { address, abi, client } = await getContract(); + const walletClient = getWalletClient(); + const user = useUser(); - const sellerId = await createParticipant(etherStore.seller); - etherStore.setSellerId(sellerId.id); + if (!walletClient) { + throw new Error("Wallet client not initialized"); + } - const deposit = await p2pContract.deposit( - sellerId, - encodeBytes32String(""), - getTokenAddress(etherStore.selectedToken), - parseEther(etherStore.seller.offer), - true - ); + const [account] = await walletClient.getAddresses(); - await deposit.wait(); + const sellerId = await createParticipant(user.seller.value); + user.setSellerId(sellerId.id); - return deposit; + const hash = await walletClient.writeContract({ + address, + abi, + functionName: "deposit", + args: [ + sellerId.id, + toHex("", { size: 32 }), + getTokenAddress(user.selectedToken.value), + parseEther(user.seller.value.offer), + true, + ], + account, + }); + + const receipt = await client.waitForTransactionReceipt({ hash }); + return receipt; }; export { approveTokens, addDeposit }; diff --git a/src/blockchain/wallet.ts b/src/blockchain/wallet.ts index 5761240..8841718 100644 --- a/src/blockchain/wallet.ts +++ b/src/blockchain/wallet.ts @@ -1,17 +1,9 @@ -import { - Contract, - formatEther, - getAddress, - Interface, - Log, - LogDescription, -} from "ethers"; -import { useEtherStore } from "@/store/ether"; +import { decodeEventLog, formatEther, type Log, parseAbi } from "viem"; +import { useUser } from "@/composables/useUser"; -import { getContract, getProvider } from "./provider"; -import { getTokenAddress, isPossibleNetwork } from "./addresses"; +import { getPublicClient, getWalletClient, getContract } from "./provider"; +import { getTokenAddress } from "./addresses"; -import mockToken from "@/utils/smart_contract_files/MockToken.json"; import p2pix from "@/utils/smart_contract_files/P2PIX.json"; import { getValidDeposits } from "./events"; @@ -22,43 +14,36 @@ import type { UnreleasedLock } from "@/model/UnreleasedLock"; import type { Pix } from "@/model/Pix"; export const updateWalletStatus = async (): Promise => { - const etherStore = useEtherStore(); + const user = useUser(); - const provider = await getProvider(); - const signer = await provider?.getSigner(); - const network = await provider?.getNetwork(); - const chainId = network?.chainId; - if (!isPossibleNetwork(Number(chainId))) { - window.alert("Invalid chain!:" + chainId); + const publicClient = getPublicClient(); + const walletClient = getWalletClient(); + + if (!publicClient || !walletClient) { + console.error("Client not initialized"); return; } - etherStore.setNetworkId(Number(chainId)); - const mockTokenContract = new Contract( - getTokenAddress(etherStore.selectedToken), - mockToken.abi, - signer - ); + // Get balance + const [account] = await walletClient.getAddresses(); + const balance = await publicClient.getBalance({ address: account }); - const walletAddress = await provider?.send("eth_requestAccounts", []); - const balance = await mockTokenContract.balanceOf(walletAddress[0]); - - etherStore.setBalance(formatEther(balance)); - etherStore.setWalletAddress(getAddress(walletAddress[0])); + user.setWalletAddress(account); + user.setBalance(formatEther(balance)); }; export const listValidDepositTransactionsByWalletAddress = async ( walletAddress: string ): Promise => { - const etherStore = useEtherStore(); + const user = useUser(); const walletDeposits = await getValidDeposits( - getTokenAddress(etherStore.selectedToken), - etherStore.networkName + getTokenAddress(user.selectedToken.value), + user.networkName.value ); if (walletDeposits) { return walletDeposits .filter((deposit) => deposit.seller == walletAddress) - .sort((a, b) => { + .sort((a: ValidDeposit, b: ValidDeposit) => { return b.blockNumber - a.blockNumber; }); } @@ -66,10 +51,15 @@ export const listValidDepositTransactionsByWalletAddress = async ( return []; }; -const getLockStatus = async (id: [BigInt]): Promise => { - const p2pContract = await getContract(); - const res = await p2pContract.getLocksStatus([id]); - return res[1][0]; +const getLockStatus = async (id: bigint): Promise => { + const { address, abi, client } = await getContract(); + const result = await client.readContract({ + address, + abi, + functionName: "getLocksStatus", + args: [[id]], + }); + return result[1][0]; }; const filterLockStatus = async ( @@ -78,31 +68,38 @@ const filterLockStatus = async ( const txs: WalletTransaction[] = []; for (const transaction of transactions) { - const IPix2Pix = new Interface(p2pix.abi); - const decoded = IPix2Pix.parseLog({ - topics: transaction.topics, - data: transaction.data, - }); - if (!decoded) continue; - const tx: WalletTransaction = { - token: decoded.args.token ? decoded.args.token : "", - blockNumber: transaction.blockNumber, - amount: decoded.args.amount - ? Number(formatEther(decoded.args.amount)) - : -1, - seller: decoded.args.seller ? decoded.args.seller : "", - buyer: decoded.args.buyer ? decoded.args.buyer : "", - event: decoded.name, - lockStatus: - decoded.name == "LockAdded" - ? await getLockStatus(decoded.args.lockID) - : -1, - transactionHash: transaction.transactionHash - ? transaction.transactionHash - : "", - transactionID: decoded.args.lockID ? decoded.args.lockID.toString() : "", - }; - txs.push(tx); + try { + const decoded = decodeEventLog({ + abi: p2pix.abi, + data: transaction.data, + topics: transaction.topics, + }); + + if (!decoded || !decoded.args) continue; + + // Type assertion to handle the args safely + const args = decoded.args as Record; + + const tx: WalletTransaction = { + token: args.token ? String(args.token) : "", + blockNumber: Number(transaction.blockNumber), + amount: args.amount ? Number(formatEther(args.amount)) : -1, + seller: args.seller ? String(args.seller) : "", + buyer: args.buyer ? String(args.buyer) : "", + event: decoded.eventName || "", + lockStatus: + decoded.eventName == "LockAdded" && args.lockID + ? await getLockStatus(args.lockID) + : -1, + transactionHash: transaction.transactionHash + ? transaction.transactionHash + : "", + transactionID: args.lockID ? args.lockID.toString() : "", + }; + txs.push(tx); + } catch (error) { + console.error("Error decoding log", error); + } } return txs; }; @@ -110,162 +107,224 @@ const filterLockStatus = async ( export const listAllTransactionByWalletAddress = async ( walletAddress: string ): Promise => { - const p2pContract = await getContract(true); + const { address, client } = await getContract(true); // Get deposits - const filterDeposits = p2pContract.filters.DepositAdded([walletAddress]); - const eventsDeposits = await p2pContract.queryFilter( - filterDeposits, - 0, - "latest" - ); + const depositLogs = await client.getLogs({ + address, + event: parseAbi([ + "event DepositAdded(address indexed seller, address token, uint256 amount)", + ])[0], + args: { + seller: walletAddress, + }, + fromBlock: 0n, + toBlock: "latest", + }); console.log("Fetched all wallet deposits"); // Get locks - const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]); - const eventsAddedLocks = await p2pContract.queryFilter( - filterAddedLocks, - 0, - "latest" - ); + const lockLogs = await client.getLogs({ + address, + event: parseAbi([ + "event LockAdded(address indexed buyer, uint256 indexed lockID, address seller, address token, uint256 amount)", + ])[0], + args: { + buyer: walletAddress, + }, + fromBlock: 0n, + toBlock: "latest", + }); console.log("Fetched all wallet locks"); // Get released locks - const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]); - const eventsReleasedLocks = await p2pContract.queryFilter( - filterReleasedLocks, - 0, - "latest" - ); + const releasedLogs = await client.getLogs({ + address, + event: parseAbi([ + "event LockReleased(address indexed buyer, uint256 indexed lockID, string e2eId)", + ])[0], + args: { + buyer: walletAddress, + }, + fromBlock: 0n, + toBlock: "latest", + }); console.log("Fetched all wallet released locks"); // Get withdrawn deposits - const filterWithdrawnDeposits = p2pContract.filters.DepositWithdrawn([ - walletAddress, - ]); - const eventsWithdrawnDeposits = await p2pContract.queryFilter( - filterWithdrawnDeposits - ); + const withdrawnLogs = await client.getLogs({ + address, + event: parseAbi([ + "event DepositWithdrawn(address indexed seller, address token, uint256 amount)", + ])[0], + args: { + seller: walletAddress, + }, + fromBlock: 0n, + toBlock: "latest", + }); console.log("Fetched all wallet withdrawn deposits"); - const lockStatusFiltered = await filterLockStatus( - [ - ...eventsDeposits, - ...eventsAddedLocks, - ...eventsReleasedLocks, - ...eventsWithdrawnDeposits, - ].sort((a, b) => { - return b.blockNumber - a.blockNumber; - }) - ); + const allLogs = [ + ...depositLogs, + ...lockLogs, + ...releasedLogs, + ...withdrawnLogs, + ].sort((a: Log, b: Log) => { + return Number(b.blockNumber) - Number(a.blockNumber); + }); - return lockStatusFiltered; + return await filterLockStatus(allLogs); }; // get wallet's release transactions export const listReleaseTransactionByWalletAddress = async ( walletAddress: string -): Promise => { - const p2pContract = await getContract(true); +) => { + const { address, client } = await getContract(true); - const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]); - const eventsReleasedLocks = await p2pContract.queryFilter( - filterReleasedLocks, - 0, - "latest" - ); + const releasedLogs = await client.getLogs({ + address, + event: parseAbi([ + "event LockReleased(address indexed buyer, uint256 indexed lockID, string e2eId)", + ])[0], + args: { + buyer: walletAddress, + }, + fromBlock: 0n, + toBlock: "latest", + }); - return eventsReleasedLocks - .sort((a, b) => { - return b.blockNumber - a.blockNumber; + return releasedLogs + .sort((a: Log, b: Log) => { + return Number(b.blockNumber) - Number(a.blockNumber); }) - .map((lock) => { - const IPix2Pix = new Interface(p2pix.abi); - const decoded = IPix2Pix.parseLog({ - topics: lock.topics, - data: lock.data, - }); - return decoded; + .map((log: Log) => { + try { + return decodeEventLog({ + abi: p2pix.abi, + data: log.data, + topics: log.topics, + }); + } catch (error) { + console.error("Error decoding log", error); + return null; + } }) - .filter((lock) => lock !== null); + .filter((decoded: any) => decoded !== null); }; -const listLockTransactionByWalletAddress = async ( - walletAddress: string -): Promise => { - const p2pContract = await getContract(true); +const listLockTransactionByWalletAddress = async (walletAddress: string) => { + const { address, client } = await getContract(true); - const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]); - const eventsReleasedLocks = await p2pContract.queryFilter(filterAddedLocks); + const lockLogs = await client.getLogs({ + address, + event: parseAbi([ + "event LockAdded(address indexed buyer, uint256 indexed lockID, address seller, address token, uint256 amount)", + ])[0], + args: { + buyer: walletAddress, + }, + fromBlock: 0n, + toBlock: "latest", + }); - return eventsReleasedLocks - .sort((a, b) => { - return b.blockNumber - a.blockNumber; + return lockLogs + .sort((a: Log, b: Log) => { + return Number(b.blockNumber) - Number(a.blockNumber); }) - .map((lock) => { - const IPix2Pix = new Interface(p2pix.abi); - const decoded = IPix2Pix.parseLog({ - topics: lock.topics, - data: lock.data, - }); - return decoded; + .map((log: Log) => { + try { + return decodeEventLog({ + abi: p2pix.abi, + data: log.data, + topics: log.topics, + }); + } catch (error) { + console.error("Error decoding log", error); + return null; + } }) - .filter((lock) => lock !== null); + .filter((decoded: any) => decoded !== null); }; -const listLockTransactionBySellerAddress = async ( - sellerAddress: string -): Promise => { - const p2pContract = await getContract(true); +const listLockTransactionBySellerAddress = async (sellerAddress: string) => { + const { address, client } = await getContract(true); console.log("Will get locks as seller", sellerAddress); - const filterAddedLocks = p2pContract.filters.LockAdded(); - const eventsReleasedLocks = await p2pContract.queryFilter( - filterAddedLocks - // 0, - // "latest" - ); - return eventsReleasedLocks - .map((lock) => { - const IPix2Pix = new Interface(p2pix.abi); - const decoded = IPix2Pix.parseLog({ - topics: lock.topics, - data: lock.data, - }); - return decoded; + + const lockLogs = await client.getLogs({ + address, + event: parseAbi([ + "event LockAdded(address indexed buyer, uint256 indexed lockID, address seller, address token, uint256 amount)", + ])[0], + fromBlock: 0n, + toBlock: "latest", + }); + + return lockLogs + .map((log: Log) => { + try { + return decodeEventLog({ + abi: p2pix.abi, + data: log.data, + topics: log.topics, + }); + } catch (error) { + console.error("Error decoding log", error); + return null; + } }) - .filter((lock) => lock !== null) + .filter((decoded: any) => decoded !== null) .filter( - (lock) => lock.args.seller.toLowerCase() == sellerAddress.toLowerCase() + (decoded: any) => + decoded.args && + decoded.args.seller && + decoded.args.seller.toLowerCase() === sellerAddress.toLowerCase() ); }; export const checkUnreleasedLock = async ( walletAddress: string ): Promise => { - const p2pContract = await getContract(); + const { address, abi, client } = await getContract(); const pixData: Pix = { pixKey: "", }; const addedLocks = await listLockTransactionByWalletAddress(walletAddress); - const lockStatus = await p2pContract.getLocksStatus( - addedLocks.map((lock) => lock.args?.lockID) - ); + + if (!addedLocks.length) return undefined; + + const lockIds = addedLocks.map((lock: any) => lock.args.lockID); + + const lockStatus = await client.readContract({ + address, + abi, + functionName: "getLocksStatus", + args: [lockIds], + }); + const unreleasedLockId = lockStatus[1].findIndex( - (lockStatus: number) => lockStatus == 1 + (status: number) => status == 1 ); - if (unreleasedLockId != -1) { - const _lockID = lockStatus[0][unreleasedLockId]; - const lock = await p2pContract.mapLocks(_lockID); + if (unreleasedLockId !== -1) { + const lockID = lockStatus[0][unreleasedLockId]; + + const lock = await client.readContract({ + address, + abi, + functionName: "mapLocks", + args: [lockID], + }); const pixTarget = lock.pixTarget; - const amount = formatEther(lock?.amount); + const amount = formatEther(lock.amount); pixData.pixKey = pixTarget; pixData.value = Number(amount); return { - lockID: _lockID, + lockID, pix: pixData, }; } @@ -274,27 +333,33 @@ export const checkUnreleasedLock = async ( export const getActiveLockAmount = async ( walletAddress: string ): Promise => { - const p2pContract = await getContract(true); + const { address, abi, client } = await getContract(true); const lockSeller = await listLockTransactionBySellerAddress(walletAddress); - const lockStatus = await p2pContract.getLocksStatus( - lockSeller.map((lock) => lock.args?.lockID) - ); + if (!lockSeller.length) return 0; - const activeLockAmount = await lockStatus[1].reduce( - async (sumValue: Promise, currentStatus: number, index: number) => { - const currValue = await sumValue; - let valueToSum = 0; + const lockIds = lockSeller.map((lock: any) => lock.args.lockID); - if (currentStatus == 1) { - const lock = await p2pContract.mapLocks(lockStatus[0][index]); - valueToSum = Number(formatEther(lock?.amount)); - } + const lockStatus = await client.readContract({ + address, + abi, + functionName: "getLocksStatus", + args: [lockIds], + }); - return currValue + valueToSum; - }, - Promise.resolve(0) - ); + let activeLockAmount = 0; + for (let i = 0; i < lockStatus[1].length; i++) { + if (lockStatus[1][i] === 1) { + const lockId = lockStatus[0][i]; + const lock = await client.readContract({ + address, + abi, + functionName: "mapLocks", + args: [lockId], + }); + activeLockAmount += Number(formatEther(lock.amount)); + } + } return activeLockAmount; }; diff --git a/src/components/BuyConfirmedComponent/BuyConfirmedComponent.vue b/src/components/BuyConfirmedComponent/BuyConfirmedComponent.vue index 1c88b10..2f1f15d 100644 --- a/src/components/BuyConfirmedComponent/BuyConfirmedComponent.vue +++ b/src/components/BuyConfirmedComponent/BuyConfirmedComponent.vue @@ -8,8 +8,7 @@ import { import CustomButton from "@/components/CustomButton/CustomButton.vue"; import type { ValidDeposit } from "@/model/ValidDeposit"; import type { WalletTransaction } from "@/model/WalletTransaction"; -import { useEtherStore } from "@/store/ether"; -import { storeToRefs } from "pinia"; +import { useUser } from "@/composables/useUser"; import { onMounted, ref, watch } from "vue"; import ListingComponent from "../ListingComponent/ListingComponent.vue"; @@ -19,8 +18,8 @@ const props = defineProps<{ isCurrentStep: boolean; }>(); -const etherStore = useEtherStore(); -const { walletAddress } = storeToRefs(etherStore); +const user = useUser(); +const { walletAddress } = useUser(); const lastWalletTransactions = ref([]); const depositList = ref([]); @@ -29,7 +28,7 @@ const activeLockAmount = ref(0); // methods const getWalletTransactions = async () => { - etherStore.setLoadingWalletTransactions(true); + user.setLoadingWalletTransactions(true); if (walletAddress.value) { const walletDeposits = await listValidDepositTransactionsByWalletAddress( walletAddress.value @@ -48,20 +47,20 @@ const getWalletTransactions = async () => { lastWalletTransactions.value = allUserTransactions; } } - etherStore.setLoadingWalletTransactions(false); + user.setLoadingWalletTransactions(false); }; const callWithdraw = async (amount: string) => { if (amount) { - etherStore.setLoadingWalletTransactions(true); - const withdraw = await withdrawDeposit(amount, etherStore.selectedToken); + user.setLoadingWalletTransactions(true); + const withdraw = await withdrawDeposit(amount, user.selectedToken.value); if (withdraw) { console.log("Saque realizado!"); await getWalletTransactions(); } else { console.log("Não foi possível realizar o saque!"); } - etherStore.setLoadingWalletTransactions(false); + user.setLoadingWalletTransactions(false); } }; @@ -93,14 +92,14 @@ onMounted(async () => {

Tokens recebidos

- {{ props.tokenAmount }} {{ etherStore.selectedToken }} + {{ props.tokenAmount }} {{ user.selectedToken }}

Não encontrou os tokens?
Clique no botão abaixo para
- cadastrar o {{ etherStore.selectedToken }} em sua carteira. + cadastrar o {{ user.selectedToken }} em sua carteira.

@@ -150,10 +149,6 @@ p { @apply font-medium text-base text-gray-900; } -input[type="number"] { - -moz-appearance: textfield; -} - input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; diff --git a/src/components/ListingComponent/ListingComponent.vue b/src/components/ListingComponent/ListingComponent.vue index 1ffe1ea..754d400 100644 --- a/src/components/ListingComponent/ListingComponent.vue +++ b/src/components/ListingComponent/ListingComponent.vue @@ -1,10 +1,8 @@ diff --git a/src/components/SellerSteps/SellerSearchComponent.vue b/src/components/SellerSteps/SellerSearchComponent.vue index b3de2e9..2276507 100644 --- a/src/components/SellerSteps/SellerSearchComponent.vue +++ b/src/components/SellerSteps/SellerSearchComponent.vue @@ -1,17 +1,15 @@ + + + + diff --git a/src/components/TopBar/TopBar.vue b/src/components/TopBar/TopBar.vue index 5e7c925..eb98578 100644 --- a/src/components/TopBar/TopBar.vue +++ b/src/components/TopBar/TopBar.vue @@ -1,7 +1,6 @@