Update all code to be able to release. Still having issues on Release transaction.

This commit is contained in:
Filipe Soccol
2025-06-28 12:16:36 -03:00
parent ed5d3b5726
commit 2370051243
13 changed files with 459 additions and 338 deletions

View File

@@ -1,6 +1,15 @@
import { getContract } from "./provider";
import { getTokenAddress } from "./addresses";
import { parseEther } from "viem";
import {
bytesToHex,
encodeAbiParameters,
keccak256,
parseAbiParameters,
parseEther,
stringToBytes,
stringToHex,
toBytes,
} from "viem";
import type { TokenEnum } from "@/model/NetworkEnum";
export const addLock = async (
@@ -54,18 +63,26 @@ export const withdrawDeposit = async (
return receipt.status === "success";
};
export const releaseLock = async (solicitation: any): Promise<any> => {
export const releaseLock = async (
lockID: string,
pixtarget: string,
signature: string
): Promise<any> => {
const { address, abi, wallet, client, account } = await getContract();
console.log("Releasing lock", { lockID, pixtarget, signature });
if (!wallet) {
throw new Error("Wallet not connected");
}
// Convert pixtarget to bytes32
const pixTimestamp = keccak256(stringToBytes(pixtarget));
const { request } = await client.simulateContract({
address: address as `0x${string}`,
abi,
functionName: "release",
args: [solicitation.lockId, solicitation.e2eId],
args: [BigInt(lockID), pixTimestamp, signature],
account: account as `0x${string}`,
});

View File

@@ -118,7 +118,6 @@ export const listAllTransactionByWalletAddress = async (
});
const data = await response.json();
console.log("Subgraph data:", data);
// Convert all transactions to common WalletTransaction format
const transactions: WalletTransaction[] = [];

View File

@@ -160,9 +160,10 @@ showInitialItems();
<template>
<div
class="main-container max-w-md flex justify-center items-center min-h-[200px]"
class="main-container max-w-md flex justify-center items-center min-h-[200px] w-16 h-16"
v-if="loadingWalletTransactions"
>
Carregando ofertas...
<SpinnerComponent width="8" height="8"></SpinnerComponent>
</div>
<div class="main-container max-w-md" v-else>

View File

@@ -1,11 +1,13 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ref, onMounted, onUnmounted } from "vue";
import CustomButton from "@/components/CustomButton/CustomButton.vue";
import CustomModal from "@/components//CustomModal/CustomModal.vue";
import { createSolicitation, type Offer } from "@/utils/bbPay";
import SpinnerComponent from "@/components/SpinnerComponent.vue";
import { createSolicitation, getSolicitation, type Offer } from "@/utils/bbPay";
import { getSellerParticipantId } from "@/blockchain/wallet";
import { hexToString } from "viem";
import { getUnreleasedLockById } from "@/blockchain/events";
import QRCode from "qrcode";
// Props
interface Props {
@@ -15,14 +17,70 @@ interface Props {
const props = defineProps<Props>();
const qrCode = ref<string>("");
const isPixValid = ref<boolean>(false);
const qrCodeSvg = ref<string>("");
const showWarnModal = ref<boolean>(true);
const pixTarget = ref<string>("");
const releaseSignature = ref<string>("");
const solicitationData = ref<any>(null);
const pollingInterval = ref<NodeJS.Timeout | null>(null);
// Function to generate QR code SVG
const generateQrCodeSvg = async (text: string) => {
try {
const svgString = await QRCode.toString(text, {
type: "svg",
width: 192, // 48 * 4 for better quality
margin: 2,
color: {
dark: "#000000",
light: "#FFFFFF",
},
});
qrCodeSvg.value = svgString;
} catch (error) {
console.error("Error generating QR code SVG:", error);
}
};
// Emits
const emit = defineEmits(["pixValidated"]);
// Function to check solicitation status
const checkSolicitationStatus = async () => {
if (!solicitationData.value?.numeroSolicitacao) {
return;
}
try {
const response = await getSolicitation(
solicitationData.value.numeroSolicitacao
);
if (response.signature) {
pixTarget.value = response.pixTarget;
releaseSignature.value = response.signature;
// Stop polling when payment is confirmed
if (pollingInterval.value) {
clearInterval(pollingInterval.value);
pollingInterval.value = null;
}
}
} catch (error) {
console.error("Error checking solicitation status:", error);
}
};
// Function to start polling
const startPolling = () => {
// Clear any existing interval
if (pollingInterval.value) {
clearInterval(pollingInterval.value);
}
// Start new polling interval (10 seconds)
pollingInterval.value = setInterval(checkSolicitationStatus, 10000);
};
onMounted(async () => {
try {
const { tokenAddress, sellerAddress, amount } = await getUnreleasedLockById(
@@ -45,11 +103,24 @@ onMounted(async () => {
// Update qrCode if the response contains QR code data
if (response?.informacoesPIX?.textoQrCode) {
qrCode.value = response.informacoesPIX?.textoQrCode;
// Generate SVG QR code
await generateQrCodeSvg(qrCode.value);
}
// Start polling for solicitation status
startPolling();
} catch (error) {
console.error("Error creating solicitation:", error);
}
});
// Clean up interval on component unmount
onUnmounted(() => {
if (pollingInterval.value) {
clearInterval(pollingInterval.value);
pollingInterval.value = null;
}
});
</script>
<template>
@@ -69,7 +140,17 @@ onMounted(async () => {
<div
class="flex-col items-center justify-center flex w-full bg-white sm:p-8 p-4 rounded-lg break-normal"
>
<img alt="Qr code image" :src="qrCode" class="w-48 h-48" />
<div
v-if="qrCodeSvg"
v-html="qrCodeSvg"
class="w-48 h-48 flex items-center justify-center"
></div>
<div
v-else
class="w-48 h-48 flex items-center justify-center rounded-lg"
>
<SpinnerComponent width="8" height="8"></SpinnerComponent>
</div>
<span class="text-center font-bold">Código pix</span>
<div class="break-words w-4/5">
<span class="text-center text-xs">
@@ -85,9 +166,13 @@ onMounted(async () => {
/>
</div>
<CustomButton
:is-disabled="isPixValid == false"
:text="'Enviar para a rede'"
@button-clicked="emit('pixValidated', releaseSignature)"
:is-disabled="releaseSignature === ''"
:text="
releaseSignature ? 'Enviar para a rede' : 'Validando pagamento...'
"
@button-clicked="
emit('pixValidated', { pixTarget, signature: releaseSignature })
"
/>
</div>
<CustomModal

View File

@@ -20,7 +20,6 @@ const getCustomClass = () => {
<template>
<div v-if="props.show ? props.show : true">
Aguarde...
<svg
aria-hidden="true"
:class="getCustomClass()"

View File

@@ -1,76 +0,0 @@
import qrcode from "qrcode";
import type { QRCodeToDataURLOptions } from "qrcode";
import { crc16ccitt } from "crc";
import type { Pix } from "@/model/Pix";
const pix = ({
pixKey,
merchantCity = "city",
merchantName = "name",
value,
message,
cep,
transactionId = "***",
currency = 986,
countryCode = "BR",
}: Pix) => {
const payloadKeyString = generatePixKey(pixKey, message);
const payload: string[] = [
formatEMV("00", "01"), //Payload Format Indicator
formatEMV("26", payloadKeyString), // Merchant Account Information
formatEMV("52", "0000"), //Merchant Category Code
formatEMV("53", String(currency)), // Transaction Currency
];
if (String(value) === "0") {
value = undefined;
}
if (value) {
payload.push(formatEMV("54", value.toFixed(2)));
}
payload.push(formatEMV("58", countryCode.toUpperCase())); // Country Code
payload.push(formatEMV("59", merchantName)); // Merchant Name
payload.push(formatEMV("60", merchantCity)); // Merchant City
if (cep) {
payload.push(formatEMV("61", cep)); // Postal Code
}
payload.push(formatEMV("62", formatEMV("05", transactionId))); // Additional Data Field Template
payload.push("6304");
const stringPayload = payload.join("");
const crcResult = crc16ccitt(stringPayload)
.toString(16)
.toUpperCase()
.padStart(4, "0");
const payloadPIX = `${stringPayload}${crcResult}`;
return {
payload: (): string => payloadPIX,
base64QrCode: (options?: QRCodeToDataURLOptions): Promise<string> =>
qrcode.toDataURL(payloadPIX, options),
};
};
const generatePixKey = (pixKey: string, message?: string): string => {
const payload: string[] = [
formatEMV("00", "BR.GOV.BCB.PIX"),
formatEMV("01", pixKey),
];
if (message) {
payload.push(formatEMV("02", message));
}
return payload.join("");
};
const formatEMV = (id: string, param: string): string => {
const len = param?.length?.toString().padStart(2, "0");
return `${id}${len}${param}`;
};
export { pix };

View File

@@ -22,7 +22,6 @@ export interface Offer {
// https://apoio.developers.bb.com.br/sandbox/spec/665797498bb48200130fc32c
export const createParticipant = async (participant: Participant) => {
console.log("Creating participant", participant);
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/register`, {
method: "POST",
headers: {
@@ -49,7 +48,6 @@ export const createParticipant = async (participant: Participant) => {
};
export const createSolicitation = async (offer: Offer) => {
console.log("Creating solicitation", offer);
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/request`, {
method: "POST",
headers: {
@@ -68,14 +66,10 @@ export const getSolicitation = async (id: string) => {
`${import.meta.env.VITE_APP_API_URL}/release/${id}`
);
const obj: any = response.json();
const obj: any = await response.json();
return {
id: obj.numeroSolicitacao,
lockId: obj.codigoConciliacaoSolicitacao,
amount: obj.valorSolicitacao,
qrcode: obj.pix.textoQrCode,
status: obj.valorSomatorioPagamentosEfetivados >= obj.valorSolicitacao,
signature: obj.assinatura,
pixTarget: obj.pixTarget,
signature: obj.signature,
};
};

View File

@@ -24,7 +24,6 @@ const openItem = (index: number) => {
faq.value[selectedSection.value].items[index].content = marked(
faq.value[selectedSection.value].items[index].content
);
console.log(marked(faq.value[selectedSection.value].items[index].content));
};
</script>

View File

@@ -58,20 +58,22 @@ const confirmBuyClick = async (
}
};
const releaseTransaction = async (lockId: string) => {
const releaseTransaction = async ({
pixTarget,
signature,
}: {
pixTarget: string;
signature: string;
}) => {
flowStep.value = Step.List;
showBuyAlert.value = true;
loadingRelease.value = true;
const solicitation = await getSolicitation(lockId);
const release = await releaseLock(lockID.value, pixTarget, signature);
await release.wait();
if (solicitation.status) {
const release = await releaseLock(solicitation);
await release.wait();
await updateWalletStatus();
loadingRelease.value = false;
}
await updateWalletStatus();
loadingRelease.value = false;
};
const checkForUnreleasedLocks = async (): Promise<void> => {

View File

@@ -48,19 +48,15 @@ const callWithdraw = async (amount: string) => {
const getWalletTransactions = async () => {
user.setLoadingWalletTransactions(true);
if (walletAddress.value) {
console.log("Will fetch all required data...");
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
walletAddress.value
);
console.log("Fetched deposits");
const allUserTransactions = await listAllTransactionByWalletAddress(
walletAddress.value
);
console.log("Fetched all transactions");
activeLockAmount.value = await getActiveLockAmount(walletAddress.value);
console.log("Fetched active lock amount");
if (walletDeposits) {
depositList.value = walletDeposits;