Merge branch 'buy-refactor'
16
README.md
@ -102,3 +102,19 @@ cd P2Pix-Front-End
|
||||
# Run docker-compose up command
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
### Backend Communication
|
||||
|
||||
Backend Repo: `https://gitea.kosmos.org/hueso/helpix`
|
||||
|
||||
Backend Endpoint: `https://api.p2pix.co/release/1279331`
|
||||
|
||||
curl -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer {api-key}" \
|
||||
-d '{"query": "{ depositAddeds { id seller token amount } }"}' \
|
||||
https://api.studio.thegraph.com/query/113713/p-2-pix/sepolia
|
||||
|
||||
https://api.studio.thegraph.com/query/113713/p-2-pix/1
|
||||
|
||||
curl --request POST --url 'https://api.hm.bb.com.br/testes-portal-desenvolvedor/v1/boletos-pix/pagar?gw-app-key=95cad3f03fd9013a9d15005056825665' --header 'content-type: application/json' --data '{"pix":"00020101021226070503***63041654" }'
|
@ -2,9 +2,11 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||
<link rel="icon" href="/p2pix.svg" type="image/svg+xml">
|
||||
<link rel="shortcut icon" href="/p2pix.svg" type="image/svg+xml">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<title>P2Pix</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
24
package.json
@ -5,12 +5,10 @@
|
||||
"start": "vite --host=0.0.0.0 --port 3000",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest",
|
||||
"serve": "vue-cli-service serve",
|
||||
"coverage": "vitest run --coverage",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
|
||||
"type-check": "vue-tsc --skipLibCheck --noEmit",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore --fix",
|
||||
"lint:fix": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -18,12 +16,14 @@
|
||||
"@headlessui/vue": "^1.7.3",
|
||||
"@heroicons/vue": "^2.0.12",
|
||||
"@vueuse/core": "^9.12.0",
|
||||
"@web3-onboard/injected-wallets": "^2.11.2",
|
||||
"@web3-onboard/vue": "^2.9.0",
|
||||
"alchemy-sdk": "^2.3.0",
|
||||
"axios": "^1.2.1",
|
||||
"crc": "^3.8.0",
|
||||
"marked": "^4.2.12",
|
||||
"pinia": "^2.0.23",
|
||||
"qrcode": "^1.5.1",
|
||||
"viem": "^2.31.3",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue": "^3.2.41",
|
||||
"vue-markdown": "^2.2.4",
|
||||
"vue-router": "^4.1.5"
|
||||
@ -31,33 +31,27 @@
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@pinia/testing": "^0.0.14",
|
||||
"@rushstack/eslint-patch": "^1.1.4",
|
||||
"@types/crc": "^3.8.0",
|
||||
"@types/jest": "^27.0.0",
|
||||
"@types/marked": "^4.0.8",
|
||||
"@types/node": "^16.11.68",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/vue-markdown": "^2.2.1",
|
||||
"@vitejs/plugin-vue": "^3.1.2",
|
||||
"@vitejs/plugin-vue-jsx": "^2.0.1",
|
||||
"@vitest/coverage-c8": "^0.28.2",
|
||||
"@vue/eslint-config-prettier": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.0",
|
||||
"@vue/test-utils": "^2.2.7",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"@wagmi/cli": "^2.3.1",
|
||||
"autoprefixer": "^10.4.12",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"ethers": "^5.7.2",
|
||||
"jsdom": "^21.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss": "^8.4.18",
|
||||
"prettier": "^2.7.1",
|
||||
"tailwindcss": "^3.2.1",
|
||||
"typescript": "~4.7.4",
|
||||
"typescript": "~5.8.2",
|
||||
"vite": "^3.1.8",
|
||||
"vitest": "^0.28.1",
|
||||
"vue-tsc": "^1.0.8"
|
||||
"vue-tsc": "^2.2.8"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 2.9 KiB |
72
public/p2pix.svg
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="81.253998"
|
||||
height="81.253998"
|
||||
viewBox="0 0 81.253998 81.253998"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="p2pix.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview8"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="5.0617761"
|
||||
inkscape:cx="129.5"
|
||||
inkscape:cy="23.805873"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8" />
|
||||
<path
|
||||
d="m 21.877862,66.609161 c 0,4.29324 -2.73444,7.334258 -6.49095,7.334258 -2.197613,0 -3.884411,-1.07331 -4.906471,-2.657693 v 8.739769 H 7.5927121 v -20.44393 h 2.8877289 v 2.402197 c 1.02206,-1.609964 2.708858,-2.708857 4.932061,-2.708857 3.67975,0 6.46536,3.041058 6.46536,7.334256 z m -2.91311,0 c 0,-2.683233 -1.73796,-4.574316 -4.19099,-4.574316 -2.453445,0 -4.318904,1.891083 -4.318904,4.574316 0,2.708816 1.865459,4.574317 4.318904,4.574317 2.42745,0 4.19099,-1.865501 4.19099,-4.574317 z"
|
||||
fill="#14b8a6"
|
||||
id="path1"
|
||||
style="stroke-width:0.419393" />
|
||||
<path
|
||||
d="m 37.722102,70.876818 v 2.759941 h -13.56988 v -2.069955 l 5.18789,-4.548777 c 4.01191,-3.501007 5.03397,-4.395445 5.03397,-5.979827 0,-1.558841 -1.20114,-2.836605 -3.39876,-2.836605 -2.17203,0 -3.37317,1.252181 -3.7565,3.296595 h -2.9131 c 0.40891,-3.731044 3.09218,-6.056523 6.79752,-6.056523 3.80767,0 6.18437,2.453268 6.18437,5.468744 0,2.708816 -1.89105,4.31878 -5.54564,7.513127 l -2.81077,2.427739 -0.0512,0.02554 z"
|
||||
fill="#f59e0b"
|
||||
id="path2"
|
||||
style="stroke-width:0.419393" />
|
||||
<path
|
||||
d="m 54.869392,66.609161 c 0,4.29324 -2.73444,7.334258 -6.49094,7.334258 -2.19804,0 -3.88442,-1.07331 -4.9069,-2.657693 v 8.739769 h -2.88752 v -20.44393 h 2.88752 v 2.402197 c 1.02248,-1.609964 2.70886,-2.708857 4.93248,-2.708857 3.67975,0 6.46536,3.041058 6.46536,7.334256 z m -2.91352,0 c 0,-2.683233 -1.73755,-4.574316 -4.19099,-4.574316 -2.45303,0 -4.31849,1.891083 -4.31849,4.574316 0,2.708816 1.86546,4.574317 4.31849,4.574317 2.42786,0 4.19099,-1.865501 4.19099,-4.574317 z"
|
||||
fill="#14b8a6"
|
||||
id="path3"
|
||||
style="stroke-width:0.419393" />
|
||||
<path
|
||||
d="m 61.104502,55.007239 c 0,1.047748 -0.79223,1.814394 -1.89104,1.814394 -1.04764,0 -1.86546,-0.766646 -1.86546,-1.814394 0,-1.022194 0.81782,-1.763286 1.86546,-1.763286 1.09881,0 1.89104,0.741092 1.89104,1.763286 z m -3.32201,4.574326 h 2.88752 v 14.055194 h -2.88752 z"
|
||||
fill="#14b8a6"
|
||||
id="path4"
|
||||
style="stroke-width:0.419393" />
|
||||
<path
|
||||
d="m 73.089912,73.636759 -3.39876,-5.034308 -3.27126,5.034308 h -3.47551 l 5.03439,-7.23201 -4.75298,-6.823143 h 3.44992 l 3.11735,4.702064 3.01585,-4.702064 h 3.47509 l -4.75297,6.874225 5.0088,7.180928 z"
|
||||
fill="#14b8a6"
|
||||
id="path5"
|
||||
style="stroke-width:0.419393" />
|
||||
<path
|
||||
d="m 8.11425,0 c 3.66615,0 6.76355,2.43731 7.76895,5.77851 3.8591,0.2742 7.5557,1.9498 10.2875,4.702 l 14.7458,14.7356 c 0.0508,0.0508 0.1117,0.1016 0.1625,0.1625 l 16.1676,16.1574 c 1.9702,1.9803 4.6918,3.1076 7.4744,3.1076 0.0102,0 0.0203,0 0.0305,0 h 0.8937 c 1.2187,-2.9349 4.113,-4.9965 7.4948,-4.9965 4.4785,0 8.1142,3.6357 8.1142,8.1142 0,4.4786 -3.6357,8.1143 -8.1142,8.1143 -3.6154,0 -6.6621,-2.3561 -7.7182,-5.6262 h -0.6601 c -0.0102,0 -0.0305,0 -0.0407,0 -4.2551,0 -8.4189,-1.7264 -11.4147,-4.7426 L 38.4995,30.70011 c -0.0609,-0.0508 -0.132,-0.1016 -0.1929,-0.1625 L 22.1898,14.42081 c -1.7671,-1.7772 -4.1536,-2.8638 -6.6519,-3.067 -1.2491,2.8639 -4.1028,4.8645 -7.43381,4.8645 C 3.63567,16.21831 0,12.59281 0,8.11421 0,3.63571 3.63567,0 8.11425,0 Z"
|
||||
fill="#f59e0b"
|
||||
id="path6" />
|
||||
<path
|
||||
d="m 8.11425,39.64711 c 3.38175,0 6.26595,2.0616 7.49475,4.9965 h 0.8937 c 0.0102,0 0.0203,0 0.0305,0 2.7927,0 5.5144,-1.1272 7.4744,-3.1075 l 9.6477,-9.6478 3.9505,3.9505 -9.6578,9.6579 c -3.0061,3.0162 -7.1597,4.7426 -11.4148,4.7426 -0.0102,0 -0.0305,0 -0.0407,0 h -0.6601 c -1.046,3.2599 -4.1028,5.6262 -7.71815,5.6262 C 3.63567,55.86551 0,52.22981 0,47.75121 c 0,-4.4786 3.63567,-8.1041 8.11425,-8.1041 z"
|
||||
fill="#f59e0b"
|
||||
id="path7" />
|
||||
<path
|
||||
d="m 73.1298,16.21831 c -3.3818,0 -6.266,-2.0615 -7.4948,-4.9965 h -0.8937 c -0.0101,0 -0.0203,0 -0.0304,0 -2.7928,0 -5.5145,1.1273 -7.4745,3.1076 l -9.6477,9.6478 -3.9505,-3.9505 9.6579,-9.6579 c 3.006,-3.0162 7.1596,-4.7426 11.4148,-4.7426 0.0101,0 0.0304,0 0.0406,0 h 0.6601 c 1.0562,-3.2701 4.113,-5.6262 7.7182,-5.6262 4.4786,0 8.1142,3.6357 8.1142,8.1143 0,4.4785 -3.6356,8.104 -8.1142,8.104 z"
|
||||
fill="#f59e0b"
|
||||
id="path8" />
|
||||
</svg>
|
After Width: | Height: | Size: 5.0 KiB |
70
src/App.vue
@ -1,20 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from "vue-router";
|
||||
import TopBar from "@/components/TopBar/TopBar.vue";
|
||||
import SpinnerComponent from "@/components/SpinnerComponent.vue";
|
||||
import ToasterComponent from "@/components/ToasterComponent.vue";
|
||||
import { init, useOnboard } from "@web3-onboard/vue";
|
||||
import injectedModule from "@web3-onboard/injected-wallets";
|
||||
import { Networks } from "./model/Networks";
|
||||
import { NetworkEnum } from "./model/NetworkEnum";
|
||||
import { ref } from "vue";
|
||||
|
||||
const route = useRoute();
|
||||
const injected = injectedModule();
|
||||
const targetNetwork = ref(NetworkEnum.sepolia);
|
||||
|
||||
const web3Onboard = init({
|
||||
wallets: [injected],
|
||||
chains: [
|
||||
{
|
||||
id: Networks[NetworkEnum.sepolia].chainId,
|
||||
token: "ETH",
|
||||
label: "Sepolia",
|
||||
rpcUrl: import.meta.env.VITE_SEPOLIA_API_URL,
|
||||
},
|
||||
{
|
||||
id: Networks[NetworkEnum.rootstock].chainId,
|
||||
token: "tRBTC",
|
||||
label: "Rootstock Testnet",
|
||||
rpcUrl: import.meta.env.VITE_ROOTSTOCK_API_URL,
|
||||
},
|
||||
],
|
||||
connect: {
|
||||
autoConnectLastWallet: true,
|
||||
},
|
||||
});
|
||||
|
||||
const { connectedWallet } = useOnboard();
|
||||
if (!connectedWallet) {
|
||||
web3Onboard.connectWallet();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TopBar />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense>
|
||||
<component :is="Component"></component>
|
||||
<template #fallback>
|
||||
<div class="flex w-full h-full justify-center items-center">
|
||||
<SpinnerComponent :width="'16'" :height="'16'"></SpinnerComponent>
|
||||
<div class="p-3 sm:p-4 md:p-8">
|
||||
<TopBar />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Transition name="page" mode="out-in" appear>
|
||||
<div :key="route.fullPath">
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<component :is="Component" />
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div class="flex w-full h-full justify-center items-center">
|
||||
<SpinnerComponent :width="'16'" :height="'16'" />
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</div>
|
||||
</template>
|
||||
</Suspense>
|
||||
</template>
|
||||
</RouterView>
|
||||
</Transition>
|
||||
</template>
|
||||
</RouterView>
|
||||
<ToasterComponent :targetNetwork="targetNetwork" />
|
||||
</div>
|
||||
</template>
|
||||
|
BIN
src/assets/Trial and expirations.jpg
Normal file
After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 1.0 MiB |
1
src/assets/brx.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" fill="#efb914" fill-rule="nonzero" r="16"/><path d="M21.002 9.855A7.947 7.947 0 0124 15.278l-2.847-.708a5.357 5.357 0 00-3.86-3.667c-2.866-.713-5.76.991-6.465 3.806s1.05 5.675 3.917 6.388a5.373 5.373 0 005.134-1.43l2.847.707a7.974 7.974 0 01-5.2 3.385L16.716 27l-2.596-.645.644-2.575a8.28 8.28 0 01-1.298-.323l-.643 2.575-2.596-.646.81-3.241c-2.378-1.875-3.575-4.996-2.804-8.081s3.297-5.281 6.28-5.823L15.323 5l2.596.645-.644 2.575a8.28 8.28 0 011.298.323l.643-2.575 2.596.646z" fill="#fff"/></g></svg>
|
After Width: | Height: | Size: 644 B |
3
src/assets/chevron.svg
Normal 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 id="chevronDown" fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="currentColor"/>
|
||||
</svg>
|
After Width: | Height: | Size: 508 B |
@ -1,3 +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="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 491 B |
@ -1,3 +0,0 @@
|
||||
<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="M1.64645 4.64645C1.84171 4.45118 2.15829 4.45118 2.35355 4.64645L8 10.2929L13.6464 4.64645C13.8417 4.45118 14.1583 4.45118 14.3536 4.64645C14.5488 4.84171 14.5488 5.15829 14.3536 5.35355L8.35355 11.3536C8.15829 11.5488 7.84171 11.5488 7.64645 11.3536L1.64645 5.35355C1.45118 5.15829 1.45118 4.84171 1.64645 4.64645Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 484 B |
@ -1,3 +1,3 @@
|
||||
<svg width="16" height="8" viewBox="0 0 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3536 7.35355C13.1583 7.54882 12.8417 7.54882 12.6464 7.35355L7 1.70711L1.35355 7.35355C1.15829 7.54881 0.841709 7.54881 0.646446 7.35355C0.451184 7.15829 0.451184 6.84171 0.646446 6.64645L6.64645 0.646446C6.84171 0.451184 7.15829 0.451184 7.35355 0.646446L13.3536 6.64645C13.5488 6.84171 13.5488 7.15829 13.3536 7.35355Z" fill="#111827"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.3536 7.35355C13.1583 7.54882 12.8417 7.54882 12.6464 7.35355L7 1.70711L1.35355 7.35355C1.15829 7.54881 0.841709 7.54881 0.646446 7.35355C0.451184 7.15829 0.451184 6.84171 0.646446 6.64645L6.64645 0.646446C6.84171 0.451184 7.15829 0.451184 7.35355 0.646446L13.3536 6.64645C13.5488 6.84171 13.5488 7.15829 13.3536 7.35355Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 498 B |
@ -1,3 +1,7 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.00016 0.333496C3.31816 0.333496 0.333496 3.32283 0.333496 7.0115C0.333496 9.9615 2.2435 12.4648 4.89283 13.3475C5.22616 13.4088 5.3475 13.2028 5.3475 13.0255C5.3475 12.8675 5.34216 12.4468 5.33883 11.8902C3.48416 12.2935 3.09283 10.9948 3.09283 10.9948C2.79016 10.2228 2.35283 10.0175 2.35283 10.0175C1.7475 9.60416 2.39883 9.61216 2.39883 9.61216C3.0675 9.65883 3.4195 10.3002 3.4195 10.3002C4.01416 11.3202 4.98016 11.0255 5.3595 10.8548C5.42083 10.4235 5.59283 10.1295 5.7835 9.96283C4.3035 9.79416 2.74683 9.22083 2.74683 6.66216C2.74683 5.9335 3.00683 5.33683 3.43283 4.87016C3.36416 4.7015 3.1355 4.02216 3.49816 3.1035C3.49816 3.1035 4.05816 2.9235 5.3315 3.7875C5.87534 3.63917 6.43645 3.56362 7.00016 3.56283C7.56683 3.5655 8.13683 3.6395 8.6695 3.7875C9.94216 2.9235 10.5008 3.10283 10.5008 3.10283C10.8648 4.02216 10.6355 4.7015 10.5675 4.87016C10.9942 5.33683 11.2528 5.9335 11.2528 6.66216C11.2528 9.2275 9.6935 9.79216 8.20883 9.9575C8.44816 10.1635 8.66083 10.5708 8.66083 11.1942C8.66083 12.0862 8.65283 12.8068 8.65283 13.0255C8.65283 13.2042 8.77283 13.4122 9.1115 13.3468C10.439 12.9016 11.5931 12.0504 12.4105 10.9135C13.2279 9.77669 13.6674 8.41171 13.6668 7.0115C13.6668 3.32283 10.6815 0.333496 7.00016 0.333496Z" fill="#1F2937"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.41819,0,0,1.41819,0.0724699,0.290651)">
|
||||
<path d="M7,0.333C3.318,0.333 0.333,3.323 0.333,7.012C0.333,9.961 2.244,12.465 4.893,13.348C5.226,13.409 5.348,13.203 5.348,13.025C5.348,12.868 5.342,12.447 5.339,11.89C3.484,12.294 3.093,10.995 3.093,10.995C2.79,10.223 2.353,10.018 2.353,10.018C1.748,9.604 2.399,9.612 2.399,9.612C3.068,9.659 3.42,10.3 3.42,10.3C4.014,11.32 4.98,11.025 5.36,10.855C5.421,10.424 5.593,10.13 5.784,9.963C4.304,9.794 2.747,9.221 2.747,6.662C2.747,5.934 3.007,5.337 3.433,4.87C3.364,4.702 3.136,4.022 3.498,3.104C3.498,3.104 4.058,2.924 5.332,3.788C5.875,3.639 6.436,3.564 7,3.563C7.567,3.566 8.137,3.64 8.67,3.788C9.942,2.924 10.501,3.103 10.501,3.103C10.865,4.022 10.636,4.702 10.568,4.87C10.994,5.337 11.253,5.934 11.253,6.662C11.253,9.227 9.694,9.792 8.209,9.958C8.448,10.164 8.661,10.571 8.661,11.194C8.661,12.086 8.653,12.807 8.653,13.025C8.653,13.204 8.773,13.412 9.112,13.347C10.439,12.902 11.593,12.05 12.411,10.914C13.228,9.777 13.667,8.412 13.667,7.012C13.667,3.323 10.682,0.333 7,0.333Z" style="fill:rgb(31,41,55);"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
7
src/assets/linkedinIcon.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(0.426716,0,0,0.426716,-0.646464,-0.646464)">
|
||||
<path d="M41,4L9,4C6.24,4 4,6.24 4,9L4,41C4,43.76 6.24,46 9,46L41,46C43.76,46 46,43.76 46,41L46,9C46,6.24 43.76,4 41,4ZM17,20L17,39L11,39L11,20L17,20ZM11,14.47C11,13.07 12.2,12 14,12C15.8,12 16.93,13.07 17,14.47C17,15.87 15.88,17 14,17C12.2,17 11,15.87 11,14.47ZM39,39L33,39L33,29C33,27 32,25 29.5,24.96L29.42,24.96C27,24.96 26,27.02 26,29L26,39L20,39L20,20L26,20L26,22.56C26,22.56 27.93,20 31.81,20C35.78,20 39,22.73 39,28.26L39,39Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1001 B |
@ -6,11 +6,9 @@
|
||||
#app {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
height: fit-content;
|
||||
min-height: 100vh;
|
||||
background-image: url( './bg.svg' );
|
||||
background-size: cover;
|
||||
background: radial-gradient(ellipse at 50% -50%, rgba(49, 46, 129, 1) 60%, rgba(24, 30, 42, 1) 80%);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@ -26,3 +24,13 @@ a,
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.main-container {
|
||||
@apply flex w-full md:max-w-lg flex-col justify-center items-center px-4 sm:px-8 py-4 sm:py-6 gap-4 rounded-lg border border-gray-500 backdrop-blur-md drop-shadow-lg shadow-lg mt-10;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
appearance: textfield;
|
||||
-webkit-appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
24
src/assets/rootstock.svg
Normal file
After Width: | Height: | Size: 30 KiB |
4
src/assets/sepolia.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<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 16ZM8.16801 2.56435C8.09081 2.43177 7.89923 2.4319 7.82221 2.56458L4.82431 7.72899C4.76983 7.82284 4.80013 7.94301 4.89259 7.99981L7.89038 9.84139C7.95451 9.88079 8.03533 9.88085 8.09952 9.84154L11.1069 7.99986C11.1996 7.94308 11.23 7.82262 11.1752 7.72866L8.16801 2.56435ZM8.08754 10.7831C8.02182 10.8253 7.93759 10.8253 7.87181 10.7833L5.51555 9.27665C5.33379 9.16043 5.1222 9.37463 5.24065 9.55495L7.8123 13.4701C7.89136 13.5905 8.06789 13.5903 8.14678 13.4699L10.7098 9.55566C10.828 9.37517 10.6161 9.16127 10.4345 9.27775L8.08754 10.7831Z" fill="#3B82F6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.82232 2.56456C7.89934 2.43187 8.09092 2.43174 8.16812 2.56432L11.1754 7.72864C11.2301 7.8226 11.1997 7.94305 11.107 7.99984L8.09963 9.84152C8.03544 9.88082 7.95463 9.88077 7.89049 9.84137L4.8927 7.99979C4.80024 7.94299 4.76994 7.82282 4.82442 7.72897L7.82232 2.56456ZM7.87193 10.7833C7.9377 10.8253 8.02193 10.8253 8.08765 10.7831L10.4346 9.27773C10.6162 9.16125 10.8281 9.37515 10.7099 9.55563L8.1469 13.4698C8.06801 13.5903 7.89147 13.5904 7.81241 13.4701L5.24076 9.55492C5.12232 9.3746 5.3339 9.1604 5.51566 9.27662L7.87193 10.7833Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
32
src/assets/transitions.css
Normal file
@ -0,0 +1,32 @@
|
||||
.dropdown-enter-active,
|
||||
.dropdown-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dropdown-enter-from,
|
||||
.dropdown-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
|
||||
.page-enter-active,
|
||||
.page-leave-active {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.page-enter-from,
|
||||
.page-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(15px);
|
||||
}
|
||||
|
||||
.resize-enter-active,
|
||||
.resize-leave-active {
|
||||
max-height: 100px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.resize-enter-from,
|
||||
.resize-leave-to {
|
||||
max-height: 0px;
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
<svg width="14" height="12" viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.52683 11.5021C9.55816 11.5021 12.3102 7.33344 12.3102 3.71877C12.3102 3.6001 12.3102 3.4821 12.3022 3.36544C12.8376 2.97779 13.2997 2.49784 13.6668 1.9481C13.1675 2.16944 12.6379 2.31461 12.0955 2.37877C12.6666 2.03681 13.094 1.499 13.2982 0.865436C12.7613 1.18403 12.174 1.40859 11.5615 1.52944C11.1491 1.09061 10.6035 0.799992 10.0092 0.702573C9.41498 0.605153 8.80517 0.706369 8.27424 0.990549C7.74331 1.27473 7.32088 1.72602 7.07236 2.27454C6.82383 2.82307 6.76307 3.43823 6.8995 4.02477C5.81189 3.97026 4.7479 3.68765 3.77659 3.19528C2.80529 2.70291 1.94838 2.01179 1.2615 1.16677C0.911708 1.7689 0.804555 2.48172 0.961853 3.16008C1.11915 3.83844 1.52907 4.43135 2.10816 4.8181C1.673 4.80551 1.24725 4.68844 0.866829 4.47677V4.51144C0.867089 5.14297 1.08576 5.75497 1.48576 6.24367C1.88576 6.73238 2.44247 7.06769 3.0615 7.19277C2.6587 7.30258 2.23609 7.31854 1.82616 7.23944C2.00094 7.78309 2.34128 8.2585 2.79958 8.59918C3.25788 8.93986 3.81121 9.12875 4.38216 9.13944C3.81491 9.58515 3.16535 9.91466 2.47065 10.1091C1.77594 10.3036 1.04971 10.3592 0.333496 10.2728C1.58457 11.0757 3.04029 11.5015 4.52683 11.4994" fill="#1F2937"/>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1.44084,0,0,1.44084,-0.0860978,1.23349)">
|
||||
<path d="M4.527,11.502C9.558,11.502 12.31,7.333 12.31,3.719C12.31,3.6 12.31,3.482 12.302,3.365C12.838,2.978 13.3,2.498 13.667,1.948C13.168,2.169 12.638,2.315 12.096,2.379C12.667,2.037 13.094,1.499 13.298,0.865C12.761,1.184 12.174,1.409 11.562,1.529C11.149,1.091 10.604,0.8 10.009,0.703C9.415,0.605 8.805,0.706 8.274,0.991C7.743,1.275 7.321,1.726 7.072,2.275C6.824,2.823 6.763,3.438 6.9,4.025C5.812,3.97 4.748,3.688 3.777,3.195C2.805,2.703 1.948,2.012 1.262,1.167C0.912,1.769 0.805,2.482 0.962,3.16C1.119,3.838 1.529,4.431 2.108,4.818C1.673,4.806 1.247,4.688 0.867,4.477L0.867,4.511C0.867,5.143 1.086,5.755 1.486,6.244C1.886,6.732 2.442,7.068 3.062,7.193C2.659,7.303 2.236,7.319 1.826,7.239C2.001,7.783 2.341,8.259 2.8,8.599C3.258,8.94 3.811,9.129 4.382,9.139C3.815,9.585 3.165,9.915 2.471,10.109C1.776,10.304 1.05,10.359 0.333,10.273C1.585,11.076 3.04,11.502 4.527,11.499" style="fill:rgb(31,41,55);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,104 +0,0 @@
|
||||
import { expectTypeOf, it, expect } from "vitest";
|
||||
import {
|
||||
getTokenAddress,
|
||||
getP2PixAddress,
|
||||
getProviderUrl,
|
||||
isPossibleNetwork,
|
||||
possibleChains,
|
||||
network2Chain,
|
||||
} from "../addresses";
|
||||
|
||||
import { setActivePinia, createPinia } from "pinia";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
describe("addresses.ts types", () => {
|
||||
it("My addresses.ts types work properly", () => {
|
||||
expectTypeOf(getTokenAddress).toBeFunction();
|
||||
expectTypeOf(getP2PixAddress).toBeFunction();
|
||||
expectTypeOf(getProviderUrl).toBeFunction();
|
||||
expectTypeOf(isPossibleNetwork).toBeFunction();
|
||||
|
||||
expectTypeOf(possibleChains).toBeObject();
|
||||
expectTypeOf(network2Chain).toBeObject();
|
||||
});
|
||||
});
|
||||
|
||||
describe("addresses.ts functions", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("getTokenAddress Ethereum", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(getTokenAddress()).toBe(
|
||||
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00"
|
||||
);
|
||||
});
|
||||
|
||||
it("getTokenAddress Polygon", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
||||
expect(getTokenAddress()).toBe(
|
||||
"0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29"
|
||||
);
|
||||
});
|
||||
|
||||
it("getTokenAddress Default", () => {
|
||||
expect(getTokenAddress()).toBe(
|
||||
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00"
|
||||
);
|
||||
});
|
||||
|
||||
it("getP2PixAddress Ethereum", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(getP2PixAddress()).toBe(
|
||||
"0x2414817FF64A114d91eCFA16a834d3fCf69103d4"
|
||||
);
|
||||
});
|
||||
|
||||
it("getP2PixAddress Polygon", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
||||
expect(getP2PixAddress()).toBe(
|
||||
"0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00"
|
||||
);
|
||||
});
|
||||
|
||||
it("getP2PixAddress Default", () => {
|
||||
expect(getP2PixAddress()).toBe(
|
||||
"0x2414817FF64A114d91eCFA16a834d3fCf69103d4"
|
||||
);
|
||||
});
|
||||
|
||||
it("getProviderUrl Ethereum", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL);
|
||||
});
|
||||
|
||||
it("getProviderUrl Polygon", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.polygon);
|
||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_MUMBAI_API_URL);
|
||||
});
|
||||
|
||||
it("getProviderUrl Default", () => {
|
||||
expect(getProviderUrl()).toBe(import.meta.env.VITE_GOERLI_API_URL);
|
||||
});
|
||||
|
||||
it("isPossibleNetwork Returns", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setNetworkName(NetworkEnum.ethereum);
|
||||
expect(isPossibleNetwork("0x5")).toBe(true);
|
||||
expect(isPossibleNetwork("5")).toBe(true);
|
||||
expect(isPossibleNetwork("0x13881")).toBe(true);
|
||||
expect(isPossibleNetwork("80001")).toBe(true);
|
||||
|
||||
expect(isPossibleNetwork("")).toBe(false);
|
||||
expect(isPossibleNetwork(" ")).toBe(false);
|
||||
expect(isPossibleNetwork("0x55")).toBe(false);
|
||||
});
|
||||
});
|
2266
src/blockchain/abi.ts
Normal file
@ -1,64 +1,70 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import { NetworkEnum, TokenEnum } from "@/model/NetworkEnum";
|
||||
import { createPublicClient, http, type Address } from "viem";
|
||||
import { sepolia, rootstock } from "viem/chains";
|
||||
|
||||
const getTokenAddress = (network?: NetworkEnum): string => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const possibleTokenAddresses: { [key: string]: string } = {
|
||||
Ethereum: "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00",
|
||||
Polygon: "0xC86042E9F2977C62Da8c9dDF7F9c40fde4796A29",
|
||||
};
|
||||
|
||||
return possibleTokenAddresses[network ? network : etherStore.networkName];
|
||||
const Tokens: { [key in NetworkEnum]: { [key in TokenEnum]: Address } } = {
|
||||
[NetworkEnum.sepolia]: {
|
||||
BRZ: "0x3eBE67A2C7bdB2081CBd34ba3281E90377462289",
|
||||
// BRX: "0x3eBE67A2C7bdB2081CBd34ba3281E90377462289",
|
||||
},
|
||||
[NetworkEnum.rootstock]: {
|
||||
BRZ: "0xfE841c74250e57640390f46d914C88d22C51e82e",
|
||||
// BRX: "0xfE841c74250e57640390f46d914C88d22C51e82e",
|
||||
},
|
||||
};
|
||||
|
||||
const getP2PixAddress = (network?: NetworkEnum): string => {
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const possibleP2PixAddresses: { [key: string]: string } = {
|
||||
Ethereum: "0x2414817FF64A114d91eCFA16a834d3fCf69103d4",
|
||||
Polygon: "0x4A2886EAEc931e04297ed336Cc55c4eb7C75BA00",
|
||||
};
|
||||
|
||||
return possibleP2PixAddresses[network ? network : 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;
|
||||
export const getTokenByAddress = (address: Address) => {
|
||||
const user = useUser();
|
||||
const networksTokens = Tokens[user.networkName.value];
|
||||
for (const [token, tokenAddress] of Object.entries(networksTokens)) {
|
||||
if (tokenAddress.toLowerCase() === address.toLowerCase()) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return null;
|
||||
};
|
||||
|
||||
export {
|
||||
getTokenAddress,
|
||||
getProviderUrl,
|
||||
possibleChains,
|
||||
network2Chain,
|
||||
isPossibleNetwork,
|
||||
getP2PixAddress,
|
||||
export const getTokenAddress = (
|
||||
token: TokenEnum,
|
||||
network?: NetworkEnum
|
||||
): Address => {
|
||||
const user = useUser();
|
||||
return Tokens[network ? network : user.networkName.value][
|
||||
token
|
||||
];
|
||||
};
|
||||
|
||||
export const getP2PixAddress = (network?: NetworkEnum): Address => {
|
||||
const user = useUser();
|
||||
const possibleP2PixAddresses: { [key in NetworkEnum]: Address } = {
|
||||
[NetworkEnum.sepolia]: "0xb7cD135F5eFD9760981e02E2a898790b688939fe",
|
||||
[NetworkEnum.rootstock]: "0x98ba35eb14b38D6Aa709338283af3e922476dE34",
|
||||
};
|
||||
|
||||
return possibleP2PixAddresses[
|
||||
network ? network : user.networkName.value
|
||||
];
|
||||
};
|
||||
|
||||
export const getProviderUrl = (network?: NetworkEnum): string => {
|
||||
const user = useUser();
|
||||
const possibleProvidersUrls: { [key in NetworkEnum]: string } = {
|
||||
[NetworkEnum.sepolia]: import.meta.env.VITE_SEPOLIA_API_URL,
|
||||
[NetworkEnum.rootstock]: import.meta.env.VITE_RSK_API_URL,
|
||||
};
|
||||
|
||||
return possibleProvidersUrls[network || user.networkName.value];
|
||||
};
|
||||
|
||||
export const getProviderByNetwork = (network: NetworkEnum) => {
|
||||
const chain = network === NetworkEnum.sepolia ? sepolia : rootstock;
|
||||
return createPublicClient({
|
||||
chain,
|
||||
transport: http(getProviderUrl(network)),
|
||||
});
|
||||
};
|
||||
|
||||
export const isPossibleNetwork = (networkChain: NetworkEnum): boolean => {
|
||||
return Number(networkChain) in NetworkEnum;
|
||||
};
|
||||
|
@ -1,100 +1,95 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { getContract } from "./provider";
|
||||
import { getTokenAddress } from "./addresses";
|
||||
import {
|
||||
bytesToHex,
|
||||
encodeAbiParameters,
|
||||
keccak256,
|
||||
parseAbiParameters,
|
||||
parseEther,
|
||||
stringToBytes,
|
||||
stringToHex,
|
||||
toBytes,
|
||||
type Address,
|
||||
} from "viem";
|
||||
import type { TokenEnum } from "@/model/NetworkEnum";
|
||||
|
||||
import { getContract, getProvider } from "./provider";
|
||||
import { getP2PixAddress, getTokenAddress } 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 (
|
||||
seller: string,
|
||||
token: string,
|
||||
export const addLock = async (
|
||||
sellerAddress: Address,
|
||||
tokenAddress: Address,
|
||||
amount: number
|
||||
): Promise<string> => {
|
||||
const etherStore = useEtherStore();
|
||||
): Promise<bigint> => {
|
||||
const { address, abi, wallet, client, account } = await getContract();
|
||||
const parsedAmount = parseEther(amount.toString());
|
||||
|
||||
const p2pContract = getContract();
|
||||
if (!wallet) {
|
||||
throw new Error("Wallet not connected");
|
||||
}
|
||||
|
||||
const lock = await p2pContract.lock(
|
||||
seller,
|
||||
token,
|
||||
etherStore.walletAddress, // String "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (Example)
|
||||
ethers.constants.AddressZero, // String "0x0000000000000000000000000000000000000000"
|
||||
0,
|
||||
parseEther(String(amount)), // BigNumber
|
||||
[],
|
||||
[]
|
||||
);
|
||||
const { result, request } = await client.simulateContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "lock",
|
||||
args: [sellerAddress, tokenAddress, parsedAmount, [], []],
|
||||
account,
|
||||
});
|
||||
const hash = await wallet.writeContract(request);
|
||||
const receipt = await client.waitForTransactionReceipt({ hash });
|
||||
|
||||
const lock_rec = await lock.wait();
|
||||
const [t] = lock_rec.events;
|
||||
if (!receipt.status)
|
||||
throw new Error("Transaction failed: " + receipt.transactionHash);
|
||||
|
||||
return String(t.args.lockID);
|
||||
return result;
|
||||
};
|
||||
|
||||
const releaseLock = async (
|
||||
pixKey: number,
|
||||
amount: number,
|
||||
e2eId: string,
|
||||
lockId: string
|
||||
export const withdrawDeposit = async (
|
||||
amount: string,
|
||||
token: TokenEnum
|
||||
): Promise<boolean> => {
|
||||
const { address, abi, wallet, client, account } = await getContract();
|
||||
|
||||
if (!wallet) {
|
||||
throw new Error("Wallet not connected");
|
||||
}
|
||||
|
||||
const tokenAddress = getTokenAddress(token);
|
||||
|
||||
const { request } = await client.simulateContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "withdraw",
|
||||
args: [tokenAddress, parseEther(amount), []],
|
||||
account
|
||||
});
|
||||
|
||||
const hash = await wallet.writeContract(request);
|
||||
const receipt = await client.waitForTransactionReceipt({ hash });
|
||||
|
||||
return receipt.status === "success";
|
||||
};
|
||||
|
||||
export const releaseLock = async (
|
||||
lockID: bigint,
|
||||
pixtarget: string,
|
||||
signature: string
|
||||
): Promise<any> => {
|
||||
const mockBacenSigner = new ethers.Wallet(
|
||||
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
|
||||
);
|
||||
const { address, abi, wallet, client, account } = await getContract();
|
||||
|
||||
const messageToSign = ethers.utils.solidityKeccak256(
|
||||
["uint160", "uint256", "bytes32"],
|
||||
[
|
||||
pixKey,
|
||||
parseEther(String(amount)),
|
||||
ethers.utils.formatBytes32String(e2eId),
|
||||
]
|
||||
);
|
||||
console.log("Releasing lock", { lockID, pixtarget, signature });
|
||||
if (!wallet) {
|
||||
throw new Error("Wallet not connected");
|
||||
}
|
||||
|
||||
const messageHashBytes = ethers.utils.arrayify(messageToSign);
|
||||
const flatSig = await mockBacenSigner.signMessage(messageHashBytes);
|
||||
const provider = getProvider();
|
||||
// Convert pixtarget to bytes32
|
||||
const pixTimestamp = keccak256(stringToHex(pixtarget, { size: 32 }) );
|
||||
|
||||
const sig = ethers.utils.splitSignature(flatSig);
|
||||
const { request } = await client.simulateContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "release",
|
||||
args: [BigInt(lockID), pixTimestamp, stringToHex(signature)],
|
||||
account
|
||||
});
|
||||
|
||||
const signer = provider.getSigner();
|
||||
const p2pContract = new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
|
||||
|
||||
const release = await p2pContract.release(
|
||||
BigNumber.from(lockId),
|
||||
ethers.constants.AddressZero,
|
||||
ethers.utils.formatBytes32String(e2eId),
|
||||
sig.r,
|
||||
sig.s,
|
||||
sig.v
|
||||
);
|
||||
await release.wait();
|
||||
|
||||
return release;
|
||||
const hash = await wallet.writeContract(request);
|
||||
return client.waitForTransactionReceipt({ hash });
|
||||
};
|
||||
|
||||
const cancelDeposit = async (depositId: BigNumber): Promise<any> => {
|
||||
const contract = getContract();
|
||||
|
||||
const cancel = await contract.cancelDeposit(depositId);
|
||||
await cancel.wait();
|
||||
|
||||
return cancel;
|
||||
};
|
||||
|
||||
const withdrawDeposit = async (amount: string): Promise<any> => {
|
||||
const contract = getContract();
|
||||
|
||||
const withdraw = await contract.withdraw(
|
||||
getTokenAddress(),
|
||||
parseEther(String(amount)),
|
||||
[]
|
||||
);
|
||||
await withdraw.wait();
|
||||
|
||||
return withdraw;
|
||||
};
|
||||
|
||||
export { cancelDeposit, withdrawDeposit, addLock, releaseLock };
|
||||
|
@ -1,126 +1,176 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { Contract, ethers } from "ethers";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import { formatEther, toHex, stringToHex } from "viem";
|
||||
import type { PublicClient, Address } from "viem";
|
||||
|
||||
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";
|
||||
import { getP2PixAddress, getTokenAddress } from "./addresses";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import { p2PixAbi } from "./abi"
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import { getNetworkSubgraphURL, NetworkEnum, TokenEnum } from "@/model/NetworkEnum";
|
||||
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
||||
import type { Pix } from "@/model/Pix";
|
||||
import type { LockStatus } from "@/model/LockStatus"
|
||||
|
||||
const getNetworksLiquidity = async (): Promise<void> => {
|
||||
const etherStore = useEtherStore();
|
||||
const user = useUser();
|
||||
user.setLoadingNetworkLiquidity(true);
|
||||
|
||||
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 depositLists: ValidDeposit[][] = [];
|
||||
|
||||
const p2pContractGoerli = new ethers.Contract(
|
||||
getP2PixAddress(NetworkEnum.ethereum),
|
||||
p2pix.abi,
|
||||
goerliProvider
|
||||
for (const network of Object.values(NetworkEnum).filter(
|
||||
(v) => !isNaN(Number(v))
|
||||
)) {
|
||||
const deposits = await getValidDeposits(
|
||||
getTokenAddress(user.selectedToken.value),
|
||||
Number(network)
|
||||
);
|
||||
if (deposits) depositLists.push(deposits);
|
||||
}
|
||||
|
||||
const allDeposits = depositLists.flat();
|
||||
user.setDepositsValidList(allDeposits);
|
||||
user.setLoadingNetworkLiquidity(false);
|
||||
};
|
||||
|
||||
const getParticipantID = async (
|
||||
seller: string,
|
||||
token: string
|
||||
): Promise<string> => {
|
||||
const { address, abi, client } = await getContract();
|
||||
|
||||
const participantIDHex = await client.readContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "getPixTarget",
|
||||
args: [seller, token],
|
||||
});
|
||||
|
||||
// Remove '0x' prefix and convert hex to UTF-8 string
|
||||
const hexString =
|
||||
typeof participantIDHex === "string"
|
||||
? participantIDHex
|
||||
: toHex(participantIDHex as bigint);
|
||||
if (!hexString) throw new Error("Participant ID not found");
|
||||
const bytes = new Uint8Array(
|
||||
hexString
|
||||
.slice(2)
|
||||
.match(/.{1,2}/g)!
|
||||
.map((byte: string) => parseInt(byte, 16))
|
||||
);
|
||||
const p2pContractMumbai = new ethers.Contract(
|
||||
getP2PixAddress(NetworkEnum.polygon),
|
||||
p2pix.abi,
|
||||
mumbaiProvider
|
||||
);
|
||||
|
||||
etherStore.setLoadingNetworkLiquidity(true);
|
||||
const depositListGoerli = await getValidDeposits(
|
||||
getTokenAddress(NetworkEnum.ethereum),
|
||||
p2pContractGoerli
|
||||
);
|
||||
|
||||
const depositListMumbai = await getValidDeposits(
|
||||
getTokenAddress(NetworkEnum.polygon),
|
||||
p2pContractMumbai
|
||||
);
|
||||
|
||||
etherStore.setDepositsValidListGoerli(depositListGoerli);
|
||||
etherStore.setDepositsValidListMumbai(depositListMumbai);
|
||||
etherStore.setLoadingNetworkLiquidity(false);
|
||||
// Remove null bytes from the end of the string
|
||||
return new TextDecoder().decode(bytes).replace(/\0/g, "");
|
||||
};
|
||||
|
||||
const getValidDeposits = async (
|
||||
token: string,
|
||||
contract?: Contract
|
||||
token: Address,
|
||||
network: NetworkEnum,
|
||||
contractInfo?: { client: PublicClient; address: Address }
|
||||
): Promise<ValidDeposit[]> => {
|
||||
let p2pContract: Contract;
|
||||
let client: PublicClient, abi;
|
||||
|
||||
if (contract) {
|
||||
p2pContract = contract;
|
||||
if (contractInfo) {
|
||||
({ client } = contractInfo);
|
||||
abi = p2PixAbi;
|
||||
} else {
|
||||
p2pContract = getContract(true);
|
||||
({ abi, client } = await getContract(true));
|
||||
}
|
||||
|
||||
const filterDeposits = p2pContract.filters.DepositAdded(null);
|
||||
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
|
||||
// TODO: Remove this once we have a subgraph for rootstock
|
||||
if (network === NetworkEnum.rootstock) return [];
|
||||
|
||||
if (!contract) p2pContract = getContract(); // get metamask provider contract
|
||||
const depositList: { [key: string]: ValidDeposit } = {};
|
||||
|
||||
await Promise.all(
|
||||
eventsDeposits.map(async (deposit) => {
|
||||
// Get liquidity only for the selected token
|
||||
if (deposit.args?.token != token) return null;
|
||||
|
||||
const mappedBalance = await p2pContract.getBalance(
|
||||
deposit.args?.seller,
|
||||
token
|
||||
);
|
||||
|
||||
const mappedPixTarget = await p2pContract.getPixTarget(
|
||||
deposit.args?.seller,
|
||||
token
|
||||
);
|
||||
|
||||
let validDeposit: ValidDeposit | null = null;
|
||||
|
||||
if (mappedBalance._hex) {
|
||||
validDeposit = {
|
||||
token: token,
|
||||
blockNumber: deposit.blockNumber,
|
||||
remaining: Number(formatEther(mappedBalance._hex)),
|
||||
seller: deposit.args?.seller,
|
||||
pixKey: Number(mappedPixTarget._hex),
|
||||
};
|
||||
const body = {
|
||||
query: `
|
||||
{
|
||||
depositAddeds(where: { token: "${token}" }) {
|
||||
seller
|
||||
amount
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
if (validDeposit)
|
||||
depositList[deposit.args?.seller + token] = validDeposit;
|
||||
})
|
||||
const depositLogs = await fetch(getNetworkSubgraphURL(network), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
// remove doubles from sellers list
|
||||
const depositData = await depositLogs.json();
|
||||
const depositAddeds = depositData.data.depositAddeds;
|
||||
const uniqueSellers = depositAddeds.reduce(
|
||||
(acc: Record<Address, boolean>, deposit: any) => {
|
||||
acc[deposit.seller] = true;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<Address, boolean>
|
||||
);
|
||||
|
||||
if (!contractInfo) {
|
||||
// Get metamask provider contract
|
||||
({ abi, client } = await getContract(true));
|
||||
}
|
||||
|
||||
const depositList: { [key: string]: ValidDeposit } = {};
|
||||
|
||||
const sellersList = Object.keys(uniqueSellers) as Address[];
|
||||
// Use multicall to batch all getBalance requests
|
||||
const balanceCalls = sellersList.map((seller) => ({
|
||||
address: getP2PixAddress(network),
|
||||
abi,
|
||||
functionName: "getBalance",
|
||||
args: [seller, token],
|
||||
}));
|
||||
|
||||
const balanceResults = await client.multicall({
|
||||
contracts: balanceCalls as any,
|
||||
});
|
||||
|
||||
// Process results into the depositList
|
||||
sellersList.forEach((seller, index) => {
|
||||
const mappedBalance = balanceResults[index];
|
||||
|
||||
if (!mappedBalance.error && mappedBalance.result) {
|
||||
const validDeposit: ValidDeposit = {
|
||||
token,
|
||||
blockNumber: 0,
|
||||
remaining: Number(formatEther(mappedBalance.result as bigint)),
|
||||
seller,
|
||||
network,
|
||||
participantID: "",
|
||||
};
|
||||
depositList[seller + token] = validDeposit;
|
||||
}
|
||||
});
|
||||
return Object.values(depositList);
|
||||
};
|
||||
|
||||
const getUnreleasedLockById = async (
|
||||
lockID: string
|
||||
lockID: bigint
|
||||
): Promise<UnreleasedLock> => {
|
||||
const p2pContract = getContract();
|
||||
const pixData: Pix = {
|
||||
pixKey: "",
|
||||
};
|
||||
const { address, abi, client } = await getContract();
|
||||
|
||||
const lock = await p2pContract.mapLocks(lockID);
|
||||
|
||||
const pixTarget = lock.pixTarget;
|
||||
const amount = formatEther(lock?.amount);
|
||||
pixData.pixKey = String(Number(pixTarget));
|
||||
pixData.value = Number(amount);
|
||||
const [ , , , amount, token, seller ] = await client.readContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "mapLocks",
|
||||
args: [lockID],
|
||||
});
|
||||
|
||||
return {
|
||||
lockID: lockID,
|
||||
pix: pixData,
|
||||
lockID,
|
||||
amount: Number(formatEther(amount)),
|
||||
tokenAddress: token,
|
||||
sellerAddress: seller,
|
||||
};
|
||||
};
|
||||
|
||||
export { getValidDeposits, getNetworksLiquidity, getUnreleasedLockById };
|
||||
export {
|
||||
getValidDeposits,
|
||||
getNetworksLiquidity,
|
||||
getUnreleasedLockById,
|
||||
getParticipantID,
|
||||
};
|
||||
|
@ -1,95 +1,62 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
import p2pix from "@/utils/smart_contract_files/P2PIX.json";
|
||||
|
||||
import { p2PixAbi } from "./abi";
|
||||
import { updateWalletStatus } from "./wallet";
|
||||
import { getProviderUrl, getP2PixAddress } from "./addresses";
|
||||
import {
|
||||
getProviderUrl,
|
||||
isPossibleNetwork,
|
||||
possibleChains,
|
||||
network2Chain,
|
||||
getP2PixAddress,
|
||||
} from "./addresses";
|
||||
createPublicClient,
|
||||
createWalletClient,
|
||||
custom,
|
||||
http,
|
||||
PublicClient,
|
||||
WalletClient,
|
||||
} from "viem";
|
||||
import { sepolia, rootstock } from "viem/chains";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
|
||||
import { ethers } from "ethers";
|
||||
let walletClient: WalletClient | null = null;
|
||||
|
||||
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 getPublicClient = (): PublicClient => {
|
||||
const user = useUser();
|
||||
const rpcUrl = getProviderUrl();
|
||||
return createPublicClient({
|
||||
chain:
|
||||
Number(user.networkName.value) === sepolia.id ? sepolia : rootstock,
|
||||
transport: http(rpcUrl),
|
||||
});
|
||||
};
|
||||
|
||||
const getContract = (onlyAlchemyProvider: boolean = false) => {
|
||||
const provider = getProvider(onlyAlchemyProvider);
|
||||
const signer = provider.getSigner();
|
||||
return new ethers.Contract(getP2PixAddress(), p2pix.abi, signer);
|
||||
const getWalletClient = (): WalletClient | null => {
|
||||
return walletClient;
|
||||
};
|
||||
|
||||
const connectProvider = async (): Promise<void> => {
|
||||
const window_ = window as any;
|
||||
const connection = window_.ethereum;
|
||||
const provider = getProvider();
|
||||
const getContract = async (onlyRpcProvider = false) => {
|
||||
const client = getPublicClient();
|
||||
const address = getP2PixAddress();
|
||||
const abi = p2PixAbi;
|
||||
const wallet = onlyRpcProvider ? null : getWalletClient();
|
||||
|
||||
if (!(provider instanceof ethers.providers.Web3Provider)) {
|
||||
window.alert("Please, connect to metamask extension");
|
||||
return;
|
||||
if (!client) {
|
||||
throw new Error("Public client not initialized");
|
||||
}
|
||||
|
||||
const [account] = wallet ? await wallet.getAddresses() : [null];
|
||||
|
||||
return { address, abi, client, wallet, account };
|
||||
};
|
||||
|
||||
const connectProvider = async (p: any): Promise<void> => {
|
||||
const user = useUser();
|
||||
const chain =
|
||||
Number(user.networkName.value) === sepolia.id ? sepolia : rootstock;
|
||||
|
||||
const [account] = await p!.request({ method: "eth_requestAccounts" });
|
||||
|
||||
walletClient = createWalletClient({
|
||||
account,
|
||||
chain,
|
||||
transport: custom(p),
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
export { getPublicClient, getWalletClient, getContract, connectProvider };
|
||||
|
@ -1,44 +1,88 @@
|
||||
import { getContract, getProvider } from "./provider";
|
||||
import { getContract, getPublicClient, getWalletClient } from "./provider";
|
||||
import { getTokenAddress, getP2PixAddress } from "./addresses";
|
||||
import { parseEther } from "ethers/lib/utils";
|
||||
import { parseEther, toHex } from "viem";
|
||||
import { sepolia, rootstock } from "viem/chains";
|
||||
|
||||
import { ethers } from "ethers";
|
||||
import { mockTokenAbi } from "./abi";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import { createParticipant } from "@/utils/bbPay";
|
||||
import type { Participant } from "@/utils/bbPay";
|
||||
|
||||
import mockToken from "../utils/smart_contract_files/MockToken.json";
|
||||
const approveTokens = async (participant: Participant): Promise<any> => {
|
||||
const user = useUser();
|
||||
const publicClient = getPublicClient();
|
||||
const walletClient = getWalletClient();
|
||||
|
||||
const approveTokens = async (tokenQty: string): Promise<any> => {
|
||||
const provider = getProvider();
|
||||
const signer = provider.getSigner();
|
||||
if (!publicClient || !walletClient) {
|
||||
throw new Error("Clients not initialized");
|
||||
}
|
||||
|
||||
const tokenContract = new ethers.Contract(
|
||||
getTokenAddress(),
|
||||
mockToken.abi,
|
||||
signer
|
||||
);
|
||||
user.setSeller(participant);
|
||||
const [account] = await walletClient.getAddresses();
|
||||
|
||||
const apprv = await tokenContract.approve(
|
||||
getP2PixAddress(),
|
||||
parseEther(tokenQty)
|
||||
);
|
||||
// Get token address
|
||||
const tokenAddress = getTokenAddress(user.selectedToken.value);
|
||||
|
||||
await apprv.wait();
|
||||
return apprv;
|
||||
// Check if the token is already approved
|
||||
const allowance = await publicClient.readContract({
|
||||
address: tokenAddress,
|
||||
abi: mockTokenAbi,
|
||||
functionName: "allowance",
|
||||
args: [account, getP2PixAddress()],
|
||||
});
|
||||
|
||||
if ( allowance < parseEther(participant.offer.toString()) ) {
|
||||
// Approve tokens
|
||||
const chain = user.networkId.value === sepolia.id ? sepolia : rootstock;
|
||||
const hash = await walletClient.writeContract({
|
||||
address: tokenAddress,
|
||||
abi: mockTokenAbi,
|
||||
functionName: "approve",
|
||||
args: [getP2PixAddress(), parseEther(participant.offer.toString())],
|
||||
account,
|
||||
chain,
|
||||
});
|
||||
|
||||
await publicClient.waitForTransactionReceipt({ hash });
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const addDeposit = async (tokenQty: string, pixKey: string): Promise<any> => {
|
||||
const p2pContract = getContract();
|
||||
const addDeposit = async (): Promise<any> => {
|
||||
const { address, abi, client } = await getContract();
|
||||
const walletClient = getWalletClient();
|
||||
const user = useUser();
|
||||
|
||||
const deposit = await p2pContract.deposit(
|
||||
getTokenAddress(),
|
||||
parseEther(tokenQty),
|
||||
pixKey,
|
||||
true,
|
||||
ethers.utils.formatBytes32String("")
|
||||
);
|
||||
if (!walletClient) {
|
||||
throw new Error("Wallet client not initialized");
|
||||
}
|
||||
|
||||
await deposit.wait();
|
||||
const [account] = await walletClient.getAddresses();
|
||||
|
||||
return deposit;
|
||||
const sellerId = await createParticipant(user.seller.value);
|
||||
user.setSellerId(sellerId.id);
|
||||
if (!sellerId.id) {
|
||||
throw new Error("Failed to create participant");
|
||||
}
|
||||
const chain = user.networkId.value === sepolia.id ? sepolia : rootstock;
|
||||
const hash = await walletClient.writeContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "deposit",
|
||||
args: [
|
||||
user.networkId.value + "-" + sellerId.id,
|
||||
toHex("", { size: 32 }),
|
||||
getTokenAddress(user.selectedToken.value),
|
||||
parseEther(user.seller.value.offer.toString()),
|
||||
true,
|
||||
],
|
||||
account,
|
||||
chain,
|
||||
});
|
||||
|
||||
const receipt = await client.waitForTransactionReceipt({ hash });
|
||||
return receipt;
|
||||
};
|
||||
|
||||
export { approveTokens, addDeposit };
|
||||
|
@ -1,50 +1,48 @@
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { formatEther, hexToString, type Address } from "viem";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
|
||||
import { getContract, getProvider } from "./provider";
|
||||
import { getTokenAddress, possibleChains } from "./addresses";
|
||||
import { getPublicClient, getWalletClient, getContract } from "./provider";
|
||||
import { getTokenAddress } from "./addresses";
|
||||
|
||||
import mockToken from "@/utils/smart_contract_files/MockToken.json";
|
||||
|
||||
import { ethers, type Event, type BigNumber } from "ethers";
|
||||
import { formatEther } from "ethers/lib/utils";
|
||||
import { getValidDeposits } from "./events";
|
||||
import { getValidDeposits, getUnreleasedLockById } from "./events";
|
||||
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||
import type { UnreleasedLock } from "@/model/UnreleasedLock";
|
||||
import type { Pix } from "@/model/Pix";
|
||||
import { LockStatus } from "@/model/LockStatus";
|
||||
import { getNetworkSubgraphURL } from "@/model/NetworkEnum";
|
||||
|
||||
const updateWalletStatus = async (): Promise<void> => {
|
||||
const etherStore = useEtherStore();
|
||||
export const updateWalletStatus = async (): Promise<void> => {
|
||||
const user = useUser();
|
||||
|
||||
const provider = getProvider();
|
||||
const signer = provider.getSigner();
|
||||
const publicClient = getPublicClient();
|
||||
const walletClient = getWalletClient();
|
||||
|
||||
const { chainId } = await provider.getNetwork();
|
||||
etherStore.setNetworkName(possibleChains[chainId]);
|
||||
if (!publicClient || !walletClient) {
|
||||
console.error("Client not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
const mockTokenContract = new ethers.Contract(
|
||||
getTokenAddress(),
|
||||
mockToken.abi,
|
||||
signer
|
||||
);
|
||||
// Get balance
|
||||
const [account] = await walletClient.getAddresses();
|
||||
const balance = await publicClient.getBalance({ address: account });
|
||||
|
||||
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]));
|
||||
user.setWalletAddress(account);
|
||||
user.setBalance(formatEther(balance));
|
||||
};
|
||||
|
||||
const listValidDepositTransactionsByWalletAddress = async (
|
||||
walletAddress: string
|
||||
export const listValidDepositTransactionsByWalletAddress = async (
|
||||
walletAddress: Address
|
||||
): Promise<ValidDeposit[]> => {
|
||||
const walletDeposits = await getValidDeposits(getTokenAddress());
|
||||
|
||||
const user = useUser();
|
||||
const walletDeposits = await getValidDeposits(
|
||||
getTokenAddress(user.selectedToken.value),
|
||||
user.networkName.value
|
||||
);
|
||||
if (walletDeposits) {
|
||||
return walletDeposits
|
||||
.filter((deposit) => deposit.seller == walletAddress)
|
||||
.sort((a, b) => {
|
||||
.sort((a: ValidDeposit, b: ValidDeposit) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
});
|
||||
}
|
||||
@ -52,191 +50,431 @@ const listValidDepositTransactionsByWalletAddress = async (
|
||||
return [];
|
||||
};
|
||||
|
||||
const getLockStatus = async (id: [BigNumber]): Promise<number> => {
|
||||
const p2pContract = getContract();
|
||||
const res = await p2pContract.getLocksStatus([id]);
|
||||
|
||||
return res[1][0];
|
||||
const getLockStatus = async (id: bigint): Promise<LockStatus> => {
|
||||
const { address, abi, client } = await getContract();
|
||||
const [ sortedIDs , status ] = await client.readContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "getLocksStatus",
|
||||
args: [[id]],
|
||||
});
|
||||
return status[0];
|
||||
};
|
||||
|
||||
const filterLockStatus = async (
|
||||
transactions: Event[]
|
||||
export const listAllTransactionByWalletAddress = async (
|
||||
walletAddress: Address
|
||||
): Promise<WalletTransaction[]> => {
|
||||
const txs = await Promise.all(
|
||||
transactions.map(async (transaction) => {
|
||||
const tx: WalletTransaction = {
|
||||
token: transaction.args?.token ? transaction.args?.token : "",
|
||||
blockNumber: transaction.blockNumber ? transaction.blockNumber : -1,
|
||||
amount: transaction.args?.amount
|
||||
? Number(formatEther(transaction.args?.amount))
|
||||
: -1,
|
||||
seller: transaction.args?.seller ? transaction.args?.seller : "",
|
||||
buyer: transaction.args?.buyer ? transaction.args?.buyer : "",
|
||||
event: transaction.event ? transaction.event : "",
|
||||
lockStatus:
|
||||
transaction.event == "LockAdded"
|
||||
? await getLockStatus(transaction.args?.lockID)
|
||||
: -1,
|
||||
transactionHash: transaction.transactionHash
|
||||
? transaction.transactionHash
|
||||
: "",
|
||||
transactionID: transaction.args?.lockID
|
||||
? String(transaction.args?.lockID)
|
||||
: "",
|
||||
};
|
||||
const user = useUser();
|
||||
|
||||
return tx;
|
||||
})
|
||||
);
|
||||
// Get the current network for the subgraph URL
|
||||
const network = user.networkName.value;
|
||||
|
||||
return txs;
|
||||
};
|
||||
// Query subgraph for all relevant transactions
|
||||
const subgraphQuery = {
|
||||
query: `
|
||||
{
|
||||
depositAddeds(where: {seller: "${walletAddress.toLowerCase()}"}) {
|
||||
id
|
||||
seller
|
||||
token
|
||||
amount
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
lockAddeds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||
buyer
|
||||
lockID
|
||||
seller
|
||||
amount
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
lockReleaseds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||
buyer
|
||||
lockId
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
depositWithdrawns(where: {seller: "${walletAddress.toLowerCase()}"}) {
|
||||
seller
|
||||
token
|
||||
amount
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
const listAllTransactionByWalletAddress = async (
|
||||
walletAddress: string
|
||||
): Promise<WalletTransaction[]> => {
|
||||
const p2pContract = getContract(true);
|
||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(subgraphQuery),
|
||||
});
|
||||
|
||||
const filterDeposits = p2pContract.filters.DepositAdded([walletAddress]);
|
||||
const eventsDeposits = await p2pContract.queryFilter(filterDeposits);
|
||||
const data = await response.json();
|
||||
// Convert all transactions to common WalletTransaction format
|
||||
const transactions: WalletTransaction[] = [];
|
||||
|
||||
const filterAddedLocks = p2pContract.filters.LockAdded([walletAddress]);
|
||||
const eventsAddedLocks = await p2pContract.queryFilter(filterAddedLocks);
|
||||
// Process deposit added events
|
||||
if (data.data?.depositAddeds) {
|
||||
for (const deposit of data.data.depositAddeds) {
|
||||
transactions.push({
|
||||
token: deposit.token,
|
||||
blockNumber: parseInt(deposit.blockNumber),
|
||||
amount: parseFloat(formatEther(BigInt(deposit.amount))),
|
||||
seller: deposit.seller,
|
||||
buyer: "",
|
||||
event: "DepositAdded",
|
||||
lockStatus: undefined,
|
||||
transactionHash: deposit.transactionHash,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
|
||||
const eventsReleasedLocks = await p2pContract.queryFilter(
|
||||
filterReleasedLocks
|
||||
);
|
||||
// Process lock added events
|
||||
if (data.data?.lockAddeds) {
|
||||
for (const lock of data.data.lockAddeds) {
|
||||
// Get lock status from the contract
|
||||
const lockStatus = await getLockStatus(BigInt(lock.lockID));
|
||||
|
||||
const filterWithdrawnDeposits = p2pContract.filters.DepositWithdrawn([
|
||||
walletAddress,
|
||||
]);
|
||||
const eventsWithdrawnDeposits = await p2pContract.queryFilter(
|
||||
filterWithdrawnDeposits
|
||||
);
|
||||
transactions.push({
|
||||
token: lock.token,
|
||||
blockNumber: parseInt(lock.blockNumber),
|
||||
amount: parseFloat(formatEther(BigInt(lock.amount))),
|
||||
seller: lock.seller,
|
||||
buyer: lock.buyer,
|
||||
event: "LockAdded",
|
||||
lockStatus: lockStatus,
|
||||
transactionHash: lock.transactionHash,
|
||||
transactionID: lock.lockID.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const lockStatusFiltered = await filterLockStatus(
|
||||
[
|
||||
...eventsDeposits,
|
||||
...eventsAddedLocks,
|
||||
...eventsReleasedLocks,
|
||||
...eventsWithdrawnDeposits,
|
||||
].sort((a, b) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
})
|
||||
);
|
||||
// Process lock released events
|
||||
if (data.data?.lockReleaseds) {
|
||||
for (const release of data.data.lockReleaseds) {
|
||||
transactions.push({
|
||||
token: undefined, // Subgraph doesn't provide token in this event, we could enhance this later
|
||||
blockNumber: parseInt(release.blockNumber),
|
||||
amount: -1, // Amount not available in this event
|
||||
seller: "",
|
||||
buyer: release.buyer,
|
||||
event: "LockReleased",
|
||||
lockStatus: undefined,
|
||||
transactionHash: release.transactionHash,
|
||||
transactionID: release.lockId.toString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return lockStatusFiltered;
|
||||
// Process deposit withdrawn events
|
||||
if (data.data?.depositWithdrawns) {
|
||||
for (const withdrawal of data.data.depositWithdrawns) {
|
||||
transactions.push({
|
||||
token: withdrawal.token,
|
||||
blockNumber: parseInt(withdrawal.blockNumber),
|
||||
amount: parseFloat(formatEther(BigInt(withdrawal.amount))),
|
||||
seller: withdrawal.seller,
|
||||
buyer: "",
|
||||
event: "DepositWithdrawn",
|
||||
lockStatus: undefined,
|
||||
transactionHash: withdrawal.transactionHash,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort transactions by block number (newest first)
|
||||
return transactions.sort((a, b) => b.blockNumber - a.blockNumber);
|
||||
};
|
||||
|
||||
// get wallet's release transactions
|
||||
const listReleaseTransactionByWalletAddress = async (
|
||||
walletAddress: string
|
||||
): Promise<Event[]> => {
|
||||
const p2pContract = getContract(true);
|
||||
export const listReleaseTransactionByWalletAddress = async (
|
||||
walletAddress: Address
|
||||
) => {
|
||||
const user = useUser();
|
||||
const network = user.networkName.value;
|
||||
|
||||
const filterReleasedLocks = p2pContract.filters.LockReleased([walletAddress]);
|
||||
const eventsReleasedLocks = await p2pContract.queryFilter(
|
||||
filterReleasedLocks
|
||||
);
|
||||
|
||||
return eventsReleasedLocks.sort((a, b) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
});
|
||||
};
|
||||
|
||||
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 listLockTransactionBySellerAddress = async (
|
||||
sellerAddress: string
|
||||
): Promise<Event[]> => {
|
||||
const p2pContract = getContract(true);
|
||||
|
||||
const filterAddedLocks = p2pContract.filters.LockAdded();
|
||||
const eventsReleasedLocks = await p2pContract.queryFilter(filterAddedLocks);
|
||||
|
||||
return eventsReleasedLocks.filter((lock) =>
|
||||
lock.args?.seller
|
||||
.toHexString()
|
||||
.substring(3)
|
||||
.includes(sellerAddress.substring(2).toLowerCase())
|
||||
);
|
||||
};
|
||||
|
||||
const checkUnreleasedLock = async (
|
||||
walletAddress: string
|
||||
): Promise<UnreleasedLock | undefined> => {
|
||||
const p2pContract = getContract();
|
||||
const pixData: Pix = {
|
||||
pixKey: "",
|
||||
// Query subgraph for release transactions
|
||||
const subgraphQuery = {
|
||||
query: `
|
||||
{
|
||||
lockReleaseds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||
buyer
|
||||
lockId
|
||||
e2eId
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
// Fetch data from subgraph
|
||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(subgraphQuery),
|
||||
});
|
||||
|
||||
if (unreleasedLockId != -1) {
|
||||
const _lockID = lockStatus[0][unreleasedLockId];
|
||||
const lock = await p2pContract.mapLocks(_lockID);
|
||||
const data = await response.json();
|
||||
|
||||
const pixTarget = lock.pixTarget;
|
||||
const amount = formatEther(lock?.amount);
|
||||
pixData.pixKey = String(Number(pixTarget));
|
||||
pixData.value = Number(amount);
|
||||
// Process the subgraph response into the same format as the previous implementation
|
||||
if (!data.data?.lockReleaseds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return {
|
||||
lockID: _lockID,
|
||||
pix: pixData,
|
||||
};
|
||||
// Transform the subgraph data to match the event log decode format
|
||||
return data.data.lockReleaseds
|
||||
.sort((a: any, b: any) => {
|
||||
return parseInt(b.blockNumber) - parseInt(a.blockNumber);
|
||||
})
|
||||
.map((release: any) => {
|
||||
try {
|
||||
// Create a structure similar to the decoded event log
|
||||
return {
|
||||
eventName: "LockReleased",
|
||||
args: {
|
||||
buyer: release.buyer,
|
||||
lockID: BigInt(release.lockId),
|
||||
e2eId: release.e2eId,
|
||||
},
|
||||
// Add any other necessary fields to match the original return format
|
||||
blockNumber: BigInt(release.blockNumber),
|
||||
transactionHash: release.transactionHash,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error processing subgraph data", error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((decoded: any) => decoded !== null);
|
||||
};
|
||||
|
||||
const listLockTransactionByWalletAddress = async (walletAddress: Address) => {
|
||||
const user = useUser();
|
||||
const network = user.networkName.value;
|
||||
|
||||
// Query subgraph for lock added transactions
|
||||
const subgraphQuery = {
|
||||
query: `
|
||||
{
|
||||
lockAddeds(where: {buyer: "${walletAddress.toLowerCase()}"}) {
|
||||
buyer
|
||||
lockID
|
||||
seller
|
||||
amount
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
try {
|
||||
// Fetch data from subgraph
|
||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(subgraphQuery),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.data?.lockAddeds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Transform the subgraph data to match the event log decode format
|
||||
return data.data.lockAddeds
|
||||
.sort((a: any, b: any) => {
|
||||
return parseInt(b.blockNumber) - parseInt(a.blockNumber);
|
||||
})
|
||||
.map((lock: any) => {
|
||||
try {
|
||||
// Create a structure similar to the decoded event log
|
||||
return {
|
||||
eventName: "LockAdded",
|
||||
args: {
|
||||
buyer: lock.buyer,
|
||||
lockID: BigInt(lock.lockID),
|
||||
seller: lock.seller,
|
||||
token: lock.token,
|
||||
amount: BigInt(lock.amount),
|
||||
},
|
||||
// Add other necessary fields to match the original format
|
||||
blockNumber: BigInt(lock.blockNumber),
|
||||
transactionHash: lock.transactionHash,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error processing subgraph data", error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((decoded: any) => decoded !== null);
|
||||
} catch (error) {
|
||||
console.error("Error fetching from subgraph:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getActiveLockAmount = async (walletAddress: string): Promise<number> => {
|
||||
const p2pContract = getContract();
|
||||
const listLockTransactionBySellerAddress = async (sellerAddress: Address) => {
|
||||
const user = useUser();
|
||||
const network = user.networkName.value;
|
||||
|
||||
// Query subgraph for lock added transactions where seller matches
|
||||
const subgraphQuery = {
|
||||
query: `
|
||||
{
|
||||
lockAddeds(where: {seller: "${sellerAddress.toLowerCase()}"}) {
|
||||
buyer
|
||||
lockID
|
||||
seller
|
||||
token
|
||||
amount
|
||||
blockTimestamp
|
||||
blockNumber
|
||||
transactionHash
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
|
||||
try {
|
||||
// Fetch data from subgraph
|
||||
const response = await fetch(getNetworkSubgraphURL(network), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(subgraphQuery),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.data?.lockAddeds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Transform the subgraph data to match the event log decode format
|
||||
return data.data.lockAddeds
|
||||
.sort((a: any, b: any) => {
|
||||
return parseInt(b.blockNumber) - parseInt(a.blockNumber);
|
||||
})
|
||||
.map((lock: any) => {
|
||||
try {
|
||||
// Create a structure similar to the decoded event log
|
||||
return {
|
||||
eventName: "LockAdded",
|
||||
args: {
|
||||
buyer: lock.buyer,
|
||||
lockID: BigInt(lock.lockID),
|
||||
seller: lock.seller,
|
||||
token: lock.token,
|
||||
amount: BigInt(lock.amount),
|
||||
},
|
||||
// Add other necessary fields to match the original format
|
||||
blockNumber: BigInt(lock.blockNumber),
|
||||
transactionHash: lock.transactionHash,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error processing subgraph data", error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter((decoded: any) => decoded !== null);
|
||||
} catch (error) {
|
||||
console.error("Error fetching from subgraph:", error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const checkUnreleasedLock = async (
|
||||
walletAddress: Address
|
||||
): Promise<UnreleasedLock | undefined> => {
|
||||
const { address, abi, client } = await getContract();
|
||||
const addedLocks = await listLockTransactionByWalletAddress(walletAddress);
|
||||
|
||||
if (!addedLocks.length) return undefined;
|
||||
|
||||
const lockIds = addedLocks.map((lock: any) => lock.args.lockID);
|
||||
|
||||
const [ sortedIDs, status ] = await client.readContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "getLocksStatus",
|
||||
args: [lockIds],
|
||||
});
|
||||
|
||||
const unreleasedLockId = status.findIndex(
|
||||
(status: LockStatus) => status == LockStatus.Active
|
||||
);
|
||||
|
||||
if (unreleasedLockId !== -1)
|
||||
return getUnreleasedLockById(sortedIDs[unreleasedLockId]);
|
||||
};
|
||||
|
||||
export const getActiveLockAmount = async (
|
||||
walletAddress: Address
|
||||
): Promise<number> => {
|
||||
const { address, abi, client } = await getContract(true);
|
||||
const lockSeller = await listLockTransactionBySellerAddress(walletAddress);
|
||||
|
||||
const lockStatus = await p2pContract.getLocksStatus(
|
||||
lockSeller.map((lock) => lock.args?.lockID)
|
||||
if (!lockSeller.length) return 0;
|
||||
|
||||
const lockIds = lockSeller.map((lock: any) => lock.args.lockID);
|
||||
|
||||
const [ sortedIDs, status ] = await client.readContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "getLocksStatus",
|
||||
args: [lockIds],
|
||||
});
|
||||
|
||||
const mapLocksRequests = status.map((id: LockStatus) =>
|
||||
client.readContract({
|
||||
address: address,
|
||||
abi,
|
||||
functionName: "mapLocks",
|
||||
args: [BigInt(id)],
|
||||
})
|
||||
);
|
||||
|
||||
const activeLockAmount = await lockStatus[1].reduce(
|
||||
async (sumValue: Promise<number>, currentStatus: number, index: number) => {
|
||||
const currValue = await sumValue;
|
||||
let valueToSum = 0;
|
||||
const mapLocksResults = await client.multicall({
|
||||
contracts: mapLocksRequests as any,
|
||||
});
|
||||
|
||||
if (currentStatus == 1) {
|
||||
const lock = await p2pContract.mapLocks(lockStatus[0][index]);
|
||||
valueToSum = Number(formatEther(lock?.amount));
|
||||
}
|
||||
|
||||
return currValue + valueToSum;
|
||||
},
|
||||
Promise.resolve(0)
|
||||
);
|
||||
|
||||
return activeLockAmount;
|
||||
return mapLocksResults.reduce((total: number, lock: any, index: number) => {
|
||||
if (status[index] === 1) {
|
||||
return total + Number(formatEther(lock.amount));
|
||||
}
|
||||
return total;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
export {
|
||||
updateWalletStatus,
|
||||
listValidDepositTransactionsByWalletAddress,
|
||||
listAllTransactionByWalletAddress,
|
||||
listReleaseTransactionByWalletAddress,
|
||||
checkUnreleasedLock,
|
||||
getActiveLockAmount,
|
||||
export const getSellerParticipantId = async (
|
||||
sellerAddress: Address,
|
||||
tokenAddress: Address
|
||||
): Promise<string> => {
|
||||
const { address, abi, client } = await getContract();
|
||||
|
||||
const participantId = await client.readContract({
|
||||
address,
|
||||
abi,
|
||||
functionName: "getPixTarget",
|
||||
args: [sellerAddress, tokenAddress],
|
||||
});
|
||||
return hexToString(participantId);
|
||||
};
|
||||
|
@ -8,8 +8,7 @@ import {
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import ListingComponent from "../ListingComponent/ListingComponent.vue";
|
||||
|
||||
@ -19,8 +18,8 @@ const props = defineProps<{
|
||||
isCurrentStep: boolean;
|
||||
}>();
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
const { walletAddress } = storeToRefs(etherStore);
|
||||
const user = useUser();
|
||||
const { walletAddress } = useUser();
|
||||
|
||||
const lastWalletTransactions = ref<WalletTransaction[]>([]);
|
||||
const depositList = ref<ValidDeposit[]>([]);
|
||||
@ -29,7 +28,7 @@ const activeLockAmount = ref<number>(0);
|
||||
// methods
|
||||
|
||||
const getWalletTransactions = async () => {
|
||||
etherStore.setLoadingWalletTransactions(true);
|
||||
user.setLoadingWalletTransactions(true);
|
||||
if (walletAddress.value) {
|
||||
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
|
||||
walletAddress.value
|
||||
@ -48,20 +47,20 @@ const getWalletTransactions = async () => {
|
||||
lastWalletTransactions.value = allUserTransactions;
|
||||
}
|
||||
}
|
||||
etherStore.setLoadingWalletTransactions(false);
|
||||
user.setLoadingWalletTransactions(false);
|
||||
};
|
||||
|
||||
const callWithdraw = async (amount: string) => {
|
||||
if (amount) {
|
||||
etherStore.setLoadingWalletTransactions(true);
|
||||
const withdraw = await withdrawDeposit(amount);
|
||||
user.setLoadingWalletTransactions(true);
|
||||
const withdraw = await withdrawDeposit(amount, user.selectedToken.value);
|
||||
if (withdraw) {
|
||||
console.log("Saque realizado!");
|
||||
await getWalletTransactions();
|
||||
} else {
|
||||
console.log("Não foi possível realizar o saque!");
|
||||
}
|
||||
etherStore.setLoadingWalletTransactions(false);
|
||||
user.setLoadingWalletTransactions(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -86,19 +85,21 @@ onMounted(async () => {
|
||||
para a sua carteira!
|
||||
</span>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div class="main-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">{{ props.tokenAmount }} BRZ</p>
|
||||
<p class="text-2xl text-gray-900">
|
||||
{{ props.tokenAmount }} {{ user.selectedToken }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<p class="text-sm">
|
||||
<b>Não encontrou os tokens? </b><br />Clique no botão abaixo para
|
||||
<br />
|
||||
cadastrar o BRZ em sua carteira.
|
||||
cadastrar o {{ user.selectedToken }} em sua carteira.
|
||||
</p>
|
||||
</div>
|
||||
<CustomButton :text="'Cadastrar token'" @buttonClicked="() => {}" />
|
||||
@ -143,42 +144,13 @@ p {
|
||||
.text {
|
||||
@apply text-white 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 w-full max-w-xs md:max-w-lg flex-col justify-center items-center px-8 py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
||||
}
|
||||
|
||||
.last-release-info {
|
||||
@apply font-medium text-base text-gray-900;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.lg-view {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sm-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.lg-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sm-view {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,37 +0,0 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import BuyConfirmedComponent from "../BuyConfirmedComponent.vue";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
|
||||
describe("BuyConfirmedComponent.vue", async () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
const wrapper = mount(BuyConfirmedComponent, {
|
||||
props: {
|
||||
tokenAmount: 1,
|
||||
isCurrentStep: false,
|
||||
},
|
||||
});
|
||||
|
||||
// test("Test component Header Text", () => {
|
||||
// expect(wrapper.html()).toContain("Os tokens já foram transferidos");
|
||||
// expect(wrapper.html()).toContain("para a sua carteira!");
|
||||
// });
|
||||
|
||||
// test("Test component Container Text", () => {
|
||||
// expect(wrapper.html()).toContain("Tokens recebidos");
|
||||
// expect(wrapper.html()).toContain("BRZ");
|
||||
// expect(wrapper.html()).toContain("Não encontrou os tokens?");
|
||||
// expect(wrapper.html()).toContain("Clique no botão abaixo para");
|
||||
// expect(wrapper.html()).toContain("cadastrar o BRZ em sua carteira.");
|
||||
// });
|
||||
|
||||
test("Test makeAnotherTransactionEmit", async () => {
|
||||
wrapper.vm.$emit("makeAnotherTransaction");
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted("makeAnotherTransaction")).toBeTruthy();
|
||||
});
|
||||
});
|
@ -38,9 +38,9 @@ switch (props.type) {
|
||||
<div
|
||||
class="modal-overlay sm:h-12 h-full inset-0 absolute backdrop-blur-sm sm:backdrop-blur-none"
|
||||
>
|
||||
<div class="modal px-12 pl-72 text-center sm:flex justify-between hidden">
|
||||
<div class="modal px-12 text-center sm:flex justify-between hidden">
|
||||
<div class="flex items-center">
|
||||
<p class="text-black tracking-tighter leading-tight my-2">
|
||||
<p class="text-black tracking-tighter leading-tight py-2">
|
||||
{{ alertText }}
|
||||
</p>
|
||||
<button v-if="props.type === 'redirect'" @click="$emit('go-to-lock')">
|
||||
@ -110,7 +110,6 @@ switch (props.type) {
|
||||
border-radius: 10px;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
padding-left: v-bind(alertPaddingLeft);
|
||||
}
|
||||
|
||||
.modal-mobile {
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import CustomButton from "../CustomButton.vue";
|
||||
|
||||
describe("CustomButton.vue", () => {
|
||||
test("Test button content", () => {
|
||||
const wrapper = mount(CustomButton, {
|
||||
props: {
|
||||
text: "Testing",
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("Testing");
|
||||
});
|
||||
|
||||
test("Test if disabled props works", () => {
|
||||
const wrapper = mount(CustomButton, {
|
||||
props: {
|
||||
isDisabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
//@ts-ignore
|
||||
const button = wrapper.find(".button") as HTMLButtonElement;
|
||||
//@ts-ignore
|
||||
expect(button.element.disabled).toBe(true);
|
||||
});
|
||||
});
|
@ -17,50 +17,52 @@ if (props.isRedirectModal) {
|
||||
</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 só 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">
|
||||
<div>
|
||||
<div
|
||||
class="modal-overlay inset-0 fixed hidden md:block 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 só será processada após efetuado o pagamento do Pix. 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-white-400 mt-2 font-semibold"
|
||||
class="border-2 border-solid border-amber-400 mt-2"
|
||||
>
|
||||
Não
|
||||
Entendi
|
||||
</button>
|
||||
<button
|
||||
@click="$emit('go-to-lock')"
|
||||
class="border-2 border-solid border-white-400 mt-2 font-semibold"
|
||||
</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"
|
||||
>
|
||||
Sim
|
||||
</button>
|
||||
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>
|
||||
</div>
|
||||
|
@ -1,27 +0,0 @@
|
||||
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");
|
||||
});
|
||||
});
|
@ -1,17 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { withdrawDeposit } from "@/blockchain/buyerMethods";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import SpinnerComponent from "../SpinnerComponent.vue";
|
||||
import { decimalCount } from "@/utils/decimalCount";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { useFloating, arrow, offset, flip, shift } from "@floating-ui/vue";
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
const user = useUser();
|
||||
|
||||
// props
|
||||
const props = defineProps<{
|
||||
@ -22,8 +20,9 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits(["depositWithdrawn"]);
|
||||
|
||||
const { loadingWalletTransactions } = storeToRefs(etherStore);
|
||||
const remaining = ref<number>(0.0);
|
||||
const { loadingWalletTransactions } = user;
|
||||
|
||||
const remaining = ref<number>(0);
|
||||
const itemsToShow = ref<WalletTransaction[]>([]);
|
||||
const withdrawAmount = ref<string>("");
|
||||
const withdrawButtonOpacity = ref<number>(0.6);
|
||||
@ -84,8 +83,7 @@ watch(withdrawAmount, (): void => {
|
||||
});
|
||||
|
||||
const getRemaining = (): number => {
|
||||
if (props.validDeposits instanceof Array) {
|
||||
// Here we are getting only the first element of the list because
|
||||
if (props.validDeposits.length > 0) {
|
||||
// in this release only the BRL token is being used.
|
||||
const deposit = props.validDeposits[0];
|
||||
remaining.value = deposit ? deposit.remaining : 0;
|
||||
@ -95,7 +93,7 @@ const getRemaining = (): number => {
|
||||
};
|
||||
|
||||
const getExplorer = (): string => {
|
||||
return etherStore.networkName == NetworkEnum.ethereum
|
||||
return user.networkName.value == NetworkEnum.sepolia
|
||||
? "Etherscan"
|
||||
: "Polygonscan";
|
||||
};
|
||||
@ -106,8 +104,8 @@ const showInitialItems = (): void => {
|
||||
|
||||
const openEtherscanUrl = (transactionHash: string): void => {
|
||||
const networkUrl =
|
||||
etherStore.networkName == NetworkEnum.ethereum
|
||||
? "goerli.etherscan.io"
|
||||
user.networkName.value == NetworkEnum.sepolia
|
||||
? "sepolia.etherscan.io"
|
||||
: "mumbai.polygonscan.com";
|
||||
const url = `https://${networkUrl}/tx/${transactionHash}`;
|
||||
window.open(url, "_blank");
|
||||
@ -161,10 +159,14 @@ showInitialItems();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="blur-container" v-if="loadingWalletTransactions">
|
||||
<div
|
||||
class="main-container max-w-md flex justify-center items-center min-h-[200px] w-16 h-16"
|
||||
v-if="loadingWalletTransactions"
|
||||
>
|
||||
Carregando ofertas...
|
||||
<SpinnerComponent width="8" height="8"></SpinnerComponent>
|
||||
</div>
|
||||
<div class="blur-container" v-if="!loadingWalletTransactions">
|
||||
<div class="main-container max-w-md" v-else>
|
||||
<div
|
||||
class="w-full bg-white p-4 sm:p-6 rounded-lg"
|
||||
v-if="props.validDeposits.length > 0"
|
||||
@ -175,11 +177,13 @@ showInitialItems();
|
||||
Saldo disponível
|
||||
</p>
|
||||
<p class="text-xl leading-7 font-semibold text-gray-900">
|
||||
{{ getRemaining() }} BRZ
|
||||
{{ getRemaining() }} {{ user.selectedToken.value }}
|
||||
</p>
|
||||
<div class="flex gap-2 w-32 sm:w-56" v-if="activeLockAmount != 0">
|
||||
<span class="text-xs font-normal text-gray-400" ref="infoText">{{
|
||||
`com ${activeLockAmount.toFixed(2)} BRZ em lock`
|
||||
`com ${activeLockAmount.toFixed(2)} ${
|
||||
user.selectedToken.value
|
||||
} em lock`
|
||||
}}</span>
|
||||
<div
|
||||
class="absolute mt-[2px] md-view"
|
||||
@ -187,7 +191,7 @@ showInitialItems();
|
||||
>
|
||||
<img
|
||||
alt="info image"
|
||||
src="@/assets/info.svg"
|
||||
src="@/assets/info.svg?url"
|
||||
aria-describedby="tooltip"
|
||||
ref="reference"
|
||||
@mouseover="showInfoTooltip = true"
|
||||
@ -213,7 +217,7 @@ showInitialItems();
|
||||
>
|
||||
<img
|
||||
alt="Withdraw image"
|
||||
src="@/assets/withdraw.svg"
|
||||
src="@/assets/withdraw.svg?url"
|
||||
class="w-3 h-3 sm:w-4 sm:h-4"
|
||||
/>
|
||||
<span class="last-release-info">Sacar</span>
|
||||
@ -260,7 +264,7 @@ showInitialItems();
|
||||
>
|
||||
<img
|
||||
alt="Withdraw image"
|
||||
src="@/assets/withdraw.svg"
|
||||
src="@/assets/withdraw.svg?url"
|
||||
class="w-3 h-3 sm:w-4 sm:h-4"
|
||||
/>
|
||||
<span class="last-release-info">Sacar</span>
|
||||
@ -282,10 +286,10 @@ showInitialItems();
|
||||
class="text-xl sm:text-xl leading-7 font-semibold text-gray-900"
|
||||
>
|
||||
{{ item.amount }}
|
||||
BRZ
|
||||
<!-- {{ getTokenByAddress(item.token) }} -->
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<div
|
||||
class="bg-amber-300 status-text"
|
||||
v-if="getEventName(item.event) == 'Reserva' && item.lockStatus == 1"
|
||||
@ -315,7 +319,7 @@ showInitialItems();
|
||||
<span class="last-release-info">{{ getExplorer() }}</span>
|
||||
<img
|
||||
alt="Redirect image"
|
||||
src="@/assets/redirect.svg"
|
||||
src="@/assets/redirect.svg?url"
|
||||
class="w-3 h-3 sm:w-4 sm:h-4"
|
||||
/>
|
||||
</div>
|
||||
@ -386,10 +390,6 @@ p {
|
||||
@apply text-white text-center;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center px-4 py-3 sm:px-8 sm:py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md w-auto;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
@apply grid grid-cols-4 grid-flow-row items-center px-8 py-6 gap-4 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10 w-auto;
|
||||
}
|
||||
@ -412,6 +412,7 @@ p {
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { expect } from "vitest";
|
||||
import { MockValidDeposits } from "@/model/mock/ValidDepositMock";
|
||||
import { MockWalletTransactions } from "@/model/mock/WalletTransactionMock";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
|
||||
describe("ListingComponent.vue", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
useEtherStore().setLoadingWalletTransactions(false);
|
||||
});
|
||||
|
||||
test("Test Message when an empty array is received", () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
validDeposits: [],
|
||||
walletTransactions: [],
|
||||
activeLockAmount: 0,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("Não há nenhuma transação anterior");
|
||||
});
|
||||
|
||||
test("Test number of elements in the list first render", () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
validDeposits: [],
|
||||
walletTransactions: MockWalletTransactions,
|
||||
activeLockAmount: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const elements = wrapper.findAll(".item-container");
|
||||
|
||||
expect(elements).toHaveLength(3);
|
||||
});
|
||||
|
||||
test("Test load more button behavior", async () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
validDeposits: MockValidDeposits,
|
||||
walletTransactions: MockWalletTransactions,
|
||||
activeLockAmount: 0,
|
||||
},
|
||||
});
|
||||
const btn = wrapper.find("button");
|
||||
|
||||
let elements = wrapper.findAll(".item-container");
|
||||
expect(elements).toHaveLength(3);
|
||||
|
||||
await btn.trigger("click");
|
||||
|
||||
elements = wrapper.findAll(".item-container");
|
||||
|
||||
expect(elements).toHaveLength(5);
|
||||
});
|
||||
|
||||
test("Test withdraw offer button emit", async () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
validDeposits: MockValidDeposits,
|
||||
walletTransactions: MockWalletTransactions,
|
||||
activeLockAmount: 0,
|
||||
},
|
||||
});
|
||||
wrapper.vm.$emit("depositWithdrawn");
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.emitted("depositWithdrawn")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("Test should render lock info when active lock amount is greater than 0", () => {
|
||||
const wrapper = mount(ListingComponent, {
|
||||
props: {
|
||||
validDeposits: MockValidDeposits,
|
||||
walletTransactions: [],
|
||||
activeLockAmount: 50,
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("com 50.00 BRZ em lock");
|
||||
});
|
||||
});
|
@ -15,7 +15,7 @@ const props = defineProps({
|
||||
{{ props.title ? props.title : "Confirme em sua carteira" }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="blur-container sm:w-[26rem] w-[20rem]">
|
||||
<div class="main-container max-w-md">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-4 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
@ -24,7 +24,7 @@ const props = defineProps({
|
||||
>
|
||||
<img
|
||||
alt="Polygon image"
|
||||
src="@/assets/validating.svg"
|
||||
src="@/assets/validating.svg?url"
|
||||
width="96"
|
||||
height="48"
|
||||
/>
|
||||
@ -60,10 +60,6 @@ const props = defineProps({
|
||||
@apply text-white text-center;
|
||||
}
|
||||
|
||||
.blur-container {
|
||||
@apply flex flex-col justify-center items-center sm:px-8 px-6 py-6 gap-2 rounded-lg shadow-md shadow-gray-600 backdrop-blur-md mt-10;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import LoadingComponent from "../LoadingComponent.vue";
|
||||
|
||||
describe("Loading.vue", () => {
|
||||
test("Test loading content with received props", () => {
|
||||
const wrapper = mount(LoadingComponent, {
|
||||
props: {
|
||||
title: "MockTitle",
|
||||
message: "MockMessage",
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("MockTitle");
|
||||
expect(wrapper.html()).toContain("MockMessage");
|
||||
});
|
||||
|
||||
test("Test default text if props title isnt passed", () => {
|
||||
const wrapper = mount(LoadingComponent, {
|
||||
props: {
|
||||
message: "MockMessage",
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toContain("Confirme em sua carteira");
|
||||
expect(wrapper.html()).toContain("MockMessage");
|
||||
});
|
||||
});
|
@ -1,86 +1,124 @@
|
||||
<script setup lang="ts">
|
||||
import { pix } from "@/utils/QrCodePix";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
import CustomModal from "@/components//CustomModal/CustomModal.vue";
|
||||
import api from "@/services/index";
|
||||
import SpinnerComponent from "@/components/SpinnerComponent.vue";
|
||||
import { createSolicitation, getSolicitation, type Offer } from "@/utils/bbPay";
|
||||
import { getSellerParticipantId } from "@/blockchain/wallet";
|
||||
import { getUnreleasedLockById } from "@/blockchain/events";
|
||||
import QRCode from "qrcode";
|
||||
|
||||
// props and store references
|
||||
const props = defineProps({
|
||||
pixTarget: String,
|
||||
tokenValue: Number,
|
||||
});
|
||||
// Props
|
||||
interface Props {
|
||||
lockID: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const windowSize = ref<number>(window.innerWidth);
|
||||
const qrCode = ref<string>("");
|
||||
const qrCodePayload = ref<string>("");
|
||||
const isPixValid = ref<boolean>(false);
|
||||
const isCodeInputEmpty = ref<boolean>(true);
|
||||
const qrCodeSvg = ref<string>("");
|
||||
const showWarnModal = ref<boolean>(true);
|
||||
const e2eId = ref<string>("");
|
||||
const pixTarget = ref<string>("");
|
||||
const releaseSignature = ref<string>("");
|
||||
const solicitationData = ref<any>(null);
|
||||
const pollingInterval = ref<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Function to generate QR code SVG
|
||||
const generateQrCodeSvg = async (text: string) => {
|
||||
try {
|
||||
const svgString = await QRCode.toString(text, {
|
||||
type: "svg",
|
||||
width: 192, // 48 * 4 for better quality
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: "#000000",
|
||||
light: "#FFFFFF",
|
||||
},
|
||||
});
|
||||
qrCodeSvg.value = svgString;
|
||||
} catch (error) {
|
||||
console.error("Error generating QR code SVG:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["pixValidated"]);
|
||||
|
||||
const pixQrCode = pix({
|
||||
pixKey: props.pixTarget ?? "",
|
||||
value: props.tokenValue,
|
||||
});
|
||||
|
||||
pixQrCode.base64QrCode().then((code: string) => {
|
||||
qrCode.value = code;
|
||||
});
|
||||
|
||||
qrCodePayload.value = pixQrCode.payload();
|
||||
|
||||
const handleInputEvent = async (event: any): Promise<void> => {
|
||||
const { value } = event.target;
|
||||
e2eId.value = value;
|
||||
await validatePix();
|
||||
};
|
||||
|
||||
const validatePix = async (): Promise<void> => {
|
||||
if (e2eId.value == "") {
|
||||
isPixValid.value = false;
|
||||
isCodeInputEmpty.value = true;
|
||||
// Function to check solicitation status
|
||||
const checkSolicitationStatus = async () => {
|
||||
if (!solicitationData.value?.numeroSolicitacao) {
|
||||
return;
|
||||
}
|
||||
const sellerPixKey = props.pixTarget;
|
||||
const transactionValue = props.tokenValue;
|
||||
|
||||
if (sellerPixKey && transactionValue) {
|
||||
const body_req = {
|
||||
e2e_id: e2eId.value,
|
||||
pix_key: sellerPixKey,
|
||||
pix_value: transactionValue,
|
||||
};
|
||||
try {
|
||||
const response = await getSolicitation(
|
||||
solicitationData.value.numeroSolicitacao
|
||||
);
|
||||
|
||||
isCodeInputEmpty.value = false;
|
||||
|
||||
try {
|
||||
await api.post("validate_pix", body_req);
|
||||
isPixValid.value = true;
|
||||
} catch (error) {
|
||||
isPixValid.value = false;
|
||||
if (response.signature) {
|
||||
pixTarget.value = response.pixTarget;
|
||||
releaseSignature.value = response.signature;
|
||||
// Stop polling when payment is confirmed
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isCodeInputEmpty.value = false;
|
||||
isPixValid.value = false;
|
||||
} catch (error) {
|
||||
console.error("Error checking solicitation status:", error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
() => (windowSize.value = window.innerWidth)
|
||||
);
|
||||
// Function to start polling
|
||||
const startPolling = () => {
|
||||
// Clear any existing interval
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
}
|
||||
|
||||
// Start new polling interval (10 seconds)
|
||||
pollingInterval.value = setInterval(checkSolicitationStatus, 10000);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const { tokenAddress, sellerAddress, amount } = await getUnreleasedLockById(
|
||||
BigInt(props.lockID)
|
||||
);
|
||||
|
||||
const participantId = await getSellerParticipantId(
|
||||
sellerAddress,
|
||||
tokenAddress
|
||||
);
|
||||
|
||||
const offer: Offer = {
|
||||
amount,
|
||||
sellerId: participantId,
|
||||
};
|
||||
|
||||
const response = await createSolicitation(offer);
|
||||
solicitationData.value = response;
|
||||
|
||||
// Update qrCode if the response contains QR code data
|
||||
if (response?.informacoesPIX?.textoQrCode) {
|
||||
qrCode.value = response.informacoesPIX?.textoQrCode;
|
||||
// Generate SVG QR code
|
||||
await generateQrCodeSvg(qrCode.value);
|
||||
}
|
||||
|
||||
// Start polling for solicitation status
|
||||
startPolling();
|
||||
} catch (error) {
|
||||
console.error("Error creating solicitation:", error);
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up interval on component unmount
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener(
|
||||
"resize",
|
||||
() => (windowSize.value = window.innerWidth)
|
||||
);
|
||||
if (pollingInterval.value) {
|
||||
clearInterval(pollingInterval.value);
|
||||
pollingInterval.value = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -93,83 +131,51 @@ onUnmounted(() => {
|
||||
Utilize o QR Code ou copie o código para realizar o Pix
|
||||
</span>
|
||||
<span class="text font-medium lg:text-md text-sm 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.
|
||||
Após realizar o Pix no banco de sua preferência, clique no botão abaixo
|
||||
para liberação dos tokens.
|
||||
</span>
|
||||
</div>
|
||||
<div class="blur-container sm:max-w-[28rem] max-w-[20rem] text-black">
|
||||
<div class="main-container max-w-md text-black">
|
||||
<div
|
||||
class="flex-col items-center justify-center flex w-full bg-white sm:p-8 p-4 rounded-lg break-normal"
|
||||
>
|
||||
<img alt="Qr code image" :src="qrCode" class="w-48 h-48" />
|
||||
<div
|
||||
v-if="qrCodeSvg"
|
||||
v-html="qrCodeSvg"
|
||||
class="w-48 h-48 flex items-center justify-center"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="w-48 h-48 flex items-center justify-center rounded-lg"
|
||||
>
|
||||
<SpinnerComponent width="8" height="8"></SpinnerComponent>
|
||||
</div>
|
||||
<span class="text-center font-bold">Código pix</span>
|
||||
<div class="break-words w-4/5">
|
||||
<span class="text-center text-xs">
|
||||
{{ qrCodePayload }}
|
||||
{{ qrCode }}
|
||||
</span>
|
||||
</div>
|
||||
<img
|
||||
alt="Copy PIX code"
|
||||
src="@/assets/copyPix.svg"
|
||||
src="@/assets/copyPix.svg?url"
|
||||
width="16"
|
||||
height="16"
|
||||
class="pt-2 lg:mb-5 cursor-pointer"
|
||||
/>
|
||||
<span class="text-xs text-start lg-view">
|
||||
<strong>ATENÇÃO!</strong> A transação só 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="sm:text-md text-sm w-full box-border p-2 sm:h-6 h-2 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)"
|
||||
:is-disabled="releaseSignature === ''"
|
||||
:text="
|
||||
releaseSignature ? 'Enviar para a rede' : 'Validando pagamento...'
|
||||
"
|
||||
@button-clicked="
|
||||
emit('pixValidated', { pixTarget, signature: releaseSignature })
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<CustomModal
|
||||
v-if="showWarnModal && windowSize <= 500"
|
||||
v-if="showWarnModal"
|
||||
@close-modal="showWarnModal = false"
|
||||
:isRedirectModal="false"
|
||||
/>
|
||||
@ -222,10 +228,11 @@ h2 {
|
||||
}
|
||||
|
||||
.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;
|
||||
@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 max-w-screen-sm;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
appearance: textfield;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
@ -233,22 +240,4 @@ input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.lg-view {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sm-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.lg-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sm-view {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,52 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import SpinnerComponent from "@/components/SpinnerComponent.vue";
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
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";
|
||||
import SpinnerComponent from "./SpinnerComponent.vue";
|
||||
import { getTokenImage } from "@/utils/imagesPath";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
import { TokenEnum } from "@/model/NetworkEnum";
|
||||
|
||||
// Store reference
|
||||
const etherStore = useEtherStore();
|
||||
const user = useUser();
|
||||
const selectTokenToggle = ref<boolean>(false);
|
||||
|
||||
const {
|
||||
walletAddress,
|
||||
networkName,
|
||||
depositsValidListGoerli,
|
||||
depositsValidListMumbai,
|
||||
selectedToken,
|
||||
depositsValidList,
|
||||
loadingNetworkLiquidity,
|
||||
} = storeToRefs(etherStore);
|
||||
} = user;
|
||||
|
||||
// html references
|
||||
const tokenDropdownRef = ref<any>(null);
|
||||
|
||||
// Reactive state
|
||||
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>();
|
||||
const identification = ref<string>("");
|
||||
const selectedDeposits = ref<ValidDeposit[]>();
|
||||
|
||||
import ChevronDown from "@/assets/chevronDown.svg";
|
||||
import { useOnboard } from "@web3-onboard/vue";
|
||||
import { getParticipantID } from "@/blockchain/events";
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["tokenBuy"]);
|
||||
|
||||
// Blockchain methods
|
||||
const connectAccount = async (): Promise<void> => {
|
||||
await connectProvider();
|
||||
|
||||
enableOrDisableConfirmButton();
|
||||
const { connectWallet } = useOnboard();
|
||||
await connectWallet();
|
||||
};
|
||||
|
||||
const emitConfirmButton = (): void => {
|
||||
const selectedDeposit =
|
||||
networkName.value == NetworkEnum.ethereum
|
||||
? selectedGoerliDeposit.value
|
||||
: selectedMumbaiDeposit.value;
|
||||
emit("tokenBuy", selectedDeposit, tokenValue.value);
|
||||
const emitConfirmButton = async (): Promise<void> => {
|
||||
const deposit = selectedDeposits.value?.find(
|
||||
(d) => d.network === Number(networkName.value)
|
||||
);
|
||||
if (!deposit) return;
|
||||
deposit.participantID = await getParticipantID(deposit.seller, deposit.token);
|
||||
emit("tokenBuy", deposit, tokenValue.value);
|
||||
};
|
||||
|
||||
// Debounce methods
|
||||
@ -65,47 +74,48 @@ const handleInputEvent = (event: any): void => {
|
||||
verifyLiquidity();
|
||||
};
|
||||
|
||||
const openTokenSelection = (): void => {
|
||||
selectTokenToggle.value = true;
|
||||
};
|
||||
|
||||
onClickOutside(tokenDropdownRef, () => {
|
||||
selectTokenToggle.value = false;
|
||||
});
|
||||
|
||||
const handleSelectedToken = (token: TokenEnum): void => {
|
||||
user.setSelectedToken(token);
|
||||
selectTokenToggle.value = false;
|
||||
};
|
||||
|
||||
// Verify if there is a valid deposit to buy
|
||||
const verifyLiquidity = (): void => {
|
||||
enableConfirmButton.value = false;
|
||||
selectedGoerliDeposit.value = undefined;
|
||||
selectedMumbaiDeposit.value = undefined;
|
||||
|
||||
if (tokenValue.value <= 0) {
|
||||
enableWalletButton.value = false;
|
||||
if (!walletAddress.value)
|
||||
return;
|
||||
}
|
||||
|
||||
selectedGoerliDeposit.value = verifyNetworkLiquidity(
|
||||
const selDeposits = verifyNetworkLiquidity(
|
||||
tokenValue.value,
|
||||
walletAddress.value,
|
||||
depositsValidListGoerli.value
|
||||
depositsValidList.value
|
||||
);
|
||||
selectedMumbaiDeposit.value = verifyNetworkLiquidity(
|
||||
tokenValue.value,
|
||||
walletAddress.value,
|
||||
depositsValidListMumbai.value
|
||||
selectedDeposits.value = selDeposits;
|
||||
hasLiquidity.value = !!selDeposits.find(
|
||||
(d) => d.network === Number(networkName.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;
|
||||
if (!selectedDeposits.value) {
|
||||
enableConfirmButton.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedDeposits.value.find((d) => d.network === networkName.value)) {
|
||||
enableConfirmButton.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
enableConfirmButton.value = true;
|
||||
};
|
||||
|
||||
watch(networkName, (): void => {
|
||||
@ -116,6 +126,16 @@ watch(networkName, (): void => {
|
||||
watch(walletAddress, (): void => {
|
||||
verifyLiquidity();
|
||||
});
|
||||
|
||||
// Add form submission handler
|
||||
const handleSubmit = async (e: Event): Promise<void> => {
|
||||
e.preventDefault();
|
||||
if (walletAddress.value) {
|
||||
await emitConfirmButton();
|
||||
} else {
|
||||
await connectAccount();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -131,7 +151,7 @@ watch(walletAddress, (): void => {
|
||||
tokens após realizar o Pix</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<form class="main-container" @submit="handleSubmit">
|
||||
<div class="backdrop-blur -z-10 w-full h-full"></div>
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-5 rounded-lg border-y-10"
|
||||
@ -139,51 +159,98 @@ watch(walletAddress, (): void => {
|
||||
<div class="flex justify-between sm:w-full items-center">
|
||||
<input
|
||||
type="number"
|
||||
class="border-none outline-none text-lg text-gray-900 w-3/4"
|
||||
name="tokenAmount"
|
||||
class="border-none outline-none text-lg text-gray-900"
|
||||
v-bind:class="{
|
||||
'font-semibold': tokenValue != undefined,
|
||||
'text-xl': tokenValue != undefined,
|
||||
}"
|
||||
@input="debounce(handleInputEvent, 500)($event)"
|
||||
placeholder="0 "
|
||||
placeholder="0"
|
||||
step=".01"
|
||||
required
|
||||
/>
|
||||
<div
|
||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
||||
>
|
||||
<img
|
||||
alt="Token image"
|
||||
class="sm:w-fit w-4"
|
||||
src="@/assets/brz.svg"
|
||||
/>
|
||||
<span class="text-gray-900 sm:text-lg text-md w-fit" id="brz"
|
||||
>BRZ</span
|
||||
<div class="relative overflow-visible">
|
||||
<button
|
||||
ref="tokenDropdownRef"
|
||||
class="flex flex-row items-center p-2 bg-gray-300 hover:bg-gray-200 focus:outline-indigo-800 focus:outline-2 rounded-3xl min-w-fit gap-2 transition-colors"
|
||||
@click="openTokenSelection()"
|
||||
>
|
||||
<img
|
||||
alt="Token image"
|
||||
class="sm:w-fit w-4"
|
||||
:src="getTokenImage(selectedToken)"
|
||||
/>
|
||||
<span
|
||||
class="text-gray-900 sm:text-lg text-md font-medium"
|
||||
id="token"
|
||||
>{{ selectedToken }}</span
|
||||
>
|
||||
<ChevronDown
|
||||
class="pr-4 sm:pr-0 transition-all duration-500 ease-in-out invert"
|
||||
:class="{ 'scale-y-[-1]': selectTokenToggle }"
|
||||
alt="Chevron Down"
|
||||
/>
|
||||
</button>
|
||||
<transition name="dropdown">
|
||||
<div
|
||||
v-if="selectTokenToggle"
|
||||
class="mt-2 text-gray-900 absolute right-0 z-50 w-full min-w-max"
|
||||
>
|
||||
<div
|
||||
class="bg-white rounded-xl z-10 border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||
>
|
||||
<div
|
||||
v-for="token in TokenEnum"
|
||||
:key="token"
|
||||
class="flex menu-button gap-2 px-4 cursor-pointer hover:bg-gray-300 transition-colors"
|
||||
@click="handleSelectedToken(token)"
|
||||
>
|
||||
<img
|
||||
:alt="token + ' logo'"
|
||||
width="20"
|
||||
height="20"
|
||||
:src="getTokenImage(token)"
|
||||
/>
|
||||
<span
|
||||
class="text-gray-900 py-4 text-end font-semibold text-sm"
|
||||
>
|
||||
{{ token }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="custom-divide py-2 mb-2"></div>
|
||||
<div
|
||||
class="flex justify-between"
|
||||
v-if="hasLiquidity && !loadingNetworkLiquidity"
|
||||
>
|
||||
<div class="flex justify-between" v-if="!loadingNetworkLiquidity">
|
||||
<p class="text-gray-500 font-normal text-sm w-auto">
|
||||
~ R$ {{ tokenValue.toFixed(2) }}
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<img
|
||||
alt="Polygon image"
|
||||
src="@/assets/polygon.svg"
|
||||
alt="Rootstock image"
|
||||
src="@/assets/rootstock.svg?url"
|
||||
width="24"
|
||||
height="24"
|
||||
v-if="selectedMumbaiDeposit"
|
||||
v-if="
|
||||
selectedDeposits &&
|
||||
selectedDeposits.find((d) => d.network == NetworkEnum.rootstock)
|
||||
"
|
||||
/>
|
||||
<img
|
||||
alt="Ethereum image"
|
||||
src="@/assets/ethereum.svg"
|
||||
src="@/assets/ethereum.svg?url"
|
||||
width="24"
|
||||
height="24"
|
||||
v-if="selectedGoerliDeposit"
|
||||
v-if="
|
||||
selectedDeposits &&
|
||||
selectedDeposits.find((d) => d.network == NetworkEnum.sepolia)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -206,26 +273,43 @@ watch(walletAddress, (): void => {
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-center"
|
||||
v-else-if="!hasLiquidity && !loadingNetworkLiquidity"
|
||||
v-else-if="
|
||||
!hasLiquidity && !loadingNetworkLiquidity && tokenValue > 0
|
||||
"
|
||||
>
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Atualmente não há liquidez nas redes para sua demanda</span
|
||||
>Atualmente não há liquidez nas rede selecionada para sua
|
||||
demanda</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<CustomButton
|
||||
v-if="!walletAddress"
|
||||
:text="'Conectar carteira'"
|
||||
:is-disabled="!enableWalletButton"
|
||||
@buttonClicked="connectAccount()"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
v-model="identification"
|
||||
maxlength="14"
|
||||
:pattern="'^\\d{11}$|^\\d{14}$'"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||
placeholder="Digite seu CPF ou CNPJ (somente números)"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<CustomButton
|
||||
v-if="walletAddress"
|
||||
:text="'Confirmar compra'"
|
||||
:is-disabled="!enableConfirmButton"
|
||||
@buttonClicked="emitConfirmButton()"
|
||||
type="submit"
|
||||
text="Confirmar Oferta"
|
||||
/>
|
||||
</div>
|
||||
<CustomButton
|
||||
v-else
|
||||
text="Conectar carteira"
|
||||
@buttonClicked="connectAccount()"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -252,10 +336,6 @@ watch(walletAddress, (): void => {
|
||||
@apply text-white 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 mt-10;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
@ -264,4 +344,10 @@ input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
@apply w-full py-3 px-6 rounded-lg font-semibold text-white bg-indigo-600
|
||||
hover:bg-indigo-700 disabled:bg-gray-400 disabled:cursor-not-allowed
|
||||
transition-colors duration-200;
|
||||
}
|
||||
</style>
|
||||
|
343
src/components/SellerSteps/SellerComponent.vue
Normal file
@ -0,0 +1,343 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from "vue";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
import { postProcessKey } from "@/utils/pixKeyFormat";
|
||||
import { TokenEnum } from "@/model/NetworkEnum";
|
||||
import { getTokenImage } from "@/utils/imagesPath";
|
||||
import { useOnboard } from "@web3-onboard/vue";
|
||||
import ChevronDown from "@/assets/chevron.svg";
|
||||
|
||||
// Import the bank list
|
||||
import bankList from "@/utils/files/isbpList.json";
|
||||
import type { Participant } from "@/utils/bbPay";
|
||||
|
||||
// Define Bank interface
|
||||
interface Bank {
|
||||
ISPB: string;
|
||||
longName: string;
|
||||
}
|
||||
|
||||
// html references
|
||||
const tokenDropdownRef = ref<any>(null);
|
||||
const formRef = ref<HTMLFormElement | null>(null);
|
||||
|
||||
// Reactive state
|
||||
const user = useUser();
|
||||
const { walletAddress, selectedToken } = user;
|
||||
|
||||
const offer = ref<string>("");
|
||||
const identification = ref<string>("");
|
||||
const account = ref<string>("");
|
||||
const branch = ref<string>("");
|
||||
const accountType = ref<string>("");
|
||||
const selectTokenToggle = ref<boolean>(false);
|
||||
const savingsVariation = ref<string>("");
|
||||
const errors = ref<{ [key: string]: string }>({});
|
||||
|
||||
// Bank selection
|
||||
const bankSearchQuery = ref<string>("");
|
||||
const showBankList = ref<boolean>(false);
|
||||
const selectedBank = ref<Bank | null>(null);
|
||||
|
||||
const filteredBanks = computed(() => {
|
||||
if (!bankSearchQuery.value) return [];
|
||||
return bankList
|
||||
.filter((bank) =>
|
||||
bank.longName.toLowerCase().includes(bankSearchQuery.value.toLowerCase())
|
||||
)
|
||||
.slice(0, 5);
|
||||
});
|
||||
|
||||
const handleBankSelect = (bank: Bank) => {
|
||||
selectedBank.value = bank;
|
||||
bankSearchQuery.value = bank.longName;
|
||||
showBankList.value = false;
|
||||
};
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["approveTokens"]);
|
||||
|
||||
// Methods
|
||||
const connectAccount = async (): Promise<void> => {
|
||||
const { connectWallet } = useOnboard();
|
||||
await connectWallet();
|
||||
};
|
||||
|
||||
const handleSubmit = (e: Event): void => {
|
||||
e.preventDefault();
|
||||
|
||||
const processedIdentification = postProcessKey(identification.value);
|
||||
|
||||
const data: Participant = {
|
||||
offer: offer.value,
|
||||
chainID: user.networkId.value,
|
||||
identification: processedIdentification,
|
||||
bankIspb: selectedBank.value?.ISPB,
|
||||
accountType: accountType.value,
|
||||
account: account.value,
|
||||
branch: branch.value,
|
||||
savingsVariation: savingsVariation.value || "",
|
||||
};
|
||||
|
||||
emit("approveTokens", data);
|
||||
};
|
||||
|
||||
// Token selection
|
||||
const openTokenSelection = (): void => {
|
||||
selectTokenToggle.value = true;
|
||||
};
|
||||
|
||||
const handleSelectedToken = (token: TokenEnum): void => {
|
||||
user.setSelectedToken(token);
|
||||
selectTokenToggle.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page w-full">
|
||||
<div class="text-container">
|
||||
<span
|
||||
class="text font-extrabold sm:text-5xl text-3xl sm:max-w-[29rem] max-w-[20rem]"
|
||||
>
|
||||
Venda cripto e receba em Pix
|
||||
</span>
|
||||
<span
|
||||
class="text font-medium sm:text-base text-xs sm:max-w-[28rem] max-w-[30rem] sm:tracking-normal tracking-wide"
|
||||
>
|
||||
Digite sua oferta, informe a chave Pix, selecione a rede, aprove o envio
|
||||
da transação e confirme sua oferta.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form ref="formRef" @submit="handleSubmit" class="main-container">
|
||||
<!-- Offer input -->
|
||||
<div
|
||||
class="flex justify-between items-center w-full bg-white sm:px-10 px-6 py-5 rounded-lg border-y-10 gap-4"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
v-model="offer"
|
||||
class="border-none outline-none text-gray-900 sm:w-fit w-3/4 flex-grow"
|
||||
:class="{
|
||||
'!font-medium': offer !== undefined && offer !== '',
|
||||
'text-xl': offer !== undefined && offer !== '',
|
||||
}"
|
||||
min="0.01"
|
||||
max="999999999.99"
|
||||
pattern="\d+(\.\d{0,2})?"
|
||||
placeholder="Digite sua oferta (mínimo R$0,01)"
|
||||
step=".01"
|
||||
required
|
||||
/>
|
||||
<div class="relative overflow-visible">
|
||||
<button
|
||||
ref="tokenDropdownRef"
|
||||
class="flex flex-row items-center p-2 bg-gray-300 hover:bg-gray-200 focus:outline-indigo-800 focus:outline-2 rounded-3xl min-w-fit gap-2 transition-colors"
|
||||
@click="openTokenSelection()"
|
||||
>
|
||||
<img
|
||||
alt="Token image"
|
||||
class="sm:w-fit w-4"
|
||||
:src="getTokenImage(selectedToken)"
|
||||
/>
|
||||
<span
|
||||
class="text-gray-900 sm:text-lg text-md font-medium"
|
||||
id="token"
|
||||
>
|
||||
{{ selectedToken }}
|
||||
</span>
|
||||
<ChevronDown
|
||||
class="text-gray-900 pr-4 sm:pr-0 transition-all duration-500 ease-in-out"
|
||||
:class="{ 'scale-y-[-1]': selectTokenToggle }"
|
||||
alt="Chevron Down"
|
||||
/>
|
||||
</button>
|
||||
<transition name="dropdown">
|
||||
<div
|
||||
v-if="selectTokenToggle"
|
||||
class="mt-2 text-gray-900 absolute right-0 z-50 w-full min-w-max"
|
||||
>
|
||||
<div
|
||||
class="bg-white rounded-xl z-10 border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||
>
|
||||
<div
|
||||
v-for="token in TokenEnum"
|
||||
:key="token"
|
||||
class="flex menu-button gap-2 px-4 cursor-pointer hover:bg-gray-300 transition-colors"
|
||||
@click="handleSelectedToken(token)"
|
||||
>
|
||||
<img
|
||||
:alt="token + ' logo'"
|
||||
width="20"
|
||||
height="20"
|
||||
:src="getTokenImage(token)"
|
||||
/>
|
||||
<span
|
||||
class="text-gray-900 py-4 text-end font-semibold text-sm"
|
||||
>
|
||||
{{ token }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
<!-- CPF or CNPJ input -->
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
v-model="identification"
|
||||
maxlength="14"
|
||||
:pattern="'^\\d{11}$|^\\d{14}$'"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||
:class="{ 'text-xl font-medium': identification }"
|
||||
placeholder="Digite seu CPF ou CNPJ (somente números)"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<!-- Bank selection -->
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
v-model="bankSearchQuery"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||
:class="{ 'text-xl font-medium': bankSearchQuery }"
|
||||
placeholder="Buscar banco"
|
||||
@focus="showBankList = true"
|
||||
required
|
||||
/>
|
||||
<div
|
||||
v-if="showBankList && filteredBanks.length > 0"
|
||||
class="absolute top-full left-0 right-0 mt-1 bg-white border border-gray-200 rounded-md shadow-lg z-50 max-h-60 overflow-y-auto"
|
||||
>
|
||||
<div
|
||||
v-for="bank in filteredBanks"
|
||||
:key="bank.ISPB"
|
||||
class="px-4 py-2 hover:bg-gray-100 cursor-pointer transition-colors"
|
||||
@click="handleBankSelect(bank)"
|
||||
>
|
||||
<div class="text-sm font-medium text-gray-900">
|
||||
{{ bank.longName }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">ISPB: {{ bank.ISPB }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="errors.bank" class="text-red-500 text-sm mt-2">{{
|
||||
errors.bank
|
||||
}}</span>
|
||||
</div>
|
||||
<!-- Account and Branch inputs -->
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
v-model="account"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||
:class="{ 'text-xl font-medium': account }"
|
||||
placeholder="Número da conta"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
v-model="branch"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||
:class="{ 'text-xl font-medium': branch }"
|
||||
placeholder="Agência"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Account Type Selection -->
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex gap-4">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
v-model="accountType"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full"
|
||||
required
|
||||
>
|
||||
<option value="" disabled selected>Tipo de conta</option>
|
||||
<option value="1">Conta Corrente</option>
|
||||
<option value="2">Conta Poupança</option>
|
||||
<option value="3">Conta Salário</option>
|
||||
<option value="4">Conta Pré-Paga</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Savings Account Variation -->
|
||||
<Transition name="resize">
|
||||
<input
|
||||
v-if="accountType === '2'"
|
||||
type="text"
|
||||
v-model="savingsVariation"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-full bg-white sm:px-10 px-6 py-4 rounded-lg border-y-10"
|
||||
:class="{ 'text-xl font-medium': savingsVariation }"
|
||||
placeholder="Variação da poupança"
|
||||
required
|
||||
/>
|
||||
</Transition>
|
||||
<!-- Action buttons -->
|
||||
<CustomButton v-if="walletAddress" type="submit" text="Aprovar tokens" />
|
||||
<CustomButton
|
||||
v-else
|
||||
text="Conectar carteira"
|
||||
@buttonClicked="connectAccount()"
|
||||
/>
|
||||
</form>
|
||||
</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-white text-center;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply sm:text-lg text-sm;
|
||||
}
|
||||
</style>
|
@ -1,8 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import CustomButton from "@/components/CustomButton/CustomButton.vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { decimalCount } from "@/utils/decimalCount";
|
||||
import { getTokenImage } from "@/utils/imagesPath";
|
||||
import { useOnboard } from "@web3-onboard/vue";
|
||||
|
||||
// Store
|
||||
const user = useUser();
|
||||
const { walletAddress } = user;
|
||||
|
||||
// Reactive state
|
||||
const tokenValue = ref<number>(0);
|
||||
@ -13,6 +20,12 @@ const validDecimals = ref<boolean>(true);
|
||||
// Emits
|
||||
const emit = defineEmits(["tokenBuy"]);
|
||||
|
||||
// Blockchain methods
|
||||
const connectAccount = async (): Promise<void> => {
|
||||
const { connectWallet } = useOnboard();
|
||||
await connectWallet();
|
||||
};
|
||||
|
||||
// Debounce methods
|
||||
const handleInputEvent = (event: any): void => {
|
||||
const { value } = event.target;
|
||||
@ -39,7 +52,7 @@ const handleInputEvent = (event: any): void => {
|
||||
tokens após realizar o Pix</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div class="main-container">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
@ -58,8 +71,14 @@ const handleInputEvent = (event: any): void => {
|
||||
<div
|
||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
||||
>
|
||||
<img alt="Token image" class="w-fit" src="@/assets/brz.svg" />
|
||||
<span class="text-gray-900 text-lg w-fit" id="brz">BRZ</span>
|
||||
<img
|
||||
alt="Token image"
|
||||
class="w-fit"
|
||||
:src="getTokenImage(user.selectedToken.value)"
|
||||
/>
|
||||
<span class="text-gray-900 text-lg w-fit" id="token">{{
|
||||
user.selectedToken
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -71,13 +90,13 @@ const handleInputEvent = (event: any): void => {
|
||||
<div class="flex gap-2">
|
||||
<img
|
||||
alt="Polygon image"
|
||||
src="@/assets/polygon.svg"
|
||||
src="@/assets/polygon.svg?url"
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
<img
|
||||
alt="Ethereum image"
|
||||
src="@/assets/ethereum.svg"
|
||||
src="@/assets/ethereum.svg?url"
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
@ -94,10 +113,17 @@ const handleInputEvent = (event: any): void => {
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomButton
|
||||
v-if="walletAddress"
|
||||
:text="'Conectar carteira'"
|
||||
@buttonClicked="emit('tokenBuy')"
|
||||
/>
|
||||
<CustomButton
|
||||
v-if="!walletAddress"
|
||||
:text="'Conectar carteira'"
|
||||
@buttonClicked="connectAccount()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -125,14 +151,6 @@ const handleInputEvent = (event: any): void => {
|
||||
@apply text-white 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;
|
||||
|
@ -6,8 +6,9 @@ const emit = defineEmits(["sendNetwork"]);
|
||||
|
||||
// props and store references
|
||||
const props = defineProps({
|
||||
pixKey: String,
|
||||
sellerId: String,
|
||||
offer: Number,
|
||||
selectedToken: String,
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -19,24 +20,26 @@ const props = defineProps({
|
||||
>Envie sua oferta para a rede
|
||||
</span>
|
||||
<span
|
||||
class="text text-xl font-normal sm:text-base text-xs sm:max-w-[30rem] max-w-[22rem]"
|
||||
class="text font-normal sm:text-base text-xs sm:max-w-[30rem] max-w-[22rem]"
|
||||
>Após a confirmação sua oferta estará disponível para outros usuários.
|
||||
Caso deseje retirar a oferta, será necessário aguardar 24h para receber
|
||||
os tokens de volta.</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div class="main-container">
|
||||
<div
|
||||
class="flex flex-col w-full bg-white px-10 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div>
|
||||
<p>Tokens ofertados</p>
|
||||
<p class="text-2xl text-gray-900">{{ props.offer }} BRZ</p>
|
||||
<p class="text-2xl text-gray-900">
|
||||
{{ props.offer }} {{ props.selectedToken }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<p>Chave Pix</p>
|
||||
<p class="text-xl text-gray-900 break-words">
|
||||
{{ props.pixKey }}
|
||||
{{ props.sellerId }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
@ -72,18 +75,10 @@ p {
|
||||
@apply text-white 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-8 sm: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;
|
||||
|
@ -1,186 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import CustomButton from "../CustomButton/CustomButton.vue";
|
||||
import { debounce } from "@/utils/debounce";
|
||||
import { decimalCount } from "@/utils/decimalCount";
|
||||
import { pixFormatValidation, postProcessKey } from "@/utils/pixKeyFormat";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { connectProvider } from "@/blockchain/provider";
|
||||
|
||||
// Reactive state
|
||||
const etherStore = useEtherStore();
|
||||
const { walletAddress } = storeToRefs(etherStore);
|
||||
|
||||
const offer = ref<string>("");
|
||||
const pixKey = ref<string>("");
|
||||
|
||||
const enableSelectButton = ref<boolean>(false);
|
||||
const hasLiquidity = ref<boolean>(true);
|
||||
const validDecimals = ref<boolean>(true);
|
||||
const validPixFormat = ref<boolean>(true);
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["approveTokens"]);
|
||||
|
||||
// Debounce methods
|
||||
const handleInputEvent = (event: any): void => {
|
||||
const { value } = event.target;
|
||||
|
||||
offer.value = value;
|
||||
|
||||
if (decimalCount(offer.value) > 2) {
|
||||
validDecimals.value = false;
|
||||
enableSelectButton.value = false;
|
||||
return;
|
||||
}
|
||||
validDecimals.value = true;
|
||||
};
|
||||
|
||||
const handlePixKeyInputEvent = (event: any): void => {
|
||||
const { value } = event.target;
|
||||
|
||||
pixKey.value = value;
|
||||
|
||||
if (pixFormatValidation(pixKey.value)) {
|
||||
validPixFormat.value = true;
|
||||
enableSelectButton.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
enableSelectButton.value = false;
|
||||
validPixFormat.value = false;
|
||||
};
|
||||
|
||||
const handleButtonClick = async (
|
||||
offer: string,
|
||||
pixKey: string
|
||||
): Promise<void> => {
|
||||
const postProcessedPixKey = postProcessKey(pixKey);
|
||||
if (walletAddress.value)
|
||||
emit("approveTokens", { offer, postProcessedPixKey });
|
||||
else await connectProvider();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page w-full">
|
||||
<div class="text-container">
|
||||
<span
|
||||
class="text font-extrabold sm:text-5xl text-3xl sm:max-w-[29rem] max-w-[20rem]"
|
||||
>Venda cripto e receba em Pix</span
|
||||
>
|
||||
<span
|
||||
class="text font-medium sm:text-base text-xs sm:max-w-[28rem] max-w-[30rem] sm:tracking-normal tracking-wide"
|
||||
>Digite sua oferta, informe a chave Pix, selecione a rede, aprove o
|
||||
envio da transação e confirme sua oferta.</span
|
||||
>
|
||||
</div>
|
||||
<div class="blur-container">
|
||||
<div class="backdrop-blur -z-10 w-full h-full"></div>
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-5 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex justify-between items-center">
|
||||
<input
|
||||
type="number"
|
||||
v-model="offer"
|
||||
class="border-none outline-none text-gray-900 sm:w-fit w-3/4"
|
||||
v-bind:class="{
|
||||
'font-semibold': offer != undefined,
|
||||
'text-xl': offer != undefined,
|
||||
}"
|
||||
@input="debounce(handleInputEvent, 500)($event)"
|
||||
placeholder="Digite sua oferta"
|
||||
step=".01"
|
||||
/>
|
||||
<div
|
||||
class="flex flex-row p-2 px-3 bg-gray-300 rounded-3xl min-w-fit gap-1"
|
||||
>
|
||||
<img
|
||||
alt="Token image"
|
||||
class="sm:w-fit w-4"
|
||||
src="@/assets/brz.svg"
|
||||
/>
|
||||
<span class="text-gray-900 w-fit" id="brz"> BRZ </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex pt-2 justify-center" v-if="!validDecimals">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Por favor utilize no máximo 2 casas decimais</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex pt-2 justify-center" v-else-if="!hasLiquidity">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Atualmente não há liquidez nas redes para sua demanda</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col w-full bg-white sm:px-10 px-6 py-8 rounded-lg border-y-10"
|
||||
>
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<input
|
||||
@input="debounce(handlePixKeyInputEvent, 500)($event)"
|
||||
type="text"
|
||||
v-model="pixKey"
|
||||
class="border-none outline-none sm:text-lg text-sm text-gray-900 w-fit"
|
||||
placeholder="Digite a chave Pix"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex pt-2 justify-center" v-if="!validPixFormat">
|
||||
<span class="text-red-500 font-normal text-sm"
|
||||
>Por favor utilize telefone, CPF ou CNPJ</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<CustomButton
|
||||
:text="walletAddress ? 'Aprovar tokens' : 'Conectar Carteira'"
|
||||
:isDisabled="!validDecimals || !validPixFormat"
|
||||
@buttonClicked="handleButtonClick(offer, pixKey)"
|
||||
/>
|
||||
</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-white 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 mt-10 w-auto;
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
input[type="number"]::-webkit-inner-spin-button,
|
||||
input[type="number"]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply sm:text-lg text-sm;
|
||||
}
|
||||
</style>
|
82
src/components/ToasterComponent.vue
Normal file
@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from "vue";
|
||||
import { useOnboard } from "@web3-onboard/vue";
|
||||
import { Networks } from "../model/Networks";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
|
||||
const { connectedWallet } = useOnboard();
|
||||
const user = useUser();
|
||||
const { networkId, networkName } = user;
|
||||
|
||||
const isWrongNetwork = ref(false);
|
||||
const targetNetworkName = computed(() => Networks[networkName.value].chainName);
|
||||
|
||||
const checkNetwork = () => {
|
||||
if (connectedWallet.value) {
|
||||
const chainId = connectedWallet.value.chains[0].id;
|
||||
isWrongNetwork.value = Number(chainId) !== networkId.value;
|
||||
} else {
|
||||
isWrongNetwork.value = false; // No wallet connected yet
|
||||
}
|
||||
};
|
||||
|
||||
const switchNetwork = async () => {
|
||||
try {
|
||||
if (connectedWallet.value && connectedWallet.value.provider) {
|
||||
await connectedWallet.value.provider.request({
|
||||
method: "wallet_switchEthereumChain",
|
||||
params: [
|
||||
{
|
||||
chainId: Networks[networkName.value].chainId,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to switch network:", error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(checkNetwork);
|
||||
watch(connectedWallet, checkNetwork);
|
||||
watch(networkId, checkNetwork, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="slide-up" appear>
|
||||
<div
|
||||
v-if="isWrongNetwork"
|
||||
class="fixed bottom-0 left-0 right-0 bg-red-500 text-white p-4 flex justify-between items-center z-50"
|
||||
>
|
||||
<div>
|
||||
<span class="font-bold">Wrong network!</span>
|
||||
<span> Please switch to {{ targetNetworkName }}.</span>
|
||||
</div>
|
||||
<button
|
||||
@click="switchNetwork"
|
||||
class="bg-white text-red-500 px-4 py-2 rounded font-bold hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
Switch Network
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.slide-up-enter-active,
|
||||
.slide-up-leave-active {
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.slide-up-enter-from,
|
||||
.slide-up-leave-to {
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.slide-up-enter-to,
|
||||
.slide-up-leave-from {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
@ -1,34 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { ref } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
import { connectProvider, requestNetworkChange } from "@/blockchain/provider";
|
||||
import ethereumImage from "@/assets/ethereum.svg";
|
||||
import polygonImage from "@/assets/polygon.svg";
|
||||
import { getNetworkImage } from "@/utils/imagesPath";
|
||||
import { Networks } from "@/model/Networks";
|
||||
|
||||
// Store reference
|
||||
const etherStore = useEtherStore();
|
||||
import { useOnboard } from "@web3-onboard/vue";
|
||||
|
||||
const { walletAddress, sellerView } = storeToRefs(etherStore);
|
||||
import ChevronDown from "@/assets/chevronDown.svg";
|
||||
import TwitterIcon from "@/assets/twitterIcon.svg";
|
||||
import LinkedinIcon from "@/assets/linkedinIcon.svg";
|
||||
import GithubIcon from "@/assets/githubIcon.svg";
|
||||
import { connectProvider } from "@/blockchain/provider";
|
||||
|
||||
// Use the new composable
|
||||
const user = useUser();
|
||||
const { walletAddress, sellerView, networkId } = user;
|
||||
|
||||
const menuOpenToggle = ref<boolean>(false);
|
||||
const menuHoverToggle = ref<boolean>(false);
|
||||
|
||||
const infoMenuOpenToggle = ref<boolean>(false);
|
||||
const currencyMenuOpenToggle = ref<boolean>(false);
|
||||
const currencyMenuHoverToggle = ref<boolean>(false);
|
||||
const infoMenuRef = ref<any>(null);
|
||||
const walletAddressRef = ref<any>(null);
|
||||
const currencyRef = ref<any>(null);
|
||||
|
||||
//Methods
|
||||
const connectMetaMask = async (): Promise<void> => {
|
||||
await connectProvider();
|
||||
const { connectedWallet, connectedChain, setChain, disconnectWallet } =
|
||||
useOnboard();
|
||||
|
||||
const connnectWallet = async (): Promise<void> => {
|
||||
const { connectWallet } = useOnboard();
|
||||
await connectWallet();
|
||||
};
|
||||
|
||||
watch(connectedWallet, async (newVal: any) => {
|
||||
connectProvider(newVal.provider);
|
||||
const addresses = await newVal.provider.request({ method: "eth_accounts" });
|
||||
user.setWalletAddress(addresses.shift());
|
||||
});
|
||||
|
||||
watch(connectedChain, (newVal: any) => {
|
||||
// Check if connected chain is valid, otherwise default to Sepolia (NetworkEnum.SEPOLIA)
|
||||
if (
|
||||
!newVal ||
|
||||
!Object.values(Networks).some(
|
||||
(network) => Number(network.chainId) === Number(newVal.id)
|
||||
)
|
||||
) {
|
||||
console.log(
|
||||
"Invalid or unsupported network detected, defaulting to Sepolia"
|
||||
);
|
||||
user.setNetworkId(user.networkId.value);
|
||||
return;
|
||||
}
|
||||
user.setNetworkId(newVal?.id);
|
||||
});
|
||||
|
||||
const formatWalletAddress = (): string => {
|
||||
if (!walletAddress.value)
|
||||
throw new Error("Wallet not connected");
|
||||
const walletAddressLength = walletAddress.value.length;
|
||||
const initialText = walletAddress.value.substring(0, 5);
|
||||
const finalText = walletAddress.value.substring(
|
||||
@ -38,10 +68,10 @@ const formatWalletAddress = (): string => {
|
||||
return `${initialText}...${finalText}`;
|
||||
};
|
||||
|
||||
const disconnectUser = (): void => {
|
||||
etherStore.setWalletAddress("");
|
||||
const disconnectUser = async (): Promise<void> => {
|
||||
user.setWalletAddress(null);
|
||||
await disconnectWallet({ label: connectedWallet.value?.label || "" });
|
||||
closeMenu();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const closeMenu = (): void => {
|
||||
@ -50,28 +80,23 @@ const closeMenu = (): void => {
|
||||
|
||||
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];
|
||||
try {
|
||||
await setChain({
|
||||
chainId: Networks[network].chainId,
|
||||
wallet: connectedWallet.value?.label || "",
|
||||
});
|
||||
user.setNetworkId(network);
|
||||
} catch (error) {
|
||||
console.log("Error changing network", error);
|
||||
}
|
||||
};
|
||||
|
||||
onClickOutside(walletAddressRef, () => {
|
||||
menuHoverToggle.value = false;
|
||||
menuOpenToggle.value = false;
|
||||
});
|
||||
|
||||
onClickOutside(currencyRef, () => {
|
||||
currencyMenuOpenToggle.value = false;
|
||||
currencyMenuHoverToggle.value = false;
|
||||
});
|
||||
|
||||
onClickOutside(infoMenuRef, () => {
|
||||
@ -84,91 +109,25 @@ onClickOutside(infoMenuRef, () => {
|
||||
<RouterLink :to="'/'" class="default-button">
|
||||
<img
|
||||
alt="P2Pix logo"
|
||||
class="logo lg-view"
|
||||
src="@/assets/logo.svg"
|
||||
width="75"
|
||||
class="logo hidden md:inline-block"
|
||||
width="200"
|
||||
height="75"
|
||||
src="@/assets/logo.svg?url"
|
||||
/>
|
||||
<img
|
||||
alt="P2Pix logo"
|
||||
class="logo sm-view w-10/12"
|
||||
src="@/assets/logo2.svg"
|
||||
class="logo inline-block md:hidden w-10/12"
|
||||
width="40"
|
||||
height="40"
|
||||
src="@/assets/logo2.svg?url"
|
||||
/>
|
||||
</RouterLink>
|
||||
|
||||
<div class="flex sm:gap-4 gap-2 items-center">
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
v-show="infoMenuOpenToggle"
|
||||
class="mt-10 absolute w-full text-gray-900 lg-view"
|
||||
>
|
||||
<div class="mt-2">
|
||||
<div class="bg-white rounded-md z-10 -left-32 w-52">
|
||||
<div class="menu-button gap-2 px-4 rounded-md cursor-pointer">
|
||||
<span class="text-gray-900 py-4 text-end font-semibold text-sm">
|
||||
Documentação
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<RouterLink
|
||||
:to="'/faq'"
|
||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
||||
>
|
||||
<span
|
||||
class="text-gray-900 py-4 text-end font-semibold text-sm whitespace-nowrap"
|
||||
>
|
||||
Perguntas frequentes
|
||||
</span>
|
||||
</RouterLink>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div
|
||||
class="sm:text-center sm:justify-center ml-8 sm:ml-0 gap-2 px-4 rounded-md float-right"
|
||||
>
|
||||
<div class="redirect_button flex mr-4">
|
||||
<div class="mr-6">
|
||||
<img
|
||||
alt="Twitter"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/twitterIcon.svg"
|
||||
class="cursor-pointer"
|
||||
onclick="location.href = 'https://www.twitter.com/doiim';"
|
||||
/>
|
||||
</div>
|
||||
<div class="mr-6">
|
||||
<img
|
||||
alt="Discord"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/discordIcon.svg"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
alt="Github"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/githubIcon.svg"
|
||||
class="cursor-pointer"
|
||||
onclick="location.href = 'https://github.com/doiim';"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
<button
|
||||
ref="infoMenuRef"
|
||||
class="default-button lg-view cursor-pointer"
|
||||
class="default-button hidden md:inline-block cursor-pointer"
|
||||
@click="
|
||||
[
|
||||
(infoMenuOpenToggle = !infoMenuOpenToggle),
|
||||
@ -178,41 +137,117 @@ onClickOutside(infoMenuRef, () => {
|
||||
"
|
||||
>
|
||||
<h1
|
||||
class="text-gray-50 font-semibold sm:text-base"
|
||||
class="topbar-text topbar-link"
|
||||
:style="{
|
||||
'border-bottom': infoMenuOpenToggle
|
||||
? '2px solid white'
|
||||
: 'transparent',
|
||||
: '2px solid transparent',
|
||||
}"
|
||||
>
|
||||
Menu
|
||||
</h1>
|
||||
</div>
|
||||
</button>
|
||||
<transition name="dropdown">
|
||||
<div
|
||||
v-show="infoMenuOpenToggle"
|
||||
class="mt-10 absolute w-full text-gray-900 hidden lg:inline-block"
|
||||
>
|
||||
<div class="mt-2">
|
||||
<div class="bg-white rounded-md z-10 -left-36 w-52">
|
||||
<div class="menu-button gap-2 px-4 rounded-md cursor-pointer">
|
||||
<span
|
||||
class="text-gray-900 py-4 text-end font-semibold text-sm"
|
||||
>
|
||||
Documentação
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<RouterLink
|
||||
:to="'/faq'"
|
||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
||||
>
|
||||
<span
|
||||
class="text-gray-900 py-4 text-end font-semibold text-sm whitespace-nowrap"
|
||||
>
|
||||
Perguntas frequentes
|
||||
</span>
|
||||
</RouterLink>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div
|
||||
class="sm:text-center sm:justify-center ml-8 sm:ml-0 gap-2 px-4 rounded-md"
|
||||
>
|
||||
<div class="redirect_button flex justify-around">
|
||||
<a href="https://www.twitter.com/doiim">
|
||||
<img
|
||||
alt="Twitter"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/twitterIcon.svg?url"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://www.linkedin.com/company/doiim/">
|
||||
<img
|
||||
alt="LinkedIn"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/linkedinIcon.svg?url"
|
||||
class="cursor-pointer"
|
||||
href="https://www.linkedin.com/company/doiim/"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://www.github.com/doiim">
|
||||
<img
|
||||
alt="Github"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/githubIcon.svg?url"
|
||||
class="cursor-pointer"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<RouterLink
|
||||
:to="'/faq'"
|
||||
v-if="!walletAddress"
|
||||
class="default-button sm-view"
|
||||
class="default-button inline-block md:hidden"
|
||||
>
|
||||
FAQ
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
:to="sellerView ? '/' : '/seller'"
|
||||
class="default-button sm:whitespace-normal whitespace-nowrap lg-view"
|
||||
class="default-button whitespace-nowrap w-40 sm:w-44 md:w-36 hidden md:inline-block"
|
||||
>
|
||||
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
||||
<div class="topbar-text topbar-link text-center mx-auto inline-block">
|
||||
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
:to="sellerView ? '/' : '/seller'"
|
||||
v-if="!walletAddress"
|
||||
class="default-button sm:whitespace-normal whitespace-nowrap sm-view"
|
||||
class="default-button sm:whitespace-normal whitespace-nowrap inline-block md:hidden w-40 sm:w-44 md:w-36"
|
||||
>
|
||||
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
||||
<div class="topbar-text topbar-link text-center mx-auto inline-block">
|
||||
{{ sellerView ? "Quero comprar" : "Quero vender" }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
<div class="flex flex-col" v-if="walletAddress">
|
||||
<div class="flex flex-col relative" v-if="walletAddress">
|
||||
<div
|
||||
ref="currencyRef"
|
||||
class="group top-bar-info cursor-pointer hover:bg-white h-10"
|
||||
class="top-bar-info cursor-pointer h-10 group hover:bg-gray-50 transition-all duration-500 ease-in-out"
|
||||
:class="{ 'bg-gray-50': currencyMenuOpenToggle }"
|
||||
@click="
|
||||
[
|
||||
(currencyMenuOpenToggle = !currencyMenuOpenToggle),
|
||||
@ -220,84 +255,57 @@ onClickOutside(infoMenuRef, () => {
|
||||
(infoMenuOpenToggle = false),
|
||||
]
|
||||
"
|
||||
@mouseover="currencyMenuHoverToggle = true"
|
||||
@mouseout="currencyMenuHoverToggle = false"
|
||||
:style="{
|
||||
backgroundColor: currencyMenuOpenToggle
|
||||
? '#F9F9F9'
|
||||
: currencyMenuHoverToggle
|
||||
? '#F9F9F9'
|
||||
: 'transparent',
|
||||
}"
|
||||
>
|
||||
<img
|
||||
alt="Choosed network image"
|
||||
:src="getNetworkImage(etherStore.networkName)"
|
||||
:src="getNetworkImage(NetworkEnum[user.networkName.value])"
|
||||
height="24"
|
||||
width="24"
|
||||
/>
|
||||
<span
|
||||
class="default-text group-hover:text-gray-900 lg-view"
|
||||
:style="{
|
||||
color: currencyMenuOpenToggle
|
||||
? '#000000'
|
||||
: currencyMenuHoverToggle
|
||||
? '#000000'
|
||||
: 'rgb(249 250 251)',
|
||||
}"
|
||||
class="default-text hidden sm:inline-block text-gray-50 group-hover:text-gray-900 transition-all duration-500 ease-in-out whitespace-nowrap text-ellipsis overflow-hidden"
|
||||
:class="{ '!text-gray-900': currencyMenuOpenToggle }"
|
||||
>
|
||||
{{ etherStore.networkName }}
|
||||
{{
|
||||
Networks[user.networkName.value]
|
||||
? Networks[user.networkName.value].chainName
|
||||
: "Invalid Chain"
|
||||
}}
|
||||
</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
|
||||
class="transition-all duration-500 ease-in-out mt-1"
|
||||
:class="{ 'scale-y-[-1]': currencyMenuOpenToggle }"
|
||||
>
|
||||
<ChevronDown
|
||||
viewBox="0 0 16 16"
|
||||
class="h-4 w-4 text-white group-hover:text-gray-900"
|
||||
:class="{ '!text-gray-900': currencyMenuOpenToggle }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="currencyMenuOpenToggle"
|
||||
class="mt-10 pl-3 absolute w-full text-gray-900 lg-view"
|
||||
>
|
||||
<div class="mt-2">
|
||||
<div class="bg-white rounded-md z-10">
|
||||
<transition name="dropdown">
|
||||
<div
|
||||
v-show="currencyMenuOpenToggle"
|
||||
class="mt-10 absolute text-gray-900 min-w-max left-0 hidden md:inline-block"
|
||||
>
|
||||
<div
|
||||
class="mt-2 bg-white rounded-md border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||
>
|
||||
<div
|
||||
class="menu-button gap-2 px-4 rounded-md cursor-pointer"
|
||||
@click="networkChange(NetworkEnum.ethereum)"
|
||||
v-for="(chainData, network) in Networks"
|
||||
:key="network"
|
||||
class="menu-button p-4 gap-2 cursor-pointer hover:bg-gray-200 flex items-center !justify-start whitespace-nowrap transition-colors duration-150 ease-in-out"
|
||||
@click="networkChange(network)"
|
||||
>
|
||||
<img
|
||||
alt="Ethereum image"
|
||||
:alt="chainData.chainName + ' image'"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/ethereum.svg"
|
||||
:src="getNetworkImage(NetworkEnum[network])"
|
||||
class="mr-2 ml-1"
|
||||
/>
|
||||
<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 class="text-gray-900 font-semibold text-sm">
|
||||
{{ chainData.chainName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
@ -305,29 +313,30 @@ onClickOutside(infoMenuRef, () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
v-if="!walletAddress"
|
||||
class="border-amber-500 border-2 rounded default-button lg-view"
|
||||
@click="connectMetaMask()"
|
||||
class="border-amber-500 border-2 rounded default-button hidden md:inline-block"
|
||||
@click="connnectWallet()"
|
||||
>
|
||||
Conectar carteira
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
v-if="!walletAddress"
|
||||
class="border-amber-500 border-2 rounded default-button sm-view"
|
||||
@click="connectMetaMask()"
|
||||
class="border-amber-500 border-2 rounded default-button inline-block md:hidden"
|
||||
@click="connnectWallet()"
|
||||
>
|
||||
Conectar
|
||||
</button>
|
||||
<div v-if="walletAddress" class="account-info">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex flex-col relative">
|
||||
<div
|
||||
ref="walletAddressRef"
|
||||
class="top-bar-info cursor-pointer h-10"
|
||||
class="top-bar-info cursor-pointer h-10 group hover:bg-gray-50 transition-all duration-500 ease-in-out"
|
||||
:class="{ 'bg-gray-50': menuOpenToggle }"
|
||||
@click="
|
||||
[
|
||||
(menuOpenToggle = !menuOpenToggle),
|
||||
@ -335,74 +344,61 @@ onClickOutside(infoMenuRef, () => {
|
||||
(infoMenuOpenToggle = false),
|
||||
]
|
||||
"
|
||||
@mouseover="menuHoverToggle = true"
|
||||
@mouseout="menuHoverToggle = false"
|
||||
:style="{
|
||||
backgroundColor: menuOpenToggle
|
||||
? '#F9F9F9'
|
||||
: menuHoverToggle
|
||||
? '#F9F9F9'
|
||||
: 'transparent',
|
||||
}"
|
||||
>
|
||||
<img alt="Account image" src="@/assets/account.svg" />
|
||||
<img alt="Account image" src="@/assets/account.svg?url" />
|
||||
<span
|
||||
class="default-text"
|
||||
:style="{
|
||||
color: menuOpenToggle
|
||||
? '#000000'
|
||||
: menuHoverToggle
|
||||
? '#000000'
|
||||
: 'rgb(249 250 251)',
|
||||
}"
|
||||
class="default-text text-gray-50 group-hover:text-gray-900 transition-all duration-500 ease-in-out truncate text-ellipsis"
|
||||
:class="{ '!text-gray-900': menuOpenToggle }"
|
||||
>
|
||||
{{ formatWalletAddress() }}
|
||||
</span>
|
||||
<img
|
||||
class="text-gray-900"
|
||||
v-if="!menuHoverToggle && !menuOpenToggle"
|
||||
alt="Chevron Down"
|
||||
src="@/assets/chevronDown.svg"
|
||||
/>
|
||||
<img
|
||||
v-if="menuOpenToggle"
|
||||
alt="Chevron Up"
|
||||
src="@/assets/chevronUp.svg"
|
||||
/>
|
||||
<img
|
||||
v-if="menuHoverToggle && !menuOpenToggle"
|
||||
alt="Chevron Down Black"
|
||||
src="@/assets/chevronDownBlack.svg"
|
||||
/>
|
||||
<div
|
||||
class="transition-all duration-500 ease-in-out mt-1"
|
||||
:class="{ 'scale-y-[-1]': menuOpenToggle }"
|
||||
>
|
||||
<ChevronDown
|
||||
viewBox="0 0 16 16"
|
||||
class="h-4 w-4 text-white group-hover:text-gray-900"
|
||||
:class="{ '!text-gray-900': menuOpenToggle }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="menuOpenToggle"
|
||||
class="mt-10 absolute w-full text-gray-900 lg-view"
|
||||
>
|
||||
<div class="pl-4 mt-2">
|
||||
<div class="bg-white rounded-md z-10">
|
||||
<div class="menu-button" @click="closeMenu()">
|
||||
<RouterLink to="/manage_bids" class="redirect_button">
|
||||
<transition name="dropdown">
|
||||
<div
|
||||
v-show="menuOpenToggle"
|
||||
class="mt-10 absolute w-full text-gray-900 hidden md:inline-block"
|
||||
>
|
||||
<div class="pl-4 mt-2">
|
||||
<div
|
||||
class="bg-white rounded-md z-10 border border-gray-300 drop-shadow-md shadow-md overflow-clip"
|
||||
>
|
||||
<RouterLink
|
||||
to="/manage_bids"
|
||||
class="redirect_button menu-button"
|
||||
@click="closeMenu()"
|
||||
>
|
||||
Gerenciar Ofertas
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div class="menu-button" @click="disconnectUser">
|
||||
<RouterLink to="/" class="redirect_button">
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<RouterLink
|
||||
to="/"
|
||||
class="redirect_button menu-button"
|
||||
@click="disconnectUser"
|
||||
>
|
||||
Desconectar
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="menuOpenToggle"
|
||||
class="mobile-menu fixed w-4/5 text-gray-900 sm-view"
|
||||
class="mobile-menu fixed w-4/5 text-gray-900 inline-block md:hidden"
|
||||
>
|
||||
<div class="pl-4 mt-2 h-full">
|
||||
<div class="bg-white rounded-md z-10 h-full">
|
||||
@ -433,75 +429,54 @@ onClickOutside(infoMenuRef, () => {
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div class="menu-button pb-10">
|
||||
<div class="redirect_button flex">
|
||||
<img
|
||||
alt="Twitter"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/twitterIcon.svg"
|
||||
class="mr-6"
|
||||
onclick="location.href = 'https://www.twitter.com/doiim';"
|
||||
/>
|
||||
<img
|
||||
alt="Discord"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/discordIcon.svg"
|
||||
class="mr-6"
|
||||
/>
|
||||
<img
|
||||
alt="Github"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/githubIcon.svg"
|
||||
onclick="location.href = 'https://github.com/doiim';"
|
||||
/>
|
||||
</div>
|
||||
<div class="redirect_button mx-10">
|
||||
<a
|
||||
href="https://www.twitter.com/doiim/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<TwitterIcon alt="Twitter" width="20" height="20" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.linkedin.com/company/doiim/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<LinkedinIcon alt="LinkedIn" width="20" height="20" />
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/doiim/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<GithubIcon alt="Github" width="20" height="20" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="currencyMenuOpenToggle"
|
||||
class="mobile-menu fixed w-4/5 text-gray-900 sm-view"
|
||||
class="mobile-menu fixed w-4/5 text-gray-900 inline-block md:hidden"
|
||||
>
|
||||
<div class="pl-4 mt-2 h-full">
|
||||
<div class="bg-white rounded-md z-10 h-full">
|
||||
<div
|
||||
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2"
|
||||
@click="networkChange(NetworkEnum.ethereum)"
|
||||
v-for="(chainData, network) in Networks"
|
||||
:key="network"
|
||||
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2 px-4"
|
||||
@click="networkChange(network)"
|
||||
>
|
||||
<img
|
||||
alt="Ethereum image"
|
||||
:alt="chainData.chainName + 'image'"
|
||||
width="20"
|
||||
height="20"
|
||||
src="@/assets/ethereum.svg"
|
||||
:src="getNetworkImage(NetworkEnum[network])"
|
||||
/>
|
||||
<span class="text-gray-900 py-4 text-end font-bold text-sm">
|
||||
Ethereum
|
||||
{{ chainData.chainName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
<div
|
||||
class="menu-button gap-2 sm:px-4 rounded-md cursor-pointer py-2"
|
||||
@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-bold text-sm">
|
||||
Polygon
|
||||
</span>
|
||||
</div>
|
||||
<div class="w-full flex justify-center pb-12">
|
||||
<hr class="w-4/5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -517,6 +492,13 @@ header {
|
||||
@apply sm:px-4 px-3 py-2 rounded text-gray-50 font-semibold sm:text-base text-xs hover:bg-transparent;
|
||||
}
|
||||
|
||||
.topbar-text {
|
||||
@apply text-gray-50 font-semibold sm:text-base mt-0.5 transition-all duration-500;
|
||||
}
|
||||
.topbar-link {
|
||||
@apply mt-0.5 hover:border-b-2 border-b-2 border-transparent hover:!border-white;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
@apply flex items-center gap-6;
|
||||
}
|
||||
@ -526,42 +508,24 @@ header {
|
||||
}
|
||||
|
||||
.top-bar-info {
|
||||
@apply flex justify-between gap-2 px-4 py-2 border-amber-500 border-2 sm:rounded rounded-lg items-center;
|
||||
@apply flex justify-between items-center gap-2 px-4 py-2 border-amber-500 border-2 sm:rounded rounded-lg;
|
||||
}
|
||||
|
||||
.redirect_button {
|
||||
@apply py-5 text-gray-900 sm:font-semibold font-bold sm:text-xs text-sm w-full;
|
||||
@apply py-5 px-4 text-gray-900 flex sm:font-semibold font-bold sm:text-xs text-sm justify-between;
|
||||
}
|
||||
|
||||
.menu-button {
|
||||
@apply flex sm:text-center sm:justify-center hover:bg-gray-200 ml-8 sm:ml-0;
|
||||
@apply flex sm:text-center sm:justify-center hover:bg-gray-200 hover:rounded-lg w-full;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
@apply bg-gray-200 rounded;
|
||||
}
|
||||
|
||||
.lg-view {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.sm-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-menu {
|
||||
margin-top: 1400px;
|
||||
bottom: 0px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.lg-view {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sm-view {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,35 +0,0 @@
|
||||
/* eslint-disable no-undef */
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import TopBar from "../TopBar.vue";
|
||||
import { useEtherStore } from "../../../store/ether";
|
||||
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
|
||||
describe("TopBar.vue", () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
});
|
||||
|
||||
it("should render connect wallet button", () => {
|
||||
const wrapper = shallowMount(TopBar);
|
||||
expect(wrapper.html()).toContain("Conectar carteira");
|
||||
});
|
||||
|
||||
it("should render button to change to seller view when in buyer screen", () => {
|
||||
const wrapper = shallowMount(TopBar);
|
||||
expect(wrapper.html()).toContain("Quero vender");
|
||||
});
|
||||
|
||||
it("should render button to change to seller view when in buyer screen", () => {
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setSellerView(true);
|
||||
const wrapper = shallowMount(TopBar);
|
||||
expect(wrapper.html()).toContain("Quero comprar");
|
||||
});
|
||||
|
||||
it("should render the P2Pix logo correctly", () => {
|
||||
const wrapper = shallowMount(TopBar);
|
||||
const img = wrapper.findAll(".logo");
|
||||
expect(img.length).toBe(2);
|
||||
});
|
||||
});
|
106
src/composables/useUser.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import { ref } from "vue";
|
||||
import { NetworkEnum, TokenEnum } from "../model/NetworkEnum";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import type { Participant } from "../utils/bbPay";
|
||||
import { NetworkById } from "@/model/Networks";
|
||||
import type { Address } from "viem"
|
||||
|
||||
const walletAddress = ref<Address | null>(null);
|
||||
const balance = ref("");
|
||||
const networkId = ref(11155111);
|
||||
const networkName = ref(NetworkEnum.sepolia);
|
||||
const selectedToken = ref(TokenEnum.BRZ);
|
||||
const loadingLock = ref(false);
|
||||
const sellerView = ref(false);
|
||||
const depositsValidList = ref<ValidDeposit[]>([]);
|
||||
const loadingWalletTransactions = ref(false);
|
||||
const loadingNetworkLiquidity = ref(false);
|
||||
const seller = ref<Participant>({} as Participant);
|
||||
const sellerId = ref("");
|
||||
|
||||
export function useUser() {
|
||||
// Actions become regular functions
|
||||
const setWalletAddress = (address: Address | null) => {
|
||||
walletAddress.value = address;
|
||||
};
|
||||
|
||||
const setBalance = (newBalance: string) => {
|
||||
balance.value = newBalance;
|
||||
};
|
||||
|
||||
const setSelectedToken = (token: TokenEnum) => {
|
||||
selectedToken.value = token;
|
||||
};
|
||||
|
||||
const setNetworkId = (network: string | number) => {
|
||||
networkName.value = NetworkById(network) || NetworkEnum.sepolia;
|
||||
networkId.value = Number(network);
|
||||
};
|
||||
|
||||
const setLoadingLock = (isLoading: boolean) => {
|
||||
loadingLock.value = isLoading;
|
||||
};
|
||||
|
||||
const setSellerView = (view: boolean) => {
|
||||
sellerView.value = view;
|
||||
};
|
||||
|
||||
const setDepositsValidList = (deposits: ValidDeposit[]) => {
|
||||
depositsValidList.value = deposits;
|
||||
};
|
||||
|
||||
const setLoadingWalletTransactions = (isLoading: boolean) => {
|
||||
loadingWalletTransactions.value = isLoading;
|
||||
};
|
||||
|
||||
const setLoadingNetworkLiquidity = (isLoading: boolean) => {
|
||||
loadingNetworkLiquidity.value = isLoading;
|
||||
};
|
||||
|
||||
const setSeller = (newSeller: Participant) => {
|
||||
seller.value = newSeller;
|
||||
};
|
||||
|
||||
const setSellerId = (id: string) => {
|
||||
sellerId.value = id;
|
||||
};
|
||||
|
||||
// Getters become computed or regular functions
|
||||
const getValidDepositByWalletAddress = (address: string) => {
|
||||
return depositsValidList.value
|
||||
.filter((deposit) => deposit.seller == address)
|
||||
.sort((a, b) => b.blockNumber - a.blockNumber);
|
||||
};
|
||||
|
||||
return {
|
||||
// State
|
||||
walletAddress,
|
||||
balance,
|
||||
networkId,
|
||||
networkName,
|
||||
selectedToken,
|
||||
loadingLock,
|
||||
sellerView,
|
||||
depositsValidList,
|
||||
loadingWalletTransactions,
|
||||
loadingNetworkLiquidity,
|
||||
seller,
|
||||
sellerId,
|
||||
|
||||
// Actions
|
||||
setWalletAddress,
|
||||
setBalance,
|
||||
setSelectedToken,
|
||||
setNetworkId,
|
||||
setLoadingLock,
|
||||
setSellerView,
|
||||
setDepositsValidList,
|
||||
setLoadingWalletTransactions,
|
||||
setLoadingNetworkLiquidity,
|
||||
setSeller,
|
||||
setSellerId,
|
||||
|
||||
// Getters
|
||||
getValidDepositByWalletAddress,
|
||||
};
|
||||
}
|
@ -1,13 +1,12 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
import "./assets/main.css";
|
||||
import "./assets/transitions.css";
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
app.use(createPinia());
|
||||
|
||||
app.mount("#app");
|
||||
|
5
src/model/Bank.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Bank {
|
||||
COMPE: string;
|
||||
ISPB: string;
|
||||
longName: string;
|
||||
}
|
8
src/model/LockStatus.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { Address } from "viem";
|
||||
|
||||
export enum LockStatus { // from DataTypes.sol
|
||||
Inexistent = 0, // Uninitialized Lock
|
||||
Active = 1, // Valid Lock
|
||||
Expired = 2, // Expired Lock
|
||||
Released = 3 // Already released Lock
|
||||
}
|
@ -1,4 +1,18 @@
|
||||
export enum NetworkEnum {
|
||||
ethereum = "Ethereum",
|
||||
polygon = "Polygon",
|
||||
sepolia = 11155111,
|
||||
rootstock = 31,
|
||||
}
|
||||
|
||||
export const getNetworkSubgraphURL = (network: NetworkEnum) => {
|
||||
const networkMap: Record<NetworkEnum, string> = {
|
||||
[NetworkEnum.sepolia]: import.meta.env.VITE_SEPOLIA_SUBGRAPH_URL || "",
|
||||
[NetworkEnum.rootstock]: import.meta.env.VITE_RSK_SUBGRAPH_URL || "",
|
||||
};
|
||||
|
||||
return networkMap[network] || "";
|
||||
};
|
||||
|
||||
export enum TokenEnum {
|
||||
BRZ = "BRZ",
|
||||
// BRX = 'BRX'
|
||||
}
|
||||
|
37
src/model/Networks.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { NetworkEnum } from "@/model/NetworkEnum";
|
||||
|
||||
export const NetworkById = (
|
||||
chainId: string | number
|
||||
): NetworkEnum | undefined => {
|
||||
const normalizedChainId =
|
||||
typeof chainId === "number" ? chainId : Number(chainId);
|
||||
|
||||
for (const [network, details] of Object.entries(Networks)) {
|
||||
if (Number(details.chainId) === normalizedChainId) {
|
||||
return network as unknown as NetworkEnum;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const Networks = {
|
||||
[NetworkEnum.sepolia]: {
|
||||
chainId: "0xAA36A7",
|
||||
chainName: "Sepolia Testnet",
|
||||
},
|
||||
[NetworkEnum.rootstock]: {
|
||||
chainId: "0x1F",
|
||||
chainName: "Rootstock Testnet",
|
||||
rpcUrls: ["https://public-node.testnet.rsk.co/"],
|
||||
iconUrls: [
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAYAAACWwljjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAPOSURBVHgBxVhNUhpBFH6vGdxp4S4LoSYnEE8gnEA4AbpMJUQ4gXgCRJK1egLxBOIJJCdwJElVllMu49Cd1z04zD/dY1H5qihmut/M93VPv59uhHdAXFaPAaEDgA2/BaeA4hq/zG+gIBAKQoyr9yshid4Jdn+2oQAYFIC4rA2zxUhgS3yrDqEAjGdIDD/YYG09aRl7L7vYd10wgPkMlcoNfdvtFhjCXJBAeyO2S5gLQuFo25bEIxjCCt8oN2Z46I+Mu4A4SbjwojQBi1+BDl5LP+JNYlhtQRmPsjjQN1ILldwY7JTXOuD9bWL/jxO8dFy7oL9TyMcIu/PeSghxlLduQUA9jwPXiAk98HLw5jFiaFfAEjRLImPR0qi7z+2VmArZ7zzqcDAS01ljCKqf7QSjxb7jKkIhTohu6rOCq64RjsNiFEo7x7ocSNMvlddhPWb0CQ6gAAw4HKZpKGFDcWhzSEG6kbQCm4dLbi9m+XlpBTHea2D31zTSNtxrAGMNdcP5FPuxfhlKdCHgASUJxcd7zUcobkAPXvkzWGyf7uVCt2M2DtkMljaHSxu92WWLAz8OjWsD+juD/4tzcpqBSh3yQrmwoNFFMZNuDB7bJRsp/hzMMQqeT+NQ96KtNEBK+SG+23XgHgUyy8FPjpPozy3M4sZwh1/nLRMOK26Mn50Z5IHjA6XkBugJSn1XHkeBbK8dJsxsl0jMEOUpm0o9+gkX+7+TI0E+0x6Hsk0ijyNYQ/4OAqWn2aF+5cLxEoRq6idqtyEPtFhp/XyMNI2p9ADFUc/iYL5h7YzEXEEyptj04mvVHxkGP4F8MS4sWDsqRr4DbyGZRiIcqCKtpRMYeTMcpVVAFewqMVPSjUkMVQTBp6BPVKeiTqN65E0qP1AvIArWC98qcQsms39oDeBEtoXFKFgLbQ76ZKiXiRH2E01UF9Go+kGDh32/LWHZAD2OQ7mGdLO4ndrqWaHZyNyD6XJUWEq6yIQqReOweCe49ivD2DNUIutjJgXpHwyUtyPbY/IMWehfBA0IZxQSQoW9rKXL+ltq0oKqYC+RB6yLKys4xEw/Idde5R02cTGOcgh1LSNnid+nihIqcN0tr48MhL89L2uoG+Dqv5Px/IwqAhkqnEi296M1OyLPqVCgdKhcuKNjlUnQL4X78cRk1E1JlMkBME1sFE0gRrRJZGs3iT44bRZP5z0wQJHzIZMMbpztN1t+FDhsMBe0YNfatimHDetgLGiZGkYapqPwYt6YIAWPDYI9fSrETfjkwwSFT2EVrV/USY+r+/GGNp2I7zoW/gdR9aOdZ/lPGgAAAABJRU5ErkJggg==",
|
||||
],
|
||||
nativeCurrency: {
|
||||
name: "tRBTC",
|
||||
symbol: "tRBTC",
|
||||
decimals: 18,
|
||||
},
|
||||
blockExplorerUrls: ["https://explorer.testnet.rootstock.io/"],
|
||||
},
|
||||
};
|
@ -1,11 +1,11 @@
|
||||
export type Pix = {
|
||||
pixKey: string;
|
||||
merchantCity?: string;
|
||||
merchantName?: string;
|
||||
value?: number;
|
||||
transactionId?: string;
|
||||
message?: string;
|
||||
cep?: string;
|
||||
currency?: number;
|
||||
countryCode?: string;
|
||||
pixKey: string;
|
||||
merchantCity?: string;
|
||||
merchantName?: string;
|
||||
value?: number;
|
||||
transactionId?: string;
|
||||
message?: string;
|
||||
cep?: string;
|
||||
currency?: number;
|
||||
countryCode?: string;
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
import type { Pix } from "./Pix";
|
||||
import { Address } from "viem";
|
||||
|
||||
export type UnreleasedLock = {
|
||||
lockID: string;
|
||||
pix: Pix;
|
||||
lockID: bigint;
|
||||
sellerAddress: Address;
|
||||
tokenAddress: Address;
|
||||
amount: number;
|
||||
};
|
||||
|
@ -1,8 +1,12 @@
|
||||
import { NetworkEnum } from "./NetworkEnum";
|
||||
import type { Address } from "viem";
|
||||
|
||||
export type ValidDeposit = {
|
||||
token: string;
|
||||
token: Address;
|
||||
blockNumber: number;
|
||||
remaining: number;
|
||||
seller: string;
|
||||
pixKey: number;
|
||||
seller: Address;
|
||||
participantID: string;
|
||||
network: NetworkEnum;
|
||||
open?: boolean;
|
||||
};
|
||||
|
@ -1,11 +1,14 @@
|
||||
import type { LockStatus } from "@/model/LockStatus"
|
||||
import type { Address } from "viem"
|
||||
|
||||
export type WalletTransaction = {
|
||||
token: string;
|
||||
token?: Address;
|
||||
blockNumber: number;
|
||||
amount: number;
|
||||
seller: string;
|
||||
buyer: string;
|
||||
event: string;
|
||||
lockStatus: number;
|
||||
lockStatus?: LockStatus;
|
||||
transactionHash: string;
|
||||
transactionID?: string;
|
||||
};
|
||||
|
@ -1,121 +0,0 @@
|
||||
import type { Event } from "ethers";
|
||||
import { vi } from "vitest";
|
||||
|
||||
export const MockEvents: Event[] = [
|
||||
{
|
||||
blockNumber: 1,
|
||||
blockHash: "0x8",
|
||||
transactionIndex: 1,
|
||||
removed: false,
|
||||
address: "0x0",
|
||||
data: "0x0",
|
||||
topics: ["0x0", "0x0"],
|
||||
transactionHash: "0x0",
|
||||
logIndex: 1,
|
||||
event: "DepositAdded",
|
||||
eventSignature: "DepositAdded(address,uint256,address,uint256)",
|
||||
args: [
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x00",
|
||||
},
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x6c6b935b8bbd400000",
|
||||
},
|
||||
],
|
||||
getBlock: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
getTransaction: vi.fn(),
|
||||
getTransactionReceipt: vi.fn(),
|
||||
},
|
||||
{
|
||||
blockNumber: 2,
|
||||
blockHash: "0x8",
|
||||
transactionIndex: 2,
|
||||
removed: false,
|
||||
address: "0x0",
|
||||
data: "0x0",
|
||||
topics: ["0x0", "0x0"],
|
||||
transactionHash: "0x0",
|
||||
logIndex: 2,
|
||||
event: "LockAdded",
|
||||
eventSignature: "LockAdded(address,uint256,address,uint256)",
|
||||
args: [
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x00",
|
||||
},
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x6c6b935b8bbd400000",
|
||||
},
|
||||
],
|
||||
getBlock: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
getTransaction: vi.fn(),
|
||||
getTransactionReceipt: vi.fn(),
|
||||
},
|
||||
{
|
||||
blockNumber: 3,
|
||||
blockHash: "0x8",
|
||||
transactionIndex: 3,
|
||||
removed: false,
|
||||
address: "0x0",
|
||||
data: "0x0",
|
||||
topics: ["0x0", "0x0"],
|
||||
transactionHash: "0x0",
|
||||
logIndex: 3,
|
||||
event: "LockReleased",
|
||||
eventSignature: "LockReleased(address,uint256,address,uint256)",
|
||||
args: [
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x00",
|
||||
},
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x6c6b935b8bbd400000",
|
||||
},
|
||||
],
|
||||
getBlock: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
getTransaction: vi.fn(),
|
||||
getTransactionReceipt: vi.fn(),
|
||||
},
|
||||
{
|
||||
blockNumber: 4,
|
||||
blockHash: "0x8",
|
||||
transactionIndex: 4,
|
||||
removed: false,
|
||||
address: "0x0",
|
||||
data: "0x0",
|
||||
topics: ["0x0", "0x0"],
|
||||
transactionHash: "0x0",
|
||||
logIndex: 4,
|
||||
event: "LockReleased",
|
||||
eventSignature: "LockReleased(address,uint256,address,uint256)",
|
||||
args: [
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x00",
|
||||
},
|
||||
"0x0",
|
||||
{
|
||||
type: "BigNumber",
|
||||
hex: "0x6c6b935b8bbd400000",
|
||||
},
|
||||
],
|
||||
getBlock: vi.fn(),
|
||||
removeListener: vi.fn(),
|
||||
getTransaction: vi.fn(),
|
||||
getTransactionReceipt: vi.fn(),
|
||||
},
|
||||
];
|
@ -1,39 +0,0 @@
|
||||
import type { ValidDeposit } from "../ValidDeposit";
|
||||
|
||||
export const MockValidDeposits: ValidDeposit[] = [
|
||||
{
|
||||
blockNumber: 1,
|
||||
token: "1",
|
||||
remaining: 70,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: 123456789,
|
||||
},
|
||||
{
|
||||
blockNumber: 2,
|
||||
token: "2",
|
||||
remaining: 200,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: 123456789,
|
||||
},
|
||||
{
|
||||
blockNumber: 3,
|
||||
token: "3",
|
||||
remaining: 1250,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: 123456789,
|
||||
},
|
||||
{
|
||||
blockNumber: 4,
|
||||
token: "4",
|
||||
remaining: 4000,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: 123456789,
|
||||
},
|
||||
{
|
||||
blockNumber: 5,
|
||||
token: "5",
|
||||
remaining: 2000,
|
||||
seller: "mockedSellerAddress",
|
||||
pixKey: 123456789,
|
||||
},
|
||||
];
|
@ -1,54 +0,0 @@
|
||||
import type { WalletTransaction } from "../WalletTransaction";
|
||||
|
||||
export const MockWalletTransactions: WalletTransaction[] = [
|
||||
{
|
||||
blockNumber: 1,
|
||||
token: "1",
|
||||
amount: 70,
|
||||
seller: "mockedSellerAddress",
|
||||
buyer: "mockedBuyerAddress",
|
||||
event: "Deposit",
|
||||
lockStatus: 0,
|
||||
transactionHash: "1",
|
||||
},
|
||||
{
|
||||
blockNumber: 2,
|
||||
token: "2",
|
||||
amount: 200,
|
||||
seller: "mockedSellerAddress",
|
||||
buyer: "mockedBuyerAddress",
|
||||
event: "Lock",
|
||||
lockStatus: 1,
|
||||
transactionHash: "2",
|
||||
},
|
||||
{
|
||||
blockNumber: 3,
|
||||
token: "3",
|
||||
amount: 1250,
|
||||
seller: "mockedSellerAddress",
|
||||
buyer: "mockedBuyerAddress",
|
||||
event: "Release",
|
||||
lockStatus: 2,
|
||||
transactionHash: "3",
|
||||
},
|
||||
{
|
||||
blockNumber: 4,
|
||||
token: "4",
|
||||
amount: 4000,
|
||||
seller: "mockedSellerAddress",
|
||||
buyer: "mockedBuyerAddress",
|
||||
event: "Deposit",
|
||||
lockStatus: 0,
|
||||
transactionHash: "4",
|
||||
},
|
||||
{
|
||||
blockNumber: 5,
|
||||
token: "5",
|
||||
amount: 2000,
|
||||
seller: "mockedSellerAddress",
|
||||
buyer: "mockedBuyerAddress",
|
||||
event: "Deposit",
|
||||
lockStatus: 3,
|
||||
transactionHash: "5",
|
||||
},
|
||||
];
|
@ -1,14 +0,0 @@
|
||||
/* 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;
|
@ -1,59 +0,0 @@
|
||||
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 GOERLI
|
||||
depositsValidListGoerli: [] as ValidDeposit[],
|
||||
// Depósitos válidos para compra MUMBAI
|
||||
depositsValidListMumbai: [] as ValidDeposit[],
|
||||
loadingWalletTransactions: false,
|
||||
loadingNetworkLiquidity: false,
|
||||
}),
|
||||
actions: {
|
||||
setWalletAddress(walletAddress: string) {
|
||||
this.walletAddress = walletAddress;
|
||||
},
|
||||
setBalance(balance: string) {
|
||||
this.balance = balance;
|
||||
},
|
||||
setNetworkName(networkName: NetworkEnum) {
|
||||
this.networkName = networkName;
|
||||
},
|
||||
setLoadingLock(isLoadingLock: boolean) {
|
||||
this.loadingLock = isLoadingLock;
|
||||
},
|
||||
setSellerView(sellerView: boolean) {
|
||||
this.sellerView = sellerView;
|
||||
},
|
||||
setDepositsValidListGoerli(depositsValidList: ValidDeposit[]) {
|
||||
this.depositsValidListGoerli = depositsValidList;
|
||||
},
|
||||
setDepositsValidListMumbai(depositsValidList: ValidDeposit[]) {
|
||||
this.depositsValidListMumbai = depositsValidList;
|
||||
},
|
||||
setLoadingWalletTransactions(isLoadingWalletTransactions: boolean) {
|
||||
this.loadingWalletTransactions = isLoadingWalletTransactions;
|
||||
},
|
||||
setLoadingNetworkLiquidity(isLoadingNetworkLiquidity: boolean) {
|
||||
this.loadingNetworkLiquidity = isLoadingNetworkLiquidity;
|
||||
},
|
||||
},
|
||||
// Alterar para integrar com mumbai
|
||||
getters: {
|
||||
getValidDepositByWalletAddress: (state) => {
|
||||
return (walletAddress: string) =>
|
||||
state.depositsValidListGoerli
|
||||
.filter((deposit) => deposit.seller == walletAddress)
|
||||
.sort((a, b) => {
|
||||
return b.blockNumber - a.blockNumber;
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
@ -1,76 +0,0 @@
|
||||
import qrcode from "qrcode";
|
||||
import type { QRCodeToDataURLOptions } from "qrcode";
|
||||
import { crc16ccitt } from "crc";
|
||||
import type { Pix } from "@/model/Pix";
|
||||
|
||||
const pix = ({
|
||||
pixKey,
|
||||
merchantCity = "city",
|
||||
merchantName = "name",
|
||||
value,
|
||||
message,
|
||||
cep,
|
||||
transactionId = "***",
|
||||
currency = 986,
|
||||
countryCode = "BR",
|
||||
}: Pix) => {
|
||||
const payloadKeyString = generatePixKey(pixKey, message);
|
||||
|
||||
const payload: string[] = [
|
||||
formatEMV("00", "01"), //Payload Format Indicator
|
||||
formatEMV("26", payloadKeyString), // Merchant Account Information
|
||||
formatEMV("52", "0000"), //Merchant Category Code
|
||||
formatEMV("53", String(currency)), // Transaction Currency
|
||||
];
|
||||
|
||||
if (String(value) === "0") {
|
||||
value = undefined;
|
||||
}
|
||||
if (value) {
|
||||
payload.push(formatEMV("54", value.toFixed(2)));
|
||||
}
|
||||
|
||||
payload.push(formatEMV("58", countryCode.toUpperCase())); // Country Code
|
||||
payload.push(formatEMV("59", merchantName)); // Merchant Name
|
||||
payload.push(formatEMV("60", merchantCity)); // Merchant City
|
||||
|
||||
if (cep) {
|
||||
payload.push(formatEMV("61", cep)); // Postal Code
|
||||
}
|
||||
|
||||
payload.push(formatEMV("62", formatEMV("05", transactionId))); // Additional Data Field Template
|
||||
|
||||
payload.push("6304");
|
||||
|
||||
const stringPayload = payload.join("");
|
||||
const crcResult = crc16ccitt(stringPayload)
|
||||
.toString(16)
|
||||
.toUpperCase()
|
||||
.padStart(4, "0");
|
||||
|
||||
const payloadPIX = `${stringPayload}${crcResult}`;
|
||||
|
||||
return {
|
||||
payload: (): string => payloadPIX,
|
||||
base64QrCode: (options?: QRCodeToDataURLOptions): Promise<string> =>
|
||||
qrcode.toDataURL(payloadPIX, options),
|
||||
};
|
||||
};
|
||||
|
||||
const generatePixKey = (pixKey: string, message?: string): string => {
|
||||
const payload: string[] = [
|
||||
formatEMV("00", "BR.GOV.BCB.PIX"),
|
||||
formatEMV("01", pixKey),
|
||||
];
|
||||
if (message) {
|
||||
payload.push(formatEMV("02", message));
|
||||
}
|
||||
return payload.join("");
|
||||
};
|
||||
|
||||
const formatEMV = (id: string, param: string): string => {
|
||||
const len = param?.length?.toString().padStart(2, "0");
|
||||
return `${id}${len}${param}`;
|
||||
};
|
||||
|
||||
export { pix };
|
@ -1,24 +0,0 @@
|
||||
import { it, expect, vi, type Mock } from "vitest";
|
||||
import { debounce } from "../debounce";
|
||||
|
||||
vi.useFakeTimers();
|
||||
|
||||
describe("debounce function test", () => {
|
||||
let mockFunction: Mock;
|
||||
let debounceFunction: Function;
|
||||
|
||||
beforeEach(() => {
|
||||
mockFunction = vi.fn();
|
||||
debounceFunction = debounce(mockFunction, 1000);
|
||||
});
|
||||
|
||||
it("debounce function will be executed just once", () => {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
debounceFunction();
|
||||
}
|
||||
|
||||
vi.runAllTimers();
|
||||
|
||||
expect(mockFunction).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { it, expect } from "vitest";
|
||||
import { decimalCount } from "../decimalCount";
|
||||
|
||||
describe("decimalCount function test", () => {
|
||||
it("decimalCount should return length 1 of decimal", () => {
|
||||
expect(decimalCount("4.1")).toEqual(1);
|
||||
});
|
||||
|
||||
it("decimalCount should return length 0 because no decimal found", () => {
|
||||
expect(decimalCount("5")).toEqual(0);
|
||||
});
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
import { MockValidDeposits } from "@/model/mock/ValidDepositMock";
|
||||
import { it, expect, vi } from "vitest";
|
||||
import { verifyNetworkLiquidity } from "../networkLiquidity";
|
||||
|
||||
vi.useFakeTimers();
|
||||
|
||||
describe("verifyNetworkLiquidity function test", () => {
|
||||
it("verifyNetworkLiquidity should return an element from valid deposit list when searching for other deposits", () => {
|
||||
const liquidityElement = verifyNetworkLiquidity(
|
||||
MockValidDeposits[0].remaining,
|
||||
"strangeWalletAddress",
|
||||
MockValidDeposits
|
||||
);
|
||||
expect(liquidityElement).toEqual(MockValidDeposits[0]);
|
||||
});
|
||||
|
||||
it("verifyNetworkLiquidity should return undefined when all deposits on valid deposit list match connected wallet addres", () => {
|
||||
const liquidityElement = verifyNetworkLiquidity(
|
||||
MockValidDeposits[0].remaining,
|
||||
MockValidDeposits[0].seller,
|
||||
[MockValidDeposits[0]]
|
||||
);
|
||||
expect(liquidityElement).toEqual(undefined);
|
||||
});
|
||||
});
|
75
src/utils/bbPay.ts
Normal file
@ -0,0 +1,75 @@
|
||||
export interface Participant {
|
||||
offer: string;
|
||||
chainID: number;
|
||||
identification: string;
|
||||
bankIspb?: string;
|
||||
accountType: string;
|
||||
account: string;
|
||||
branch: string;
|
||||
savingsVariation?: string;
|
||||
}
|
||||
|
||||
export interface ParticipantWithID extends Participant {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface Offer {
|
||||
amount: number;
|
||||
sellerId: string;
|
||||
}
|
||||
|
||||
// Specs for BB Pay Sandbox
|
||||
// https://apoio.developers.bb.com.br/sandbox/spec/665797498bb48200130fc32c
|
||||
|
||||
export const createParticipant = async (participant: Participant) => {
|
||||
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/register`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
chainID: participant.chainID,
|
||||
tipoDocumento: 1,
|
||||
numeroDocumento: participant.identification,
|
||||
numeroConta: participant.account,
|
||||
numeroAgencia: participant.branch,
|
||||
tipoConta: participant.accountType,
|
||||
codigoIspb: participant.bankIspb,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error creating participant: ${response.statusText}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
if (data.errors || data.erros) {
|
||||
throw new Error(`Error creating participant: ${JSON.stringify(data)}`);
|
||||
}
|
||||
return { ...participant, id: data.numeroParticipante } as ParticipantWithID;
|
||||
};
|
||||
|
||||
export const createSolicitation = async (offer: Offer) => {
|
||||
const response = await fetch(`${import.meta.env.VITE_APP_API_URL}/request`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
amount: offer.amount,
|
||||
pixTarget: offer.sellerId.split("-").pop(),
|
||||
}),
|
||||
});
|
||||
return response.json();
|
||||
};
|
||||
|
||||
export const getSolicitation = async (id: string) => {
|
||||
const response = await fetch(
|
||||
`${import.meta.env.VITE_APP_API_URL}/release/${id}`
|
||||
);
|
||||
|
||||
const obj: any = await response.json();
|
||||
|
||||
return {
|
||||
pixTarget: obj.pixTarget,
|
||||
signature: obj.signature,
|
||||
};
|
||||
};
|
2142
src/utils/files/isbpList.json
Normal file
30
src/utils/imagesPath.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { TokenEnum } from "@/model/NetworkEnum";
|
||||
|
||||
export const imagesPath = import.meta.glob<string>("@/assets/*.{png,svg}", {
|
||||
eager: true,
|
||||
query: "?url",
|
||||
import: "default",
|
||||
});
|
||||
|
||||
export const getNetworkImage = (networkName: string): string => {
|
||||
try {
|
||||
const path = Object.keys(imagesPath).find((key) =>
|
||||
key.endsWith(`${networkName.toLowerCase()}.svg`)
|
||||
);
|
||||
return path ? imagesPath[path] : "";
|
||||
} catch (error) {
|
||||
console.error("Error fetching network image");
|
||||
const path = Object.keys(imagesPath).find((key) =>
|
||||
key.endsWith(`invalidIcon.svg`)
|
||||
);
|
||||
return path ? imagesPath[path] : "";
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
export const getTokenImage = (tokenName: TokenEnum): string => {
|
||||
const path = Object.keys(imagesPath).find((key) =>
|
||||
key.endsWith(`${tokenName.toLowerCase()}.svg`)
|
||||
);
|
||||
return path ? imagesPath[path] : "";
|
||||
};
|
@ -1,23 +1,40 @@
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import type { Address } from "viem";
|
||||
|
||||
const verifyNetworkLiquidity = (
|
||||
tokenValue: number,
|
||||
walletAddress: string,
|
||||
walletAddress: Address,
|
||||
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;
|
||||
});
|
||||
): ValidDeposit[] => {
|
||||
const filteredDepositList = validDepositList
|
||||
.filter((element) => {
|
||||
const remaining = element.remaining;
|
||||
if (
|
||||
tokenValue!! <= remaining &&
|
||||
tokenValue!! != 0 &&
|
||||
element.seller !== walletAddress
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return b.remaining - a.remaining;
|
||||
});
|
||||
|
||||
return element;
|
||||
const uniqueNetworkDeposits = filteredDepositList.reduce(
|
||||
(acc: ValidDeposit[], current) => {
|
||||
const existingNetwork = acc.find(
|
||||
(deposit) => deposit.network === current.network
|
||||
);
|
||||
if (!existingNetwork) {
|
||||
acc.push(current);
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
return uniqueNetworkDeposits;
|
||||
};
|
||||
|
||||
export { verifyNetworkLiquidity };
|
||||
|
@ -24,7 +24,6 @@ const openItem = (index: number) => {
|
||||
faq.value[selectedSection.value].items[index].content = marked(
|
||||
faq.value[selectedSection.value].items[index].content
|
||||
);
|
||||
console.log(marked(faq.value[selectedSection.value].items[index].content));
|
||||
};
|
||||
</script>
|
||||
|
||||
@ -34,7 +33,7 @@ const openItem = (index: number) => {
|
||||
<span class="text font-extrabold text-5xl max-w-[50rem]"
|
||||
>Perguntas Frequentes
|
||||
</span>
|
||||
<span class="text text-xl font-medium text-base max-w-[40rem]"
|
||||
<span class="text font-medium text-base max-w-[40rem]"
|
||||
>Não conseguiu uma resposta para sua dúvida? Acesse a comunidade do
|
||||
Discord para falar diretamente conosco.</span
|
||||
>
|
||||
@ -61,13 +60,13 @@ const openItem = (index: number) => {
|
||||
<div class="flex cursor-pointer" @click="openItem(index)">
|
||||
<img
|
||||
alt="plus"
|
||||
src="@/assets/plus.svg"
|
||||
src="@/assets/plus.svg?url"
|
||||
class="mr-3"
|
||||
v-if="!item.isOpen"
|
||||
/>
|
||||
<img
|
||||
alt="plus"
|
||||
src="@/assets/minus.svg"
|
||||
src="@/assets/minus.svg?url"
|
||||
class="mr-3"
|
||||
v-if="item.isOpen"
|
||||
/>
|
||||
@ -134,11 +133,4 @@ h4 {
|
||||
.text {
|
||||
@apply text-white 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;
|
||||
}
|
||||
</style>
|
||||
|
@ -3,15 +3,16 @@ import SearchComponent from "@/components/SearchComponent.vue";
|
||||
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
|
||||
import BuyConfirmedComponent from "@/components/BuyConfirmedComponent/BuyConfirmedComponent.vue";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import QrCodeComponent from "@/components/QrCodeComponent.vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { addLock, releaseLock } from "@/blockchain/buyerMethods";
|
||||
import { updateWalletStatus, checkUnreleasedLock } from "@/blockchain/wallet";
|
||||
import { getNetworksLiquidity } from "@/blockchain/events";
|
||||
import type { ValidDeposit } from "@/model/ValidDeposit";
|
||||
import { getUnreleasedLockById } from "@/blockchain/events";
|
||||
import CustomAlert from "@/components/CustomAlert/CustomAlert.vue";
|
||||
import { getSolicitation } from "@/utils/bbPay";
|
||||
import type { Address } from "viem";
|
||||
|
||||
enum Step {
|
||||
Search,
|
||||
@ -19,13 +20,14 @@ enum Step {
|
||||
List,
|
||||
}
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setSellerView(false);
|
||||
const user = useUser();
|
||||
user.setSellerView(false);
|
||||
|
||||
// States
|
||||
const { loadingLock, walletAddress, networkName } = storeToRefs(etherStore);
|
||||
const { loadingLock, walletAddress, networkName } = user;
|
||||
const flowStep = ref<Step>(Step.Search);
|
||||
const pixTarget = ref<number>();
|
||||
const participantID = ref<string>();
|
||||
const sellerAddress = ref<Address>();
|
||||
const tokenAmount = ref<number>();
|
||||
const lockID = ref<string>("");
|
||||
const loadingRelease = ref<boolean>(false);
|
||||
@ -37,53 +39,52 @@ const confirmBuyClick = async (
|
||||
selectedDeposit: ValidDeposit,
|
||||
tokenValue: number
|
||||
) => {
|
||||
// finish buy screen
|
||||
pixTarget.value = selectedDeposit.pixKey;
|
||||
participantID.value = selectedDeposit.participantID;
|
||||
tokenAmount.value = tokenValue;
|
||||
|
||||
// Makes lock with deposit ID and the Amount
|
||||
if (selectedDeposit) {
|
||||
flowStep.value = Step.Buy;
|
||||
etherStore.setLoadingLock(true);
|
||||
user.setLoadingLock(true);
|
||||
|
||||
await addLock(selectedDeposit.seller, selectedDeposit.token, tokenValue)
|
||||
.then((_lockID) => {
|
||||
lockID.value = _lockID;
|
||||
lockID.value = String(_lockID);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
flowStep.value = Step.Search;
|
||||
});
|
||||
|
||||
etherStore.setLoadingLock(false);
|
||||
user.setLoadingLock(false);
|
||||
}
|
||||
};
|
||||
|
||||
const releaseTransaction = async (e2eId: string) => {
|
||||
const releaseTransaction = async ({
|
||||
pixTarget,
|
||||
signature,
|
||||
}: {
|
||||
pixTarget: string;
|
||||
signature: string;
|
||||
}) => {
|
||||
flowStep.value = Step.List;
|
||||
showBuyAlert.value = true;
|
||||
loadingRelease.value = true;
|
||||
|
||||
if (lockID.value && tokenAmount.value && pixTarget.value) {
|
||||
const release = await releaseLock(
|
||||
pixTarget.value,
|
||||
tokenAmount.value,
|
||||
e2eId,
|
||||
lockID.value
|
||||
);
|
||||
await release.wait();
|
||||
const release = await releaseLock(BigInt(lockID.value), pixTarget, signature);
|
||||
await release.wait();
|
||||
|
||||
await updateWalletStatus();
|
||||
loadingRelease.value = false;
|
||||
}
|
||||
await updateWalletStatus();
|
||||
loadingRelease.value = false;
|
||||
};
|
||||
|
||||
const checkForUnreleasedLocks = async (): Promise<void> => {
|
||||
const walletLocks = await checkUnreleasedLock(walletAddress.value);
|
||||
if (walletLocks) {
|
||||
lockID.value = walletLocks.lockID;
|
||||
tokenAmount.value = walletLocks.pix.value;
|
||||
pixTarget.value = Number(walletLocks.pix.pixKey);
|
||||
if (!walletAddress.value)
|
||||
throw new Error("Wallet not connected");
|
||||
const lock = await checkUnreleasedLock(walletAddress.value);
|
||||
if (lock) {
|
||||
lockID.value = String(lock.lockID);
|
||||
tokenAmount.value = lock.amount;
|
||||
sellerAddress.value = lock.sellerAddress;
|
||||
showModal.value = true;
|
||||
} else {
|
||||
flowStep.value = Step.Search;
|
||||
@ -92,11 +93,11 @@ const checkForUnreleasedLocks = async (): Promise<void> => {
|
||||
};
|
||||
|
||||
if (paramLockID) {
|
||||
const lockToRedirect = await getUnreleasedLockById(paramLockID as string);
|
||||
const lockToRedirect = await getUnreleasedLockById(paramLockID);
|
||||
if (lockToRedirect) {
|
||||
lockID.value = lockToRedirect.lockID;
|
||||
tokenAmount.value = lockToRedirect.pix.value;
|
||||
pixTarget.value = Number(lockToRedirect.pix.pixKey);
|
||||
lockID.value = String(lockToRedirect.lockID);
|
||||
tokenAmount.value = lockToRedirect.amount;
|
||||
sellerAddress.value = lockToRedirect.sellerAddress;
|
||||
flowStep.value = Step.Buy;
|
||||
} else {
|
||||
flowStep.value = Step.Search;
|
||||
@ -119,46 +120,47 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SearchComponent
|
||||
v-if="flowStep == Step.Search"
|
||||
@token-buy="confirmBuyClick"
|
||||
/>
|
||||
<CustomAlert
|
||||
v-if="flowStep == Step.Search && showModal"
|
||||
:type="'redirect'"
|
||||
@close-alert="showModal = false"
|
||||
@go-to-lock="flowStep = Step.Buy"
|
||||
/>
|
||||
<CustomAlert
|
||||
v-if="
|
||||
flowStep == Step.List && showBuyAlert && !loadingLock && !loadingRelease
|
||||
"
|
||||
:type="'buy'"
|
||||
@close-alert="showBuyAlert = false"
|
||||
/>
|
||||
<div v-if="flowStep == Step.Buy">
|
||||
<QrCodeComponent
|
||||
:pixTarget="String(pixTarget)"
|
||||
:tokenValue="tokenAmount"
|
||||
@pix-validated="releaseTransaction"
|
||||
v-if="!loadingLock"
|
||||
<div>
|
||||
<SearchComponent
|
||||
v-if="flowStep == Step.Search"
|
||||
@token-buy="confirmBuyClick"
|
||||
/>
|
||||
<LoadingComponent
|
||||
v-if="loadingLock"
|
||||
:message="'A transação está sendo enviada para a rede'"
|
||||
<CustomAlert
|
||||
v-if="flowStep == Step.Search && showModal"
|
||||
:type="'redirect'"
|
||||
@close-alert="showModal = false"
|
||||
@go-to-lock="flowStep = Step.Buy"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="flowStep == Step.List">
|
||||
<div class="flex flex-col gap-10" v-if="!loadingRelease">
|
||||
<BuyConfirmedComponent
|
||||
:tokenAmount="tokenAmount"
|
||||
:is-current-step="flowStep == Step.List"
|
||||
@make-another-transaction="flowStep = Step.Search"
|
||||
<CustomAlert
|
||||
v-if="
|
||||
flowStep == Step.List && showBuyAlert && !loadingLock && !loadingRelease
|
||||
"
|
||||
:type="'buy'"
|
||||
@close-alert="showBuyAlert = false"
|
||||
/>
|
||||
<div v-if="flowStep == Step.Buy">
|
||||
<QrCodeComponent
|
||||
:lockID="lockID"
|
||||
@pix-validated="releaseTransaction"
|
||||
v-if="!loadingLock"
|
||||
/>
|
||||
<LoadingComponent
|
||||
v-if="loadingLock"
|
||||
:message="'A transação está sendo enviada para a rede'"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="flowStep == Step.List">
|
||||
<div class="flex flex-col gap-10" v-if="!loadingRelease">
|
||||
<BuyConfirmedComponent
|
||||
:tokenAmount="tokenAmount"
|
||||
:is-current-step="flowStep == Step.List"
|
||||
@make-another-transaction="flowStep = Step.Search"
|
||||
/>
|
||||
</div>
|
||||
<LoadingComponent
|
||||
v-if="loadingRelease"
|
||||
:message="'A transação está sendo enviada para a rede. Em breve os tokens serão depositados em sua carteira.'"
|
||||
/>
|
||||
</div>
|
||||
<LoadingComponent
|
||||
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>
|
||||
|
@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref, onMounted, watch } from "vue";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import ListingComponent from "@/components/ListingComponent/ListingComponent.vue";
|
||||
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
|
||||
import CustomAlert from "@/components/CustomAlert/CustomAlert.vue";
|
||||
import { ref, watch, onMounted } from "vue";
|
||||
import {
|
||||
listValidDepositTransactionsByWalletAddress,
|
||||
listAllTransactionByWalletAddress,
|
||||
@ -16,9 +15,8 @@ import type { WalletTransaction } from "@/model/WalletTransaction";
|
||||
|
||||
import router from "@/router/index";
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
|
||||
const { walletAddress, networkName } = storeToRefs(etherStore);
|
||||
const user = useUser();
|
||||
const { walletAddress, networkName, selectedToken } = user;
|
||||
const loadingWithdraw = ref<boolean>(false);
|
||||
const showAlert = ref<boolean>(false);
|
||||
|
||||
@ -31,7 +29,7 @@ const callWithdraw = async (amount: string) => {
|
||||
loadingWithdraw.value = true;
|
||||
let withdraw;
|
||||
try {
|
||||
withdraw = await withdrawDeposit(amount);
|
||||
withdraw = await withdrawDeposit(amount, selectedToken.value);
|
||||
} catch {
|
||||
loadingWithdraw.value = false;
|
||||
}
|
||||
@ -48,7 +46,7 @@ const callWithdraw = async (amount: string) => {
|
||||
};
|
||||
|
||||
const getWalletTransactions = async () => {
|
||||
etherStore.setLoadingWalletTransactions(true);
|
||||
user.setLoadingWalletTransactions(true);
|
||||
if (walletAddress.value) {
|
||||
const walletDeposits = await listValidDepositTransactionsByWalletAddress(
|
||||
walletAddress.value
|
||||
@ -67,7 +65,7 @@ const getWalletTransactions = async () => {
|
||||
transactionsList.value = allUserTransactions;
|
||||
}
|
||||
}
|
||||
etherStore.setLoadingWalletTransactions(false);
|
||||
user.setLoadingWalletTransactions(false);
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
@ -87,30 +85,32 @@ watch(networkName, async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CustomAlert
|
||||
v-if="showAlert"
|
||||
:type="'withdraw'"
|
||||
@close-alert="showAlert = false"
|
||||
/>
|
||||
<div class="page">
|
||||
<div class="header" v-if="!loadingWithdraw && !walletAddress">
|
||||
Por Favor Conecte Sua Carteira
|
||||
</div>
|
||||
<div class="header" v-if="!loadingWithdraw && walletAddress">
|
||||
Gerenciar Ofertas
|
||||
</div>
|
||||
<div class="w-full max-w-4xl">
|
||||
<ListingComponent
|
||||
v-if="!loadingWithdraw && walletAddress"
|
||||
:valid-deposits="depositList"
|
||||
:wallet-transactions="transactionsList"
|
||||
:active-lock-amount="activeLockAmount"
|
||||
@deposit-withdrawn="callWithdraw"
|
||||
></ListingComponent>
|
||||
<LoadingComponent
|
||||
v-if="loadingWithdraw"
|
||||
:message="'A transação está sendo enviada para a rede. Em breve os tokens serão depositados em sua carteira.'"
|
||||
/>
|
||||
<div>
|
||||
<CustomAlert
|
||||
v-if="showAlert"
|
||||
:type="'withdraw'"
|
||||
@close-alert="showAlert = false"
|
||||
/>
|
||||
<div class="page">
|
||||
<div class="header" v-if="!loadingWithdraw && !walletAddress">
|
||||
Por Favor Conecte Sua Carteira
|
||||
</div>
|
||||
<div class="header" v-if="!loadingWithdraw && walletAddress">
|
||||
Gerenciar Ofertas
|
||||
</div>
|
||||
<div class="w-full max-w-4xl flex justify-center">
|
||||
<ListingComponent
|
||||
v-if="!loadingWithdraw && walletAddress"
|
||||
:valid-deposits="depositList"
|
||||
:wallet-transactions="transactionsList"
|
||||
:active-lock-amount="activeLockAmount"
|
||||
@deposit-withdrawn="callWithdraw"
|
||||
></ListingComponent>
|
||||
<LoadingComponent
|
||||
v-if="loadingWithdraw"
|
||||
:message="'A transação está sendo enviada para a rede. Em breve os tokens serão depositados em sua carteira.'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import WantSellComponent from "@/components/SellerSteps/WantSellComponent.vue";
|
||||
import { ref } from "vue";
|
||||
|
||||
import SellerComponent from "@/components/SellerSteps/SellerComponent.vue";
|
||||
import SendNetwork from "@/components/SellerSteps/SendNetwork.vue";
|
||||
import LoadingComponent from "@/components/LoadingComponent/LoadingComponent.vue";
|
||||
import { useUser } from "@/composables/useUser";
|
||||
import { approveTokens, addDeposit } from "@/blockchain/sellerMethods";
|
||||
|
||||
import { ref } from "vue";
|
||||
import { useEtherStore } from "@/store/ether";
|
||||
import CustomAlert from "@/components/CustomAlert/CustomAlert.vue";
|
||||
import type { Participant } from "@/utils/bbPay";
|
||||
|
||||
enum Step {
|
||||
Search,
|
||||
@ -14,26 +15,18 @@ enum Step {
|
||||
Network,
|
||||
}
|
||||
|
||||
const etherStore = useEtherStore();
|
||||
etherStore.setSellerView(true);
|
||||
const user = useUser();
|
||||
user.setSellerView(true);
|
||||
|
||||
const flowStep = ref<Step>(Step.Sell);
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const offerValue = ref<string>("");
|
||||
const pixKeyBuyer = ref<string>("");
|
||||
const showAlert = ref<boolean>(false);
|
||||
|
||||
// Verificar tipagem
|
||||
const approveOffer = async (args: {
|
||||
offer: string;
|
||||
postProcessedPixKey: string;
|
||||
}) => {
|
||||
const approveOffer = async (args: Participant) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
offerValue.value = args.offer;
|
||||
pixKeyBuyer.value = args.postProcessedPixKey;
|
||||
await approveTokens(args.offer);
|
||||
await approveTokens(args);
|
||||
flowStep.value = Step.Network;
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
@ -46,8 +39,8 @@ const approveOffer = async (args: {
|
||||
const sendNetwork = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
if (offerValue.value && pixKeyBuyer.value) {
|
||||
await addDeposit(String(offerValue.value), pixKeyBuyer.value);
|
||||
if (user.seller.value) {
|
||||
await addDeposit();
|
||||
flowStep.value = Step.Sell;
|
||||
loading.value = false;
|
||||
showAlert.value = true;
|
||||
@ -61,28 +54,31 @@ const sendNetwork = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="flowStep == Step.Sell">
|
||||
<WantSellComponent v-if="!loading" @approve-tokens="approveOffer" />
|
||||
<LoadingComponent
|
||||
v-if="loading"
|
||||
:message="'A transação está sendo enviada para a rede.'"
|
||||
/>
|
||||
</div>
|
||||
<CustomAlert
|
||||
v-if="flowStep == Step.Sell && showAlert"
|
||||
:type="'sell'"
|
||||
@close-alert="showAlert = false"
|
||||
/>
|
||||
<div v-if="flowStep == Step.Network">
|
||||
<SendNetwork
|
||||
:pixKey="pixKeyBuyer"
|
||||
:offer="Number(offerValue)"
|
||||
v-if="!loading"
|
||||
@send-network="sendNetwork"
|
||||
/>
|
||||
<LoadingComponent
|
||||
v-if="loading"
|
||||
:message="'A transação está sendo enviada para a rede.'"
|
||||
<div>
|
||||
<div v-if="flowStep == Step.Sell">
|
||||
<SellerComponent v-if="!loading" @approve-tokens="approveOffer" />
|
||||
<LoadingComponent
|
||||
v-if="loading"
|
||||
:message="'A transação está sendo enviada para a rede.'"
|
||||
/>
|
||||
</div>
|
||||
<CustomAlert
|
||||
v-if="flowStep == Step.Sell && showAlert"
|
||||
:type="'sell'"
|
||||
@close-alert="showAlert = false"
|
||||
/>
|
||||
<div v-if="flowStep == Step.Network">
|
||||
<SendNetwork
|
||||
:sellerId="user.sellerId.value"
|
||||
:offer="Number(user.seller.value.offer)"
|
||||
:selected-token="user.selectedToken.value"
|
||||
v-if="!loading"
|
||||
@send-network="sendNetwork"
|
||||
/>
|
||||
<LoadingComponent
|
||||
v-if="loading"
|
||||
:message="'A transação está sendo enviada para a rede.'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,8 +1,14 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.node.json",
|
||||
"include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"cypress.config.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"types": ["node"]
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,41 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.web.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"include": [
|
||||
"env.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.vue",
|
||||
"scripts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"types": ["jest", "node"],
|
||||
"resolveJsonModule": true
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
],
|
||||
"preserveValueImports": false,
|
||||
"importsNotUsedAsValues": "remove",
|
||||
"verbatimModuleSyntax": false
|
||||
},
|
||||
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.config.json"
|
||||
}
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"./node_modules"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,26 +1,33 @@
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
|
||||
import { defineConfig } from "vitest/config";
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||
import svgLoader from "vite-svg-loader";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
coverage: {
|
||||
provider: "c8",
|
||||
all: true,
|
||||
src: ["./src"],
|
||||
exclude: ["model/**", "**/__tests__/**"],
|
||||
reporter: ["text", "lcov", "html"],
|
||||
build: {
|
||||
target: "esnext",
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
target: "esnext",
|
||||
define: {
|
||||
global: "globalThis",
|
||||
},
|
||||
supported: {
|
||||
bigint: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [vue(), vueJsx()],
|
||||
plugins: [vue(), vueJsx(), svgLoader()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
"viem/errors": fileURLToPath(
|
||||
new URL("./node_modules/viem/errors", import.meta.url)
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
11
wagmi.config.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { defineConfig } from '@wagmi/cli'
|
||||
import { hardhat } from '@wagmi/cli/plugins'
|
||||
|
||||
export default defineConfig({
|
||||
out: 'src/blockchain/abi.ts',
|
||||
contracts: [],
|
||||
plugins: [
|
||||
hardhat({
|
||||
project: '../p2pix-smart-contracts',
|
||||
}),],
|
||||
})
|