This commit is contained in:
Georges KABBOUCHI 2022-06-09 17:51:35 +03:00
commit 627ab9fe56
10 changed files with 6114 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
node_modules
*.log*
.nuxt
.nitro
.cache
.output
.env
dist

42
README.md Normal file
View File

@ -0,0 +1,42 @@
# Nuxt 3 Minimal Starter
Look at the [nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
## Setup
Make sure to install the dependencies:
```bash
# yarn
yarn install
# npm
npm install
# pnpm
pnpm install --shamefully-hoist
```
## Development Server
Start the development server on http://localhost:3000
```bash
npm run dev
```
## Production
Build the application for production:
```bash
npm run build
```
Locally preview production build:
```bash
npm run preview
```
Checkout the [deployment documentation](https://v3.nuxtjs.org/guide/deploy/presets) for more information.

8
app.vue Normal file
View File

@ -0,0 +1,8 @@
<template>
<Html class="h-full bg-gray-50">
<Body class="h-full" />
</Html>
<div class="min-h-full flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<NuxtPage />
</div>
</template>

10
nuxt.config.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineNuxtConfig } from 'nuxt'
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss'],
build: {
transpile: [/ethers/, "@heroicons/vue"]
}
})

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview"
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^5.1.2",
"@tailwindcss/forms": "^0.5.2",
"nuxt": "3.0.0-rc.3"
},
"dependencies": {
"@heroicons/vue": "^1.0.6",
"ethers": "^5.6.8",
"qrcode.vue": "^3.3.3"
}
}

354
pages/[address].vue Normal file
View File

@ -0,0 +1,354 @@
<script setup lang="ts">
import QrcodeVue from "qrcode.vue";
import {
CheckIcon,
XIcon,
ExclamationIcon,
RefreshIcon,
} from "@heroicons/vue/solid";
import { ethers } from "ethers";
const networks = {
mainnet: "https://rpc.ankr.com/eth",
polygon: "https://rpc.ankr.com/polygon",
avalanche: "https://rpc.ankr.com/avalanche",
fantom: "https://rpc.ankr.com/fantom",
optimism: "https://rpc.ankr.com/optimism",
arbitrum: "https://rpc.ankr.com/arbitrum",
};
const networkProviderMap = Object.keys(networks).reduce((acc, curr) => {
acc[curr] = new ethers.providers.JsonRpcProvider(networks[curr]);
return acc;
}, {});
const gnosisSafeAbi = [
{
inputs: [],
name: "getOwners",
outputs: [{ internalType: "address[]", name: "", type: "address[]" }],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "getThreshold",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
];
const statusIconBackground = {
success: "bg-green-500",
error: "bg-red-500",
warning: "bg-orange-500",
};
const statusIcon = {
success: CheckIcon,
error: XIcon,
warning: ExclamationIcon,
};
type TaskCheckResponse = {
status: "success" | "error" | "warning";
metadata?: object;
};
type TaskCheckFun = ({
address: string,
provider: any,
}) => PromiseLike<TaskCheckResponse>;
type Task = {
description: string;
networkResults?: {
[network: string]: TaskCheckResponse;
};
statusStrategy?: "some" | "every";
check: TaskCheckFun;
};
const tasks: Array<Task> = [
{
description: "This is an EOA",
async check({ address, provider }) {
const code = await provider.getCode(address);
if (code != "0x") {
return { status: "error" };
}
return { status: "success" };
},
},
{
description: "This is an smart contract address",
statusStrategy: "some",
async check({ address, provider }) {
const code = await provider.getCode(address);
if (code === "0x") {
return { status: "error" };
}
return { status: "success" };
},
},
{
description: "This is an gnosis safe address",
statusStrategy: "some",
async check({ address, provider }) {
const contract = new ethers.Contract(address, gnosisSafeAbi, provider);
try {
const [owners, threshold] = await Promise.all([
contract.getOwners(),
contract.getThreshold(),
]);
return {
status: "success",
metadata: { owners, threshold: threshold.toString() },
};
} catch (error) {
return { status: "error" };
}
},
},
{
description: "This address has transactions",
statusStrategy: "some",
async check({ address, provider }) {
const count = await provider.getTransactionCount(address);
if (count === 0) {
return { status: "error" };
}
return { status: "success", metadata: { count } };
},
},
];
const taskResults = ref([]);
const route = useRoute();
const addressParam = String(route.params.address);
const mainnetProvider = new ethers.providers.JsonRpcProvider(networks.mainnet);
const address = ref(ethers.utils.isAddress(addressParam) ? addressParam : "");
const shortenAddress = () => {
return address.value.substr(0, 8) + "..." + address.value.substr(-6);
};
const ens = ref("");
const detectedNetworks = ref(["mainnet", "polygon"]);
onMounted(async () => {
if (!address.value) {
try {
address.value = await mainnetProvider.resolveName(addressParam);
ens.value = addressParam;
} catch (error) {}
}
if (!address.value) {
// do something
return;
}
for (let index = 0; index < tasks.length; index++) {
const task = tasks[index];
taskResults.value.push({
description: task.description,
networkResults: {
mainnet: {
status: "success",
},
polygon: {
status: "success",
},
avalanche: {
status: "success",
},
fantom: {
status: "success",
},
optimism: {
status: "success",
},
arbitrum: {
status: "success",
},
},
status: "success",
loading: true,
});
await Promise.allSettled(
Object.keys(networks).map(async (network) => {
const result = await task.check({
address: address.value,
provider: networkProviderMap[network],
});
taskResults.value[index].networkResults[network] = result;
})
);
taskResults.value[index].status = Object.values(
taskResults.value[index].networkResults
)[task.statusStrategy || "every"](({ status }) => status === "success")
? "success"
: "error";
taskResults.value[index].loading = false;
}
if (address.value) {
ens.value = await mainnetProvider.lookupAddress(address.value);
}
});
</script>
<template>
<div class="sm:mx-auto sm:w-full sm:max-w-xl">
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<div class="flex flex-col items-center">
<QrcodeVue
:size="196"
:value="address"
class="rounded-lg p-2 border-2 border-gray-200"
foreground="#052740"
/>
<div class="mt-8">
<div class="text-center">
<div class="text-gray-600 text-sm">
<div class="font-bold">{{ shortenAddress() }}</div>
<div class="text-gray-500" v-if="ens">({{ ens }})</div>
</div>
</div>
</div>
<div class="mt-3">
<div class="text-center flex flex-wrap gap-2">
<div
v-for="network in detectedNetworks"
class="capitalize bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm shadow-sm"
>
{{ network }}
</div>
</div>
</div>
<div class="mt-8 w-full px-4">
<div class="flow-root">
<ul role="list" class="-mb-5">
<li v-for="(task, taskIdx) in taskResults" :key="taskIdx">
<div class="relative pb-5">
<span
v-if="taskIdx !== taskResults.length - 1"
class="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200"
aria-hidden="true"
/>
<div class="relative flex space-x-3">
<div v-if="task.loading">
<span
:class="[
'bg-gray-400',
'h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white',
]"
>
<RefreshIcon
class="h-5 w-5 text-white animate-spin"
aria-hidden="true"
/>
</span>
</div>
<div v-else>
<span
:class="[
statusIconBackground[task.status],
'h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white',
]"
>
<component
:is="statusIcon[task.status]"
class="h-5 w-5 text-white"
aria-hidden="true"
/>
</span>
</div>
<div class="min-w-0 flex-1 pt-1.5">
<div>
<p class="text-sm text-gray-500">
{{ task.description }}
<span
v-if="task.network"
class="text-gray-500 text-sm"
>
({{ task.network }})
</span>
</p>
</div>
<div
class="ml-4 text-sm whitespace-nowrap text-gray-500 space-y-2"
>
<template
v-for="(result, network) in task.networkResults"
>
<div v-if="result.metadata">
<div class="capitalize text-bold">
{{ network }}
</div>
<div class="ml-4">
<ul class="space-y-2 list-disc">
<li v-for="(value, key) in result.metadata">
<span class="font-semibold capitalize">{{
key
}}</span
>:
<div
class="ml-10"
v-if="Array.isArray(value)"
>
<ul class="list-decimal">
<li v-for="val in value">
{{ val }}
</li>
</ul>
</div>
<span v-else class="text-xs">{{
String(value)
}}</span>
</li>
</ul>
</div>
</div>
</template>
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>

7
pages/index.vue Normal file
View File

@ -0,0 +1,7 @@
<template>
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<div>Enter any address</div>
</div>
</div>
</template>

8
tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
// tailwind.config.js
module.exports = {
// ...
plugins: [
// ...
require('@tailwindcss/forms'),
],
}

4
tsconfig.json Normal file
View File

@ -0,0 +1,4 @@
{
// https://v3.nuxtjs.org/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

5654
yarn.lock Normal file

File diff suppressed because it is too large Load Diff