From ca46eb573ae5d184f1c65c06987f4984536a08f6 Mon Sep 17 00:00:00 2001
From: Timur Badretdinov <4247901+Destiner@users.noreply.github.com>
Date: Sun, 15 Jun 2025 19:35:46 +0100
Subject: [PATCH] Improve verification error logging
---
app/app.vue | 19 +++--
app/components/contract/ChainItem.vue | 50 +++++++++++-
app/components/contract/ChainList.vue | 19 ++++-
app/data/cache.json | 1 -
app/pages/contract/[address].vue | 35 ++++++++
app/utils/verification.ts | 28 ++++++-
server/api/verify.ts | 111 ++++++++++++++++++++++++++
7 files changed, 249 insertions(+), 14 deletions(-)
create mode 100644 server/api/verify.ts
diff --git a/app/app.vue b/app/app.vue
index 5b83613..0d18887 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -42,14 +42,17 @@ import IconGitHub from '@/components/__common/icon/GitHub.vue';
--font-size-small: 14px;
--font-size-medium: 16px;
--font-size-big: 20px;
- --font-sans: 'Inter Variable', -apple-system, 'BlinkMacSystemFont',
- avenir next, avenir, segoe ui, helvetica neue, helvetica, 'Ubuntu', roboto,
- noto, arial, sans-serif;
- --font-serif: 'Iowan Old Style', 'Apple Garamond', 'Baskerville',
- 'Times New Roman', 'Droid Serif', 'Times', 'Source Serif Pro', serif,
- 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
- --font-mono: 'Inconsolata Variable', 'Menlo', 'Consolas', 'Monaco',
- 'Liberation Mono', 'Lucida Console', monospace;
+ --font-sans:
+ 'Inter Variable', -apple-system, 'BlinkMacSystemFont', avenir next, avenir,
+ segoe ui, helvetica neue, helvetica, 'Ubuntu', roboto, noto, arial,
+ sans-serif;
+ --font-serif:
+ 'Iowan Old Style', 'Apple Garamond', 'Baskerville', 'Times New Roman',
+ 'Droid Serif', 'Times', 'Source Serif Pro', serif, 'Apple Color Emoji',
+ 'Segoe UI Emoji', 'Segoe UI Symbol';
+ --font-mono:
+ 'Inconsolata Variable', 'Menlo', 'Consolas', 'Monaco', 'Liberation Mono',
+ 'Lucida Console', monospace;
}
.app {
diff --git a/app/components/contract/ChainItem.vue b/app/components/contract/ChainItem.vue
index 0007bf8..f219dce 100644
--- a/app/components/contract/ChainItem.vue
+++ b/app/components/contract/ChainItem.vue
@@ -28,12 +28,20 @@
+
+ {{ verifying ? 'Verifying...' : 'Verify' }}
+
diff --git a/app/components/contract/ChainList.vue b/app/components/contract/ChainList.vue
index ded05a7..ef3aefb 100644
--- a/app/components/contract/ChainList.vue
+++ b/app/components/contract/ChainList.vue
@@ -7,6 +7,9 @@
:status="chain.status"
:address="address"
:verification="chain.verification"
+ :verifiedChains="verifiedChains"
+ @verificationUpdate="update(chain.id, $event)"
+ @log="log"
/>
@@ -21,13 +24,19 @@ import ChainItem from './ChainItem.vue';
import type { Chain } from '@/utils/chains';
import type { VerificationStatus } from '@/utils/verification';
-const { chains } = defineProps<{
+const emit = defineEmits<{
+ (e: 'updateVerification', chain: Chain, status: VerificationStatus): void;
+ (e: 'log', message: string): void;
+}>();
+
+const { chains, verifiedChains } = defineProps<{
address: Address;
chains: {
id: Chain;
status: Status;
verification: VerificationStatus | null;
}[];
+ verifiedChains: Chain[];
}>();
const sortedChains = computed(() => {
@@ -51,6 +60,14 @@ const sortedChains = computed(() => {
return statusToPriority(a.status) - statusToPriority(b.status);
});
});
+
+function update(chain: Chain, status: VerificationStatus): void {
+ emit('updateVerification', chain, status);
+}
+
+function log(message: string): void {
+ emit('log', message);
+}
diff --git a/app/utils/verification.ts b/app/utils/verification.ts
index 6a330e1..a5063eb 100644
--- a/app/utils/verification.ts
+++ b/app/utils/verification.ts
@@ -9,6 +9,11 @@ interface CheckVerificationResponse {
type VerificationStatus = 'verified' | 'unverified' | 'unknown';
+interface VerifyContractResponse {
+ status: 'ok' | 'error';
+ error?: string;
+}
+
async function checkContractVerification(
address: Address,
chain: Chain,
@@ -28,5 +33,26 @@ async function checkContractVerification(
return checkResponse.status;
}
-export { checkContractVerification };
+async function verifyContract(
+ address: Address,
+ chain: Chain,
+ sourceChain: Chain,
+): Promise<{ success: boolean; error?: string }> {
+ const verifyResponse = await ky
+ .post('/api/verify', {
+ json: {
+ chain: chain.toString(),
+ address,
+ sourceChain: sourceChain.toString(),
+ },
+ })
+ .json();
+
+ return {
+ success: verifyResponse.status === 'ok',
+ error: verifyResponse.error,
+ };
+}
+
+export { checkContractVerification, verifyContract };
export type { VerificationStatus };
diff --git a/server/api/verify.ts b/server/api/verify.ts
new file mode 100644
index 0000000..80cc431
--- /dev/null
+++ b/server/api/verify.ts
@@ -0,0 +1,111 @@
+import { defineEventHandler, readBody } from 'h3';
+import ky from 'ky';
+import type { Address } from 'viem';
+
+interface VerifyRequestBody {
+ chain: string;
+ address: Address;
+ sourceChain: string;
+}
+
+interface GetSourceCodeResponse {
+ status: '0' | '1';
+ message: 'OK' | 'NOTOK';
+ result: {
+ SourceCode: string;
+ ABI: string;
+ ContractName: string;
+ CompilerVersion: string;
+ OptimizationUsed: string;
+ Runs: string;
+ ConstructorArguments: string;
+ EVMVersion: string;
+ Library: string;
+ LicenseType: string;
+ Proxy: string;
+ Implementation: string;
+ SwarmSource: string;
+ }[];
+}
+
+interface VerifySourceCodeResponse {
+ status: '0' | '1';
+ message: string;
+ result: string;
+}
+
+interface VerifyResponse {
+ status: 'ok' | 'error';
+ error?: string;
+}
+
+export default defineEventHandler(async (event): Promise => {
+ const etherscanApiKey = process.env.ETHERSCAN_API_KEY;
+ if (!etherscanApiKey) {
+ return { status: 'error', error: 'Missing ETHERSCAN_API_KEY' };
+ }
+
+ const { chain, address, sourceChain } =
+ (await readBody(event)) || {};
+
+ if (!chain || !address || !sourceChain) {
+ return { status: 'error', error: 'Missing parameters' };
+ }
+
+ try {
+ const source = await ky
+ .get('https://api.etherscan.io/v2/api', {
+ searchParams: {
+ chainid: sourceChain,
+ module: 'contract',
+ action: 'getsourcecode',
+ address,
+ apikey: etherscanApiKey,
+ },
+ timeout: 5_000,
+ })
+ .json();
+
+ if (source.status !== '1' || source.message !== 'OK') {
+ return {
+ status: 'error',
+ error: Array.isArray(source.result)
+ ? JSON.stringify(source.result)
+ : String(source.result || source.message),
+ };
+ }
+
+ const data = source.result[0];
+ if (!data || !data.SourceCode) {
+ return { status: 'error', error: 'Empty source code' };
+ }
+
+ const verify = await ky
+ .post('https://api.etherscan.io/v2/api', {
+ searchParams: {
+ chainid: chain,
+ module: 'contract',
+ action: 'verifysourcecode',
+ apikey: etherscanApiKey,
+ contractaddress: address,
+ sourceCode: data.SourceCode,
+ contractname: data.ContractName,
+ compilerversion: data.CompilerVersion,
+ optimizationUsed: data.OptimizationUsed,
+ runs: data.Runs,
+ constructorArguements: data.ConstructorArguments,
+ evmVersion: data.EVMVersion,
+ licenseType: data.LicenseType,
+ },
+ timeout: 5_000,
+ })
+ .json();
+
+ if (verify.status === '1') {
+ return { status: 'ok' };
+ }
+ return { status: 'error', error: verify.result || verify.message };
+ } catch (err: unknown) {
+ return { status: 'error', error: (err as Error).message };
+ }
+});