This commit is contained in:
EsioFreitas 2023-01-25 17:35:31 -03:00
commit 80e3296131
34 changed files with 978 additions and 15317 deletions

View File

@ -1 +1,3 @@
VITE_API_URL=http://localhost:8000/
VITE_API_URL=http://localhost:8000/
VITE_GOERLI_API_URL={GOERLI_API_URL_ALCHEMY}
VITE_MUMBAI_API_URL={MUMBAI_API_URL_ALCHEMY}

View File

@ -6,9 +6,30 @@
This application aims to create a democratic and secure solution for the purchase and sale of ERC20 tokens, through the PIX, integrating the functionalities of smart contracts (smart contracts) of the blockchain with a receipt by digital signature. Allowing the integration of national financial system transactions to public blockchains, dispensing with custody through intermediaries.
# Table of Contents
* [Metamask Tutorial](#metamask-tutorial)
* [Recommended IDE Setup](#recommended-ide-setup)
* [Dependencies](#dependencies)
* [Build Setup](#build-setup)
## Metamask Tutorial
### Installation
Install the Metamask extension at https://metamask.io/download/
### Enable Testnets on Metamask
Go to Settings -> Advanced -> Show Testnets
Now you can select the Goerli testnet.
### Add Polygon Mumbai to your Metamask
To add the Mumbai network, follow the instructions at:
https://www.youtube.com/watch?v=Jegmru0Q0j4
### Import the MBRL token
Go to Import Tokens and paste the following address: `0x294003F602c321627152c6b7DED3EAb5bEa853Ee`
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
@ -31,12 +52,14 @@ See [Vite Configuration Reference](https://vitejs.dev/config/).
## Dependencies
### Smart Contract
It is necessary to be running the smart contract locally to have access to all the functionalities of the application. The smart contract repository and instructions on how to run it can be found at [https://github.com/doiim/p2pix-smart-contracts](https://github.com/doiim/p2pix-smart-contracts)
### API
For full operation of the application, it is necessary to correctly configure the variable that points to the api in the .env file, in the repository there is an .env.example file, just rename it to just .env and modify the variable `VITE_API_URL`. The api can be run locally see [https://github.com/liftlearning/Pix-Explorer-Back-End](https://github.com/liftlearning/Pix-Explorer-Back-End), or it can be pointed to just her staging address: [https://p2pix-block-explorer-api-staging.vercel.app/](https://p2pix-block-explorer-api-staging.vercel.app/)
### Alchemy Keys
In the .env file, set `VITE_GOERLI_API_URL=https://eth-goerli.g.alchemy.com/v2/Zu9m4b2U_EzVU_zd-vgZDOleY8OF1DNP` and `VITE_MUMBAI_API_URL=https://polygon-mumbai.g.alchemy.com/v2/ZANeCqfj6VsXGpOH6gWAP6SIVIgD9Pwv`
You can also replace it with your own Alchemy Keys if you have one.
## Build Setup
The application can be tested by its trial version [https://p2pix-staging.vercel.app/](https://p2pix-staging.vercel.app/), the only requirement is to be running the smart contract of local way. To run the application locally, there are two different ways:

10686
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
"dependencies": {
"@headlessui/vue": "^1.7.3",
"@heroicons/vue": "^2.0.12",
"alchemy-sdk": "^2.3.0",
"axios": "^1.2.1",
"crc": "^3.8.0",
"marked": "^4.2.12",

View File

@ -0,0 +1,64 @@
import { useEtherStore } from "@/store/ether";
import { NetworkEnum } from "@/model/NetworkEnum";
const getTokenAddress = (): string => {
const etherStore = useEtherStore();
const possibleTokenAddresses: { [key: string]: string } = {
Ethereum: "0x294003F602c321627152c6b7DED3EAb5bEa853Ee",
Polygon: "0x294003F602c321627152c6b7DED3EAb5bEa853Ee",
};
return possibleTokenAddresses[etherStore.networkName];
};
const getP2PixAddress = (): string => {
const etherStore = useEtherStore();
const possibleP2PixAddresses: { [key: string]: string } = {
Ethereum: "0x5f3EFA9A90532914545CEf527C530658af87e196",
Polygon: "0x5f3EFA9A90532914545CEf527C530658af87e196",
};
return possibleP2PixAddresses[etherStore.networkName];
};
const getProviderUrl = (): string => {
const etherStore = useEtherStore();
const possibleProvidersUrls: { [key: string]: string } = {
Ethereum: import.meta.env.VITE_GOERLI_API_URL,
Polygon: import.meta.env.VITE_MUMBAI_API_URL,
};
return possibleProvidersUrls[etherStore.networkName];
};
const possibleChains: { [key: string]: NetworkEnum } = {
"0x5": NetworkEnum.ethereum,
"5": NetworkEnum.ethereum,
"0x13881": NetworkEnum.polygon,
"80001": NetworkEnum.polygon,
};
const network2Chain: { [key: string]: string } = {
Ethereum: "0x5",
Polygon: "0x13881",
Localhost: "0x7a69",
};
const isPossibleNetwork = (networkChain: string): boolean => {
if (Object.keys(possibleChains).includes(networkChain)) {
return true;
}
return false;
};
export {
getTokenAddress,
getProviderUrl,
possibleChains,
network2Chain,
isPossibleNetwork,
getP2PixAddress,
};

View File

@ -0,0 +1,94 @@
import { useEtherStore } from "@/store/ether";
import { getContract, getProvider } from "./provider";
import { getP2PixAddress } from "./addresses";
import p2pix from "../utils/smart_contract_files/P2PIX.json";
import { BigNumber, ethers } from "ethers";
import { parseEther } from "ethers/lib/utils";
const addLock = async (
depositId: BigNumber,
amount: number
): Promise<string> => {
const etherStore = useEtherStore();
const p2pContract = getContract();
const lock = await p2pContract.lock(
depositId, // BigNumber
etherStore.walletAddress, // String "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (Example)
ethers.constants.AddressZero, // String "0x0000000000000000000000000000000000000000"
0,
parseEther(String(amount)), // BigNumber
[],
[]
);
const lock_rec = await lock.wait();
const [t] = lock_rec.events;
return t.args.lockID;
};
const releaseLock = async (
pixKey: string,
amount: number,
e2eId: string,
lockId: string
): Promise<any> => {
const mockBacenSigner = new ethers.Wallet(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
);
const messageToSign = ethers.utils.solidityKeccak256(
["string", "uint256", "bytes32"],
[
pixKey,
parseEther(String(amount)),
ethers.utils.formatBytes32String(e2eId),
]
);
const messageHashBytes = ethers.utils.arrayify(messageToSign);
const flatSig = await mockBacenSigner.signMessage(messageHashBytes);
const provider = getProvider();
const sig = ethers.utils.splitSignature(flatSig);
const signer = provider.getSigner();
const p2pContract = new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
const release = await p2pContract.release(
lockId,
ethers.constants.AddressZero,
ethers.utils.formatBytes32String(e2eId),
sig.r,
sig.s,
sig.v
);
await release.wait();
return release;
};
const cancelDeposit = async (depositId: BigNumber): Promise<any> => {
const contract = getContract();
const cancel = await contract.cancelDeposit(depositId);
await cancel.wait();
return cancel;
};
const withdrawDeposit = async (depositId: BigNumber): Promise<any> => {
const contract = getContract();
const withdraw = await contract.withdraw(depositId, []);
await withdraw.wait();
return withdraw;
};
export { cancelDeposit, withdrawDeposit, addLock, releaseLock };

84
src/blockchain/events.ts Normal file
View File

@ -0,0 +1,84 @@
import { useEtherStore } from "@/store/ether";
import { Contract, ethers } from "ethers";
import p2pix from "../utils/smart_contract_files/P2PIX.json";
import { formatEther } from "ethers/lib/utils";
import { getContract } from "./provider";
import type { ValidDeposit } from "@/model/ValidDeposit";
const getNetworksLiquidity = async (): Promise<void> => {
const etherStore = useEtherStore();
console.log("Loading events");
const goerliProvider = new ethers.providers.JsonRpcProvider(
import.meta.env.VITE_GOERLI_API_URL,
5
); // goerli provider
const mumbaiProvider = new ethers.providers.JsonRpcProvider(
import.meta.env.VITE_MUMBAI_API_URL,
80001
); // mumbai provider
const p2pContractGoerli = new ethers.Contract(
"0x5f3EFA9A90532914545CEf527C530658af87e196",
p2pix.abi,
goerliProvider
);
const p2pContractMumbai = new ethers.Contract(
"0x5f3EFA9A90532914545CEf527C530658af87e196",
p2pix.abi,
mumbaiProvider
);
const depositListGoerli = await getValidDeposits(p2pContractGoerli);
const depositListMumbai = await getValidDeposits(p2pContractMumbai);
etherStore.setDepositsValidListGoerli(depositListGoerli);
console.log(depositListGoerli);
etherStore.setDepositsValidListMumbai(depositListMumbai);
console.log(depositListMumbai);
};
const getValidDeposits = async (
contract?: Contract
): Promise<ValidDeposit[]> => {
let p2pContract: Contract;
if (contract) {
p2pContract = contract;
} else {
p2pContract = getContract(true);
}
const filterDeposits = p2pContract.filters.DepositAdded(null);
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
p2pContract = getContract(); // get metamask provider contract
const depositList = await Promise.all(
eventsDeposits.map(async (deposit) => {
const mappedDeposit = await p2pContract.mapDeposits(
deposit.args?.depositID
);
let validDeposit: ValidDeposit | null = null;
if (mappedDeposit.valid) {
validDeposit = {
blockNumber: deposit.blockNumber,
depositID: deposit.args?.depositID,
remaining: Number(formatEther(mappedDeposit.remaining)),
seller: mappedDeposit.seller,
pixKey: mappedDeposit.pixTarget,
};
}
return validDeposit;
})
);
return depositList.filter((deposit) => deposit) as ValidDeposit[];
};
export { getValidDeposits, getNetworksLiquidity };

View File

@ -0,0 +1,95 @@
import { useEtherStore } from "@/store/ether";
import p2pix from "../utils/smart_contract_files/P2PIX.json";
import { updateWalletStatus } from "./wallet";
import {
getProviderUrl,
isPossibleNetwork,
possibleChains,
network2Chain,
getP2PixAddress,
} from "./addresses";
import { ethers } from "ethers";
const getProvider = (
onlyAlchemyProvider: boolean = false
): ethers.providers.Web3Provider | ethers.providers.JsonRpcProvider => {
const window_ = window as any;
const connection = window_.ethereum;
if (!connection || onlyAlchemyProvider)
return new ethers.providers.JsonRpcProvider(getProviderUrl()); // alchemy provider
return new ethers.providers.Web3Provider(connection); // metamask provider
};
const getContract = (onlyAlchemyProvider: boolean = false) => {
const provider = getProvider(onlyAlchemyProvider);
const signer = provider.getSigner();
return new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
};
const connectProvider = async (): Promise<void> => {
const window_ = window as any;
const connection = window_.ethereum;
const provider = getProvider();
if (!(provider instanceof ethers.providers.Web3Provider)) {
window.alert("Please, connect to metamask extension");
return;
}
await updateWalletStatus();
listenToNetworkChange(connection);
listenToWalletChange(connection);
};
const listenToWalletChange = (connection: any): void => {
connection.on("accountsChanged", async () => {
console.log("Changed account!");
updateWalletStatus();
});
};
const listenToNetworkChange = (connection: any) => {
const etherStore = useEtherStore();
connection.on("chainChanged", (networkChain: string) => {
console.log("Changed network!");
if (isPossibleNetwork(networkChain)) {
etherStore.setNetworkName(possibleChains[networkChain]);
updateWalletStatus();
} else {
window.alert("Invalid chain!");
}
});
};
const requestNetworkChange = async (network: string): Promise<boolean> => {
const etherStore = useEtherStore();
if (!etherStore.walletAddress) return true;
try {
const window_ = window as any;
await window_.ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: network2Chain[network] }], // chainId must be in hexadecimal numbers
});
} catch {
return false;
}
return true;
};
export {
getProvider,
getContract,
connectProvider,
listenToNetworkChange,
requestNetworkChange,
};

View File

@ -0,0 +1,44 @@
import { getContract, getProvider } from "./provider";
import { getTokenAddress, getP2PixAddress } from "./addresses";
import { parseEther } from "ethers/lib/utils";
import { ethers } from "ethers";
import mockToken from "../utils/smart_contract_files/MockToken.json";
const approveTokens = async (tokenQty: string): Promise<any> => {
const provider = getProvider();
const signer = provider.getSigner();
const tokenContract = new ethers.Contract(
getTokenAddress(),
mockToken.abi,
signer
);
const apprv = await tokenContract.approve(
getP2PixAddress(),
parseEther(tokenQty)
);
await apprv.wait();
console.log(apprv);
return apprv;
};
const addDeposit = async (tokenQty: string, pixKey: string): Promise<any> => {
const p2pContract = getContract();
const deposit = await p2pContract.deposit(
getTokenAddress(),
parseEther(tokenQty),
pixKey,
ethers.utils.formatBytes32String("")
);
await deposit.wait();
return deposit;
};
export { approveTokens, addDeposit };

95
src/blockchain/wallet.ts Normal file
View File

@ -0,0 +1,95 @@
import { useEtherStore } from "@/store/ether";
import { getContract, getProvider } from "./provider";
import { getTokenAddress, possibleChains } from "./addresses";
import mockToken from "../utils/smart_contract_files/MockToken.json";
import { ethers, type Event } from "ethers";
import { formatEther } from "ethers/lib/utils";
import { getValidDeposits } from "./events";
import type { ValidDeposit } from "@/model/ValidDeposit";
const updateWalletStatus = async (): Promise<void> => {
const etherStore = useEtherStore();
const provider = getProvider();
const signer = provider.getSigner();
const { chainId } = await provider.getNetwork();
etherStore.setNetworkName(possibleChains[chainId]);
const mockTokenContract = new ethers.Contract(
getTokenAddress(),
mockToken.abi,
signer
);
const walletAddress = await provider.send("eth_requestAccounts", []);
const balance = await mockTokenContract.balanceOf(walletAddress[0]);
etherStore.setBalance(formatEther(balance));
etherStore.setWalletAddress(ethers.utils.getAddress(walletAddress[0]));
};
const listValidDepositTransactionsByWalletAddress = async (
walletAddress: string
): Promise<ValidDeposit[]> => {
const walletDeposits = await getValidDeposits();
if (walletDeposits) {
return walletDeposits
.filter((deposit) => deposit.seller == walletAddress)
.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
}
return [];
};
const listAllTransactionByWalletAddress = async (
walletAddress: string
): Promise<Event[]> => {
const p2pContract = getContract();
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;
}
);
};
// get wallet's release transactions
const listReleaseTransactionByWalletAddress = async (
walletAddress: string
): Promise<Event[]> => {
const p2pContract = getContract();
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
const eventsReleasedLocks = await p2pContract.queryFilter(
filterReleasedLocks
);
return eventsReleasedLocks.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
};
export {
updateWalletStatus,
listValidDepositTransactionsByWalletAddress,
listAllTransactionByWalletAddress,
listReleaseTransactionByWalletAddress,
};

View File

@ -1,11 +1,12 @@
<script setup lang="ts">
import CustomButton from "@/components/CustomButton.vue";
import ListingComponent from "@/components/ListingComponent.vue";
import type { Event } from "ethers";
// props
const props = defineProps<{
lastWalletReleaseTransactions: any[];
tokenAmount: Number | undefined;
lastWalletReleaseTransactions: Event[];
tokenAmount: number | undefined;
}>();
// Emits

View File

@ -1,42 +1,62 @@
<script setup lang="ts">
import blockchain from "@/utils/blockchain";
import { NetworkEnum } from "@/model/NetworkEnum";
import type { ValidDeposit } from "@/model/ValidDeposit";
import { useEtherStore } from "@/store/ether";
import { formatEther } from "@ethersproject/units";
import type { Event } from "ethers";
import { ref, watch } from "vue";
// props
const props = defineProps<{
walletTransactions: any[];
walletTransactions: (Event | ValidDeposit)[];
isManageMode: boolean;
}>();
const itemsToShow = ref<any[]>([]);
const etherStore = useEtherStore();
const itemsToShow = ref<(Event | ValidDeposit)[]>([]);
// Methods
const showInitialItems = () => {
const isValidDeposit = (
deposit: Event | ValidDeposit
): deposit is ValidDeposit => {
return (deposit as ValidDeposit).depositID !== undefined;
};
const showInitialItems = (): void => {
itemsToShow.value = props.walletTransactions.slice(0, 3);
};
const formatEventsAmount = (amount: any) => {
try {
const formated = blockchain.formatBigNumber(amount);
return formated;
} catch {
return "";
}
};
const openEtherscanUrl = (url: string) => {
const openEtherscanUrl = (transactionHash: string): void => {
const networkUrl =
etherStore.networkName == NetworkEnum.ethereum
? "goerli.etherscan.io"
: "mumbai.polygonscan.com";
const url = `https://${networkUrl}/tx/${transactionHash}`;
window.open(url, "_blank");
};
const loadMore = () => {
const loadMore = (): void => {
const itemsShowing = itemsToShow.value.length;
itemsToShow.value?.push(
...props.walletTransactions.slice(itemsShowing, itemsShowing + 3)
);
};
const getEventName = (event: string | undefined): string => {
if (!event) return "Desconhecido";
const possibleEventName: { [key: string]: string } = {
DepositAdded: "Oferta",
LockAdded: "Compra",
LockReleased: "Reserva",
};
return possibleEventName[event];
};
// watch props changes
watch(props, async () => {
watch(props, async (): Promise<void> => {
const itemsToShowQty = itemsToShow.value.length;
if (itemsToShowQty == 0) showInitialItems();
else
@ -75,11 +95,11 @@ showInitialItems();
<div
class="grid grid-cols-4 grid-flow-row w-full bg-white px-6 py-4 rounded-lg"
v-for="(item, index) in itemsToShow"
:key="item.depositID"
:key="item.blockNumber"
>
<span class="last-release-info">
{{
item?.args ? formatEventsAmount(item?.args.amount) : item?.remaining
isValidDeposit(item) ? item.remaining : formatEther(item.args?.amount)
}}
BRZ
</span>
@ -87,55 +107,38 @@ showInitialItems();
<!-- TODO: change this hardcoded date -->
<span class="last-release-info"> 20 out 2022 </span>
<span class="last-release-info" v-if="!props.isManageMode">
{{ getEventName((item as Event).event) }}
</span>
<div
v-if="!props.isManageMode"
class="flex gap-2 cursor-pointer items-center justify-self-center"
@click="openEtherscanUrl((item as Event)?.transactionHash)"
>
<span class="last-release-info">Etherscan</span>
<img alt="Redirect image" src="@/assets/redirect.svg" />
</div>
<div
v-if="props.isManageMode"
class="flex gap-2 cursor-pointer items-center justify-self-center"
@click="emit('cancelDeposit', item.depositID, index)"
@click="emit('cancelDeposit', (item as ValidDeposit).depositID, index)"
>
<span class="last-release-info">Cancelar</span>
<img alt="Cancel image" src="@/assets/cancel.svg" />
</div>
<span
class="last-release-info"
v-if="item.event == 'DepositAdded' && !props.isManageMode"
>
{{ "Oferta" }}
</span>
<span
class="last-release-info"
v-if="item.event == 'LockAdded' && !props.isManageMode"
>
{{ "Reserva" }}
</span>
<span
class="last-release-info"
v-if="item.event == 'LockReleased' && !props.isManageMode"
>
{{ "Compra" }}
</span>
<div
v-if="props.isManageMode"
class="flex gap-2 cursor-pointer items-center justify-self-center"
@click="emit('withdrawDeposit', item.depositID, index)"
@click="
emit('withdrawDeposit', (item as ValidDeposit).depositID, index)
"
>
<span class="last-release-info">Retirar</span>
<img alt="Cancel image" src="@/assets/withdraw.svg" />
</div>
<div
v-if="!props.isManageMode"
class="flex gap-2 cursor-pointer items-center justify-self-center"
@click="
openEtherscanUrl(`https://etherscan.io/tx/${item?.transactionHash}`)
"
>
<span class="last-release-info">Etherscan</span>
<img alt="Redirect image" src="@/assets/redirect.svg" />
</div>
</div>
<div
class="flex flex-col justify-center items-center w-full mt-2 gap-2"

View File

@ -24,19 +24,20 @@ 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 handleInputEvent = async (event: any): Promise<void> => {
const { value } = event.target;
e2eId.value = value;
validatePix();
await validatePix();
};
const validatePix = async () => {
const validatePix = async (): Promise<void> => {
if (e2eId.value == "") {
isPixValid.value = false;
isCodeInputEmpty.value = true;
@ -146,7 +147,7 @@ const validatePix = async () => {
<CustomButton
:is-disabled="isPixValid == false"
:text="'Enviar para a rede'"
@button-clicked="emit('pixValidated', { e2eId })"
@button-clicked="emit('pixValidated', e2eId)"
/>
</div>
</div>

View File

@ -1,41 +1,61 @@
<script setup lang="ts">
import { ref } from "vue";
import { ref, watch } from "vue";
import CustomButton from "../components/CustomButton.vue";
import { debounce } from "@/utils/debounce";
import { useEtherStore } from "@/store/ether";
import { storeToRefs } from "pinia";
import blockchain from "../utils/blockchain";
import { connectProvider } from "@/blockchain/provider";
import { verifyNetworkLiquidity } from "@/utils/networkLiquidity";
import { NetworkEnum } from "@/model/NetworkEnum";
import type { ValidDeposit } from "@/model/ValidDeposit";
import { decimalCount } from "@/utils/decimalCount";
// Store reference
const etherStore = useEtherStore();
const { walletAddress, depositsValidList } = storeToRefs(etherStore);
const {
walletAddress,
networkName,
depositsValidListGoerli,
depositsValidListMumbai,
} = storeToRefs(etherStore);
// Reactive state
const tokenValue = ref(0);
const enableSelectButton = ref(false);
const hasLiquidity = ref(true);
const validDecimals = ref(true);
const selectedDeposit = ref();
const tokenValue = ref<number>(0);
const enableConfirmButton = ref<boolean>(false);
const enableWalletButton = ref<boolean>(false);
const hasLiquidity = ref<boolean>(true);
const validDecimals = ref<boolean>(true);
const selectedGoerliDeposit = ref<ValidDeposit>();
const selectedMumbaiDeposit = ref<ValidDeposit>();
// Emits
const emit = defineEmits(["tokenBuy"]);
// Blockchain methods
const connectAccount = async () => {
await blockchain.connectProvider();
verifyLiquidity();
const connectAccount = async (): Promise<void> => {
await connectProvider();
enableOrDisableConfirmButton();
};
const emitConfirmButton = (): void => {
const selectedDeposit =
networkName.value == NetworkEnum.ethereum
? selectedGoerliDeposit.value
: selectedMumbaiDeposit.value;
emit("tokenBuy", selectedDeposit, tokenValue.value);
};
// Debounce methods
const handleInputEvent = (event: any) => {
const handleInputEvent = (event: any): void => {
const { value } = event.target;
tokenValue.value = Number(value);
if (decimalCount(tokenValue.value) > 2) {
if (decimalCount(String(tokenValue.value)) > 2) {
validDecimals.value = false;
enableSelectButton.value = false;
enableConfirmButton.value = false;
return;
}
validDecimals.value = true;
@ -43,41 +63,52 @@ const handleInputEvent = (event: any) => {
verifyLiquidity();
};
// Enable button methods
// Check if has more than 2 decimal places
const decimalCount = (num: Number) => {
const numStr = String(num);
if (numStr.includes(".")) {
return numStr.split(".")[1].length;
}
return 0;
};
// Verify if there is a valid deposit to buy
const verifyLiquidity = () => {
enableSelectButton.value = false;
selectedDeposit.value = null;
if (!walletAddress.value || tokenValue.value <= 0) return;
const verifyLiquidity = (): void => {
enableConfirmButton.value = false;
selectedGoerliDeposit.value = undefined;
selectedMumbaiDeposit.value = undefined;
depositsValidList.value.find((element) => {
const remaining = element.remaining;
if (
tokenValue.value!! <= remaining &&
tokenValue.value!! != 0 &&
element.seller !== walletAddress.value
) {
enableSelectButton.value = true;
hasLiquidity.value = true;
selectedDeposit.value = element;
return true;
}
return false;
});
if (tokenValue.value <= 0) {
enableWalletButton.value = false;
return;
}
if (!enableSelectButton.value) {
selectedGoerliDeposit.value = verifyNetworkLiquidity(
tokenValue.value,
walletAddress.value,
depositsValidListGoerli.value
);
selectedMumbaiDeposit.value = verifyNetworkLiquidity(
tokenValue.value,
walletAddress.value,
depositsValidListMumbai.value
);
enableOrDisableConfirmButton();
if (selectedGoerliDeposit.value || selectedMumbaiDeposit.value) {
hasLiquidity.value = true;
enableWalletButton.value = true;
} else {
hasLiquidity.value = false;
enableWalletButton.value = true;
}
};
const enableOrDisableConfirmButton = (): void => {
if (selectedGoerliDeposit.value && networkName.value == NetworkEnum.ethereum)
enableConfirmButton.value = true;
else if (
selectedMumbaiDeposit.value &&
networkName.value == NetworkEnum.polygon
)
enableConfirmButton.value = true;
else enableConfirmButton.value = false;
};
watch(networkName, (): void => {
enableOrDisableConfirmButton();
});
</script>
<template>
@ -126,12 +157,14 @@ const verifyLiquidity = () => {
src="@/assets/polygon.svg"
width="24"
height="24"
v-if="selectedMumbaiDeposit"
/>
<img
alt="Ethereum image"
src="@/assets/ethereum.svg"
width="24"
height="24"
v-if="selectedGoerliDeposit"
/>
</div>
</div>
@ -149,13 +182,14 @@ const verifyLiquidity = () => {
<CustomButton
v-if="!walletAddress"
:text="'Conectar carteira'"
:is-disabled="!enableWalletButton"
@buttonClicked="connectAccount()"
/>
<CustomButton
v-if="walletAddress"
:text="'Confirmar compra'"
:is-disabled="!enableSelectButton"
@buttonClicked="emit('tokenBuy', { selectedDeposit, tokenValue })"
:is-disabled="!enableConfirmButton"
@buttonClicked="emitConfirmButton()"
/>
</div>
</div>

View File

@ -2,39 +2,30 @@
import { ref } from "vue";
import CustomButton from "../../components/CustomButton.vue";
import { debounce } from "@/utils/debounce";
import { decimalCount } from "@/utils/decimalCount";
// Reactive state
const tokenValue = ref(0);
const enableSelectButton = ref(false);
const hasLiquidity = ref(true);
const validDecimals = ref(true);
const tokenValue = ref<number>(0);
const enableSelectButton = ref<boolean>(false);
const hasLiquidity = ref<boolean>(true);
const validDecimals = ref<boolean>(true);
// Emits
const emit = defineEmits(["tokenBuy"]);
// Debounce methods
const handleInputEvent = (event: any) => {
const handleInputEvent = (event: any): void => {
const { value } = event.target;
tokenValue.value = Number(value);
if (decimalCount(tokenValue.value) > 2) {
if (decimalCount(String(tokenValue.value)) > 2) {
validDecimals.value = false;
enableSelectButton.value = false;
return;
}
validDecimals.value = true;
};
// Enable button methods
// Check if has more than 2 decimal places
const decimalCount = (num: Number) => {
const numStr = String(num);
if (numStr.includes(".")) {
return numStr.split(".")[1].length;
}
return 0;
};
</script>
<template>

View File

@ -2,23 +2,30 @@
import { ref } from "vue";
import CustomButton from "../CustomButton.vue";
import { debounce } from "@/utils/debounce";
import { decimalCount } from "@/utils/decimalCount";
import { useEtherStore } from "@/store/ether";
import { storeToRefs } from "pinia";
import { connectProvider } from "@/blockchain/provider";
// Reactive state
const offer = ref<string | number>("");
const etherStore = useEtherStore();
const { walletAddress } = storeToRefs(etherStore);
const offer = ref<string>("");
const pixKey = ref<string>("");
const enableSelectButton = ref(false);
const hasLiquidity = ref(true);
const validDecimals = ref(true);
const enableSelectButton = ref<boolean>(false);
const hasLiquidity = ref<boolean>(true);
const validDecimals = ref<boolean>(true);
// Emits
const emit = defineEmits(["approveTokens"]);
// Debounce methods
const handleInputEvent = (event: any) => {
const handleInputEvent = (event: any): void => {
const { value } = event.target;
offer.value = Number(value);
offer.value = value;
if (decimalCount(offer.value) > 2) {
validDecimals.value = false;
@ -28,14 +35,12 @@ const handleInputEvent = (event: any) => {
validDecimals.value = true;
};
// Enable button methods
// Check if has more than 2 decimal places
const decimalCount = (num: Number) => {
const numStr = String(num);
if (numStr.includes(".")) {
return numStr.split(".")[1].length;
}
return 0;
const handleButtonClick = async (
offer: string,
pixKey: string
): Promise<void> => {
if (walletAddress.value) emit("approveTokens", { offer, pixKey });
else await connectProvider();
};
</script>
@ -99,8 +104,8 @@ const decimalCount = (num: Number) => {
</div>
</div>
<CustomButton
:text="'Aprovar tokens'"
@buttonClicked="emit('approveTokens', { offer, pixKey })"
:text="walletAddress ? 'Aprovar tokens' : 'Conectar Carteira'"
@buttonClicked="handleButtonClick(offer, pixKey)"
/>
</div>
</div>

View File

@ -2,12 +2,15 @@
import { storeToRefs } from "pinia";
import { useEtherStore } from "../store/ether";
import { ref } from "vue";
import blockchain from "../utils/blockchain";
import { NetworkEnum } from "@/model/NetworkEnum";
import { connectProvider, requestNetworkChange } from "../blockchain/provider";
import ethereumImage from "../assets/ethereum.svg";
import polygonImage from "../assets/polygon.svg";
// Store reference
const etherStore = useEtherStore();
const { walletAddress, balance, sellerView } = storeToRefs(etherStore);
const { walletAddress, sellerView } = storeToRefs(etherStore);
const menuOpenToggle = ref<boolean>(false);
const menuHoverToggle = ref<boolean>(false);
@ -16,8 +19,8 @@ const currencyMenuOpenToggle = ref<boolean>(false);
const currencyMenuHoverToggle = ref<boolean>(false);
//Methods
const connectMetaMask = () => {
blockchain.connectProvider();
const connectMetaMask = async (): Promise<void> => {
await connectProvider();
};
const formatWalletAddress = (): string => {
@ -30,20 +33,31 @@ const formatWalletAddress = (): string => {
return `${initialText}...${finalText}`;
};
const formatWalletBalance = (): String => {
const fixed = Number(balance.value);
return fixed.toFixed(2);
};
const disconnectUser = () => {
const disconnectUser = (): void => {
etherStore.setWalletAddress("");
closeMenu();
window.location.reload();
};
const closeMenu = () => {
const closeMenu = (): void => {
menuOpenToggle.value = false;
};
const networkChange = async (network: NetworkEnum): Promise<void> => {
currencyMenuOpenToggle.value = false;
const change = await requestNetworkChange(network);
if (change) etherStore.setNetworkName(network);
};
const getNetworkImage = (networkName: NetworkEnum): string => {
let validImages = {
Ethereum: ethereumImage,
Polygon: polygonImage,
Localhost: ethereumImage,
};
return validImages[networkName];
};
</script>
<template>
@ -63,6 +77,102 @@ const closeMenu = () => {
<RouterLink :to="sellerView ? '/' : '/seller'" class="default-button">
{{ sellerView ? "Quero comprar" : "Quero vender" }}
</RouterLink>
<div class="flex flex-col" v-if="walletAddress">
<div
class="group top-bar-info cursor-pointer hover:bg-white"
@click="
[
(currencyMenuOpenToggle = !currencyMenuOpenToggle),
(menuOpenToggle = false),
]
"
@mouseover="currencyMenuHoverToggle = true"
@mouseout="currencyMenuHoverToggle = false"
:style="{
backgroundColor: currencyMenuOpenToggle
? '#F9F9F9'
: currencyMenuHoverToggle
? '#F9F9F9'
: 'transparent',
}"
>
<img
alt="Choosed network image"
:src="getNetworkImage(etherStore.networkName)"
/>
<span
class="default-text group-hover:text-gray-900"
:style="{
color: currencyMenuOpenToggle
? '#000000'
: currencyMenuHoverToggle
? '#000000'
: 'rgb(249 250 251)',
}"
>
{{ etherStore.networkName }}
</span>
<img
class="text-gray-900"
v-if="!currencyMenuHoverToggle && !currencyMenuOpenToggle"
alt="Chevron Down"
src="@/assets/chevronDown.svg"
/>
<img
v-if="currencyMenuOpenToggle"
alt="Chevron Up"
src="@/assets/chevronUp.svg"
/>
<img
v-if="currencyMenuHoverToggle && !currencyMenuOpenToggle"
alt="Chevron Down Black"
src="@/assets/chevronDownBlack.svg"
/>
</div>
<div
v-show="currencyMenuOpenToggle"
class="mt-10 pl-3 absolute w-full text-gray-900"
>
<div class="mt-2">
<div class="bg-white rounded-md z-10">
<div
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
@click="networkChange(NetworkEnum.ethereum)"
>
<img
alt="Ethereum image"
width="20"
height="20"
src="@/assets/ethereum.svg"
/>
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
Ethereum
</span>
</div>
<div class="w-full flex justify-center">
<hr class="w-4/5" />
</div>
<div
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
@click="networkChange(NetworkEnum.polygon)"
>
<img
alt="Polygon image"
width="20"
height="20"
src="@/assets/polygon.svg"
/>
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
Polygon
</span>
</div>
<div class="w-full flex justify-center">
<hr class="w-4/5" />
</div>
</div>
</div>
</div>
</div>
<button
type="button"
v-if="!walletAddress"
@ -72,95 +182,6 @@ const closeMenu = () => {
Conectar carteira
</button>
<div v-if="walletAddress" class="account-info">
<div class="flex flex-col">
<div
class="group top-bar-info cursor-pointer hover:bg-white"
@click="
[
(currencyMenuOpenToggle = !currencyMenuOpenToggle),
(menuOpenToggle = false),
]
"
@mouseover="currencyMenuHoverToggle = true"
@mouseout="currencyMenuHoverToggle = false"
:style="{
backgroundColor: currencyMenuOpenToggle
? '#F9F9F9'
: currencyMenuHoverToggle
? '#F9F9F9'
: 'transparent',
}"
>
<img alt="Ethereum image" src="@/assets/ethereum.svg" />
<span
class="default-text group-hover:text-gray-900"
:style="{
color: currencyMenuOpenToggle
? '#000000'
: currencyMenuHoverToggle
? '#000000'
: 'rgb(249 250 251)',
}"
>
Ethereum
</span>
<img
class="text-gray-900"
v-if="!currencyMenuHoverToggle && !currencyMenuOpenToggle"
alt="Chevron Down"
src="@/assets/chevronDown.svg"
/>
<img
v-if="currencyMenuOpenToggle"
alt="Chevron Up"
src="@/assets/chevronUp.svg"
/>
<img
v-if="currencyMenuHoverToggle && !currencyMenuOpenToggle"
alt="Chevron Down Black"
src="@/assets/chevronDownBlack.svg"
/>
</div>
<div
v-show="currencyMenuOpenToggle"
class="mt-10 pl-3 absolute w-full text-gray-900"
>
<div class="mt-2">
<div class="bg-white rounded-md z-10">
<div class="menu-button gap-2 px-4 rounded-md cursor-pointer">
<img
alt="Ethereum image"
width="20"
height="20"
src="@/assets/ethereum.svg"
/>
<span
class="text-gray-900 py-4 text-end font-semibold text-sm"
>
Ethereum
</span>
</div>
<div class="w-full flex justify-center">
<hr class="w-4/5" />
</div>
<div class="menu-button gap-2 px-4 rounded-md cursor-pointer">
<img
alt="Polygon image"
width="20"
height="20"
src="@/assets/polygon.svg"
/>
<span
class="text-gray-900 py-4 text-end font-semibold text-sm"
>
Polygon
</span>
<hr />
</div>
</div>
</div>
</div>
</div>
<div class="flex flex-col">
<div
class="top-bar-info cursor-pointer"
@ -241,12 +262,6 @@ const closeMenu = () => {
</div>
</div>
</div>
<div class="top-bar-info">
<span class="default-text text-sm">
MBRZ: {{ formatWalletBalance() }}
</span>
</div>
<!-- Temporary div, just to show a wallet's balance -->
</div>
</div>
</header>

4
src/model/NetworkEnum.ts Normal file
View File

@ -0,0 +1,4 @@
export enum NetworkEnum {
ethereum = "Ethereum",
polygon = "Polygon",
}

10
src/model/ValidDeposit.ts Normal file
View File

@ -0,0 +1,10 @@
import type { BigNumber } from "ethers";
export type ValidDeposit = {
depositID: BigNumber;
blockNumber: number;
remaining: number;
seller: string;
pixKey: string;
pixTarget?: string;
};

View File

@ -1,6 +1,5 @@
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import MockView from "../views/MockView.vue";
import TransactionHistoryView from "../views/TransactionHistoryView.vue";
import FaqView from "../views/FaqView.vue";
import ManageBidsView from "../views/ManageBidsView.vue";
@ -19,11 +18,6 @@ const router = createRouter({
name: "seller",
component: SellerView,
},
{
path: "/mock",
name: "mock",
component: MockView,
},
{
path: "/transaction_history",
name: "transaction history",

View File

@ -1,23 +1,18 @@
import { NetworkEnum } from "@/model/NetworkEnum";
import type { ValidDeposit } from "@/model/ValidDeposit";
import { defineStore } from "pinia";
export const useEtherStore = defineStore("ether", {
state: () => ({
walletAddress: "",
balance: "",
networkName: NetworkEnum.ethereum,
loadingLock: false,
sellerView: 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[],
// Depósitos válidos para compra GOERLI
depositsValidListGoerli: [] as ValidDeposit[],
// Depósitos válidos para compra MUMBAI
depositsValidListMumbai: [] as ValidDeposit[],
}),
actions: {
setWalletAddress(walletAddress: string) {
@ -26,35 +21,27 @@ export const useEtherStore = defineStore("ether", {
setBalance(balance: string) {
this.balance = balance;
},
setNetworkName(networkName: NetworkEnum) {
this.networkName = networkName;
},
setLoadingLock(isLoadingLock: boolean) {
this.loadingLock = isLoadingLock;
},
setSellerView(sellerView: boolean) {
this.sellerView = sellerView;
},
setDepositsValidList(depositsValidList: any[]) {
this.depositsValidList = depositsValidList;
setDepositsValidListGoerli(depositsValidList: ValidDeposit[]) {
this.depositsValidListGoerli = 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;
setDepositsValidListMumbai(depositsValidList: ValidDeposit[]) {
this.depositsValidListMumbai = depositsValidList;
},
},
// Alterar para integrar com mumbai
getters: {
getValidDepositByWalletAddress: (state) => {
return (walletAddress: string) =>
state.depositsValidList
state.depositsValidListGoerli
.filter((deposit) => deposit.seller == walletAddress)
.sort((a, b) => {
return b.blockNumber - a.blockNumber;

View File

@ -1,531 +0,0 @@
import { useEtherStore } from "@/store/ether";
import { BigNumber, ethers } from "ethers";
// Smart contract imports
import mockToken from "./smart_contract_files/MockToken.json";
import p2pix from "./smart_contract_files/P2PIX.json";
import addresses from "./smart_contract_files/localhost.json";
// Mock wallets import
import { wallets } from "./smart_contract_files/wallets.json";
// Wallet methods
// Update wallet state (balance and address)
const updateWalletStatus = async () => {
const etherStore = useEtherStore();
const provider = getProvider();
if (!provider) return;
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.setBalance(formatBigNumber(balance));
etherStore.setWalletAddress(ethers.utils.getAddress(walletAddress[0]));
};
const updateWalletBalance = async () => {
const etherStore = useEtherStore();
const provider = getProvider();
if (!provider) return;
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.setBalance(formatBigNumber(balance));
};
// 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 all wallet transactions
const listAllTransactionByWalletAddress = 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;
}
);
};
// get wallet's deposit transactions
const listDepositTransactionByWalletAddress = async (
walletAddress: string
): Promise<any[]> => {
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);
return eventsDeposits.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
};
// get wallet's deposit transactions
const listValidDepositTransactionsByWalletAddress = async (
walletAddress: string
): Promise<any[]> => {
const walletDeposits = await getValidDeposits();
if (walletDeposits) {
return walletDeposits
.filter((deposit) => deposit.seller == walletAddress)
.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
}
return [];
};
// get wallet's lock transactions
const listLockTransactionByWalletAddress = 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 filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]);
const eventsAddedLocks = await p2pContract.queryFilter(filterAddedLocks);
return eventsAddedLocks.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
};
// get wallet's release transactions
const listReleaseTransactionByWalletAddress = 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 filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
const eventsReleasedLocks = await p2pContract.queryFilter(
filterReleasedLocks
);
return eventsReleasedLocks.sort((a, b) => {
return b.blockNumber - a.blockNumber;
});
};
//get valid deposits
const getValidDeposits = async (): Promise<any[] | undefined> => {
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 filterDeposits = p2pContract.filters.DepositAdded(null);
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
const depositList: any[] = await Promise.all(
eventsDeposits
.map(async (deposit) => {
const mappedDeposit = await mapDeposits(deposit.args?.depositID);
let validDeposit = {};
if (mappedDeposit.valid) {
validDeposit = {
blockNumber: deposit.blockNumber,
depositID: deposit.args?.depositID,
remaining: formatBigNumber(mappedDeposit.remaining),
seller: mappedDeposit.seller,
pixKey: mappedDeposit.pixTarget,
};
}
return validDeposit;
})
.filter((deposit) => deposit)
);
return depositList;
};
// Update events at store methods
const updateValidDeposits = async () => {
const etherStore = useEtherStore();
const deposits = await getValidDeposits();
if (deposits) etherStore.setDepositsValidList(deposits);
};
const updateDepositAddedEvents = 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 filterDeposits = p2pContract.filters.DepositAdded(null);
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
etherStore.setDepositsAddedList(eventsDeposits);
console.log("DEPOSITS", eventsDeposits);
};
const updateLockAddedEvents = 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.LockAdded(null);
const eventsLocks = await p2pContract.queryFilter(filterLocks);
etherStore.setLocksAddedList(eventsLocks);
console.log("LOCKS", 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 filterReleases = p2pContract.filters.LockReleased(null);
const eventsReleases = await p2pContract.queryFilter(filterReleases);
etherStore.setLocksReleasedList(eventsReleases);
console.log("RELEASES", eventsReleases);
};
// Provider methods
const connectProvider = async () => {
const window_ = window as any;
const connection = window_.ethereum;
await updateWalletStatus();
await updateValidDeposits();
await updateDepositAddedEvents();
await updateLockAddedEvents();
await updateLockReleasedEvents();
connection.on("accountsChanged", async () => {
await updateWalletStatus();
});
};
const getProvider = (): ethers.providers.Web3Provider | null => {
const window_ = window as any;
const connection = window_.ethereum;
if (!connection) return null;
return new ethers.providers.Web3Provider(connection);
};
// Deposit methods
const approveTokens = async (tokenQty: Number) => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const tokenContract = new ethers.Contract(
addresses.token,
mockToken.abi,
signer
);
const apprv = await tokenContract.approve(
addresses.p2pix,
formatEther(String(tokenQty))
);
await apprv.wait();
return apprv;
};
// Gets value and pix key from user's form to create a deposit in the blockchain
const addDeposit = async (tokenQty: Number, pixKey: String) => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const deposit = await p2pContract.deposit(
addresses.token,
formatEther(String(tokenQty)),
pixKey,
ethers.utils.formatBytes32String("")
);
await deposit.wait();
await updateWalletStatus();
await updateDepositAddedEvents();
await updateValidDeposits();
};
const mockDeposit = async (tokenQty: Number, pixKey: String) => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const tokenContract = new ethers.Contract(
addresses.token,
mockToken.abi,
signer
);
const apprv = await tokenContract.approve(
addresses.p2pix,
formatEther(String(tokenQty))
);
await apprv.wait();
const p2pContract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const deposit = await p2pContract.deposit(
addresses.token,
formatEther(String(tokenQty)),
pixKey,
ethers.utils.formatBytes32String("")
);
await deposit.wait();
await updateWalletStatus();
await updateValidDeposits();
await updateDepositAddedEvents();
};
// cancel a deposit by its Id
const cancelDeposit = async (depositId: BigNumber): Promise<Boolean> => {
const provider = getProvider();
if (!provider) return false;
const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
await contract.cancelDeposit(depositId);
await updateWalletBalance();
await updateValidDeposits();
return true;
};
// withdraw a deposit by its Id
const withdrawDeposit = async (depositId: BigNumber): Promise<Boolean> => {
const provider = getProvider();
if (!provider) return false;
const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
await contract.withdraw(depositId, []);
await updateWalletBalance();
await updateValidDeposits();
return true;
};
// Get specific deposit data by its ID
const mapDeposits = async (depositId: BigNumber): Promise<any> => {
const provider = getProvider();
if (!provider) return;
const signer = provider.getSigner();
const contract = new ethers.Contract(addresses.p2pix, p2pix.abi, signer);
const deposit = await contract.mapDeposits(depositId);
return deposit;
};
// Lock methods
// Gets value from user's form to create a lock in the blockchain
const addLock = async (depositId: BigNumber, 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, // BigNumber
etherStore.walletAddress, // String "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (Example)
ethers.constants.AddressZero, // String "0x0000000000000000000000000000000000000000"
0,
formatEther(String(amount)), // BigNumber
[],
[]
);
lock.wait();
while (etherStore.locksAddedList.length === oldEventsLen) {
await updateLockAddedEvents();
await updateValidDeposits();
}
return lock;
};
// Get specific lock data by its ID
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);
return lock;
};
// Releases lock by specific ID and other additional data
const releaseLock = async (
pixKey: string,
amount: Number,
e2eId: string,
lockId: string
) => {
const provider = getProvider();
if (!provider) return;
const mockBacenSigner = new ethers.Wallet(
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
);
const messageToSign = ethers.utils.solidityKeccak256(
["string", "uint256", "bytes32"],
[
pixKey,
formatEther(String(amount)),
ethers.utils.formatBytes32String(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,
ethers.constants.AddressZero,
ethers.utils.formatBytes32String(e2eId),
sig.r,
sig.s,
sig.v
);
release.wait();
await updateLockReleasedEvents();
await updateValidDeposits();
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 {
connectProvider,
formatEther,
updateWalletStatus,
splitTokens,
listValidDepositTransactionsByWalletAddress,
listAllTransactionByWalletAddress,
listReleaseTransactionByWalletAddress,
listDepositTransactionByWalletAddress,
listLockTransactionByWalletAddress,
approveTokens,
addDeposit,
cancelDeposit,
withdrawDeposit,
mockDeposit,
mapDeposits,
formatBigNumber,
addLock,
mapLocks,
releaseLock,
updateLockAddedEvents,
updateValidDeposits,
getValidDeposits,
};

View File

@ -0,0 +1,6 @@
export const decimalCount = (numStr: string): number => {
if (numStr.includes(".")) {
return numStr.split(".")[1].length;
}
return 0;
};

View File

@ -0,0 +1,23 @@
import type { ValidDeposit } from "@/model/ValidDeposit";
const verifyNetworkLiquidity = (
tokenValue: number,
walletAddress: string,
validDepositList: ValidDeposit[]
): ValidDeposit | undefined => {
const element = validDepositList.find((element) => {
const remaining = element.remaining;
if (
tokenValue!! <= remaining &&
tokenValue!! != 0 &&
element.seller !== walletAddress
) {
return true;
}
return false;
});
return element;
};
export { verifyNetworkLiquidity };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
{
"signers": [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
],
"p2pix": "0x5f3EFA9A90532914545CEf527C530658af87e196",
"token": "0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
}

View File

@ -0,0 +1,8 @@
{
"signers": [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
],
"p2pix": "0x5f3EFA9A90532914545CEf527C530658af87e196",
"token": "0x294003F602c321627152c6b7DED3EAb5bEa853Ee"
}

View File

@ -2,12 +2,17 @@
import SearchComponent from "../components/SearchComponent.vue";
import ValidationComponent from "../components/LoadingComponent.vue";
import BuyConfirmedComponent from "@/components/BuyConfirmedComponent.vue";
import blockchain from "../utils/blockchain";
import { ref } from "vue";
import { ref, onMounted } from "vue";
import { useEtherStore } from "@/store/ether";
import QrCodeComponent from "../components/QrCodeComponent.vue";
import { storeToRefs } from "pinia";
import { addLock, releaseLock } from "@/blockchain/buyerMethods";
import { updateWalletStatus } from "@/blockchain/wallet";
import { getNetworksLiquidity } from "@/blockchain/events";
import { listReleaseTransactionByWalletAddress } from "@/blockchain/wallet";
import type { Event } from "ethers";
import type { ValidDeposit } from "@/model/ValidDeposit";
enum Step {
Search,
@ -19,33 +24,33 @@ const etherStore = useEtherStore();
etherStore.setSellerView(false);
// States
const { loadingLock, walletAddress, locksAddedList } = storeToRefs(etherStore);
const { loadingLock, walletAddress } = 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 lastWalletReleaseTransactions = ref<any[]>([]);
const _lockID = ref<string>("");
const loadingRelease = ref<boolean>(false);
const lastWalletReleaseTransactions = ref<Event[]>([]);
const confirmBuyClick = async ({ selectedDeposit, tokenValue }: any) => {
const confirmBuyClick = async (
selectedDeposit: ValidDeposit,
tokenValue: number
) => {
// finish buy screen
const depositDetail = selectedDeposit;
const depositId = selectedDeposit.depositID;
pixTarget.value = selectedDeposit.pixKey;
tokenAmount.value = tokenValue;
// Makes lock with deposit ID and the Amount
if (depositDetail) {
if (selectedDeposit) {
flowStep.value = Step.Buy;
etherStore.setLoadingLock(true);
await blockchain
.addLock(depositId, tokenValue)
.then((lock) => {
lockTransactionHash.value = lock.hash;
await addLock(selectedDeposit.depositID, tokenValue)
.then((lockID) => {
_lockID.value = lockID;
})
.catch(() => {
.catch((err) => {
console.log(err);
flowStep.value = Step.Search;
});
@ -53,38 +58,34 @@ const confirmBuyClick = async ({ selectedDeposit, tokenValue }: any) => {
}
};
const releaseTransaction = async ({ e2eId }: any) => {
const releaseTransaction = async (e2eId: string) => {
flowStep.value = Step.List;
loadingRelease.value = true;
const findLock = locksAddedList.value.find((element) => {
if (element.transactionHash === lockTransactionHash.value) {
lockId.value = element.args.lockID;
return true;
}
return false;
});
if (findLock && tokenAmount.value) {
const release = await blockchain.releaseLock(
if (_lockID.value && tokenAmount.value) {
const release = await releaseLock(
pixTarget.value,
tokenAmount.value,
e2eId,
lockId.value
_lockID.value
);
release.wait();
await blockchain
.listReleaseTransactionByWalletAddress(walletAddress.value.toLowerCase())
.then((releaseTransactions) => {
if (releaseTransactions)
lastWalletReleaseTransactions.value = releaseTransactions;
});
await listReleaseTransactionByWalletAddress(
walletAddress.value.toLowerCase()
).then((releaseTransactions) => {
if (releaseTransactions)
lastWalletReleaseTransactions.value = releaseTransactions;
});
await blockchain.updateWalletStatus();
await updateWalletStatus();
loadingRelease.value = false;
}
};
onMounted(async () => {
await getNetworksLiquidity();
});
</script>
<template>

View File

@ -1,28 +1,31 @@
<script setup lang="ts">
import { useEtherStore } from "@/store/ether";
import { storeToRefs } from "pinia";
import blockchain from "../utils/blockchain";
import ListingComponent from "@/components/ListingComponent.vue";
import type { BigNumber } from "ethers";
import { ref, watch } from "vue";
import { ref, watch, onMounted } from "vue";
import { cancelDeposit, withdrawDeposit } from "@/blockchain/buyerMethods";
import { listValidDepositTransactionsByWalletAddress } from "@/blockchain/wallet";
import type { ValidDeposit } from "@/model/ValidDeposit";
const etherStore = useEtherStore();
const { walletAddress } = storeToRefs(etherStore);
const depositList = ref<any[]>([]);
const { walletAddress, networkName } = storeToRefs(etherStore);
const depositList = ref<ValidDeposit[]>([]);
if (walletAddress.value) {
const walletDeposits =
await blockchain.listValidDepositTransactionsByWalletAddress(
onMounted(async () => {
if (walletAddress.value) {
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
walletAddress.value
);
if (walletDeposits) {
depositList.value = walletDeposits;
if (walletDeposits) {
depositList.value = walletDeposits;
}
}
}
});
const handleCancelDeposit = async (depositID: BigNumber, index: number) => {
const response = await blockchain.cancelDeposit(depositID);
const response = await cancelDeposit(depositID);
if (response == true) {
console.log("Depósito cancelado com sucesso.");
depositList.value.splice(index, 1);
@ -30,7 +33,7 @@ const handleCancelDeposit = async (depositID: BigNumber, index: number) => {
};
const handleWithDrawDeposit = async (depositID: BigNumber, index: number) => {
const response = await blockchain.withdrawDeposit(depositID);
const response = await withdrawDeposit(depositID);
if (response == true) {
console.log("Token retirado com sucesso.");
depositList.value.splice(index, 1);
@ -38,13 +41,23 @@ const handleWithDrawDeposit = async (depositID: BigNumber, index: number) => {
};
watch(walletAddress, async () => {
const walletDeposits =
await blockchain.listValidDepositTransactionsByWalletAddress(
walletAddress.value
);
if (walletDeposits) {
depositList.value = walletDeposits;
}
await listValidDepositTransactionsByWalletAddress(walletAddress.value)
.then((res) => {
if (res) depositList.value = res;
})
.catch(() => {
depositList.value = [];
});
});
watch(networkName, async () => {
await listValidDepositTransactionsByWalletAddress(walletAddress.value)
.then((res) => {
if (res) depositList.value = res;
})
.catch(() => {
depositList.value = [];
});
});
</script>

View File

@ -1,142 +0,0 @@
<script setup lang="ts">
import type { BigNumber } from "ethers";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { useEtherStore } from "../store/ether";
import blockchain from "../utils/blockchain";
// Blockchain Data
const etherStore = useEtherStore();
const { depositsValidList } = storeToRefs(etherStore);
const { depositsAddedList } = storeToRefs(etherStore);
const { locksAddedList } = storeToRefs(etherStore);
// Buyer's flow Data
const depositValue = ref<Number>();
const depositPixKey = ref<string>("");
// Split tokens between wallets in wallets.json
const splitTokens = async () => {
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 = () => {
if (!depositValue.value || !depositPixKey.value) return;
blockchain.mockDeposit(depositValue.value, depositPixKey.value);
};
// Get specific deposit data by its ID
const mapDeposit = (depositId: BigNumber) => {
const deposit = blockchain.mapDeposits(depositId);
return deposit;
};
// Lock methods
// Get specific lock data by its ID
const mapLock = (lockId: string) => {
const lock = blockchain.mapLocks(lockId);
return lock;
};
</script>
<template>
<div class="page">
<div class="flex flex-col gap-4 justify-start items-start w-2/3">
<div class="flex gap-4 w-full justify-between">
<input
type="number"
class="default-input"
placeholder="Quantidade de tokens"
v-model="depositValue"
/>
<input
type="text"
class="default-input"
placeholder="Chave pix"
v-model="depositPixKey"
/>
<button type="button" class="default-button" @click="mockDeposit()">
Mockar depósitos
</button>
</div>
<button type="button" class="default-button" @click="splitTokens()">
Dividir tokens
</button>
</div>
<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="deposit in depositsAddedList"
:key="deposit.blockNumber"
@click="mapDeposit(deposit.args.depositID)"
>
Seller:<br />{{ formatWalletAddress(deposit.args.seller) }}<br />
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>
</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="valid in depositsValidList"
:key="valid.depositID"
@click="mapDeposit(valid.depositID)"
>
Buyer:<br />{{ formatWalletAddress(valid.seller) }}<br />
MRBZ: {{ valid.remaining }}
</li>
</ul>
</div>
</template>
<style scoped>
header {
@apply flex flex-row justify-between w-full items-center;
}
.default-button {
@apply p-2 rounded border-2 border-amber-400 text-gray-50 font-extrabold text-base w-full;
}
.default-input {
@apply border-none outline-none text-lg text-gray-900 w-64 p-2 rounded-lg;
}
.page {
@apply flex gap-8 mt-24;
}
@media (max-width: 1024px) {
.page {
@apply flex-wrap;
}
}
</style>

View File

@ -2,7 +2,7 @@
import WantSellComponent from "../components/SellerSteps/WantSellComponent.vue";
import SendNetwork from "../components/SellerSteps/SendNetwork.vue";
import ValidationComponent from "../components/LoadingComponent.vue";
import blockchain from "../utils/blockchain";
import { approveTokens, addDeposit } from "../blockchain/sellerMethods";
import { ref } from "vue";
import { useEtherStore } from "@/store/ether";
@ -19,19 +19,20 @@ etherStore.setSellerView(true);
const flowStep = ref<Step>(Step.Sell);
const loading = ref<boolean>(false);
const offerValue = ref<number>();
const offerValue = ref<string>("");
const pixKeyBuyer = ref<string>("");
// Verificar tipagem
const approveOffer = async ({ offer, pixKey }: any) => {
const approveOffer = async (args: { offer: string; pixKey: string }) => {
loading.value = true;
try {
offerValue.value = offer;
pixKeyBuyer.value = pixKey;
await blockchain.approveTokens(Number(offerValue.value));
offerValue.value = args.offer;
pixKeyBuyer.value = args.pixKey;
await approveTokens(args.offer);
flowStep.value = Step.Network;
loading.value = false;
} catch {
} catch (err) {
console.log(err);
flowStep.value = Step.Sell;
loading.value = false;
}
@ -41,7 +42,7 @@ const sendNetwork = async () => {
loading.value = true;
try {
if (offerValue.value && pixKeyBuyer.value) {
await blockchain.addDeposit(offerValue.value, pixKeyBuyer.value);
await addDeposit(String(offerValue.value), pixKeyBuyer.value);
flowStep.value = Step.Sell;
loading.value = false;
}

View File

@ -1,34 +1,42 @@
<script setup lang="ts">
import { useEtherStore } from "@/store/ether";
import { storeToRefs } from "pinia";
import { ref, watch } from "vue";
import { ref, watch, onMounted } from "vue";
import ListingComponent from "@/components/ListingComponent.vue";
import blockchain from "../utils/blockchain";
import { listAllTransactionByWalletAddress } from "@/blockchain/wallet";
import type { Event } from "ethers";
import type { ValidDeposit } from "@/model/ValidDeposit";
const etherStore = useEtherStore();
const { walletAddress } = storeToRefs(etherStore);
const allUserTransactions = ref<any[]>([]);
const { walletAddress, networkName } = storeToRefs(etherStore);
const allUserTransactions = ref<(Event | ValidDeposit)[]>([]);
if (walletAddress.value) {
await blockchain
.listAllTransactionByWalletAddress(walletAddress.value)
.then((res) => {
onMounted(async () => {
if (walletAddress.value) {
await listAllTransactionByWalletAddress(walletAddress.value).then((res) => {
if (res) allUserTransactions.value = res;
});
}
watch(walletAddress, async (newValue) => {
await blockchain.listAllTransactionByWalletAddress(newValue).then((res) => {
if (res) allUserTransactions.value = res;
});
}
});
watch(walletAddress, async (newValue) => {
console.log(newValue);
await listAllTransactionByWalletAddress(newValue)
.then((res) => {
if (res) allUserTransactions.value = res;
})
.catch(() => {
allUserTransactions.value = [];
});
});
watch(allUserTransactions, (newValue) => {
console.log(newValue);
watch(networkName, async () => {
await listAllTransactionByWalletAddress(walletAddress.value)
.then((res) => {
if (res) allUserTransactions.value = res;
})
.catch(() => {
allUserTransactions.value = [];
});
});
</script>

3595
yarn.lock

File diff suppressed because it is too large Load Diff