feat: shared @harb/web3 package + landing wallet connect (#157) (#159)

This commit is contained in:
johba 2026-02-19 20:18:27 +01:00
parent 66106077ba
commit db3633425a
17 changed files with 18548 additions and 199 deletions

View file

@ -0,0 +1,15 @@
{
"name": "@harb/web3",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts",
"dependencies": {
"@wagmi/vue": "^0.2.8",
"viem": "^2.22.13"
},
"peerDependencies": {
"vue": "^3.5.0"
}
}

View file

@ -0,0 +1,59 @@
import { ref, watch, type Ref } from 'vue';
import { useAccount, useChainId } from '@wagmi/vue';
import { createPublicClient, erc20Abi, formatUnits, http } from 'viem';
import { getHarbConfig, KRAIKEN_LOCAL_CHAIN } from '../config';
/**
* Read-only KRK token balance for the connected wallet.
* Lightweight no contract writes, no staking, just balanceOf.
*/
export function useTokenBalance(tokenAddress: Ref<`0x${string}` | undefined> | `0x${string}`) {
const { address, isConnected } = useAccount();
const chainId = useChainId();
const balance = ref(0n);
const formatted = ref('0');
const loading = ref(false);
async function refresh() {
const token = typeof tokenAddress === 'string' ? tokenAddress : tokenAddress.value;
if (!address.value || !token) {
balance.value = 0n;
formatted.value = '0';
return;
}
loading.value = true;
try {
const config = getHarbConfig();
const chain = config.chains.find(c => c.id === chainId.value) ?? KRAIKEN_LOCAL_CHAIN;
const rpcUrl = chain.rpcUrls.default.http[0];
const client = createPublicClient({ chain, transport: http(rpcUrl) });
const result = await client.readContract({
abi: erc20Abi,
address: token,
functionName: 'balanceOf',
args: [address.value],
});
balance.value = result;
formatted.value = formatUnits(result, 18);
} catch {
balance.value = 0n;
formatted.value = '0';
} finally {
loading.value = false;
}
}
watch([address, isConnected], () => {
if (isConnected.value) {
void refresh();
} else {
balance.value = 0n;
formatted.value = '0';
}
}, { immediate: true });
return { balance, formatted, loading, refresh };
}

View file

@ -0,0 +1,87 @@
import { http, createConfig, createStorage } from '@wagmi/vue';
import { baseSepolia } from '@wagmi/vue/chains';
import { coinbaseWallet, injected, walletConnect } from '@wagmi/vue/connectors';
import { defineChain } from 'viem';
/**
* Shared wagmi config for all harb apps.
* RPC URL and WalletConnect project ID are passed in to keep this package env-agnostic.
*/
export interface HarbWeb3Options {
rpcUrl?: string;
walletConnectProjectId?: string;
}
const defaults = {
rpcUrl: '/api/rpc',
walletConnectProjectId: 'd8e5ecb0353c02e21d4c0867d4473ac5',
};
export const KRAIKEN_LOCAL_CHAIN = defineChain({
id: 31337,
name: 'Kraiken Local Fork',
network: 'kraiken-local',
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrls: {
default: { http: ['http://localhost:8545'] },
public: { http: ['http://localhost:8545'] },
},
blockExplorers: {
default: { name: 'Local Explorer', url: '' },
},
testnet: true,
});
let _config: ReturnType<typeof createConfig> | null = null;
export function createHarbConfig(opts: HarbWeb3Options = {}) {
const rpcUrl = opts.rpcUrl ?? defaults.rpcUrl;
const wcProjectId = opts.walletConnectProjectId ?? defaults.walletConnectProjectId;
// Build chain with provided RPC URL
const chain = defineChain({
...KRAIKEN_LOCAL_CHAIN,
rpcUrls: {
default: { http: [rpcUrl] },
public: { http: [rpcUrl] },
},
});
_config = createConfig({
chains: [chain, baseSepolia],
storage: createStorage({ storage: window.localStorage }),
connectors: [
injected(),
walletConnect({
projectId: wcProjectId,
metadata: {
name: 'Kraiken',
description: 'Connect your wallet with Kraiken',
url: 'https://kraiken.eth.limo',
icons: [''],
},
}),
coinbaseWallet({
appName: 'Kraiken',
darkMode: true,
preference: { options: 'all', telemetry: false },
}),
],
transports: {
[chain.id]: http(rpcUrl),
[baseSepolia.id]: http(),
},
});
// Default to local chain
if (_config.state.chainId !== chain.id) {
_config.setState(state => ({ ...state, chainId: chain.id }));
}
return _config;
}
export function getHarbConfig() {
if (!_config) throw new Error('@harb/web3: call createHarbConfig() first');
return _config;
}

View file

@ -0,0 +1,6 @@
export { createHarbConfig, getHarbConfig, KRAIKEN_LOCAL_CHAIN } from './config';
export type { HarbWeb3Options } from './config';
export { useTokenBalance } from './composables/useTokenBalance';
// Re-export commonly used wagmi composables so consumers don't need to import @wagmi/vue directly
export { useAccount, useConnect, useDisconnect, useChainId } from '@wagmi/vue';