Merge pull request #45 from liftlearning/lock_redirection

Redirecionamento de locks
This commit is contained in:
Bruno Esteves 2023-02-15 18:34:28 -03:00 committed by GitHub
commit ad15cbde53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 290 additions and 105 deletions

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import TopBar from "./components/TopBar/TopBar.vue"; import TopBar from "@/components/TopBar/TopBar.vue";
</script> </script>
<template> <template>

View File

@ -55,7 +55,7 @@ describe("addresses.ts functions", () => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
etherStore.setNetworkName(NetworkEnum.ethereum); etherStore.setNetworkName(NetworkEnum.ethereum);
expect(getP2PixAddress()).toBe( expect(getP2PixAddress()).toBe(
"0xefa5cE4351cda51192509cf8De7d8881ADAE95DD" "0x2414817FF64A114d91eCFA16a834d3fCf69103d4"
); );
}); });
@ -63,13 +63,13 @@ describe("addresses.ts functions", () => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
etherStore.setNetworkName(NetworkEnum.polygon); etherStore.setNetworkName(NetworkEnum.polygon);
expect(getP2PixAddress()).toBe( expect(getP2PixAddress()).toBe(
"0xA9258eBb157E4cf5e756b77FDD0DF09C2F73240b" "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00"
); );
}); });
it("getP2PixAddress Default", () => { it("getP2PixAddress Default", () => {
expect(getP2PixAddress()).toBe( expect(getP2PixAddress()).toBe(
"0xefa5cE4351cda51192509cf8De7d8881ADAE95DD" "0x2414817FF64A114d91eCFA16a834d3fCf69103d4"
); );
}); });

View File

@ -1,7 +1,7 @@
import { useEtherStore } from "@/store/ether"; import { useEtherStore } from "@/store/ether";
import { NetworkEnum } from "@/model/NetworkEnum"; import { NetworkEnum } from "@/model/NetworkEnum";
const getTokenAddress = (): string => { const getTokenAddress = (network?: NetworkEnum): string => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
const possibleTokenAddresses: { [key: string]: string } = { const possibleTokenAddresses: { [key: string]: string } = {
@ -9,10 +9,10 @@ const getTokenAddress = (): string => {
Polygon: "0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29", Polygon: "0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29",
}; };
return possibleTokenAddresses[etherStore.networkName]; return possibleTokenAddresses[network ? network : etherStore.networkName];
}; };
const getP2PixAddress = (): string => { const getP2PixAddress = (network?: NetworkEnum): string => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
const possibleP2PixAddresses: { [key: string]: string } = { const possibleP2PixAddresses: { [key: string]: string } = {
@ -20,7 +20,7 @@ const getP2PixAddress = (): string => {
Polygon: "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00", Polygon: "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00",
}; };
return possibleP2PixAddresses[etherStore.networkName]; return possibleP2PixAddresses[network ? network : etherStore.networkName];
}; };
const getProviderUrl = (): string => { const getProviderUrl = (): string => {

View File

@ -3,7 +3,7 @@ import { useEtherStore } from "@/store/ether";
import { getContract, getProvider } from "./provider"; import { getContract, getProvider } from "./provider";
import { getP2PixAddress, getTokenAddress } from "./addresses"; import { getP2PixAddress, getTokenAddress } from "./addresses";
import p2pix from "../utils/smart_contract_files/P2PIX.json"; import p2pix from "@/utils/smart_contract_files/P2PIX.json";
import { BigNumber, ethers } from "ethers"; import { BigNumber, ethers } from "ethers";
import { parseEther } from "ethers/lib/utils"; import { parseEther } from "ethers/lib/utils";

View File

@ -1,10 +1,12 @@
import { useEtherStore } from "@/store/ether"; import { useEtherStore } from "@/store/ether";
import { Contract, ethers } from "ethers"; import { Contract, ethers } from "ethers";
import p2pix from "../utils/smart_contract_files/P2PIX.json"; import p2pix from "@/utils/smart_contract_files/P2PIX.json";
import { formatEther } from "ethers/lib/utils"; import { formatEther } from "ethers/lib/utils";
import { getContract } from "./provider"; import { getContract } from "./provider";
import type { ValidDeposit } from "@/model/ValidDeposit"; import type { ValidDeposit } from "@/model/ValidDeposit";
import { getP2PixAddress, getTokenAddress } from "./addresses";
import { NetworkEnum } from "@/model/NetworkEnum";
const getNetworksLiquidity = async (): Promise<void> => { const getNetworksLiquidity = async (): Promise<void> => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
@ -20,23 +22,23 @@ const getNetworksLiquidity = async (): Promise<void> => {
); // mumbai provider ); // mumbai provider
const p2pContractGoerli = new ethers.Contract( const p2pContractGoerli = new ethers.Contract(
"0x2414817FF64A114d91eCFA16a834d3fCf69103d4", getP2PixAddress(NetworkEnum.ethereum),
p2pix.abi, p2pix.abi,
goerliProvider goerliProvider
); );
const p2pContractMumbai = new ethers.Contract( const p2pContractMumbai = new ethers.Contract(
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00", getP2PixAddress(NetworkEnum.polygon),
p2pix.abi, p2pix.abi,
mumbaiProvider mumbaiProvider
); );
const depositListGoerli = await getValidDeposits( const depositListGoerli = await getValidDeposits(
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00", getTokenAddress(NetworkEnum.ethereum),
p2pContractGoerli p2pContractGoerli
); );
const depositListMumbai = await getValidDeposits( const depositListMumbai = await getValidDeposits(
"0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29", getTokenAddress(NetworkEnum.polygon),
p2pContractMumbai p2pContractMumbai
); );

View File

@ -1,6 +1,6 @@
import { useEtherStore } from "@/store/ether"; import { useEtherStore } from "@/store/ether";
import p2pix from "../utils/smart_contract_files/P2PIX.json"; import p2pix from "@/utils/smart_contract_files/P2PIX.json";
import { updateWalletStatus } from "./wallet"; import { updateWalletStatus } from "./wallet";
import { import {

View File

@ -3,12 +3,14 @@ import { useEtherStore } from "@/store/ether";
import { getContract, getProvider } from "./provider"; import { getContract, getProvider } from "./provider";
import { getTokenAddress, possibleChains } from "./addresses"; import { getTokenAddress, possibleChains } from "./addresses";
import mockToken from "../utils/smart_contract_files/MockToken.json"; import mockToken from "@/utils/smart_contract_files/MockToken.json";
import { ethers, type Event } from "ethers"; import { ethers, type Event } from "ethers";
import { formatEther } from "ethers/lib/utils"; import { formatEther } from "ethers/lib/utils";
import { getValidDeposits } from "./events"; import { getValidDeposits } from "./events";
import type { ValidDeposit } from "@/model/ValidDeposit"; import type { ValidDeposit } from "@/model/ValidDeposit";
import type { UnreleasedLock } from "@/model/UnreleasedLock";
import type { Pix } from "@/model/Pix";
const updateWalletStatus = async (): Promise<void> => { const updateWalletStatus = async (): Promise<void> => {
const etherStore = useEtherStore(); const etherStore = useEtherStore();
@ -97,9 +99,55 @@ const listReleaseTransactionByWalletAddress = async (
}); });
}; };
const listLockTransactionByWalletAddress = async (
walletAddress: string
): Promise<Event[]> => {
const p2pContract = getContract(true);
const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]);
const eventsReleasedLocks = await p2pContract.queryFilter(filterAddedLocks);
return eventsReleasedLocks.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
};
const checkUnreleasedLock = async (
walletAddress: string
): Promise<UnreleasedLock | undefined> => {
const p2pContract = getContract();
const pixData: Pix = {
pixKey: "",
};
const addedLocks = await listLockTransactionByWalletAddress(walletAddress);
const lockStatus = await p2pContract.getLocksStatus(
addedLocks.map((lock) => lock.args?.lockID)
);
const unreleasedLockId = lockStatus[1].findIndex(
(lockStatus: number) => lockStatus == 1
);
if (unreleasedLockId != -1) {
const _lockID = lockStatus[0][unreleasedLockId];
const lock = await p2pContract.mapLocks(_lockID);
const pixTarget = lock.pixTarget;
const amount = formatEther(lock?.amount);
pixData.pixKey = String(Number(pixTarget));
pixData.value = Number(amount);
return {
lockID: _lockID,
pix: pixData,
};
}
};
export { export {
updateWalletStatus, updateWalletStatus,
listValidDepositTransactionsByWalletAddress, listValidDepositTransactionsByWalletAddress,
listAllTransactionByWalletAddress, listAllTransactionByWalletAddress,
listReleaseTransactionByWalletAddress, listReleaseTransactionByWalletAddress,
checkUnreleasedLock,
}; };

View File

@ -1,67 +0,0 @@
<script setup lang="ts"></script>
<template>
<div
class="modal-overlay inset-0 fixed justify-center backdrop-blur-sm sm:backdrop-blur-none"
>
<div class="modal px-5 text-center">
<p
class="text-black tracking-tighter leading-tight my-6 mx-2 text-justify"
>
<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.
</p>
<button
@click="$emit('close-modal')"
class="border-2 border-solid border-amber-400 mt-2"
>
Entendi
</button>
</div>
</div>
</template>
<style scoped>
.modal-overlay {
display: flex !important;
}
.modal {
background-color: white;
height: 250px;
width: 300px;
margin-top: 50%;
border-radius: 10px;
}
.close {
cursor: pointer;
}
.close-img {
width: 25px;
}
.check {
width: 150px;
}
h6 {
font-weight: 500;
font-size: 28px;
margin: 20px 0;
}
p {
font-size: 16px;
}
button {
width: 100px;
height: 40px;
color: black;
font-size: 14px;
border-radius: 10px;
}
</style>

View File

@ -0,0 +1,113 @@
<script setup lang="ts">
import { ref } from "vue";
const props = defineProps({
isRedirectModal: Boolean,
});
const modalColor = ref<string>("white");
const modalHeight = ref<string>("250px");
const pFontSize = ref<string>("16px");
if (props.isRedirectModal) {
modalColor.value = "rgba(251, 191, 36, 1)";
modalHeight.value = "150px";
pFontSize.value = "20px";
}
</script>
<template>
<div
class="modal-overlay inset-0 fixed justify-center backdrop-blur-sm sm:backdrop-blur-none"
v-if="!isRedirectModal"
>
<div class="modal px-5 text-center">
<p
class="text-black tracking-tighter leading-tight my-6 mx-2 text-justify"
>
<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.
</p>
<button
@click="$emit('close-modal')"
class="border-2 border-solid border-amber-400 mt-2"
>
Entendi
</button>
</div>
</div>
<div
class="modal-overlay inset-0 fixed justify-center backdrop-blur-sm"
v-if="isRedirectModal"
>
<div class="modal px-5 text-center">
<p
class="text-black text-lg tracking-tighter leading-tight my-6 mx-2 text-justify font-semibold"
>
Retomar a última compra?
</p>
<div class="flex justify-around items-center px-2">
<button
@click="$emit('close-modal')"
class="border-2 border-solid border-white-400 mt-2 font-semibold"
>
Não
</button>
<button
@click="$emit('go-to-lock')"
class="border-2 border-solid border-white-400 mt-2 font-semibold"
>
Sim
</button>
</div>
</div>
</div>
</template>
<style scoped>
.modal-overlay {
display: flex !important;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
}
.modal {
background-color: v-bind(modalColor);
height: v-bind(modalHeight);
width: 300px;
border-radius: 10px;
}
.close {
cursor: pointer;
}
.close-img {
width: 25px;
}
.check {
width: 150px;
}
h6 {
font-weight: 500;
font-size: 28px;
margin: 20px 0;
}
p {
font-size: v-bind(pFontSize);
}
button {
width: 100px;
height: 40px;
color: black;
font-size: 14px;
border-radius: 10px;
}
</style>

View File

@ -0,0 +1,27 @@
import { mount } from "@vue/test-utils";
import CustomModal from "../CustomModal.vue";
describe("CustomModal test", () => {
test("Test custom modal when receive is redirect modal props as false", () => {
const wrapper = mount(CustomModal, {
props: {
isRedirectModal: false,
},
});
expect(wrapper.html()).toContain("ATENÇÃO!");
expect(wrapper.html()).toContain("Entendi");
});
test("Test custom modal when receive is redirect modal props as true", () => {
const wrapper = mount(CustomModal, {
props: {
isRedirectModal: true,
},
});
expect(wrapper.html()).toContain("Retomar a última compra?");
expect(wrapper.html()).toContain("Não");
expect(wrapper.html()).toContain("Sim");
});
});

View File

@ -1,10 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { pix } from "../utils/QrCodePix"; import { pix } from "@/utils/QrCodePix";
import { ref } from "vue"; import { onMounted, onUnmounted, ref } from "vue";
import { debounce } from "@/utils/debounce"; import { debounce } from "@/utils/debounce";
import CustomButton from "./CustomButton/CustomButton.vue"; import CustomButton from "@/components/CustomButton/CustomButton.vue";
import CustomModal from "./CustomModal.vue"; import CustomModal from "@/components//CustomModal/CustomModal.vue";
import api from "../services/index"; import api from "@/services/index";
// props and store references // props and store references
const props = defineProps({ const props = defineProps({
@ -12,11 +12,12 @@ const props = defineProps({
tokenValue: Number, tokenValue: Number,
}); });
const windowSize = ref<number>(window.innerWidth);
const qrCode = ref<string>(""); const qrCode = ref<string>("");
const qrCodePayload = ref<string>(""); const qrCodePayload = ref<string>("");
const isPixValid = ref<boolean>(false); const isPixValid = ref<boolean>(false);
const isCodeInputEmpty = ref<boolean>(true); const isCodeInputEmpty = ref<boolean>(true);
const showModal = ref<boolean>(true); const showWarnModal = ref<boolean>(true);
const e2eId = ref<string>(""); const e2eId = ref<string>("");
// Emits // Emits
@ -68,6 +69,19 @@ const validatePix = async (): Promise<void> => {
isPixValid.value = false; isPixValid.value = false;
} }
}; };
onMounted(() => {
window.addEventListener(
"resize",
() => (windowSize.value = window.innerWidth)
);
});
onUnmounted(() => {
window.removeEventListener(
"resize",
() => (windowSize.value = window.innerWidth)
);
});
</script> </script>
<template> <template>
@ -154,7 +168,11 @@ const validatePix = async (): Promise<void> => {
@button-clicked="emit('pixValidated', e2eId)" @button-clicked="emit('pixValidated', e2eId)"
/> />
</div> </div>
<CustomModal v-if="showModal" @close-modal="showModal = false" /> <CustomModal
v-if="showWarnModal && windowSize <= 500"
@close-modal="showWarnModal = false"
:isRedirectModal="false"
/>
</div> </div>
</template> </template>

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import CustomButton from "../CustomButton/CustomButton.vue"; import CustomButton from "@/components/CustomButton/CustomButton.vue";
import { debounce } from "@/utils/debounce"; import { debounce } from "@/utils/debounce";
import { decimalCount } from "@/utils/decimalCount"; import { decimalCount } from "@/utils/decimalCount";

View File

@ -0,0 +1,6 @@
import type { Pix } from "./Pix";
export type UnreleasedLock = {
lockID: string;
pix: Pix;
};

View File

@ -1,7 +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 FaqView from "../views/FaqView.vue"; import FaqView from "@/views/FaqView.vue";
import ManageBidsView from "../views/ManageBidsView.vue"; import ManageBidsView from "@/views/ManageBidsView.vue";
import SellerView from "@/views/SellerView.vue"; import SellerView from "@/views/SellerView.vue";
const router = createRouter({ const router = createRouter({

View File

@ -2,14 +2,16 @@
import SearchComponent from "@/components/SearchComponent.vue"; import SearchComponent from "@/components/SearchComponent.vue";
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue"; import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
import BuyConfirmedComponent from "@/components/BuyConfirmedComponent/BuyConfirmedComponent.vue"; import BuyConfirmedComponent from "@/components/BuyConfirmedComponent/BuyConfirmedComponent.vue";
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
import { useEtherStore } from "@/store/ether"; import { useEtherStore } from "@/store/ether";
import QrCodeComponent from "@/components/QrCodeComponent.vue"; import QrCodeComponent from "@/components/QrCodeComponent.vue";
import CustomModal from "@/components/CustomModal/CustomModal.vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { addLock, releaseLock } from "@/blockchain/buyerMethods"; import { addLock, releaseLock } from "@/blockchain/buyerMethods";
import { import {
updateWalletStatus, updateWalletStatus,
listReleaseTransactionByWalletAddress, listReleaseTransactionByWalletAddress,
checkUnreleasedLock,
} from "@/blockchain/wallet"; } from "@/blockchain/wallet";
import { getNetworksLiquidity } from "@/blockchain/events"; import { getNetworksLiquidity } from "@/blockchain/events";
import type { Event } from "ethers"; import type { Event } from "ethers";
@ -25,12 +27,13 @@ const etherStore = useEtherStore();
etherStore.setSellerView(false); etherStore.setSellerView(false);
// States // States
const { loadingLock, walletAddress } = storeToRefs(etherStore); const { loadingLock, walletAddress, networkName } = storeToRefs(etherStore);
const flowStep = ref<Step>(Step.Search); const flowStep = ref<Step>(Step.Search);
const pixTarget = ref<number>(); const pixTarget = ref<number>();
const tokenAmount = ref<number>(); const tokenAmount = ref<number>();
const _lockID = ref<string>(""); const lockID = ref<string>("");
const loadingRelease = ref<boolean>(false); const loadingRelease = ref<boolean>(false);
const showModal = ref<boolean>(false);
const lastWalletReleaseTransactions = ref<Event[]>([]); const lastWalletReleaseTransactions = ref<Event[]>([]);
const confirmBuyClick = async ( const confirmBuyClick = async (
@ -47,8 +50,8 @@ const confirmBuyClick = async (
etherStore.setLoadingLock(true); etherStore.setLoadingLock(true);
await addLock(selectedDeposit.seller, selectedDeposit.token, tokenValue) await addLock(selectedDeposit.seller, selectedDeposit.token, tokenValue)
.then((lockID) => { .then((_lockID) => {
_lockID.value = lockID; lockID.value = _lockID;
}) })
.catch((err) => { .catch((err) => {
console.log(err); console.log(err);
@ -63,12 +66,12 @@ const releaseTransaction = async (e2eId: string) => {
flowStep.value = Step.List; flowStep.value = Step.List;
loadingRelease.value = true; loadingRelease.value = true;
if (_lockID.value && tokenAmount.value && pixTarget.value) { if (lockID.value && tokenAmount.value && pixTarget.value) {
const release = await releaseLock( const release = await releaseLock(
pixTarget.value, pixTarget.value,
tokenAmount.value, tokenAmount.value,
e2eId, e2eId,
_lockID.value lockID.value
); );
release.wait(); release.wait();
@ -84,6 +87,32 @@ const releaseTransaction = async (e2eId: string) => {
} }
}; };
const checkForUnreleasedLocks = async (): Promise<void> => {
const walletLocks = await checkUnreleasedLock(walletAddress.value);
console.log(walletLocks);
if (walletLocks) {
lockID.value = walletLocks.lockID;
tokenAmount.value = walletLocks.pix.value;
pixTarget.value = Number(walletLocks.pix.pixKey);
showModal.value = true;
} else {
flowStep.value = Step.Search;
showModal.value = false;
}
};
if (walletAddress.value) {
await checkForUnreleasedLocks();
}
watch(walletAddress, async () => {
await checkForUnreleasedLocks();
});
watch(networkName, async () => {
if (walletAddress.value) await checkForUnreleasedLocks();
});
onMounted(async () => { onMounted(async () => {
await getNetworksLiquidity(); await getNetworksLiquidity();
}); });
@ -94,6 +123,12 @@ onMounted(async () => {
v-if="flowStep == Step.Search" v-if="flowStep == Step.Search"
@token-buy="confirmBuyClick" @token-buy="confirmBuyClick"
/> />
<CustomModal
v-if="flowStep == Step.Search && showModal"
:isRedirectModal="true"
@close-modal="showModal = false"
@go-to-lock="flowStep = Step.Buy"
/>
<div v-if="flowStep == Step.Buy"> <div v-if="flowStep == Step.Buy">
<QrCodeComponent <QrCodeComponent
:pixTarget="String(pixTarget)" :pixTarget="String(pixTarget)"

View File

@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import WantSellComponent from "../components/SellerSteps/WantSellComponent.vue"; import WantSellComponent from "@/components/SellerSteps/WantSellComponent.vue";
import SendNetwork from "../components/SellerSteps/SendNetwork.vue"; import SendNetwork from "@/components/SellerSteps/SendNetwork.vue";
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue"; import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
import { approveTokens, addDeposit } from "../blockchain/sellerMethods"; import { approveTokens, addDeposit } from "@/blockchain/sellerMethods";
import { ref } from "vue"; import { ref } from "vue";
import { useEtherStore } from "@/store/ether"; import { useEtherStore } from "@/store/ether";
@ -23,7 +23,10 @@ const offerValue = ref<string>("");
const pixKeyBuyer = ref<string>(""); const pixKeyBuyer = ref<string>("");
// Verificar tipagem // Verificar tipagem
const approveOffer = async (args: { offer: string; postProcessedPixKey: string }) => { const approveOffer = async (args: {
offer: string;
postProcessedPixKey: string;
}) => {
loading.value = true; loading.value = true;
try { try {
offerValue.value = args.offer; offerValue.value = args.offer;