Compare commits

..

No commits in common. "develop" and "buy-refactor" have entirely different histories.

12 changed files with 101 additions and 52 deletions

View File

@ -5,8 +5,8 @@ import SpinnerComponent from "@/components/SpinnerComponent.vue";
import ToasterComponent from "@/components/ToasterComponent.vue"; import ToasterComponent from "@/components/ToasterComponent.vue";
import { init, useOnboard } from "@web3-onboard/vue"; import { init, useOnboard } from "@web3-onboard/vue";
import injectedModule from "@web3-onboard/injected-wallets"; import injectedModule from "@web3-onboard/injected-wallets";
import { Networks } from "@/model/Networks"; import { Networks } from "./model/Networks";
import { NetworkEnum } from "@/model/NetworkEnum"; import { NetworkEnum } from "./model/NetworkEnum";
import { ref } from "vue"; import { ref } from "vue";
const route = useRoute(); const route = useRoute();
@ -15,12 +15,20 @@ const targetNetwork = ref(NetworkEnum.sepolia);
const web3Onboard = init({ const web3Onboard = init({
wallets: [injected], wallets: [injected],
chains: Object.entries(Networks).map(([, network]) => ({ chains: [
id: network.chainId, {
token: network.token, id: Networks[NetworkEnum.sepolia].chainId,
label: network.chainName, token: "ETH",
rpcUrl: network.rpcUrl, label: "Sepolia",
})), rpcUrl: import.meta.env.VITE_SEPOLIA_API_URL,
},
{
id: Networks[NetworkEnum.rootstock].chainId,
token: "tRBTC",
label: "Rootstock Testnet",
rpcUrl: import.meta.env.VITE_ROOTSTOCK_API_URL,
},
],
connect: { connect: {
autoConnectLastWallet: true, autoConnectLastWallet: true,
}, },

View File

@ -1,7 +1,7 @@
import { useUser } from "@/composables/useUser"; import { useUser } from "@/composables/useUser";
import { NetworkEnum, TokenEnum } from "@/model/NetworkEnum"; import { NetworkEnum, TokenEnum } from "@/model/NetworkEnum";
import { createPublicClient, http, type Address } from "viem"; import { createPublicClient, http, type Address } from "viem";
import { sepolia, rootstockTestnet } from "viem/chains"; import { sepolia, rootstock } from "viem/chains";
const Tokens: { [key in NetworkEnum]: { [key in TokenEnum]: Address } } = { const Tokens: { [key in NetworkEnum]: { [key in TokenEnum]: Address } } = {
[NetworkEnum.sepolia]: { [NetworkEnum.sepolia]: {
@ -39,7 +39,7 @@ export const getP2PixAddress = (network?: NetworkEnum): Address => {
const user = useUser(); const user = useUser();
const possibleP2PixAddresses: { [key in NetworkEnum]: Address } = { const possibleP2PixAddresses: { [key in NetworkEnum]: Address } = {
[NetworkEnum.sepolia]: "0xb7cD135F5eFD9760981e02E2a898790b688939fe", [NetworkEnum.sepolia]: "0xb7cD135F5eFD9760981e02E2a898790b688939fe",
[NetworkEnum.rootstock]: "0x57Dcba05980761169508886eEdc6f5E7EC0411Dc", [NetworkEnum.rootstock]: "0x98ba35eb14b38D6Aa709338283af3e922476dE34",
}; };
return possibleP2PixAddresses[ return possibleP2PixAddresses[
@ -58,7 +58,7 @@ export const getProviderUrl = (network?: NetworkEnum): string => {
}; };
export const getProviderByNetwork = (network: NetworkEnum) => { export const getProviderByNetwork = (network: NetworkEnum) => {
const chain = network === NetworkEnum.sepolia ? sepolia : rootstockTestnet; const chain = network === NetworkEnum.sepolia ? sepolia : rootstock;
return createPublicClient({ return createPublicClient({
chain, chain,
transport: http(getProviderUrl(network)), transport: http(getProviderUrl(network)),

View File

@ -1,9 +1,15 @@
import { getContract } from "./provider"; import { getContract } from "./provider";
import { getTokenAddress } from "./addresses"; import { getTokenAddress } from "./addresses";
import { import {
bytesToHex,
encodeAbiParameters,
keccak256,
parseAbiParameters,
parseEther, parseEther,
stringToBytes,
stringToHex,
toBytes,
type Address, type Address,
type TransactionReceipt,
} from "viem"; } from "viem";
import type { TokenEnum } from "@/model/NetworkEnum"; import type { TokenEnum } from "@/model/NetworkEnum";
@ -63,20 +69,24 @@ export const withdrawDeposit = async (
export const releaseLock = async ( export const releaseLock = async (
lockID: bigint, lockID: bigint,
pixTimestamp: `0x${string}`&{lenght:34}, pixtarget: string,
signature: `0x${string}` signature: string
): Promise<TransactionReceipt> => { ): Promise<any> => {
const { address, abi, wallet, client, account } = await getContract(); const { address, abi, wallet, client, account } = await getContract();
console.log("Releasing lock", { lockID, pixtarget, signature });
if (!wallet) { if (!wallet) {
throw new Error("Wallet not connected"); throw new Error("Wallet not connected");
} }
// Convert pixtarget to bytes32
const pixTimestamp = keccak256(stringToHex(pixtarget, { size: 32 }) );
const { request } = await client.simulateContract({ const { request } = await client.simulateContract({
address, address,
abi, abi,
functionName: "release", functionName: "release",
args: [BigInt(lockID), pixTimestamp, signature], args: [BigInt(lockID), pixTimestamp, stringToHex(signature)],
account account
}); });

View File

@ -32,8 +32,8 @@ const getNetworksLiquidity = async (): Promise<void> => {
}; };
const getParticipantID = async ( const getParticipantID = async (
seller: Address, seller: string,
token: Address token: string
): Promise<string> => { ): Promise<string> => {
const { address, abi, client } = await getContract(); const { address, abi, client } = await getContract();

View File

@ -9,7 +9,7 @@ import {
PublicClient, PublicClient,
WalletClient, WalletClient,
} from "viem"; } from "viem";
import { sepolia, rootstockTestnet } from "viem/chains"; import { sepolia, rootstock } from "viem/chains";
import { useUser } from "@/composables/useUser"; import { useUser } from "@/composables/useUser";
let walletClient: WalletClient | null = null; let walletClient: WalletClient | null = null;
@ -19,7 +19,7 @@ const getPublicClient = (): PublicClient => {
const rpcUrl = getProviderUrl(); const rpcUrl = getProviderUrl();
return createPublicClient({ return createPublicClient({
chain: chain:
Number(user.networkName.value) === sepolia.id ? sepolia : rootstockTestnet, Number(user.networkName.value) === sepolia.id ? sepolia : rootstock,
transport: http(rpcUrl), transport: http(rpcUrl),
}); });
}; };
@ -46,7 +46,7 @@ const getContract = async (onlyRpcProvider = false) => {
const connectProvider = async (p: any): Promise<void> => { const connectProvider = async (p: any): Promise<void> => {
const user = useUser(); const user = useUser();
const chain = const chain =
Number(user.networkName.value) === sepolia.id ? sepolia : rootstockTestnet; Number(user.networkName.value) === sepolia.id ? sepolia : rootstock;
const [account] = await p!.request({ method: "eth_requestAccounts" }); const [account] = await p!.request({ method: "eth_requestAccounts" });

View File

@ -1,7 +1,7 @@
import { getContract, getPublicClient, getWalletClient } from "./provider"; import { getContract, getPublicClient, getWalletClient } from "./provider";
import { getTokenAddress, getP2PixAddress } from "./addresses"; import { getTokenAddress, getP2PixAddress } from "./addresses";
import { parseEther, toHex } from "viem"; import { parseEther, toHex } from "viem";
import { sepolia, rootstockTestnet } from "viem/chains"; import { sepolia, rootstock } from "viem/chains";
import { mockTokenAbi } from "./abi"; import { mockTokenAbi } from "./abi";
import { useUser } from "@/composables/useUser"; import { useUser } from "@/composables/useUser";
@ -33,7 +33,7 @@ const approveTokens = async (participant: Participant): Promise<any> => {
if ( allowance < parseEther(participant.offer.toString()) ) { if ( allowance < parseEther(participant.offer.toString()) ) {
// Approve tokens // Approve tokens
const chain = user.networkId.value === sepolia.id ? sepolia : rootstockTestnet; const chain = user.networkId.value === sepolia.id ? sepolia : rootstock;
const hash = await walletClient.writeContract({ const hash = await walletClient.writeContract({
address: tokenAddress, address: tokenAddress,
abi: mockTokenAbi, abi: mockTokenAbi,
@ -65,7 +65,7 @@ const addDeposit = async (): Promise<any> => {
if (!sellerId.id) { if (!sellerId.id) {
throw new Error("Failed to create participant"); throw new Error("Failed to create participant");
} }
const chain = user.networkId.value === sepolia.id ? sepolia : rootstockTestnet; const chain = user.networkId.value === sepolia.id ? sepolia : rootstock;
const hash = await walletClient.writeContract({ const hash = await walletClient.writeContract({
address, address,
abi, abi,

View File

@ -1,4 +1,4 @@
import { formatEther, type Address } from "viem"; import { formatEther, hexToString, type Address } from "viem";
import { useUser } from "@/composables/useUser"; import { useUser } from "@/composables/useUser";
import { getPublicClient, getWalletClient, getContract } from "./provider"; import { getPublicClient, getWalletClient, getContract } from "./provider";
@ -463,3 +463,18 @@ export const getActiveLockAmount = async (
return total; return total;
}, 0); }, 0);
}; };
export const getSellerParticipantId = async (
sellerAddress: Address,
tokenAddress: Address
): Promise<string> => {
const { address, abi, client } = await getContract();
const participantId = await client.readContract({
address,
abi,
functionName: "getPixTarget",
args: [sellerAddress, tokenAddress],
});
return hexToString(participantId);
};

View File

@ -4,7 +4,7 @@ import CustomButton from "@/components/CustomButton/CustomButton.vue";
import CustomModal from "@/components//CustomModal/CustomModal.vue"; import CustomModal from "@/components//CustomModal/CustomModal.vue";
import SpinnerComponent from "@/components/SpinnerComponent.vue"; import SpinnerComponent from "@/components/SpinnerComponent.vue";
import { createSolicitation, getSolicitation, type Offer } from "@/utils/bbPay"; import { createSolicitation, getSolicitation, type Offer } from "@/utils/bbPay";
import { getParticipantID } from "@/blockchain/events"; import { getSellerParticipantId } from "@/blockchain/wallet";
import { getUnreleasedLockById } from "@/blockchain/events"; import { getUnreleasedLockById } from "@/blockchain/events";
import QRCode from "qrcode"; import QRCode from "qrcode";
@ -18,7 +18,7 @@ const props = defineProps<Props>();
const qrCode = ref<string>(""); const qrCode = ref<string>("");
const qrCodeSvg = ref<string>(""); const qrCodeSvg = ref<string>("");
const showWarnModal = ref<boolean>(true); const showWarnModal = ref<boolean>(true);
const pixTimestamp = ref<string>(""); const pixTarget = ref<string>("");
const releaseSignature = ref<string>(""); const releaseSignature = ref<string>("");
const solicitationData = ref<any>(null); const solicitationData = ref<any>(null);
const pollingInterval = ref<NodeJS.Timeout | null>(null); const pollingInterval = ref<NodeJS.Timeout | null>(null);
@ -56,7 +56,7 @@ const checkSolicitationStatus = async () => {
); );
if (response.signature) { if (response.signature) {
pixTimestamp.value = response.pixTimestamp; pixTarget.value = response.pixTarget;
releaseSignature.value = response.signature; releaseSignature.value = response.signature;
// Stop polling when payment is confirmed // Stop polling when payment is confirmed
if (pollingInterval.value) { if (pollingInterval.value) {
@ -86,7 +86,7 @@ onMounted(async () => {
BigInt(props.lockID) BigInt(props.lockID)
); );
const participantId = await getParticipantID( const participantId = await getSellerParticipantId(
sellerAddress, sellerAddress,
tokenAddress tokenAddress
); );
@ -170,7 +170,7 @@ onUnmounted(() => {
releaseSignature ? 'Enviar para a rede' : 'Validando pagamento...' releaseSignature ? 'Enviar para a rede' : 'Validando pagamento...'
" "
@button-clicked=" @button-clicked="
emit('pixValidated', { pixTimestamp, signature: releaseSignature }) emit('pixValidated', { pixTarget, signature: releaseSignature })
" "
/> />
</div> </div>

View File

@ -2,6 +2,7 @@ import { ref } from "vue";
import { NetworkEnum, TokenEnum } from "../model/NetworkEnum"; import { NetworkEnum, TokenEnum } from "../model/NetworkEnum";
import type { ValidDeposit } from "@/model/ValidDeposit"; import type { ValidDeposit } from "@/model/ValidDeposit";
import type { Participant } from "../utils/bbPay"; import type { Participant } from "../utils/bbPay";
import { NetworkById } from "@/model/Networks";
import type { Address } from "viem" import type { Address } from "viem"
const walletAddress = ref<Address | null>(null); const walletAddress = ref<Address | null>(null);
@ -32,7 +33,7 @@ export function useUser() {
}; };
const setNetworkId = (network: string | number) => { const setNetworkId = (network: string | number) => {
networkName.value = Number(network) as NetworkEnum || NetworkEnum.sepolia; networkName.value = NetworkById(network) || NetworkEnum.sepolia;
networkId.value = Number(network); networkId.value = Number(network);
}; };

View File

@ -1,26 +1,37 @@
import { NetworkEnum } from "@/model/NetworkEnum"; import { NetworkEnum } from "@/model/NetworkEnum";
export interface NetworkConfig { export const NetworkById = (
chainId: string; chainId: string | number
chainName: string; ): NetworkEnum | undefined => {
token: string; const normalizedChainId =
rpcUrl?: string; typeof chainId === "number" ? chainId : Number(chainId);
blockExplorerUrl?: string;
}
export const Networks: { [key in NetworkEnum]: NetworkConfig } = { for (const [network, details] of Object.entries(Networks)) {
if (Number(details.chainId) === normalizedChainId) {
return network as unknown as NetworkEnum;
}
}
return undefined;
};
export const Networks = {
[NetworkEnum.sepolia]: { [NetworkEnum.sepolia]: {
chainId: "0xAA36A7", chainId: "0xAA36A7",
chainName: "Sepolia Testnet", chainName: "Sepolia Testnet",
token: "ETH",
rpcUrl: import.meta.env.VITE_SEPOLIA_API_URL,
blockExplorerUrl: "https://sepolia.etherscan.io",
}, },
[NetworkEnum.rootstock]: { [NetworkEnum.rootstock]: {
chainId: "0x1F", chainId: "0x1F",
chainName: "Rootstock Testnet", chainName: "Rootstock Testnet",
token: "tRBTC", rpcUrls: ["https://public-node.testnet.rsk.co/"],
rpcUrl: import.meta.env.VITE_ROOTSTOCK_API_URL, iconUrls: [
blockExplorerUrl: "https://explorer.testnet.rsk.co", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAPOSURBVHgBxVhNUhpBFH6vGdxp4S4LoSYnEE8gnEA4AbpMJUQ4gXgCRJK1egLxBOIJJCdwJElVllMu49Cd1z04zD/dY1H5qihmut/M93VPv59uhHdAXFaPAaEDgA2/BaeA4hq/zG+gIBAKQoyr9yshid4Jdn+2oQAYFIC4rA2zxUhgS3yrDqEAjGdIDD/YYG09aRl7L7vYd10wgPkMlcoNfdvtFhjCXJBAeyO2S5gLQuFo25bEIxjCCt8oN2Z46I+Mu4A4SbjwojQBi1+BDl5LP+JNYlhtQRmPsjjQN1ILldwY7JTXOuD9bWL/jxO8dFy7oL9TyMcIu/PeSghxlLduQUA9jwPXiAk98HLw5jFiaFfAEjRLImPR0qi7z+2VmArZ7zzqcDAS01ljCKqf7QSjxb7jKkIhTohu6rOCq64RjsNiFEo7x7ocSNMvlddhPWb0CQ6gAAw4HKZpKGFDcWhzSEG6kbQCm4dLbi9m+XlpBTHea2D31zTSNtxrAGMNdcP5FPuxfhlKdCHgASUJxcd7zUcobkAPXvkzWGyf7uVCt2M2DtkMljaHSxu92WWLAz8OjWsD+juD/4tzcpqBSh3yQrmwoNFFMZNuDB7bJRsp/hzMMQqeT+NQ96KtNEBK+SG+23XgHgUyy8FPjpPozy3M4sZwh1/nLRMOK26Mn50Z5IHjA6XkBugJSn1XHkeBbK8dJsxsl0jMEOUpm0o9+gkX+7+TI0E+0x6Hsk0ijyNYQ/4OAqWn2aF+5cLxEoRq6idqtyEPtFhp/XyMNI2p9ADFUc/iYL5h7YzEXEEyptj04mvVHxkGP4F8MS4sWDsqRr4DbyGZRiIcqCKtpRMYeTMcpVVAFewqMVPSjUkMVQTBp6BPVKeiTqN65E0qP1AvIArWC98qcQsms39oDeBEtoXFKFgLbQ76ZKiXiRH2E01UF9Go+kGDh32/LWHZAD2OQ7mGdLO4ndrqWaHZyNyD6XJUWEq6yIQqReOweCe49ivD2DNUIutjJgXpHwyUtyPbY/IMWehfBA0IZxQSQoW9rKXL+ltq0oKqYC+RB6yLKys4xEw/Idde5R02cTGOcgh1LSNnid+nihIqcN0tr48MhL89L2uoG+Dqv5Px/IwqAhkqnEi296M1OyLPqVCgdKhcuKNjlUnQL4X78cRk1E1JlMkBME1sFE0gRrRJZGs3iT44bRZP5z0wQJHzIZMMbpztN1t+FDhsMBe0YNfatimHDetgLGiZGkYapqPwYt6YIAWPDYI9fSrETfjkwwSFT2EVrV/USY+r+/GGNp2I7zoW/gdR9aOdZ/lPGgAAAABJRU5ErkJggg==",
],
nativeCurrency: {
name: "tRBTC",
symbol: "tRBTC",
decimals: 18,
},
blockExplorerUrls: ["https://explorer.testnet.rootstock.io/"],
}, },
}; };

View File

@ -61,15 +61,15 @@ export const createSolicitation = async (offer: Offer) => {
return response.json(); return response.json();
}; };
export const getSolicitation = async (id: bigint): Promise<{pixTimestamp: `0x${string}`, signature: `0x${string}`}> => { export const getSolicitation = async (id: string) => {
const response = await fetch( const response = await fetch(
`${import.meta.env.VITE_APP_API_URL}/release/${id}` `${import.meta.env.VITE_APP_API_URL}/release/${id}`
); );
const obj = await response.json(); const obj: any = await response.json();
return { return {
pixTimestamp: obj.pixTimestamp, pixTarget: obj.pixTarget,
signature: obj.signature, signature: obj.signature,
}; };
}; };

View File

@ -59,15 +59,19 @@ const confirmBuyClick = async (
} }
}; };
const releaseTransaction = async (params: { const releaseTransaction = async ({
pixTimestamp: `0x${string}`&{lenght:34}, pixTarget,
signature: `0x${string}`, signature,
}: {
pixTarget: string;
signature: string;
}) => { }) => {
flowStep.value = Step.List; flowStep.value = Step.List;
showBuyAlert.value = true; showBuyAlert.value = true;
loadingRelease.value = true; loadingRelease.value = true;
const release = await releaseLock(BigInt(lockID.value), params.pixTimestamp, params.signature); const release = await releaseLock(BigInt(lockID.value), pixTarget, signature);
await release.wait();
await updateWalletStatus(); await updateWalletStatus();
loadingRelease.value = false; loadingRelease.value = false;