parent
66106077ba
commit
db3633425a
17 changed files with 18548 additions and 199 deletions
|
|
@ -58,16 +58,20 @@ steps:
|
||||||
npm config set fund false
|
npm config set fund false
|
||||||
npm config set audit false
|
npm config set audit false
|
||||||
./scripts/build-kraiken-lib.sh
|
./scripts/build-kraiken-lib.sh
|
||||||
npm install --prefix landing --no-audit --no-fund
|
# Root install links workspace packages (@harb/web3) + all workspace members
|
||||||
|
npm install --no-audit --no-fund
|
||||||
|
# Landing (workspace member — deps already installed by root)
|
||||||
npm run lint --prefix landing
|
npm run lint --prefix landing
|
||||||
npm run build --prefix landing
|
npm run build --prefix landing
|
||||||
npm install --prefix web-app --no-audit --no-fund
|
# Web-app (workspace member)
|
||||||
npm run lint --prefix web-app
|
npm run lint --prefix web-app
|
||||||
npm run test --prefix web-app -- --run
|
npm run test --prefix web-app -- --run
|
||||||
npm run build --prefix web-app
|
npm run build --prefix web-app
|
||||||
|
# Ponder (standalone — not a workspace member)
|
||||||
npm install --prefix services/ponder --no-audit --no-fund
|
npm install --prefix services/ponder --no-audit --no-fund
|
||||||
npm run lint --prefix services/ponder
|
npm run lint --prefix services/ponder
|
||||||
npm run build --prefix services/ponder
|
npm run build --prefix services/ponder
|
||||||
|
# TxnBot (standalone)
|
||||||
npm install --prefix services/txnBot --no-audit --no-fund
|
npm install --prefix services/txnBot --no-audit --no-fund
|
||||||
npm run lint --prefix services/txnBot
|
npm run lint --prefix services/txnBot
|
||||||
npm run test --prefix services/txnBot
|
npm run test --prefix services/txnBot
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,20 @@ services:
|
||||||
echo "webapp/src updated from workspace"
|
echo "webapp/src updated from workspace"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Overlay @harb/web3 shared package from workspace
|
||||||
|
if [ -d "$WS/packages/web3" ]; then
|
||||||
|
mkdir -p /app/packages/web3
|
||||||
|
cp -r "$WS/packages/web3/." /app/packages/web3/
|
||||||
|
# Link @harb/web3 into web-app node_modules
|
||||||
|
mkdir -p /app/web-app/node_modules/@harb
|
||||||
|
ln -sf /app/packages/web3 /app/web-app/node_modules/@harb/web3
|
||||||
|
# Symlink wagmi/viem into packages dir so @harb/web3 can resolve them
|
||||||
|
mkdir -p /app/packages/web3/node_modules
|
||||||
|
ln -sf /app/web-app/node_modules/@wagmi /app/packages/web3/node_modules/@wagmi
|
||||||
|
ln -sf /app/web-app/node_modules/viem /app/packages/web3/node_modules/viem
|
||||||
|
echo "@harb/web3 linked with wagmi/viem deps"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "=== Starting webapp (pre-built image + source overlay) ==="
|
echo "=== Starting webapp (pre-built image + source overlay) ==="
|
||||||
cd /app/web-app
|
cd /app/web-app
|
||||||
# Explicitly set CI=true to disable Vue DevTools in vite.config.ts
|
# Explicitly set CI=true to disable Vue DevTools in vite.config.ts
|
||||||
|
|
@ -194,6 +208,23 @@ services:
|
||||||
echo "landing/src updated from workspace"
|
echo "landing/src updated from workspace"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Overlay @harb/web3 shared package
|
||||||
|
if [ -d "$WS/packages/web3" ]; then
|
||||||
|
mkdir -p /app/packages/web3
|
||||||
|
cp -r "$WS/packages/web3/." /app/packages/web3/
|
||||||
|
# Landing CI image doesn't have wagmi — install it
|
||||||
|
cd /app/landing
|
||||||
|
npm install --no-audit --no-fund @wagmi/vue viem 2>/dev/null || true
|
||||||
|
# Link @harb/web3
|
||||||
|
mkdir -p /app/landing/node_modules/@harb
|
||||||
|
ln -sf /app/packages/web3 /app/landing/node_modules/@harb/web3
|
||||||
|
# Symlink wagmi/viem into packages dir for resolution
|
||||||
|
mkdir -p /app/packages/web3/node_modules
|
||||||
|
ln -sf /app/landing/node_modules/@wagmi /app/packages/web3/node_modules/@wagmi 2>/dev/null || true
|
||||||
|
ln -sf /app/landing/node_modules/viem /app/packages/web3/node_modules/viem 2>/dev/null || true
|
||||||
|
echo "@harb/web3 linked for landing"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "=== Starting landing (pre-built image + source overlay) ==="
|
echo "=== Starting landing (pre-built image + source overlay) ==="
|
||||||
cd /app/landing
|
cd /app/landing
|
||||||
exec npm run dev -- --host 0.0.0.0 --port 5174
|
exec npm run dev -- --host 0.0.0.0 --port 5174
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sass": "^1.83.4",
|
"sass": "^1.83.4",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0",
|
||||||
|
"@harb/web3": "*",
|
||||||
|
"@wagmi/vue": "^0.2.8",
|
||||||
|
"viem": "^2.22.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tsconfig/node22": "^22.0.0",
|
"@tsconfig/node22": "^22.0.0",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<SocialButton type="telegram" href="https://t.me/kraikenportal"></SocialButton>
|
<SocialButton type="telegram" href="https://t.me/kraikenportal"></SocialButton>
|
||||||
<SocialButton type="twitter" href="https://x.com/KrAIkenProtocol"></SocialButton>
|
<SocialButton type="twitter" href="https://x.com/KrAIkenProtocol"></SocialButton>
|
||||||
</div>
|
</div>
|
||||||
|
<WalletButton />
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-trigger" @click.stop="toggleMenu">
|
<div class="menu-trigger" @click.stop="toggleMenu">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
|
@ -42,6 +43,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, useRouter } from 'vue-router';
|
import { RouterLink, useRouter } from 'vue-router';
|
||||||
import SocialButton from './SocialButton.vue';
|
import SocialButton from './SocialButton.vue';
|
||||||
|
import WalletButton from './WalletButton.vue';
|
||||||
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
import { onMounted, onUnmounted, ref, computed } from 'vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
|
||||||
95
landing/src/components/WalletButton.vue
Normal file
95
landing/src/components/WalletButton.vue
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useAccount, useConnect, useDisconnect } from '@harb/web3';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const { address, isConnected } = useAccount();
|
||||||
|
const { connectors, connect } = useConnect();
|
||||||
|
const { disconnect } = useDisconnect();
|
||||||
|
const showConnectors = ref(false);
|
||||||
|
|
||||||
|
function shortAddress(addr: string) {
|
||||||
|
return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConnect(connector: (typeof connectors)[number]) {
|
||||||
|
connect({ connector });
|
||||||
|
showConnectors.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="wallet-button">
|
||||||
|
<template v-if="isConnected && address">
|
||||||
|
<button class="wallet-button__connected" @click="disconnect()">
|
||||||
|
{{ shortAddress(address) }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button class="wallet-button__connect" @click="showConnectors = !showConnectors">
|
||||||
|
Connect Wallet
|
||||||
|
</button>
|
||||||
|
<div v-if="showConnectors" class="wallet-button__dropdown">
|
||||||
|
<button
|
||||||
|
v-for="connector in connectors"
|
||||||
|
:key="connector.uid"
|
||||||
|
class="wallet-button__option"
|
||||||
|
@click="handleConnect(connector)"
|
||||||
|
>
|
||||||
|
{{ connector.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
.wallet-button
|
||||||
|
position: relative
|
||||||
|
|
||||||
|
&__connect, &__connected
|
||||||
|
padding: 0.5rem 1rem
|
||||||
|
border-radius: 8px
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2)
|
||||||
|
background: rgba(255, 255, 255, 0.08)
|
||||||
|
color: #fff
|
||||||
|
cursor: pointer
|
||||||
|
font-size: 0.9rem
|
||||||
|
transition: background 0.2s
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: rgba(255, 255, 255, 0.15)
|
||||||
|
|
||||||
|
&__connect
|
||||||
|
background: #3b82f6
|
||||||
|
border-color: #3b82f6
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: #2563eb
|
||||||
|
|
||||||
|
&__dropdown
|
||||||
|
position: absolute
|
||||||
|
top: 100%
|
||||||
|
right: 0
|
||||||
|
margin-top: 0.5rem
|
||||||
|
background: #1a1a2e
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15)
|
||||||
|
border-radius: 8px
|
||||||
|
padding: 0.5rem
|
||||||
|
min-width: 200px
|
||||||
|
z-index: 100
|
||||||
|
|
||||||
|
&__option
|
||||||
|
display: block
|
||||||
|
width: 100%
|
||||||
|
padding: 0.5rem 0.75rem
|
||||||
|
background: none
|
||||||
|
border: none
|
||||||
|
color: #fff
|
||||||
|
cursor: pointer
|
||||||
|
text-align: left
|
||||||
|
border-radius: 4px
|
||||||
|
font-size: 0.85rem
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: rgba(255, 255, 255, 0.1)
|
||||||
|
</style>
|
||||||
137
landing/src/components/WalletCard.vue
Normal file
137
landing/src/components/WalletCard.vue
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed } from 'vue';
|
||||||
|
import { useAccount } from '@harb/web3';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const { address, isConnected } = useAccount();
|
||||||
|
|
||||||
|
const holder = ref<{
|
||||||
|
balance: string;
|
||||||
|
totalEthSpent: string;
|
||||||
|
totalTokensAcquired: string;
|
||||||
|
} | null>(null);
|
||||||
|
const stats = ref<{ currentPriceWei: string } | null>(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
|
const endpoint = `${window.location.origin}/api/graphql`;
|
||||||
|
|
||||||
|
async function fetchWalletData(addr: string) {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const res = await axios.post(endpoint, {
|
||||||
|
query: `{
|
||||||
|
holders(address: "${addr.toLowerCase()}") {
|
||||||
|
balance
|
||||||
|
totalEthSpent
|
||||||
|
totalTokensAcquired
|
||||||
|
}
|
||||||
|
protocolStatss(where: { id: "0x01" }) {
|
||||||
|
items { currentPriceWei }
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
});
|
||||||
|
holder.value = res.data?.data?.holders ?? null;
|
||||||
|
stats.value = res.data?.data?.protocolStatss?.items?.[0] ?? null;
|
||||||
|
} catch {
|
||||||
|
holder.value = null;
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch([address, isConnected], () => {
|
||||||
|
if (isConnected.value && address.value) {
|
||||||
|
void fetchWalletData(address.value);
|
||||||
|
} else {
|
||||||
|
holder.value = null;
|
||||||
|
}
|
||||||
|
}, { immediate: true });
|
||||||
|
|
||||||
|
function fmt(wei: string, decimals = 4) {
|
||||||
|
const n = Number(wei) / 1e18;
|
||||||
|
return n.toLocaleString('en', { maximumFractionDigits: decimals });
|
||||||
|
}
|
||||||
|
|
||||||
|
const balanceKrk = computed(() => fmt(holder.value?.balance ?? '0', 2));
|
||||||
|
const hasPosition = computed(() => holder.value && BigInt(holder.value.balance) > 0n);
|
||||||
|
|
||||||
|
const avgCost = computed(() => {
|
||||||
|
if (!holder.value) return 0;
|
||||||
|
const spent = Number(holder.value.totalEthSpent) / 1e18;
|
||||||
|
const acquired = Number(holder.value.totalTokensAcquired) / 1e18;
|
||||||
|
return acquired > 0 ? spent / acquired : 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentPrice = computed(() =>
|
||||||
|
stats.value ? Number(stats.value.currentPriceWei) / 1e18 : 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const pnlPercent = computed(() => {
|
||||||
|
if (avgCost.value === 0) return 0;
|
||||||
|
return (currentPrice.value / avgCost.value - 1) * 100;
|
||||||
|
});
|
||||||
|
|
||||||
|
const pnlClass = computed(() => (pnlPercent.value >= 0 ? 'positive' : 'negative'));
|
||||||
|
|
||||||
|
const appUrl = computed(() => `/app/#/wallet/${address.value}`);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="isConnected && hasPosition" class="wallet-card" :class="pnlClass">
|
||||||
|
<div class="wallet-card__balance">{{ balanceKrk }} KRK</div>
|
||||||
|
<div v-if="avgCost > 0" class="wallet-card__pnl">
|
||||||
|
{{ pnlPercent >= 0 ? '+' : '' }}{{ pnlPercent.toFixed(1) }}%
|
||||||
|
</div>
|
||||||
|
<a :href="appUrl" class="wallet-card__link">View Dashboard →</a>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="isConnected && !loading && !hasPosition" class="wallet-card wallet-card--empty">
|
||||||
|
<span>No KRK yet</span>
|
||||||
|
<a href="#get-krk" class="wallet-card__link">Get KRK →</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="sass" scoped>
|
||||||
|
.wallet-card
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
gap: 1rem
|
||||||
|
padding: 0.75rem 1.25rem
|
||||||
|
border-radius: 12px
|
||||||
|
background: rgba(255, 255, 255, 0.06)
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15)
|
||||||
|
margin-top: 1rem
|
||||||
|
|
||||||
|
&.positive
|
||||||
|
border-color: rgba(16, 185, 129, 0.4)
|
||||||
|
background: rgba(16, 185, 129, 0.06)
|
||||||
|
|
||||||
|
&.negative
|
||||||
|
border-color: rgba(239, 68, 68, 0.4)
|
||||||
|
background: rgba(239, 68, 68, 0.06)
|
||||||
|
|
||||||
|
&--empty
|
||||||
|
color: #9a9898
|
||||||
|
font-size: 0.85rem
|
||||||
|
|
||||||
|
&__balance
|
||||||
|
font-weight: 700
|
||||||
|
font-size: 1.1rem
|
||||||
|
|
||||||
|
&__pnl
|
||||||
|
font-weight: 600
|
||||||
|
|
||||||
|
.positive &
|
||||||
|
color: #10b981
|
||||||
|
|
||||||
|
.negative &
|
||||||
|
color: #ef4444
|
||||||
|
|
||||||
|
&__link
|
||||||
|
margin-left: auto
|
||||||
|
color: #3b82f6
|
||||||
|
text-decoration: none
|
||||||
|
font-size: 0.85rem
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
text-decoration: underline
|
||||||
|
</style>
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
import './assets/styles/main.sass';
|
import './assets/styles/main.sass';
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
|
import { WagmiPlugin } from '@wagmi/vue';
|
||||||
|
import { createHarbConfig } from '@harb/web3';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
|
||||||
|
const rpcUrl = import.meta.env.VITE_LOCAL_RPC_URL ?? '/api/rpc';
|
||||||
|
const config = createHarbConfig({ rpcUrl });
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
app.use(WagmiPlugin, { config });
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="blur-effect"></div>
|
<div class="blur-effect"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<WalletCard />
|
||||||
<LiveStats />
|
<LiveStats />
|
||||||
<div class="k-container">
|
<div class="k-container">
|
||||||
<section class="how-it-works-section">
|
<section class="how-it-works-section">
|
||||||
|
|
@ -88,6 +89,7 @@
|
||||||
import KButton from '@/components/KButton.vue';
|
import KButton from '@/components/KButton.vue';
|
||||||
import LeftRightComponent from '@/components/LeftRightComponent.vue';
|
import LeftRightComponent from '@/components/LeftRightComponent.vue';
|
||||||
import LiveStats from '@/components/LiveStats.vue';
|
import LiveStats from '@/components/LiveStats.vue';
|
||||||
|
import WalletCard from '@/components/WalletCard.vue';
|
||||||
import { useMobile } from '@/composables/useMobile';
|
import { useMobile } from '@/composables/useMobile';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
|
|
||||||
18217
package-lock.json
generated
18217
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -9,5 +9,10 @@
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"test:e2e": "playwright test"
|
"test:e2e": "playwright test"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*",
|
||||||
|
"landing",
|
||||||
|
"web-app"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
packages/web3/package.json
Normal file
15
packages/web3/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
59
packages/web3/src/composables/useTokenBalance.ts
Normal file
59
packages/web3/src/composables/useTokenBalance.ts
Normal 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 };
|
||||||
|
}
|
||||||
87
packages/web3/src/config.ts
Normal file
87
packages/web3/src/config.ts
Normal 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;
|
||||||
|
}
|
||||||
6
packages/web3/src/index.ts
Normal file
6
packages/web3/src/index.ts
Normal 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';
|
||||||
|
|
@ -16,8 +16,9 @@ export default [
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
parser: tsParser,
|
parser: tsParser,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
projectService: true,
|
projectService: {
|
||||||
project: [resolve(__dirname, 'tsconfig.app.json')],
|
defaultProject: resolve(__dirname, 'tsconfig.app.json'),
|
||||||
|
},
|
||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "^4.2.5",
|
||||||
"vue-tippy": "^6.6.0",
|
"vue-tippy": "^6.6.0",
|
||||||
"vue-toastification": "^2.0.0-rc.5"
|
"vue-toastification": "^2.0.0-rc.5",
|
||||||
|
"@harb/web3": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/vue": "^4.3.0",
|
"@iconify/vue": "^4.3.0",
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,10 @@
|
||||||
import { http, createConfig, createStorage } from '@wagmi/vue';
|
/**
|
||||||
import { baseSepolia } from '@wagmi/vue/chains';
|
* Web-app wagmi config — delegates to @harb/web3 shared package.
|
||||||
import { coinbaseWallet, injected, walletConnect } from '@wagmi/vue/connectors';
|
* Re-exports for backward compatibility with existing imports.
|
||||||
import { defineChain } from 'viem';
|
*/
|
||||||
|
import { createHarbConfig, KRAIKEN_LOCAL_CHAIN } from '@harb/web3';
|
||||||
|
|
||||||
const LOCAL_RPC_URL = import.meta.env.VITE_LOCAL_RPC_URL ?? '/api/rpc';
|
const LOCAL_RPC_URL = import.meta.env.VITE_LOCAL_RPC_URL ?? '/api/rpc';
|
||||||
|
|
||||||
export const KRAIKEN_LOCAL_CHAIN = defineChain({
|
export { KRAIKEN_LOCAL_CHAIN };
|
||||||
id: 31337,
|
export const config = createHarbConfig({ rpcUrl: LOCAL_RPC_URL });
|
||||||
name: 'Kraiken Local Fork',
|
|
||||||
network: 'kraiken-local',
|
|
||||||
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
|
|
||||||
rpcUrls: {
|
|
||||||
default: { http: [LOCAL_RPC_URL] },
|
|
||||||
public: { http: [LOCAL_RPC_URL] },
|
|
||||||
},
|
|
||||||
blockExplorers: {
|
|
||||||
default: { name: 'Local Explorer', url: '' },
|
|
||||||
},
|
|
||||||
testnet: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const config = createConfig({
|
|
||||||
chains: [KRAIKEN_LOCAL_CHAIN, baseSepolia],
|
|
||||||
storage: createStorage({ storage: window.localStorage }),
|
|
||||||
|
|
||||||
connectors: [
|
|
||||||
// Injected wallets (MetaMask, Brave, etc.) - also supports E2E test wallet mocks
|
|
||||||
injected(),
|
|
||||||
walletConnect({
|
|
||||||
projectId: 'd8e5ecb0353c02e21d4c0867d4473ac5',
|
|
||||||
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: {
|
|
||||||
[KRAIKEN_LOCAL_CHAIN.id]: http(LOCAL_RPC_URL),
|
|
||||||
[baseSepolia.id]: http(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && config.state.chainId !== KRAIKEN_LOCAL_CHAIN.id) {
|
|
||||||
config.setState(state => ({ ...state, chainId: KRAIKEN_LOCAL_CHAIN.id }));
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue