From e93cac6086fdd3846a8b035334ad82881145e9e4 Mon Sep 17 00:00:00 2001 From: Filipe Soccol Date: Mon, 31 Mar 2025 10:26:57 -0300 Subject: [PATCH 1/4] Migrated project to Viem, removing Ethers completelly. Not finished tests. --- src/blockchain/__tests__/addresses.spec.ts | 16 +- src/blockchain/addresses.ts | 58 ++- src/blockchain/buyerMethods.ts | 179 +++++--- src/blockchain/events.ts | 143 +++--- src/blockchain/provider.ts | 55 ++- src/blockchain/sellerMethods.ts | 105 +++-- src/blockchain/wallet.ts | 417 +++++++++++------- .../BuyConfirmedComponent.vue | 22 +- .../ListingComponent/ListingComponent.vue | 4 +- .../__tests__/ListingComponent.spec.ts | 11 +- src/components/SearchComponent.vue | 14 +- .../SellerSteps/SellerComponent.vue | 14 +- .../SellerSteps/SellerSearchComponent.vue | 16 +- src/components/TopBar/TopBar.vue | 21 +- .../TopBar/__tests__/TopBar.spec.ts | 18 +- src/store/{ether.ts => viem.ts} | 2 +- src/views/HomeView.vue | 12 +- src/views/ManageBidsView.vue | 14 +- src/views/SellerView.vue | 20 +- 19 files changed, 672 insertions(+), 469 deletions(-) rename src/store/{ether.ts => viem.ts} (97%) diff --git a/src/blockchain/__tests__/addresses.spec.ts b/src/blockchain/__tests__/addresses.spec.ts index 450417d..a8bd566 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 { useViemStore } from "@/store/viem"; describe("addresses.ts types", () => { it("My addresses.ts types work properly", () => { @@ -25,7 +25,7 @@ describe("addresses.ts functions", () => { }); it("getTokenAddress Ethereum", () => { - const etherStore = useEtherStore(); + const etherStore = useViemStore(); etherStore.setNetworkId(NetworkEnum.sepolia); expect(getTokenAddress(TokenEnum.BRZ)).toBe( "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00" @@ -33,7 +33,7 @@ describe("addresses.ts functions", () => { }); it("getTokenAddress Rootstock", () => { - const etherStore = useEtherStore(); + const etherStore = useViemStore(); etherStore.setNetworkId(NetworkEnum.rootstock); expect(getTokenAddress(TokenEnum.BRZ)).toBe( "0xfE841c74250e57640390f46d914C88d22C51e82e" @@ -47,7 +47,7 @@ describe("addresses.ts functions", () => { }); it("getP2PixAddress Ethereum", () => { - const etherStore = useEtherStore(); + const etherStore = useViemStore(); etherStore.setNetworkId(NetworkEnum.sepolia); expect(getP2PixAddress()).toBe( "0x2414817FF64A114d91eCFA16a834d3fCf69103d4" @@ -55,7 +55,7 @@ describe("addresses.ts functions", () => { }); it("getP2PixAddress Rootstock", () => { - const etherStore = useEtherStore(); + const etherStore = useViemStore(); etherStore.setNetworkId(NetworkEnum.rootstock); expect(getP2PixAddress()).toBe( "0x98ba35eb14b38D6Aa709338283af3e922476dE34" @@ -69,13 +69,13 @@ describe("addresses.ts functions", () => { }); it("getProviderUrl Ethereum", () => { - const etherStore = useEtherStore(); + const etherStore = useViemStore(); etherStore.setNetworkId(NetworkEnum.sepolia); expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL); }); it("getProviderUrl Rootstock", () => { - const etherStore = useEtherStore(); + const etherStore = useViemStore(); etherStore.setNetworkId(NetworkEnum.rootstock); expect(getProviderUrl()).toBe(import.meta.env.VITE_ROOTSTOCK_API_URL); }); @@ -85,7 +85,7 @@ describe("addresses.ts functions", () => { }); it("isPossibleNetwork Returns", () => { - const etherStore = useEtherStore(); + const etherStore = useViemStore(); etherStore.setNetworkId(NetworkEnum.sepolia); expect(isPossibleNetwork(0x5 as NetworkEnum)).toBe(true); expect(isPossibleNetwork(5 as NetworkEnum)).toBe(true); diff --git a/src/blockchain/addresses.ts b/src/blockchain/addresses.ts index f957138..8059b8f 100644 --- a/src/blockchain/addresses.ts +++ b/src/blockchain/addresses.ts @@ -1,6 +1,7 @@ -import { useEtherStore } from "@/store/ether"; +import { useViemStore } from "@/store/viem"; 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,54 @@ 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 viemStore = useViemStore(); + const networksTokens = Tokens[viemStore.networkName]; + 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 viemStore = useViemStore(); + return Tokens[network ? network : viemStore.networkName][token]; }; -const getP2PixAddress = (network?: NetworkEnum): string => { - const etherStore = useEtherStore(); +export const getP2PixAddress = (network?: NetworkEnum): string => { + const viemStore = useViemStore(); 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 : viemStore.networkName]; }; -const getProviderUrl = (network?: NetworkEnum): string => { - const etherStore = useEtherStore(); +export const getProviderUrl = (network?: NetworkEnum): string => { + const viemStore = useViemStore(); 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 || viemStore.networkName]; }; -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..e0f2f8b 100644 --- a/src/blockchain/buyerMethods.ts +++ b/src/blockchain/buyerMethods.ts @@ -1,8 +1,6 @@ -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, getWalletClient } from "./provider"; +import { getTokenAddress } from "./addresses"; +import { parseEther, stringToHex, toHex } from "viem"; import type { TokenEnum } from "@/model/NetworkEnum"; import { createSolicitation } from "../utils/bbPay"; @@ -13,82 +11,131 @@ const addLock = async ( token: string, amount: number ): Promise => { - const p2pContract = await getContract(); - - const lock = await p2pContract.lock( - sellerId, - token, - parseEther(String(amount)), // BigNumber - [], - [] - ); - - const lock_rec = await lock.wait(); - const [t] = lock_rec.events; - + const { address, abi, client } = await getContract(); + const walletClient = getWalletClient(); + + if (!walletClient) { + throw new Error("Wallet client not initialized"); + } + + const [account] = await walletClient.getAddresses(); + + const hash = await walletClient.writeContract({ + address, + abi, + functionName: 'lock', + args: [ + sellerId, + token, + parseEther(String(amount)), + [], + [] + ], + account + }); + + const receipt = await client.waitForTransactionReceipt({ hash }); + const logs = receipt.logs; + + // Extract the lockID from transaction logs + // This is a simplified approach - in production you'll want more robust log parsing + const lockId = logs[0].topics[2]; // Simplified - adjust based on actual event structure + const offer: Offer = { amount, - lockId: String(t.args.lockID), + lockId: String(lockId), sellerId: sellerId, }; - const solicitation = await createSolicitation(offer); - - return; + + await createSolicitation(offer); + + return String(lockId); }; -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 releaseLock = async ( + pixKey: string, + amount: number, + e2eId: string, + lockId: string +): Promise => { + const { address, abi, client } = await getContract(); + const walletClient = getWalletClient(); + + if (!walletClient) { + throw new Error("Wallet client not initialized"); + } + + const [account] = await walletClient.getAddresses(); + + // In a real implementation, you would get this signature from your backend + // This is just a placeholder for the mock implementation + const signature = "0x1234567890"; + + const hash = await walletClient.writeContract({ + address, + abi, + functionName: 'release', + args: [ + BigInt(lockId), + toHex(e2eId, { size: 32 }), + signature + ], + account + }); + + const receipt = await client.waitForTransactionReceipt({ hash }); + return receipt; }; const cancelDeposit = async (depositId: bigint): Promise => { - const contract = await getContract(); - - const cancel = await contract.cancelDeposit(depositId); - await cancel.wait(); - - return cancel; + const { address, abi, client } = await getContract(); + const walletClient = getWalletClient(); + + if (!walletClient) { + throw new Error("Wallet client not initialized"); + } + + const [account] = await walletClient.getAddresses(); + + const hash = await walletClient.writeContract({ + address, + abi, + functionName: 'cancelDeposit', + args: [depositId], + account + }); + + const receipt = await client.waitForTransactionReceipt({ hash }); + return receipt; }; const withdrawDeposit = async ( amount: string, token: TokenEnum ): Promise => { - const contract = await getContract(); - - const withdraw = await contract.withdraw( - getTokenAddress(token), - parseEther(String(amount)), - [] - ); - await withdraw.wait(); - - return withdraw; + const { address, abi, client } = await getContract(); + const walletClient = getWalletClient(); + + if (!walletClient) { + throw new Error("Wallet client not initialized"); + } + + const [account] = await walletClient.getAddresses(); + + const hash = await walletClient.writeContract({ + address, + abi, + functionName: 'withdraw', + args: [ + getTokenAddress(token), + parseEther(String(amount)), + [] + ], + account + }); + + const receipt = await client.waitForTransactionReceipt({ hash }); + return receipt; }; export { cancelDeposit, withdrawDeposit, addLock, releaseLock }; diff --git a/src/blockchain/events.ts b/src/blockchain/events.ts index b9d98cf..481d3ee 100644 --- a/src/blockchain/events.ts +++ b/src/blockchain/events.ts @@ -1,5 +1,5 @@ -import { useEtherStore } from "@/store/ether"; -import { Contract, formatEther, Interface } from "ethers"; +import { useViemStore } from "@/store/viem"; +import { formatEther, decodeEventLog, parseAbi, toHex, type PublicClient, type Address } from "viem"; import p2pix from "@/utils/smart_contract_files/P2PIX.json"; import { getContract } from "./provider"; @@ -14,8 +14,8 @@ import type { UnreleasedLock } from "@/model/UnreleasedLock"; import type { Pix } from "@/model/Pix"; const getNetworksLiquidity = async (): Promise => { - const etherStore = useEtherStore(); - etherStore.setLoadingNetworkLiquidity(true); + const viemStore = useViemStore(); + viemStore.setLoadingNetworkLiquidity(true); const depositLists: ValidDeposit[][] = []; @@ -23,31 +23,40 @@ 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) - ); - + + // Get public client for this network + const client = getProviderByNetwork(network as NetworkEnum); + const address = getP2PixAddress(network as NetworkEnum); + depositLists.push( await getValidDeposits( - getTokenAddress(etherStore.selectedToken, network as NetworkEnum), + getTokenAddress(viemStore.selectedToken, network as NetworkEnum), network as NetworkEnum, - p2pContract + { client, address } ) ); } - etherStore.setDepositsValidList(depositLists.flat()); - etherStore.setLoadingNetworkLiquidity(false); + viemStore.setDepositsValidList(depositLists.flat()); + viemStore.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 +68,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; - - if (contract) { - p2pContract = contract; + let client:PublicClient, address, abi; + + if (contractInfo) { + ({ client, address } = contractInfo); + abi = p2pix.abi; } else { - p2pContract = await getContract(true); + ({ address, abi, client } = await getContract(true)); } - const filterDeposits = p2pContract.filters.DepositAdded(null); - const eventsDeposits = await p2pContract.queryFilter( - filterDeposits - // 0, - // "latest" - ); - if (!contract) p2pContract = await getContract(); // get metamask provider contract + 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 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 + }); + + // 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: deposit.blockNumber, - remaining: Number(formatEther(mappedBalance)), - seller: decoded.args.seller, - network, - pixKey: "", - }; + 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 +137,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..85a62d9 100644 --- a/src/blockchain/provider.ts +++ b/src/blockchain/provider.ts @@ -1,27 +1,52 @@ 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 { useViemStore } from "@/store/viem"; -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 viemStore = useViemStore(); + const rpcUrl = getProviderUrl(); + return createPublicClient({ + chain: viemStore.networkName === 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"); + const viemStore = useViemStore(); + const chain = viemStore.networkName === 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..7027773 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 { useViemStore } from "@/store/viem"; 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(); - - etherStore.setSeller(participant); - const tokenContract = new Contract( - getTokenAddress(etherStore.selectedToken), - mockToken.abi, - signer - ); - + const viemStore = useViemStore(); + const publicClient = getPublicClient(); + const walletClient = getWalletClient(); + + if (!publicClient || !walletClient) { + throw new Error("Clients not initialized"); + } + + viemStore.setSeller(participant); + const [account] = await walletClient.getAddresses(); + + // Get token address + const tokenAddress = getTokenAddress(viemStore.selectedToken); + // 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)) { // 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)], + account + }); + + await publicClient.waitForTransactionReceipt({ hash }); return true; } return true; }; const addDeposit = async (): Promise => { - const p2pContract = await getContract(); - const etherStore = useEtherStore(); - - const sellerId = await createParticipant(etherStore.seller); - etherStore.setSellerId(sellerId.id); - - const deposit = await p2pContract.deposit( - sellerId, - encodeBytes32String(""), - getTokenAddress(etherStore.selectedToken), - parseEther(etherStore.seller.offer), - true - ); - - await deposit.wait(); - - return deposit; + const { address, abi, client } = await getContract(); + const walletClient = getWalletClient(); + const viemStore = useViemStore(); + + if (!walletClient) { + throw new Error("Wallet client not initialized"); + } + + const [account] = await walletClient.getAddresses(); + + const sellerId = await createParticipant(viemStore.seller); + viemStore.setSellerId(sellerId.id); + + const hash = await walletClient.writeContract({ + address, + abi, + functionName: 'deposit', + args: [ + sellerId.id, + toHex("", { size: 32 }), + getTokenAddress(viemStore.selectedToken), + parseEther(viemStore.seller.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..988096b 100644 --- a/src/blockchain/wallet.ts +++ b/src/blockchain/wallet.ts @@ -1,14 +1,13 @@ import { - Contract, + decodeEventLog, formatEther, getAddress, - Interface, - Log, - LogDescription, -} from "ethers"; -import { useEtherStore } from "@/store/ether"; + type Log, + parseAbi, +} from "viem"; +import { useViemStore } from "@/store/viem"; -import { getContract, getProvider } from "./provider"; +import { getPublicClient, getWalletClient, getContract } from "./provider"; import { getTokenAddress, isPossibleNetwork } from "./addresses"; import mockToken from "@/utils/smart_contract_files/MockToken.json"; @@ -22,43 +21,51 @@ import type { UnreleasedLock } from "@/model/UnreleasedLock"; import type { Pix } from "@/model/Pix"; export const updateWalletStatus = async (): Promise => { - const etherStore = useEtherStore(); + const viemStore = useViemStore(); - const provider = await getProvider(); - const signer = await provider?.getSigner(); - const network = await provider?.getNetwork(); - const chainId = network?.chainId; + const publicClient = getPublicClient(); + const walletClient = getWalletClient(); + + if (!publicClient || !walletClient) { + console.error("Client not initialized"); + return; + } + + const chainId = await publicClient.getChainId(); if (!isPossibleNetwork(Number(chainId))) { window.alert("Invalid chain!:" + chainId); return; } - etherStore.setNetworkId(Number(chainId)); + viemStore.setNetworkId(Number(chainId)); - const mockTokenContract = new Contract( - getTokenAddress(etherStore.selectedToken), - mockToken.abi, - signer - ); + // Get account address + const [address] = await walletClient.getAddresses(); + + // Get token balance + const tokenAddress = getTokenAddress(viemStore.selectedToken); + const balanceResult = await publicClient.readContract({ + address: tokenAddress, + abi: mockToken.abi, + functionName: 'balanceOf', + args: [address] + }); - const walletAddress = await provider?.send("eth_requestAccounts", []); - const balance = await mockTokenContract.balanceOf(walletAddress[0]); - - etherStore.setBalance(formatEther(balance)); - etherStore.setWalletAddress(getAddress(walletAddress[0])); + viemStore.setBalance(formatEther(balanceResult)); + viemStore.setWalletAddress(getAddress(address)); }; export const listValidDepositTransactionsByWalletAddress = async ( walletAddress: string ): Promise => { - const etherStore = useEtherStore(); + const viemStore = useViemStore(); const walletDeposits = await getValidDeposits( - getTokenAddress(etherStore.selectedToken), - etherStore.networkName + getTokenAddress(viemStore.selectedToken), + viemStore.networkName ); if (walletDeposits) { return walletDeposits .filter((deposit) => deposit.seller == walletAddress) - .sort((a, b) => { + .sort((a: ValidDeposit, b: ValidDeposit) => { return b.blockNumber - a.blockNumber; }); } @@ -66,10 +73,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 +90,40 @@ 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) + 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, - transactionHash: transaction.transactionHash - ? transaction.transactionHash - : "", - transactionID: decoded.args.lockID ? decoded.args.lockID.toString() : "", - }; - txs.push(tx); + 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 +131,212 @@ const filterLockStatus = async ( export const listAllTransactionByWalletAddress = async ( walletAddress: string ): Promise => { - const p2pContract = await getContract(true); + const { address, abi, 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, abi, 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 { address, abi, 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 { address, abi, 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 +345,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); + + const lockStatus = await client.readContract({ + address, + abi, + functionName: 'getLocksStatus', + args: [lockIds] + }); - if (currentStatus == 1) { - const lock = await p2pContract.mapLocks(lockStatus[0][index]); - valueToSum = Number(formatEther(lock?.amount)); - } - - 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..4f22afc 100644 --- a/src/components/BuyConfirmedComponent/BuyConfirmedComponent.vue +++ b/src/components/BuyConfirmedComponent/BuyConfirmedComponent.vue @@ -8,8 +8,8 @@ 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 { useViemStore } from "@/store/viem"; +import { NetworkEnum } from "@/model/NetworkEnum"; import { onMounted, ref, watch } from "vue"; import ListingComponent from "../ListingComponent/ListingComponent.vue"; @@ -19,8 +19,8 @@ const props = defineProps<{ isCurrentStep: boolean; }>(); -const etherStore = useEtherStore(); -const { walletAddress } = storeToRefs(etherStore); +const viemStore = useViemStore(); +const { walletAddress } = storeToRefs(viemStore); const lastWalletTransactions = ref([]); const depositList = ref([]); @@ -29,7 +29,7 @@ const activeLockAmount = ref(0); // methods const getWalletTransactions = async () => { - etherStore.setLoadingWalletTransactions(true); + viemStore.setLoadingWalletTransactions(true); if (walletAddress.value) { const walletDeposits = await listValidDepositTransactionsByWalletAddress( walletAddress.value @@ -48,20 +48,20 @@ const getWalletTransactions = async () => { lastWalletTransactions.value = allUserTransactions; } } - etherStore.setLoadingWalletTransactions(false); + viemStore.setLoadingWalletTransactions(false); }; const callWithdraw = async (amount: string) => { if (amount) { - etherStore.setLoadingWalletTransactions(true); - const withdraw = await withdrawDeposit(amount, etherStore.selectedToken); + viemStore.setLoadingWalletTransactions(true); + const withdraw = await withdrawDeposit(amount, viemStore.selectedToken); if (withdraw) { console.log("Saque realizado!"); await getWalletTransactions(); } else { console.log("Não foi possível realizar o saque!"); } - etherStore.setLoadingWalletTransactions(false); + viemStore.setLoadingWalletTransactions(false); } }; @@ -93,14 +93,14 @@ onMounted(async () => {

Tokens recebidos

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

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

diff --git a/src/components/ListingComponent/ListingComponent.vue b/src/components/ListingComponent/ListingComponent.vue index 1ffe1ea..876cb63 100644 --- a/src/components/ListingComponent/ListingComponent.vue +++ b/src/components/ListingComponent/ListingComponent.vue @@ -3,7 +3,7 @@ import { withdrawDeposit } from "@/blockchain/buyerMethods"; import { NetworkEnum } from "@/model/NetworkEnum"; import type { ValidDeposit } from "@/model/ValidDeposit"; import type { WalletTransaction } from "@/model/WalletTransaction"; -import { useEtherStore } from "@/store/ether"; +import { useViemStore } from "@/store/viem"; import { storeToRefs } from "pinia"; import { ref, watch, onMounted } from "vue"; import SpinnerComponent from "../SpinnerComponent.vue"; @@ -12,7 +12,7 @@ import { debounce } from "@/utils/debounce"; import { getTokenByAddress } from "@/blockchain/addresses"; import { useFloating, arrow, offset, flip, shift } from "@floating-ui/vue"; -const etherStore = useEtherStore(); +const etherStore = useViemStore(); // props const props = defineProps<{ diff --git a/src/components/ListingComponent/__tests__/ListingComponent.spec.ts b/src/components/ListingComponent/__tests__/ListingComponent.spec.ts index 89869bd..7de5266 100644 --- a/src/components/ListingComponent/__tests__/ListingComponent.spec.ts +++ b/src/components/ListingComponent/__tests__/ListingComponent.spec.ts @@ -1,15 +1,14 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; import { mount } from "@vue/test-utils"; -import ListingComponent from "@/components/ListingComponent/ListingComponent.vue"; -import { createPinia, setActivePinia } from "pinia"; -import { expect } from "vitest"; +import ListingComponent from "../ListingComponent.vue"; +import SpinnerComponent from "../../SpinnerComponent.vue"; +import { useViemStore } from "@/store/viem"; import { MockValidDeposits } from "@/model/mock/ValidDepositMock"; import { MockWalletTransactions } from "@/model/mock/WalletTransactionMock"; -import { useEtherStore } from "@/store/ether"; describe("ListingComponent.vue", () => { beforeEach(() => { - setActivePinia(createPinia()); - useEtherStore().setLoadingWalletTransactions(false); + useViemStore().setLoadingWalletTransactions(false); }); test("Test Message when an empty array is received", () => { diff --git a/src/components/SearchComponent.vue b/src/components/SearchComponent.vue index 5c03f01..270902f 100644 --- a/src/components/SearchComponent.vue +++ b/src/components/SearchComponent.vue @@ -1,21 +1,21 @@ diff --git a/src/components/SellerSteps/SellerSearchComponent.vue b/src/components/SellerSteps/SellerSearchComponent.vue index b3de2e9..9fffac3 100644 --- a/src/components/SellerSteps/SellerSearchComponent.vue +++ b/src/components/SellerSteps/SellerSearchComponent.vue @@ -1,17 +1,17 @@ diff --git a/src/components/SellerSteps/SellerSearchComponent.vue b/src/components/SellerSteps/SellerSearchComponent.vue index 9fffac3..5be8f5d 100644 --- a/src/components/SellerSteps/SellerSearchComponent.vue +++ b/src/components/SellerSteps/SellerSearchComponent.vue @@ -1,7 +1,6 @@ + + + + diff --git a/src/components/TopBar/TopBar.vue b/src/components/TopBar/TopBar.vue index ce98d80..61c3937 100644 --- a/src/components/TopBar/TopBar.vue +++ b/src/components/TopBar/TopBar.vue @@ -1,8 +1,7 @@