diff --git a/.woodpecker/e2e.yml b/.woodpecker/e2e.yml index 8b07670..a9b09e9 100644 --- a/.woodpecker/e2e.yml +++ b/.woodpecker/e2e.yml @@ -204,6 +204,19 @@ services: echo "@harb/web3 linked with wagmi/viem deps" fi + # Overlay @harb/utils shared package from workspace + if [ -d "$WS/packages/utils" ]; then + mkdir -p /app/packages/utils + cp -r "$WS/packages/utils/." /app/packages/utils/ + # Link @harb/utils into web-app node_modules + mkdir -p /app/web-app/node_modules/@harb + ln -sf /app/packages/utils /app/web-app/node_modules/@harb/utils + # Symlink viem into packages dir so @harb/utils can resolve it + mkdir -p /app/packages/utils/node_modules + ln -sf /app/web-app/node_modules/viem /app/packages/utils/node_modules/viem + echo "@harb/utils linked with viem dep" + fi + echo "=== Starting webapp (pre-built image + source overlay) ===" cd /app/web-app # Explicitly set CI=true to disable Vue DevTools in vite.config.ts diff --git a/package-lock.json b/package-lock.json index 4b446f7..52e58d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3360,6 +3360,10 @@ "resolved": "packages/ui-shared", "link": true }, + "node_modules/@harb/utils": { + "resolved": "packages/utils", + "link": true + }, "node_modules/@harb/web3": { "resolved": "packages/web3", "link": true @@ -19332,10 +19336,18 @@ "vue": "^3.5.0" } }, + "packages/utils": { + "name": "@harb/utils", + "version": "0.1.0", + "dependencies": { + "viem": "^2.22.13" + } + }, "packages/web3": { "name": "@harb/web3", "version": "0.1.0", "dependencies": { + "@harb/utils": "*", "@wagmi/vue": "^0.2.8", "viem": "^2.22.13" }, @@ -19347,6 +19359,7 @@ "name": "harb-staking", "version": "0.0.0", "dependencies": { + "@harb/utils": "*", "@harb/web3": "*", "@tanstack/vue-query": "^5.64.2", "@vue/test-utils": "^2.4.6", diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 0000000..a81e266 --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,11 @@ +{ + "name": "@harb/utils", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "dependencies": { + "viem": "^2.22.13" + } +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 0000000..aebb78d --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,34 @@ +import { getAddress, isAddress, type Address } from 'viem'; + +export function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +export function coerceString(value: unknown): string | null { + if (typeof value === 'string') { + const trimmed = value.trim(); + if (trimmed.length > 0) return trimmed; + } + return null; +} + +export function getErrorMessage(error: unknown, fallback: string): string { + if (error instanceof Error) { + const msg = coerceString(error.message); + if (msg) return msg; + } + if (isRecord(error)) { + const short = coerceString(error.shortMessage); + if (short) return short; + const msg = coerceString(error.message); + if (msg) return msg; + } + return fallback; +} + +export function ensureAddress(value: string, label: string): Address { + if (!value || !isAddress(value)) { + throw new Error(`${label} is not a valid address`); + } + return getAddress(value); +} diff --git a/packages/web3/package.json b/packages/web3/package.json index dd6ae81..8f095f6 100644 --- a/packages/web3/package.json +++ b/packages/web3/package.json @@ -6,6 +6,7 @@ "main": "src/index.ts", "types": "src/index.ts", "dependencies": { + "@harb/utils": "*", "@wagmi/vue": "^0.2.8", "viem": "^2.22.13" }, diff --git a/web-app/package.json b/web-app/package.json index 9cc8650..ddc06e7 100644 --- a/web-app/package.json +++ b/web-app/package.json @@ -33,7 +33,8 @@ "vue-router": "^4.2.5", "vue-tippy": "^6.6.0", "vue-toastification": "^2.0.0-rc.5", - "@harb/web3": "*" + "@harb/web3": "*", + "@harb/utils": "*" }, "devDependencies": { "@iconify/vue": "^4.3.0", diff --git a/web-app/src/composables/useSwapKrk.ts b/web-app/src/composables/useSwapKrk.ts index a0172bd..4eebb36 100644 --- a/web-app/src/composables/useSwapKrk.ts +++ b/web-app/src/composables/useSwapKrk.ts @@ -1,11 +1,12 @@ import { computed, ref } from 'vue'; import { useAccount } from '@wagmi/vue'; import { readContract, writeContract } from '@wagmi/core'; -import { erc20Abi, getAddress, isAddress, maxUint256, parseEther, zeroAddress, type Abi, type Address } from 'viem'; +import { erc20Abi, maxUint256, parseEther, zeroAddress, type Abi } from 'viem'; import { useToast } from 'vue-toastification'; import { config as wagmiConfig } from '@/wagmi'; import { getChain, DEFAULT_CHAIN_ID } from '@/config'; import { useWallet } from '@/composables/useWallet'; +import { getErrorMessage, ensureAddress } from '@harb/utils'; export const WETH_ABI = [ { @@ -89,39 +90,6 @@ export const UNISWAP_POOL_ABI = [ }, ] as const satisfies Abi; -export function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null; -} - -export function coerceString(value: unknown): string | null { - if (typeof value === 'string') { - const trimmed = value.trim(); - if (trimmed.length > 0) return trimmed; - } - return null; -} - -export function getErrorMessage(error: unknown, fallback: string): string { - if (error instanceof Error) { - const msg = coerceString(error.message); - if (msg) return msg; - } - if (isRecord(error)) { - const short = coerceString(error.shortMessage); - if (short) return short; - const msg = coerceString(error.message); - if (msg) return msg; - } - return fallback; -} - -export function ensureAddress(value: string, label: string): Address { - if (!value || !isAddress(value)) { - throw new Error(`${label} is not a valid address`); - } - return getAddress(value); -} - export function useSwapKrk() { const toast = useToast(); const { address, chainId } = useAccount(); diff --git a/web-app/src/views/CheatsView.vue b/web-app/src/views/CheatsView.vue index 822ee6d..102a26e 100644 --- a/web-app/src/views/CheatsView.vue +++ b/web-app/src/views/CheatsView.vue @@ -151,15 +151,8 @@ import { type Abi, type EIP1193Provider, } from 'viem'; -import { - SWAP_ROUTER_ABI, - UNISWAP_FACTORY_ABI, - UNISWAP_POOL_ABI, - isRecord, - coerceString, - getErrorMessage, - ensureAddress, -} from '@/composables/useSwapKrk'; +import { SWAP_ROUTER_ABI, UNISWAP_FACTORY_ABI, UNISWAP_POOL_ABI } from '@/composables/useSwapKrk'; +import { isRecord, coerceString, getErrorMessage, ensureAddress } from '@harb/utils'; const toast = useToast(); const statCollection = useStatCollection();