Merge branch 'list-tokens' of https://github.com/liftlearning/P2Pix-Front-End into list-tokens

This commit is contained in:
EsioFreitas 2022-12-14 09:47:23 -03:00
commit 1950ca5459
24 changed files with 1021 additions and 8294 deletions

1
.env.example Normal file
View File

@ -0,0 +1 @@
VITE_API_URL=http://localhost:8000/

2
.gitignore vendored
View File

@ -27,3 +27,5 @@ coverage
*.sln *.sln
*.sw? *.sw?
.vercel .vercel
.env

7928
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
"dependencies": { "dependencies": {
"@headlessui/vue": "^1.7.3", "@headlessui/vue": "^1.7.3",
"@heroicons/vue": "^2.0.12", "@heroicons/vue": "^2.0.12",
"axios": "^1.2.1",
"crc": "^3.8.0", "crc": "^3.8.0",
"pinia": "^2.0.23", "pinia": "^2.0.23",
"qrcode": "^1.5.1", "qrcode": "^1.5.1",

3
src/assets/copyPix.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 3H3C1.89543 3 1 3.89543 1 5V17C1 18.1046 1.89543 19 3 19H13C14.1046 19 15 18.1046 15 17V16M5 3C5 4.10457 5.89543 5 7 5H9C10.1046 5 11 4.10457 11 3M5 3C5 1.89543 5.89543 1 7 1H9C10.1046 1 11 1.89543 11 3M11 3H13C14.1046 3 15 3.89543 15 5V8M17 12H7M7 12L10 9M7 12L10 15" stroke="#111827" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 16C12.4183 16 16 12.4183 16 8C16 3.58172 12.4183 0 8 0C3.58172 0 0 3.58172 0 8C0 12.4183 3.58172 16 8 16ZM4.18025 4.18034C4.42057 3.94001 4.81022 3.94001 5.05055 4.18034L8.00008 7.12987L10.9496 4.18034C11.1899 3.94001 11.5796 3.94001 11.8199 4.18034C12.0602 4.42067 12.0602 4.81031 11.8199 5.05064L8.87039 8.00018L11.8198 10.9495C12.0601 11.1899 12.0601 11.5795 11.8198 11.8198C11.5794 12.0602 11.1898 12.0602 10.9495 11.8198L8.00008 8.87048L5.05072 11.8198C4.81039 12.0602 4.42074 12.0602 4.18041 11.8198C3.94009 11.5795 3.94009 11.1899 4.18041 10.9495L7.12978 8.00018L4.18025 5.05064C3.93992 4.81031 3.93992 4.42067 4.18025 4.18034Z" fill="#EF4444"/>
</svg>

After

Width:  |  Height:  |  Size: 808 B

3
src/assets/redirect.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.97059 0.888889C9.97059 0.397969 10.3686 0 10.8595 0H15.1111C15.602 0 16 0.397969 16 0.888889V5.23654C16 5.72746 15.602 6.12543 15.1111 6.12543C14.6202 6.12543 14.2222 5.72746 14.2222 5.23654V3.06921L8.66058 8.75646C8.31735 9.10744 7.75457 9.11373 7.40358 8.77049C7.05259 8.42726 7.04631 7.86448 7.38954 7.51349L12.9986 1.77778H10.8595C10.3686 1.77778 9.97059 1.37981 9.97059 0.888889ZM2.74997 2.67823C2.11751 2.67823 1.77778 3.11618 1.77778 3.45456V13.4459C1.77778 13.7843 2.11751 14.2222 2.74997 14.2222H12.0554C12.6878 14.2222 13.0275 13.7843 13.0275 13.4459V9.98736C13.0275 9.49644 13.4255 9.09847 13.9164 9.09847C14.4073 9.09847 14.8053 9.49644 14.8053 9.98736V13.4459C14.8053 14.9469 13.4786 16 12.0554 16H2.74997C1.32673 16 0 14.9469 0 13.4459V3.45456C0 1.9536 1.32673 0.900455 2.74997 0.900455H5.89948C6.3904 0.900455 6.78837 1.29842 6.78837 1.78934C6.78837 2.28026 6.3904 2.67823 5.89948 2.67823H2.74997Z" fill="#111827"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

3
src/assets/validIcon.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 0C6.41775 0 4.87104 0.469192 3.55544 1.34824C2.23985 2.22729 1.21447 3.47672 0.608967 4.93853C0.00346629 6.40034 -0.15496 8.00887 0.153721 9.56072C0.462403 11.1126 1.22433 12.538 2.34315 13.6569C3.46197 14.7757 4.88743 15.5376 6.43928 15.8463C7.99113 16.155 9.59966 15.9965 11.0615 15.391C12.5233 14.7855 13.7727 13.7602 14.6518 12.4446C15.5308 11.129 16 9.58225 16 8C15.9975 5.87903 15.1539 3.84563 13.6541 2.34587C12.1544 0.846118 10.121 0.00247015 8 0V0ZM12.618 5.46667L8.05467 11.6593C8.0008 11.7308 7.93322 11.7908 7.85589 11.8359C7.77856 11.881 7.69303 11.9102 7.60429 11.9219C7.51554 11.9336 7.42536 11.9274 7.339 11.9039C7.25265 11.8803 7.17186 11.8398 7.10134 11.7847L3.84267 9.17933C3.7743 9.12462 3.71737 9.05697 3.67514 8.98025C3.63291 8.90353 3.6062 8.81924 3.59654 8.73221C3.57704 8.55642 3.62816 8.38009 3.73867 8.242C3.84918 8.10391 4.01001 8.01538 4.1858 7.99587C4.36158 7.97637 4.53791 8.02749 4.676 8.138L7.39334 10.312L11.5447 4.678C11.5946 4.603 11.6593 4.53892 11.7348 4.48962C11.8102 4.44032 11.8948 4.40683 11.9836 4.39117C12.0724 4.37551 12.1634 4.37801 12.2511 4.39851C12.3389 4.41902 12.4216 4.4571 12.4942 4.51047C12.5668 4.56383 12.6279 4.63136 12.6737 4.70899C12.7194 4.78661 12.749 4.87271 12.7606 4.96209C12.7722 5.05146 12.7655 5.14226 12.741 5.22898C12.7165 5.31571 12.6746 5.39656 12.618 5.46667Z" fill="#10B981"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

15
src/assets/validating.svg Normal file
View File

@ -0,0 +1,15 @@
<svg width="128" height="83" viewBox="0 0 128 83" fill="white" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_354_816)">
<path d="M61.0484 25.8089C60.6304 25.3649 60.0296 25.1298 59.4288 25.1298C58.828 25.1298 58.2272 25.3649 57.8092 25.8089L53.2378 30.3804C52.7937 30.7983 52.5586 31.3991 52.5586 32C52.5586 32.6008 52.7937 33.2016 53.2378 33.6196L57.8092 38.191C58.6974 39.0791 60.1602 39.0791 61.0484 38.191C61.9366 37.3028 61.9366 35.84 61.0484 34.9518L58.0704 32L61.0484 29.0481C61.4925 28.6302 61.7276 28.0294 61.7276 27.4285C61.7276 26.8277 61.4925 26.2269 61.0484 25.8089Z" fill="#34D399"/>
<path d="M79.334 25.8089C78.4459 24.9208 76.983 24.9208 76.0948 25.8089C75.2067 26.6971 75.2067 28.16 76.0948 29.0481L79.0728 32L76.0948 34.9518C75.2067 35.84 75.2067 37.3028 76.0948 38.191C76.983 39.0791 78.4459 39.0791 79.334 38.191L83.9055 33.6196C84.3495 33.2016 84.5846 32.6008 84.5846 32C84.5846 31.3991 84.3495 30.7983 83.9055 30.3804L79.334 25.8089Z" fill="#34D399"/>
<path d="M68.5714 41.6914L73.1428 23.4057C73.4563 22.1518 72.6726 20.8718 71.4188 20.5584C70.1649 20.2449 68.8849 21.0286 68.5714 22.2825L64 40.5682C63.8171 41.169 63.8955 41.7959 64.209 42.3445C64.5224 42.8931 65.0449 43.2588 65.6457 43.4155C66.2465 43.5723 66.8996 43.4678 67.422 43.1282C67.9445 42.7886 68.3102 42.2661 68.4408 41.6653L68.5714 41.6914Z" fill="#34D399"/>
<path d="M59.4288 48.0131H54.8574C53.6035 48.0131 52.5586 49.0318 52.5586 50.3118C52.5586 51.5918 53.5774 52.6106 54.8574 52.6106H59.4288C60.6827 52.6106 61.7276 51.5918 61.7276 50.3118C61.7276 49.0318 60.6827 48.0131 59.4288 48.0131Z" fill="#34D399"/>
<path d="M82.2855 48.0131H68.5712C67.3174 48.0131 66.2725 49.0318 66.2725 50.3118C66.2725 51.5918 67.2912 52.6106 68.5712 52.6106H82.2855C83.5394 52.6106 84.5843 51.5918 84.5843 50.3118C84.5843 49.0318 83.5394 48.0131 82.2855 48.0131Z" fill="#34D399"/>
<path d="M126.563 32.1567L115.122 27.5853C114.573 27.3763 113.972 27.3763 113.424 27.5853L101.982 32.1567C101.12 32.4963 100.545 33.3584 100.545 34.2727V38.8441H93.7012V13.7143C93.7012 13.1135 93.4661 12.5388 93.022 12.0947C92.578 11.6506 92.0033 11.4155 91.4024 11.4155H45.7143C44.4604 11.4155 43.4155 12.4343 43.4155 13.7143V38.8702H36.5714V13.7143C36.5714 13.1135 36.3363 12.5388 35.8922 12.0947C35.4482 11.6506 34.8735 11.4155 34.2727 11.4155H27.4286V6.8702C27.4286 5.9298 26.8539 5.09388 25.9918 4.75429L14.5502 0.156735C14.0016 -0.0522449 13.4008 -0.0522449 12.8522 0.156735L1.43673 4.72816C0.574694 5.06775 0 5.9298 0 6.8702V20.5845C0 21.5249 0.574694 22.3608 1.43673 22.7004L12.8784 27.2718C13.4269 27.4808 14.0278 27.4808 14.5763 27.2718L26.018 22.7004C26.88 22.3608 27.4547 21.4988 27.4547 20.5845V16.0131H32.0261V66.2988H27.4286V61.7273C27.4286 60.7869 26.8539 59.951 25.9918 59.6114L14.5502 55.04C14.0016 54.831 13.4008 54.831 12.8522 55.04L1.43673 59.5853C0.574694 59.9249 0 60.7869 0 61.7012V75.4155C0 76.3559 0.574694 77.1918 1.43673 77.5314L12.8784 82.1029C13.4269 82.3118 14.0278 82.3118 14.5763 82.1029L26.018 77.5314C26.88 77.1918 27.4547 76.3298 27.4547 75.4155V70.8441H34.2988C34.8996 70.8441 35.4743 70.609 35.9184 70.1649C36.3624 69.7208 36.5975 69.1461 36.5975 68.5453V43.4155H43.4416V64C43.4416 65.8286 44.1731 67.5527 45.4531 68.8588C46.7331 70.1649 48.4833 70.8702 50.3118 70.8702H91.4286C93.2571 70.8702 94.9812 70.1388 96.2873 68.8588C97.5935 67.5788 98.2988 65.8286 98.2988 64V59.4286C98.2988 58.8278 98.0637 58.2531 97.6196 57.809C97.1755 57.3649 96.6008 57.1298 96 57.1298H93.7012V43.4155H100.571V47.9869C100.571 48.9273 101.146 49.7633 102.008 50.1029L113.45 54.6743C113.998 54.8833 114.599 54.8833 115.148 54.6743L126.589 50.1029C127.451 49.7633 128.026 48.9012 128.026 47.9869V34.2727C128 33.3584 127.425 32.4963 126.563 32.1567ZM11.4155 21.76L4.57143 19.0171V10.24L11.4155 12.9829V21.76ZM13.7143 8.96L8.43755 6.84408L13.7143 4.72816L18.991 6.84408L13.7143 8.96ZM22.8571 19.0171L15.9869 21.76V12.9829L22.8571 10.24V19.0171ZM11.4155 76.6171L4.57143 73.8743V65.0971L11.4155 67.84V76.6171ZM13.7143 63.8171L8.43755 61.7012L13.7143 59.5853L18.991 61.7012L13.7143 63.8171ZM22.8571 73.8743L15.9869 76.6171V67.84L22.8571 65.0971V73.8743ZM93.7012 64C93.7012 64.6008 93.4661 65.1755 93.022 65.6196C92.578 66.0637 92.0033 66.2988 91.4024 66.2988H56.738C56.9992 65.5673 57.1559 64.7837 57.1559 64V61.7012H93.7273L93.7012 64ZM54.8571 57.1559C53.6033 57.1559 52.5584 58.1747 52.5584 59.4547V64C52.5584 65.2539 51.5396 66.2988 50.2596 66.2988C48.9796 66.2988 47.9608 65.28 47.9608 64V16.0131H89.1037V57.1559H54.8571ZM111.987 49.1886L105.143 46.4457V37.6686L111.987 40.4114V49.1886ZM114.286 36.3886L109.009 34.2727L114.286 32.1567L119.562 34.2727L114.286 36.3886ZM123.429 46.4457L116.584 49.1886V40.4114L123.429 37.6686V46.4457Z" fill="#34D399"/>
</g>
<defs>
<clipPath id="clip0_354_816">
<rect width="128" height="82.2857" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,163 @@
<script setup lang="ts">
import CustomButton from "@/components/CustomButton.vue";
import { BigNumber } from "ethers";
import blockchain from "../utils/blockchain";
// props and store references
const props = defineProps({
lastWalletTransactions: Array,
tokenAmmount: BigNumber,
});
const teste = (amount: any) => {
console.log(amount);
console.log("Teste");
};
const formatEventsAmount = (amount: any) => {
try {
const formated = blockchain.formatBigNumber(amount);
return formated;
} catch {
return "";
}
};
const openEtherscanUrl = (url: string) => {
window.open(url, "_blank");
};
</script>
<template>
<div class="page">
<div class="text-container">
<span class="text font-extrabold text-5xl max-w-[50rem]"
>Os tokens foram transferidos <br />
para a sua carteira!
</span>
</div>
<div class="blur-container">
<div
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
>
<div>
<p>Tokens recebidos</p>
<p class="text-2xl text-gray-900">
{{ teste(props.tokenAmmount) }} BRZ
</p>
</div>
<div class="my-5">
<p>
<b>Não encontrou os tokens? </b>Clique no botão abaixo para <br />
cadastrar o BRZ em sua carteira.
</p>
</div>
<CustomButton
:text="'Cadastrar token na carteira'"
@buttonClicked="() => {}"
/>
</div>
</div>
<div class="blur-container-row">
<button
type="button"
class="border-amber-500 border-2 rounded default-button text-white p-2 px-50 w-full"
@click="() => {}"
>
Fazer nova transação
</button>
<button
type="button"
class="border-amber-500 border-2 rounded default-button text-white p-2"
@click="() => {}"
>
Desconectar
</button>
</div>
<div class="text-container mt-10">
<span class="text font-extrabold text-3xl max-w-[50rem]"
>Últimas transações
</span>
</div>
<div class="blur-container">
<div
class="flex flex-row justify-between w-full bg-white p-5 rounded-lg"
v-for="deposit in lastWalletTransactions"
:key="deposit?.blockNumber"
>
<p class="last-deposit-info">
{{ formatEventsAmount(deposit?.args.amount) }} BRZ
</p>
<p class="last-deposit-info">
{{
deposit?.event == "DepositAdded"
? "Depósito"
: deposit?.event == "LockAdded"
? "Reserva"
: "Compra"
}}
</p>
<div
class="flex gap-2 cursor-pointer items-center"
@click="
openEtherscanUrl(
`https://etherscan.io/tx/${deposit?.transactionHash}`
)
"
>
<p class="last-deposit-info">Etherscan</p>
<img alt="Redirect image" src="@/assets/redirect.svg" />
</div>
</div>
<button
type="button"
class="text-white mt-2"
@click="() => {}"
v-if="lastWalletTransactions?.length != 0"
>
Carregar mais
</button>
<p class="font-bold" v-if="lastWalletTransactions?.length == 0">
Não nenhuma transação anterior
</p>
</div>
</div>
</template>
<style scoped>
.page {
@apply flex flex-col items-center justify-center w-full mt-16;
}
p {
@apply text-gray-900;
}
.text-container {
@apply flex flex-col items-center justify-center gap-4;
}
.text {
@apply text-gray-800 text-center;
}
.blur-container-row {
@apply flex flex-row justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
}
.blur-container {
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-8 w-1/3;
}
.last-deposit-info {
@apply font-medium text-base;
}
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
</style>

View File

@ -0,0 +1,73 @@
<script setup lang="ts">
// prop
const props = defineProps({
title: String,
message: String,
});
</script>
<template>
<div class="page">
<div class="text-container">
<span class="text font-bold text-3xl max-w-[29rem]">{{
props.title ? props.title : "Confirme em sua carteira"
}}</span>
</div>
<div class="blur-container w-[26rem]">
<div
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
>
<div
class="flex flex-col text-center justify-center w-full items-center p-2 px-3 rounded-3xl min-w-fit gap-1"
>
<img
alt="Polygon image"
src="@/assets/validating.svg"
width="96"
height="48"
/>
<span class="text-black font-medium text-sm px-12 mt-4">
{{ $props.message }}
</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.custom-divide {
width: 100%;
border-bottom: 1px solid #d1d5db;
}
.bottom-position {
top: -20px;
right: 50%;
transform: translateX(50%);
}
.page {
@apply flex flex-col items-center justify-center w-full mt-16;
}
.text-container {
@apply flex flex-col items-center justify-center gap-4;
}
.text {
@apply text-gray-800 text-center;
}
.blur-container {
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
}
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
</style>

View File

@ -0,0 +1,217 @@
<script setup lang="ts">
import { pix } from "../utils/QrCodePix";
import { ref } from "vue";
import { debounce } from "@/utils/debounce";
import CustomButton from "./CustomButton.vue";
import api from "../services/index";
// props and store references
const props = defineProps({
pixTarget: String,
tokenValue: Number,
});
const qrCode = ref<string>("");
const qrCodePayload = ref<string>("");
const isPixValid = ref<boolean>(false);
const isCodeInputEmpty = ref<boolean>(true);
const e2eId = ref<string>("");
// Emits
const emit = defineEmits(["pixValidated"]);
const pixQrCode = pix({
pixKey: props.pixTarget ?? "",
value: props.tokenValue,
});
pixQrCode.base64QrCode().then((code: string) => {
qrCode.value = code;
});
qrCodePayload.value = pixQrCode.payload();
const handleInputEvent = (event: any) => {
const { value } = event.target;
e2eId.value = value;
validatePix();
};
const validatePix = async () => {
if (e2eId.value == "") {
isPixValid.value = false;
isCodeInputEmpty.value = true;
return;
}
var sellerPixKey = props.pixTarget;
var transactionValue = props.tokenValue;
if (sellerPixKey && transactionValue) {
var body_req = {
e2e_id: e2eId.value,
pix_key: sellerPixKey,
pix_value: transactionValue,
};
isCodeInputEmpty.value = false;
try {
await api.post("validate_pix", body_req);
isPixValid.value = true;
} catch (error) {
isPixValid.value = false;
}
} else {
isCodeInputEmpty.value = false;
isPixValid.value = false;
}
};
</script>
<template>
<div class="page">
<h2>{{ e2eId }}</h2>
<div class="text-container">
<span class="text font-extrabold text-2xl max-w-[30rem]">
Utilize o QR Code ou copie o código para realizar o Pix
</span>
<span class="text font-medium text-md max-w-[28rem]">
Após realizar o Pix no banco de sua preferência, insira o código de
autenticação para enviar a transação para a rede.
</span>
</div>
<div class="blur-container max-w-[28rem] text-black">
<div
class="flex-col items-center justify-center flex w-full bg-white p-8 rounded-lg break-normal"
>
<img :src="qrCode" class="w-48 h-48" />
<span class="text-center font-bold">Código pix</span>
<div class="break-words w-4/5">
<span class="text-center text-xs">
{{ qrCodePayload }}
</span>
</div>
<img
alt="Copy PIX code"
src="@/assets/copyPix.svg"
width="16"
height="16"
class="pt-2 mb-5 cursor-pointer"
/>
<span class="text-xs text-start">
<strong>ATENÇÃO!</strong> A transação será processada após inserir
o código de autenticação. Caso contrário não conseguiremos comprovar o
seu depósito e não será possível transferir os tokens para sua
carteira. Confira aqui como encontrar o código no comprovante.
</span>
</div>
<div
class="flex-col items-center justify-center flex w-full bg-white p-5 rounded-lg px-5"
>
<input
type="text"
placeholder="Digite o código do comprovante PIX"
@input="debounce(handleInputEvent, 500)($event)"
class="text-md w-full box-border p-2 h-6 mb-2 outline-none"
/>
<div class="custom-divide" v-if="!isCodeInputEmpty"></div>
<div
class="flex flex-col w-full"
v-if="!isPixValid && !isCodeInputEmpty"
>
<div class="flex items-center h-8">
<img
alt="Invalid Icon"
src="@/assets/invalidIcon.svg"
width="14"
class="cursor-pointer align-middle inline-block"
/>
<span class="px-1 text-red-500 font-normal text-xs"
>Código inválido. Por favor, confira e tente novamente.</span
>
</div>
</div>
<div class="flex flex-col w-full" v-else-if="isPixValid == true">
<div class="flex items-center h-8">
<img
alt="Valid Icon"
src="@/assets/validIcon.svg"
width="14"
class="cursor-pointer align-middle inline-block"
/>
<span class="px-1 text-green-500 font-normal text-sm">
Código válido.
</span>
</div>
</div>
</div>
<CustomButton
:is-disabled="isPixValid == false"
:text="'Enviar para a rede'"
@button-clicked="emit('pixValidated', { e2eId })"
/>
</div>
</div>
</template>
<style scoped>
.page {
@apply flex flex-col items-center justify-center w-full mt-16;
}
::placeholder {
/* Most modern browsers support this now. */
color: #9ca3af;
}
h4 {
color: #080808;
font-size: 14px;
}
h2 {
color: #080808;
}
.form-input {
@apply rounded-lg border border-gray-200 p-2 text-black;
}
.form-label {
@apply font-semibold tracking-wide text-emerald-50;
}
.custom-divide {
width: 100%;
border-bottom: 1px solid #d1d5db;
}
.bottom-position {
top: -20px;
right: 50%;
transform: translateX(50%);
}
.page {
@apply flex flex-col items-center justify-center w-full mt-16;
}
.text-container {
@apply flex flex-col items-center justify-center gap-4;
}
.text {
@apply text-gray-800 text-center;
}
.blur-container {
@apply flex flex-col justify-center items-center px-8 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-6;
}
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
</style>

View File

@ -6,21 +6,28 @@ import { useEtherStore } from "@/store/ether";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import blockchain from "../utils/blockchain"; import blockchain from "../utils/blockchain";
// Store reference
const etherStore = useEtherStore(); const etherStore = useEtherStore();
const { walletAddress, depositList } = storeToRefs(etherStore); const { walletAddress, depositsAddedList } = storeToRefs(etherStore);
// Reactive state
const tokenValue = ref(0); const tokenValue = ref(0);
const enableSelectButton = ref(false); const enableSelectButton = ref(false);
const hasLiquidity = ref(true); const hasLiquidity = ref(true);
const validDecimals = ref(true); const validDecimals = ref(true);
const selectedDeposit = ref(); const selectedDeposit = ref();
// Emits
const emit = defineEmits(["tokenBuy"]);
// Blockchain methods
const connectAccount = async () => { const connectAccount = async () => {
await blockchain.connectProvider(); await blockchain.connectProvider();
verifyLiquidity(); verifyLiquidity();
}; };
// Debounce methods
const handleInputEvent = (event: any) => { const handleInputEvent = (event: any) => {
const { value } = event.target; const { value } = event.target;
@ -36,6 +43,8 @@ const handleInputEvent = (event: any) => {
verifyLiquidity(); verifyLiquidity();
}; };
// Enable button methods
// Check if has more than 2 decimal places
const decimalCount = (num: Number) => { const decimalCount = (num: Number) => {
const numStr = String(num); const numStr = String(num);
if (numStr.includes(".")) { if (numStr.includes(".")) {
@ -44,34 +53,31 @@ const decimalCount = (num: Number) => {
return 0; return 0;
}; };
// Verify if there is a valid deposit to buy
const verifyLiquidity = () => { const verifyLiquidity = () => {
enableSelectButton.value = false; enableSelectButton.value = false;
selectedDeposit.value = null; selectedDeposit.value = null;
if (!walletAddress.value || tokenValue.value <= 0) return;
if (!walletAddress.value || tokenValue.value == 0) return; depositsAddedList.value.find((element) => {
const p2pixTokenValue = blockchain.formatBigNumber(element.args.amount);
depositList.value.forEach((deposit) => {
const p2pixTokenValue = blockchain.verifyDepositAmmount(
deposit.args.amount
);
if ( if (
tokenValue.value!! <= Number(p2pixTokenValue) && tokenValue.value!! <= Number(p2pixTokenValue) &&
tokenValue.value!! != 0 tokenValue.value!! != 0 &&
element.args.seller !== walletAddress.value
) { ) {
enableSelectButton.value = true; enableSelectButton.value = true;
hasLiquidity.value = true; hasLiquidity.value = true;
selectedDeposit.value = deposit; selectedDeposit.value = element;
return; return true;
} }
return false;
}); });
if (!enableSelectButton.value) { if (!enableSelectButton.value) {
hasLiquidity.value = false; hasLiquidity.value = false;
} }
}; };
const emit = defineEmits(["tokenBuy"]);
</script> </script>
<template> <template>

View File

@ -3,10 +3,12 @@ import { storeToRefs } from "pinia";
import { useEtherStore } from "../store/ether"; import { useEtherStore } from "../store/ether";
import blockchain from "../utils/blockchain"; import blockchain from "../utils/blockchain";
// Store reference
const etherStore = useEtherStore(); const etherStore = useEtherStore();
const { walletAddress, balance } = storeToRefs(etherStore); const { walletAddress, balance } = storeToRefs(etherStore);
//Methods
const connectMetaMask = () => { const connectMetaMask = () => {
blockchain.connectProvider(); blockchain.connectProvider();
}; };
@ -15,16 +17,14 @@ const formatWalletAddress = (): string => {
const walletAddressLength = walletAddress.value.length; const walletAddressLength = walletAddress.value.length;
const initialText = walletAddress.value.substring(0, 5); const initialText = walletAddress.value.substring(0, 5);
const finalText = walletAddress.value.substring( const finalText = walletAddress.value.substring(
walletAddressLength - 5, walletAddressLength - 4,
walletAddressLength - 1 walletAddressLength
); );
return `${initialText}...${finalText}`; return `${initialText}...${finalText}`;
}; };
const formatWalletBalance = (): string => { const formatWalletBalance = (): string => {
const formattedBalance = blockchain.formatEther(balance.value); const fixed = balance.value.substring(0, 8);
const fixed = formattedBalance.substring(0, 8);
return fixed; return fixed;
}; };
</script> </script>

View File

@ -1,8 +1,7 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue"; import HomeView from "../views/HomeView.vue";
import QrCodeFormVue from "../views/QrCodeForm.vue";
import MockView from "../views/MockView.vue"; import MockView from "../views/MockView.vue";
import ListView from "@/views/ListView.vue"; import ListView from "../components/ListComponent.vue";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
@ -12,11 +11,6 @@ const router = createRouter({
name: "home", name: "home",
component: HomeView, component: HomeView,
}, },
{
path: "/pix",
name: "pix",
component: QrCodeFormVue,
},
{ {
path: "/mock", path: "/mock",
name: "mock", name: "mock",

14
src/services/index.ts Normal file
View File

@ -0,0 +1,14 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import axios from "axios";
const defaultConfig = {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
};
const api = axios.create({
...defaultConfig,
baseURL: import.meta.env.VITE_API_URL,
});
export default api;

View File

@ -4,7 +4,19 @@ export const useEtherStore = defineStore("ether", {
state: () => ({ state: () => ({
walletAddress: "", walletAddress: "",
balance: "", balance: "",
depositList: [{}], loadingLock: false,
// Depósitos válidos para compra
depositsValidList: [] as any[],
// Depósitos adicionados na blockchain
depositsAddedList: [] as any[],
// Depósitos expirados na blockchain
depositsExpiredList: [] as any[],
// Locks adicionados na blockchain
locksAddedList: [] as any[],
// Locks 'released' na blockchain
locksReleasedList: [] as any[],
// Locks expirados na blockchain
locksExpiredList: [] as any[],
}), }),
actions: { actions: {
setWalletAddress(walletAddress: string) { setWalletAddress(walletAddress: string) {
@ -13,8 +25,26 @@ export const useEtherStore = defineStore("ether", {
setBalance(balance: string) { setBalance(balance: string) {
this.balance = balance; this.balance = balance;
}, },
setDepositList(depositList: any[]) { setLoadingLock(isLoadingLock: boolean) {
this.depositList = depositList; this.loadingLock = isLoadingLock;
},
setDepositsValidList(depositsValidList: any[]) {
this.depositsValidList = depositsValidList;
},
setDepositsAddedList(depositsAddedList: any[]) {
this.depositsAddedList = depositsAddedList;
},
setDepositsExpiredList(depositsExpiredList: any[]) {
this.depositsExpiredList = depositsExpiredList;
},
setLocksAddedList(locksAddedList: any[]) {
this.locksAddedList = locksAddedList;
},
setLocksReleasedList(locksReleasedList: any[]) {
this.locksReleasedList = locksReleasedList;
},
setLocksExpiredList(locksExpiredList: any[]) {
this.locksExpiredList = locksExpiredList;
}, },
}, },
}); });

View File

@ -69,7 +69,7 @@ const generatePixKey = (pixKey: string, message?: string): string => {
}; };
const formatEMV = (id: string, param: string): string => { const formatEMV = (id: string, param: string): string => {
const len = param.length.toString().padStart(2, "0"); const len = param?.length?.toString().padStart(2, "0");
return `${id}${len}${param}`; return `${id}${len}${param}`;
}; };

View File

@ -1,14 +1,16 @@
import { useEtherStore } from "@/store/ether"; import { useEtherStore } from "@/store/ether";
import { BigNumber, ethers } from "ethers"; import { BigNumber, ethers } from "ethers";
// smart contract imports // Smart contract imports
import mockToken from "./smart_contract_files/MockToken.json"; import mockToken from "./smart_contract_files/MockToken.json";
import p2pix from "./smart_contract_files/P2PIX.json"; import p2pix from "./smart_contract_files/P2PIX.json";
import addresses from "./smart_contract_files/localhost.json"; import addresses from "./smart_contract_files/localhost.json";
// Mock wallets import
import { wallets } from "./smart_contract_files/wallets.json"; import { wallets } from "./smart_contract_files/wallets.json";
const updateWalletStatus = async (walletAddress: string) => { // Wallet methods
// Update wallet state (balance and address)
const updateWalletStatus = async () => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
const provider = getProvider(); const provider = getProvider();
if (!provider) return; if (!provider) return;
@ -16,13 +18,66 @@ const updateWalletStatus = async (walletAddress: string) => {
const signer = provider.getSigner(); const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.token, mockToken.abi, signer); const contract = new ethers.Contract(addresses.token, mockToken.abi, signer);
const balance = await contract.balanceOf(walletAddress); const walletAddress = await provider.send("eth_requestAccounts", []);
etherStore.setBalance(String(balance)); const balance = await contract.balanceOf(walletAddress[0]);
etherStore.setWalletAddress(walletAddress);
etherStore.setBalance(formatBigNumber(balance));
etherStore.setWalletAddress(ethers.utils.getAddress(walletAddress[0]));
}; };
const connectProvider = async () => { // Split tokens between wallets in wallets.json
const splitTokens = async () => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const tokenContract = new ethers.Contract(
addresses.token,
mockToken.abi,
signer
);
for (let i = 0; i < wallets.length; i++) {
const tx = await tokenContract.transfer(
wallets[i],
ethers.utils.parseEther("4000000.0")
);
await tx.wait();
updateWalletStatus();
}
};
// get wallet transactions
const listTransactionByWalletAddress = async (
walletAddress: string
): Promise<any[] | undefined> => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const filterDeposits = p2pContract.filters.DepositAdded([walletAddress]);
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]);
const eventsAddedLocks = await p2pContract.queryFilter(filterAddedLocks);
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
const eventsReleasedLocks = await p2pContract.queryFilter(
filterReleasedLocks
);
return [...eventsDeposits, ...eventsAddedLocks, ...eventsReleasedLocks].sort(
(a, b) => {
return b.blockNumber - a.blockNumber;
}
);
};
// Update events at store methods
const updateDepositAddedEvents = async () => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
const window_ = window as any; const window_ = window as any;
const connection = window_.ethereum; const connection = window_.ethereum;
@ -30,48 +85,76 @@ const connectProvider = async () => {
if (!connection) return; if (!connection) return;
provider = new ethers.providers.Web3Provider(connection); provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner(); const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.token, mockToken.abi, signer);
const walletAddress = await provider.send("eth_requestAccounts", []);
const balance = await contract.balanceOf(walletAddress[0]);
etherStore.setWalletAddress(walletAddress[0]);
etherStore.setBalance(String(balance));
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer); const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const filter = p2pContract.filters.DepositAdded(null); const filterDeposits = p2pContract.filters.DepositAdded(null);
const events = await p2pContract.queryFilter(filter); const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
etherStore.setDepositsAddedList(eventsDeposits);
};
console.log(events); const updateLockAddedEvents = async () => {
etherStore.setDepositList(events); const etherStore = useEtherStore();
const window_ = window as any;
const connection = window_.ethereum;
let provider: ethers.providers.Web3Provider | null = null;
connection.on("accountsChanged", (accounts: string[]) => { if (!connection) return;
updateWalletStatus(accounts[0]); provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner();
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const filterLocks = p2pContract.filters.LockAdded(null);
const eventsLocks = await p2pContract.queryFilter(filterLocks);
etherStore.setLocksAddedList(eventsLocks);
};
const updateLockReleasedEvents = async () => {
const etherStore = useEtherStore();
const window_ = window as any;
const connection = window_.ethereum;
let provider: ethers.providers.Web3Provider | null = null;
if (!connection) return;
provider = new ethers.providers.Web3Provider(connection);
const signer = provider.getSigner();
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const filterLocks = p2pContract.filters.LockReleased(null);
const eventsLocks = await p2pContract.queryFilter(filterLocks);
etherStore.setLocksReleasedList(eventsLocks);
};
// Provider methods
const connectProvider = async () => {
const window_ = window as any;
const connection = window_.ethereum;
await updateWalletStatus();
await updateDepositAddedEvents();
await updateLockAddedEvents();
await updateLockReleasedEvents();
connection.on("accountsChanged", () => {
updateWalletStatus();
}); });
}; };
const splitTokens = async () => { const getProvider = (): ethers.providers.Web3Provider | null => {
const etherStore = useEtherStore(); const window_ = window as any;
const provider = getProvider(); const connection = window_.ethereum;
if (!provider) return;
const signer = provider.getSigner(); if (!connection) return null;
const contract = new ethers.Contract(addresses.token, mockToken.abi, signer);
for (let i = 0; i < wallets.length; i++) { return new ethers.providers.Web3Provider(connection);
const tx = await contract.transfer(
wallets[i],
ethers.utils.parseEther("4000000.0")
);
await tx.wait();
updateWalletStatus(etherStore.walletAddress);
}
}; };
const mockDeposit = async (tokenQty = "1000.0", pixKey = "00011122233") => { // Deposit methods
const etherStore = useEtherStore(); // Gets value and pix key from user's form to create a deposit in the blockchain
const addDeposit = async (tokenQty: string, pixKey: string) => {
const provider = getProvider(); const provider = getProvider();
if (!provider) return; if (!provider) return;
@ -84,80 +167,135 @@ const mockDeposit = async (tokenQty = "1000.0", pixKey = "00011122233") => {
); );
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer); const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
// first get the approval // First get the approval
const apprv = await tokenContract.approve( const apprv = await tokenContract.approve(
addresses.p2pix, addresses.p2pix,
ethers.utils.parseEther(tokenQty) formatEther(tokenQty)
); );
await apprv.wait(); await apprv.wait();
// deposit // Now we make the deposit
const deposit = await p2pContract.deposit( const deposit = await p2pContract.deposit(
addresses.token, addresses.token,
ethers.utils.parseEther(tokenQty), formatEther(tokenQty),
pixKey pixKey
); );
await deposit.wait(); await deposit.wait();
updateWalletStatus(etherStore.walletAddress); updateWalletStatus();
updateDepositAddedEvents();
const filter = p2pContract.filters.DepositAdded(null);
const events = await p2pContract.queryFilter(filter);
console.log(events);
etherStore.setDepositList(events);
};
const countDeposit = async () => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const count = await contract.depositCount();
console.log(Number(count));
}; };
// Get specific deposit data by its ID
const mapDeposits = async (depositId: BigNumber) => { const mapDeposits = async (depositId: BigNumber) => {
const provider = getProvider(); const provider = getProvider();
if (!provider) return; if (!provider) return;
const signer = provider.getSigner(); const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer); const contract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const deposit = await contract.mapDeposits(depositId.toNumber());
const deposit = await contract.mapDeposits(depositId);
console.log(deposit);
return deposit; return deposit;
}; };
const formatEther = (balance: string) => { // Lock methods
const formatted = ethers.utils.formatEther(balance); // Gets value from user's form to create a lock in the blockchain
return formatted; const addLock = async (depositId: Number, amount: Number) => {
const etherStore = useEtherStore();
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
// Make lock
const oldEventsLen = etherStore.locksAddedList.length;
const lock = await p2pContract.lock(
depositId,
etherStore.walletAddress,
ethers.constants.AddressZero,
0,
ethers.utils.parseEther(amount.toString()),
[]
);
lock.wait();
while (etherStore.locksAddedList.length === oldEventsLen) {
await updateLockAddedEvents();
}
return lock;
}; };
const verifyDepositAmmount = (ammountBigNumber: BigNumber): string => { // Get specific lock data by its ID
return ethers.utils.formatEther(ammountBigNumber); const mapLocks = async (lockId: string) => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const lock = await contract.mapLocks(lockId);
console.log(lock);
console.log("Expiration block = ", Number(lock.expirationBlock));
return lock;
}; };
const getProvider = (): ethers.providers.Web3Provider | null => { // Releases lock by specific ID and other additional data
const window_ = window as any; const releaseLock = async (
const connection = window_.ethereum; pixKey: string,
amount: string,
e2eId: Number,
lockId: string
) => {
const provider = getProvider();
if (!provider) return;
if (!connection) return null; const mockBacenSigner = new ethers.Wallet(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
);
return new ethers.providers.Web3Provider(connection); const messageToSign = ethers.utils.solidityKeccak256(
["string", "uint256", "uint256"],
[pixKey, formatEther(amount), e2eId]
);
const messageHashBytes = ethers.utils.arrayify(messageToSign);
const flatSig = await mockBacenSigner.signMessage(messageHashBytes);
const sig = ethers.utils.splitSignature(flatSig);
const signer = provider.getSigner();
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const release = await p2pContract.release(lockId, e2eId, sig.r, sig.s, sig.v);
release.wait();
await updateLockReleasedEvents();
return release;
};
// Formatting methods
const formatEther = (num: string) => {
const formattedNum = ethers.utils.parseEther(num);
return formattedNum;
};
const formatBigNumber = (num: BigNumber) => {
const formattedNum = ethers.utils.formatEther(num);
return formattedNum;
}; };
export default { export default {
connectProvider, connectProvider,
formatEther, formatEther,
splitTokens, splitTokens,
mockDeposit, listTransactionByWalletAddress,
countDeposit, addDeposit,
mapDeposits, mapDeposits,
verifyDepositAmmount, formatBigNumber,
addLock,
mapLocks,
releaseLock,
updateLockAddedEvents,
}; };

File diff suppressed because one or more lines are too long

View File

@ -1,21 +1,128 @@
<script setup lang="ts"> <script setup lang="ts">
import SearchComponent from "../components/SearchComponent.vue"; import SearchComponent from "../components/SearchComponent.vue";
import ValidationComponent from "../components/LoadingComponent.vue";
import ListComponent from "@/components/ListComponent.vue";
import blockchain from "../utils/blockchain"; import blockchain from "../utils/blockchain";
import { ref } from "vue";
import { useEtherStore } from "@/store/ether";
import QrCodeComponent from "../components/QrCodeComponent.vue";
import { storeToRefs } from "pinia";
enum Step {
Search,
Buy,
List,
}
// States
const etherStore = useEtherStore();
const { loadingLock, walletAddress, locksAddedList } = storeToRefs(etherStore);
const flowStep = ref<Step>(Step.Search);
const pixTarget = ref<string>("");
const tokenAmount = ref<number>();
const lockTransactionHash = ref<string>("");
const lockId = ref<string>("");
const loadingRelease = ref<Boolean>(false);
const lastWalletTransactions = ref<any[] | undefined>([]);
const confirmBuyClick = async ({ selectedDeposit, tokenValue }: any) => { const confirmBuyClick = async ({ selectedDeposit, tokenValue }: any) => {
// finish buy screen // finish buy screen
console.log(selectedDeposit); console.log(selectedDeposit);
let depositDetail; let depositDetail;
const depositId = selectedDeposit["args"]["depositID"];
await blockchain await blockchain
.mapDeposits(selectedDeposit["args"]["depositID"]) .mapDeposits(depositId)
.then((deposit) => (depositDetail = deposit)); .then((deposit) => (depositDetail = deposit));
console.log(tokenValue); tokenAmount.value = tokenValue;
console.log(depositDetail); pixTarget.value = depositDetail?.pixTarget;
// Makes lock with deposit ID and the Amount
if (depositDetail) {
flowStep.value = Step.Buy;
etherStore.setLoadingLock(true);
await blockchain
.addLock(depositId, tokenValue)
.then((lock) => {
lockTransactionHash.value = lock.hash;
})
.catch(() => {
flowStep.value = Step.Search;
});
etherStore.setLoadingLock(false);
}
};
const releaseTransaction = async ({ e2eId }: any) => {
flowStep.value = Step.List;
loadingRelease.value = true;
const findLockId = locksAddedList.value.find((element) => {
if (element.transactionHash === lockTransactionHash.value) {
lockId.value = element.args.lockID;
return true;
}
return false;
});
if (findLockId) {
console.log(
pixTarget.value,
String(tokenAmount.value),
Number(e2eId),
lockId.value
);
const release = await blockchain.releaseLock(
pixTarget.value,
String(tokenAmount.value),
Number(e2eId),
lockId.value
);
release.wait();
lastWalletTransactions.value =
await blockchain.listTransactionByWalletAddress(
walletAddress.value.toLowerCase()
);
console.log(tokenAmount);
loadingRelease.value = false;
}
}; };
</script> </script>
<template> <template>
<SearchComponent @token-buy="confirmBuyClick" /> <SearchComponent
v-if="flowStep == Step.Search"
@token-buy="confirmBuyClick"
/>
<div v-if="flowStep == Step.Buy">
<QrCodeComponent
:pixTarget="pixTarget"
:tokenValue="tokenAmount"
@pix-validated="releaseTransaction"
v-if="!loadingLock"
/>
<ValidationComponent
v-if="loadingLock"
:message="'A transação está sendo enviada para a rede'"
/>
</div>
<div v-if="flowStep == Step.List">
<ListComponent
v-if="!loadingRelease"
:tokenAmount="tokenAmount"
:last-wallet-transactions="lastWalletTransactions"
/>
<ValidationComponent
v-if="loadingRelease"
:message="'A transação está sendo enviada para a rede. Em breve os tokens serão depositados em sua carteira.'"
/>
</div>
</template> </template>
<style scoped></style> <style scoped></style>

View File

@ -5,38 +5,54 @@ import { ref } from "vue";
import { useEtherStore } from "../store/ether"; import { useEtherStore } from "../store/ether";
import blockchain from "../utils/blockchain"; import blockchain from "../utils/blockchain";
// Blockchain Data
const etherStore = useEtherStore(); const etherStore = useEtherStore();
const { depositsAddedList } = storeToRefs(etherStore);
const { locksAddedList } = storeToRefs(etherStore);
const { depositList } = storeToRefs(etherStore); // Buyer's flow Data
const depositValue = ref<Number>(); const depositValue = ref<Number>();
const depositPixKey = ref<string>(""); const depositPixKey = ref<string>("");
// Split tokens between wallets in wallets.json
const splitTokens = () => { const splitTokens = () => {
blockchain.splitTokens(); blockchain.splitTokens();
}; };
// Formatting methods
// Formats wallet address in 0x000...0000 format
const formatWalletAddress = (wallet: string): string => {
const walletAddressLength = wallet.length;
const initialText = wallet.substring(0, 5);
const finalText = wallet.substring(
walletAddressLength - 4,
walletAddressLength
);
return `${initialText}...${finalText}`;
};
// Deposit methods
// Gets value and pix key from user's form to create a deposit in the blockchain
const mockDeposit = () => { const mockDeposit = () => {
if (!depositValue.value || !depositPixKey.value) return; if (!depositValue.value || !depositPixKey.value) return;
blockchain.mockDeposit(depositValue.value.toString(), depositPixKey.value); blockchain.addDeposit(depositValue.value.toString(), depositPixKey.value);
};
const countDeposit = () => {
blockchain.countDeposit();
}; };
// Get specific deposit data by its ID
const mapDeposit = (depositId: BigNumber) => { const mapDeposit = (depositId: BigNumber) => {
blockchain.mapDeposits(depositId); blockchain.mapDeposits(depositId);
}; };
// Lock methods
// Get specific lock data by its ID
const mapLock = (lockId: string) => {
blockchain.mapLocks(lockId);
};
</script> </script>
<template> <template>
<div class="page"> <div class="page">
<div class="flex flex-col gap-4 justify-start items-start w-2/3"> <div class="flex flex-col gap-4 justify-start items-start w-2/3">
<button type="button" class="default-button" @click="countDeposit()">
Contar depósitos
</button>
<div class="flex gap-4 w-full justify-between"> <div class="flex gap-4 w-full justify-between">
<input <input
type="number" type="number"
@ -65,12 +81,23 @@ const mapDeposit = (depositId: BigNumber) => {
<ul class="flex flex-col justify-center items-center gap-4"> <ul class="flex flex-col justify-center items-center gap-4">
<li <li
class="text-gray-900 font-semibold text-lg cursor-pointer border-2 border-amber-400 p-2 rounded-md bg-amber-200" class="text-gray-900 font-semibold text-lg cursor-pointer border-2 border-amber-400 p-2 rounded-md bg-amber-200"
v-for="deposit in depositList" v-for="deposit in depositsAddedList"
:key="deposit['blockNumber']" :key="deposit.blockNumber"
@click="mapDeposit(deposit['args']['depositID'])" @click="mapDeposit(deposit.args.depositID)"
> >
Address:<br />{{ deposit["args"]["0"] }}<br /> Seller:<br />{{ formatWalletAddress(deposit.args.seller) }}<br />
MRBZ: {{ blockchain.formatEther(deposit["args"]["amount"]) }} MRBZ: {{ blockchain.formatBigNumber(deposit.args.amount) }}
</li>
</ul>
<ul class="flex flex-col justify-center items-center gap-4">
<li
class="text-gray-900 font-semibold text-lg cursor-pointer border-2 border-amber-400 p-2 rounded-md bg-amber-200"
v-for="lock in locksAddedList"
:key="lock.blockNumber"
@click="mapLock(lock.args.lockID)"
>
Buyer:<br />{{ formatWalletAddress(lock.args.buyer) }}<br />
MRBZ: {{ blockchain.formatBigNumber(lock.args.amount) }}
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -1,214 +0,0 @@
<script setup lang="ts">
import { pix } from "../utils/QrCodePix";
import { ref } from "vue";
const pixModel = ref({
pixKey: "",
name: "",
city: "",
transactionId: "",
value: 0,
message: "",
qrCodePayload: "",
});
const qrCode = ref<string>("");
const qrCodePayload = ref<string>("");
const toggleModal = ref<boolean>(false);
const errors = ref({
pixRequiredError: false,
nameRequiredError: false,
cityRequiredError: false,
});
const submit = () => {
errors.value["pixRequiredError"] = pixModel.value["pixKey"] == "";
if (errors.value["pixRequiredError"]) return;
const pixQrCode = pix({
pixKey: pixModel.value.pixKey,
merchantName: pixModel.value.name.trim() ? pixModel.value.name : "name",
merchantCity: pixModel.value.city.trim() ? pixModel.value.city : "city",
transactionId: pixModel.value.transactionId.trim()
? pixModel.value.transactionId
: "***",
message: pixModel.value.message,
value: pixModel.value["value"],
});
pixQrCode.base64QrCode().then((code: string) => {
qrCode.value = code;
});
qrCodePayload.value = pixQrCode.payload();
toggleModal.value = true;
};
</script>
<template>
<div class="page">
<div class="form-container">
<h2 class="text-center font-bold text-emerald-50 text-2xl">
pixModel QR Code
</h2>
<form>
<div class="grid gap-4 grid-cols-1 p-2">
<div class="col-div">
<div class="mb-2">
<label for="pixKey" class="form-label">Chave PIX</label>
<span v-if="errors['pixRequiredError']" class="required-error"
>(Esse campo é obrigatório)</span
>
</div>
<input
type="text"
name="pixKey"
id="pixKey"
class="form-input"
v-model="pixModel.pixKey"
/>
</div>
<div class="col-div">
<div class="mb-2">
<label for="name" class="form-label">Nome do beneficiário</label>
<span v-if="errors['nameRequiredError']" class="required-error"
>(Esse campo é obrigatório)</span
>
</div>
<input
type="text"
name="name"
id="name"
class="form-input"
v-model="pixModel.name"
/>
</div>
<div class="col-div">
<div class="mb-2">
<label for="city" class="form-label"
>Cidade do beneficiário</label
>
<span v-if="errors['cityRequiredError']" class="required-error"
>(Esse campo é obrigatório)</span
>
</div>
<input
type="text"
name="city"
id="city"
class="form-input"
v-model="pixModel.city"
/>
</div>
<div class="col-div">
<label for="value" class="form-label"
>Valor de transferência (Opcional)</label
>
<input
type="number"
name="value"
id="value"
class="form-input"
v-model="pixModel.value"
/>
</div>
<div class="col-div">
<label for="code" class="form-label"
>Código da transferência (Opcional)</label
>
<input
type="text"
name="code"
id="code"
class="form-input"
v-model="pixModel.transactionId"
/>
</div>
<div class="col-div">
<label for="message" class="form-label">Mensagem (Opcional)</label>
<input
type="text"
name="message"
id="message"
class="form-input"
v-model="pixModel.message"
/>
</div>
<button type="button" class="button" @click="submit">
Gerar QR code
</button>
</div>
</form>
</div>
</div>
<div
v-if="toggleModal"
class="fixed overflow-x-hidden overflow-y-auto inset-0 flex justify-center items-center z-50"
>
<div class="relative mx-auto w-auto max-w-2xl">
<div
class="bg-white w-[500px] p-2 rounded shadow-2xl flex flex-col justify-center items-center gap-2"
>
<img v-if="qrCode != ''" :src="qrCode" alt="QR code image" />
<div>
<span class="text-black font-semibold mr-1">Chave pix:</span>
<span class="text-gray-700">{{ pixModel.pixKey }}</span>
</div>
<div v-if="pixModel.name.trim() != ''">
<span class="text-black font-semibold mr-1"
>Nome do Beneficiário:</span
>
<span class="text-gray-700">{{ pixModel.name }}</span>
</div>
<div v-if="pixModel.value != 0">
<span class="text-black font-semibold mr-1"
>Valor da transferência:</span
>
<span class="text-gray-700">{{ pixModel.value }}</span>
</div>
<div class="flex flex-col w-auto break-all justify-center items-center">
<span class="text-black font-semibold mb-2">Código QR Code:</span>
<span class="text-gray-700">{{ qrCodePayload }}</span>
</div>
<button type="button" class="button" @click="toggleModal = false">
Fechar
</button>
</div>
</div>
</div>
<div v-if="toggleModal" class="fixed z-40 inset-0 opacity-25 bg-black"></div>
</template>
<style scoped>
.page {
@apply mt-8 w-full flex justify-center self-center;
}
.form-container {
background-color: var(--color-background-indigo);
@apply rounded-md w-full p-2 w-1/2;
}
.col-div {
@apply flex flex-col;
}
.form-input {
@apply rounded-lg border border-gray-200 p-2 text-black;
}
.form-label {
@apply font-semibold tracking-wide text-emerald-50;
}
.button {
@apply rounded-lg w-full border border-emerald-900 text-white py-2 bg-emerald-600 hover:bg-emerald-500;
}
.required-error {
@apply ml-2 text-red-500;
}
</style>

View File

@ -1125,6 +1125,11 @@ array-union@^2.1.0:
resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
autoprefixer@^10.4.12: autoprefixer@^10.4.12:
version "10.4.12" version "10.4.12"
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz" resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz"
@ -1137,6 +1142,15 @@ autoprefixer@^10.4.12:
picocolors "^1.0.0" picocolors "^1.0.0"
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
axios@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.1.tgz#44cf04a3c9f0c2252ebd85975361c026cb9f864a"
integrity sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==
dependencies:
follow-redirects "^1.15.0"
form-data "^4.0.0"
proxy-from-env "^1.1.0"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
@ -1315,6 +1329,13 @@ color-name@^1.1.4, color-name@~1.1.4:
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
concat-map@0.0.1: concat-map@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
@ -1397,6 +1418,11 @@ defined@^1.0.0:
resolved "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz" resolved "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz"
integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
detective@^5.2.1: detective@^5.2.1:
version "5.2.1" version "5.2.1"
resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz" resolved "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz"
@ -1915,6 +1941,20 @@ flatted@^3.1.0:
resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz" resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz"
integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
fraction.js@^4.2.0: fraction.js@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz" resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz"
@ -2407,6 +2447,18 @@ micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.2" braces "^3.0.2"
picomatch "^2.3.1" picomatch "^2.3.1"
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@ -2745,6 +2797,11 @@ prettier@^2.7.1:
resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz"
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
proxy-from-env@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
punycode@^2.1.0: punycode@^2.1.0:
version "2.1.1" version "2.1.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"