Merge pull request 'fix: feat: basic analytics funnel tracking for launch readiness (#1101)' (#1142) from fix/issue-1101 into master

This commit is contained in:
johba 2026-03-23 16:36:02 +01:00
commit 209e0c798e
19 changed files with 287 additions and 57 deletions

View file

@ -228,6 +228,15 @@ services:
echo "@harb/utils linked with viem dep" echo "@harb/utils linked with viem dep"
fi fi
# Overlay @harb/analytics shared package from workspace
if [ -d "$WS/packages/analytics" ]; then
mkdir -p /app/packages/analytics
cp -r "$WS/packages/analytics/." /app/packages/analytics/
mkdir -p /app/web-app/node_modules/@harb
ln -sf /app/packages/analytics /app/web-app/node_modules/@harb/analytics
echo "@harb/analytics linked for webapp"
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
@ -285,6 +294,15 @@ services:
echo "@harb/ui-shared linked for landing" echo "@harb/ui-shared linked for landing"
fi fi
# Overlay @harb/analytics shared package from workspace
if [ -d "$WS/packages/analytics" ]; then
mkdir -p /app/packages/analytics
cp -r "$WS/packages/analytics/." /app/packages/analytics/
mkdir -p /app/landing/node_modules/@harb
ln -sf /app/packages/analytics /app/landing/node_modules/@harb/analytics
echo "@harb/analytics 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

View file

@ -18,5 +18,9 @@
uri strip_prefix /api/txn uri strip_prefix /api/txn
reverse_proxy txn-bot:43069 reverse_proxy txn-bot:43069
} }
route /analytics* {
uri strip_prefix /analytics
reverse_proxy umami:3000
}
reverse_proxy landing:5174 reverse_proxy landing:5174
} }

16
containers/init-umami-db.sh Executable file
View file

@ -0,0 +1,16 @@
#!/bin/bash
# Creates the umami database and user if they don't already exist.
# Mounted as a postgres init script via docker-compose volumes.
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'umami') THEN
CREATE ROLE umami WITH LOGIN PASSWORD 'umami_local';
END IF;
END
\$\$;
SELECT 'CREATE DATABASE umami OWNER umami'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'umami')\gexec
EOSQL

View file

@ -26,4 +26,7 @@ if [[ -f "$CONTRACTS_ENV" ]]; then
echo "[landing-entrypoint] Contract addresses loaded: KRK=${KRAIKEN:-unset} STAKE=${STAKE:-unset}" echo "[landing-entrypoint] Contract addresses loaded: KRK=${KRAIKEN:-unset} STAKE=${STAKE:-unset}"
fi fi
export VITE_UMAMI_URL="${VITE_UMAMI_URL:-}"
export VITE_UMAMI_WEBSITE_ID="${VITE_UMAMI_WEBSITE_ID:-}"
exec npm run dev -- --host 0.0.0.0 --port 5174 exec npm run dev -- --host 0.0.0.0 --port 5174

View file

@ -34,6 +34,8 @@ if [[ "${CI:-}" == "true" ]]; then
export VITE_SWAP_ROUTER=${VITE_SWAP_ROUTER:-0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4} export VITE_SWAP_ROUTER=${VITE_SWAP_ROUTER:-0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4}
export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql} export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql}
export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn} export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn}
export VITE_UMAMI_URL="${VITE_UMAMI_URL:-}"
export VITE_UMAMI_WEBSITE_ID="${VITE_UMAMI_WEBSITE_ID:-}"
echo "[webapp-ci] Environment configured:" echo "[webapp-ci] Environment configured:"
echo " VITE_KRAIKEN_ADDRESS: ${VITE_KRAIKEN_ADDRESS}" echo " VITE_KRAIKEN_ADDRESS: ${VITE_KRAIKEN_ADDRESS}"
@ -81,5 +83,7 @@ export VITE_SWAP_ROUTER=$SWAP_ROUTER
export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql} export VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK=${VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK:-/api/graphql}
export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn} export VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK=${VITE_TXNBOT_BASE_SEPOLIA_LOCAL_FORK:-/api/txn}
export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1} export CHOKIDAR_USEPOLLING=${CHOKIDAR_USEPOLLING:-1}
export VITE_UMAMI_URL="${VITE_UMAMI_URL:-}"
export VITE_UMAMI_WEBSITE_ID="${VITE_UMAMI_WEBSITE_ID:-}"
exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/ exec npm run dev -- --host 0.0.0.0 --port 5173 --base /app/

View file

@ -52,6 +52,7 @@ services:
- POSTGRES_DB=ponder_local - POSTGRES_DB=ponder_local
volumes: volumes:
- postgres-data:/var/lib/postgresql/data - postgres-data:/var/lib/postgresql/data
- ./containers/init-umami-db.sh:/docker-entrypoint-initdb.d/init-umami-db.sh:ro,z
expose: expose:
- "5432" - "5432"
restart: unless-stopped restart: unless-stopped
@ -133,6 +134,8 @@ services:
- CHOKIDAR_USEPOLLING=1 - CHOKIDAR_USEPOLLING=1
- GIT_BRANCH=${GIT_BRANCH:-} - GIT_BRANCH=${GIT_BRANCH:-}
- VITE_ENABLE_LOCAL_SWAP=true - VITE_ENABLE_LOCAL_SWAP=true
- VITE_UMAMI_URL=${VITE_UMAMI_URL:-}
- VITE_UMAMI_WEBSITE_ID=${VITE_UMAMI_WEBSITE_ID:-}
expose: expose:
- "5173" - "5173"
ports: ports:
@ -167,6 +170,8 @@ services:
- CHOKIDAR_USEPOLLING=1 - CHOKIDAR_USEPOLLING=1
- GIT_BRANCH=${GIT_BRANCH:-} - GIT_BRANCH=${GIT_BRANCH:-}
- VITE_APP_URL=http://localhost:5173/app - VITE_APP_URL=http://localhost:5173/app
- VITE_UMAMI_URL=${VITE_UMAMI_URL:-}
- VITE_UMAMI_WEBSITE_ID=${VITE_UMAMI_WEBSITE_ID:-}
expose: expose:
- "5174" - "5174"
restart: unless-stopped restart: unless-stopped
@ -228,6 +233,30 @@ services:
retries: 3 retries: 3
start_period: 2s start_period: 2s
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
environment:
- DATABASE_URL=postgresql://umami:umami_local@postgres:5432/umami
- APP_SECRET=${UMAMI_APP_SECRET:-harb-analytics-secret}
- DISABLE_TELEMETRY=1
expose:
- "3000"
ports:
- "127.0.0.1:3000:3000"
restart: unless-stopped
networks:
- harb-network
depends_on:
postgres:
condition: service_healthy
logging: *default-logging
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:3000/api/heartbeat"]
interval: 5s
timeout: 3s
retries: 10
start_period: 15s
otterscan: otterscan:
image: otterscan/otterscan:v2.6.0 image: otterscan/otterscan:v2.6.0
environment: environment:

View file

@ -16,6 +16,7 @@ Docker Compose services (in startup order):
| **webapp** | Staking app (Vue 3) | 5173 | HTTP response | | **webapp** | Staking app (Vue 3) | 5173 | HTTP response |
| **txn-bot** | Automated `recenter()` and `payTax()` upkeep ([services/txnBot/](../services/txnBot/)) | 43069 | Process alive | | **txn-bot** | Automated `recenter()` and `payTax()` upkeep ([services/txnBot/](../services/txnBot/)) | 43069 | Process alive |
| **caddy** | Reverse proxy / TLS | 80/443 | — | | **caddy** | Reverse proxy / TLS | 80/443 | — |
| **umami** | Self-hosted analytics (Umami) | 3000 | HTTP /api/heartbeat |
| **otterscan** | Block explorer | 5100 | — | | **otterscan** | Block explorer | 5100 | — |
## txnBot Service ## txnBot Service
@ -119,6 +120,7 @@ docker inspect landing --format '{{.NetworkSettings.Networks.harb_harb-network.I
- **Ponder GraphQL:** `http://localhost:42069/graphql` - **Ponder GraphQL:** `http://localhost:42069/graphql`
- **Anvil RPC:** `http://localhost:8545` - **Anvil RPC:** `http://localhost:8545`
- **txnBot status:** `http://localhost:43069/status` - **txnBot status:** `http://localhost:43069/status`
- **Umami analytics:** `http://localhost:3000` (default login: `admin` / `umami`)
## Resource Notes ## Resource Notes
@ -140,6 +142,39 @@ For testing login: `lobsterDao`, `test123`, `lobster-x010syqe?412!`
| `VITE_KRAIKEN_ADDRESS` | from `deployments-local.json` | via `contracts.env` + entrypoint | Override KRK token address. | | `VITE_KRAIKEN_ADDRESS` | from `deployments-local.json` | via `contracts.env` + entrypoint | Override KRK token address. |
| `VITE_STAKE_ADDRESS` | from `deployments-local.json` | via `contracts.env` + entrypoint | Override Stake contract address. | | `VITE_STAKE_ADDRESS` | from `deployments-local.json` | via `contracts.env` + entrypoint | Override Stake contract address. |
| `VITE_DEFAULT_CHAIN_ID` | auto-detected (31337 on localhost) | — | Force the default chain. | | `VITE_DEFAULT_CHAIN_ID` | auto-detected (31337 on localhost) | — | Force the default chain. |
| `VITE_UMAMI_URL` | unset | via env | Full URL to Umami `script.js` (e.g. `https://analytics.kraiken.org/script.js`). Omit to disable analytics. |
| `VITE_UMAMI_WEBSITE_ID` | unset | via env | Umami website ID (UUID). Required alongside `VITE_UMAMI_URL`. |
## Analytics (Umami)
Self-hosted [Umami](https://umami.is/) provides privacy-respecting funnel analytics with no third-party tracking. The `umami` Docker service shares the `postgres` instance (separate `umami` database created by `containers/init-umami-db.sh`).
### Setup
1. Start the stack — Umami comes up automatically.
2. Open `http://localhost:3000` and log in (default: `admin` / `umami`). Change the password on first login.
3. Add a website in Umami and copy the **Website ID** (UUID).
4. Set the env vars before starting landing/webapp:
```bash
export VITE_UMAMI_URL=http://localhost:3000/script.js
export VITE_UMAMI_WEBSITE_ID=<your-website-id>
```
For staging/production behind Caddy, use the `/analytics/script.js` path instead.
### Tracked funnel events
| Event | App | Trigger |
|-------|-----|---------|
| `cta_click` | landing | User clicks a CTA button (label in event data) |
| `wallet_connect` | web-app | Wallet connected for the first time |
| `swap_initiated` | web-app | User submits a buy or sell swap (direction in event data) |
| `stake_created` | web-app | Stake position successfully created |
Page views are tracked automatically by the Umami script on every route change.
### Production deployment
On `harb-staging`, set `VITE_UMAMI_URL` and `VITE_UMAMI_WEBSITE_ID` in the environment and configure `UMAMI_APP_SECRET` to a strong random value. The Caddy route `/analytics*` proxies to the Umami container.
## Contract Addresses ## Contract Addresses

View file

@ -5,7 +5,6 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KrAIken</title> <title>KrAIken</title>
<script defer src="https://cloud.umami.is/script.js" data-website-id="6e2869f2-02a5-4346-9b3e-3c27e24ea5d3"></script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View file

@ -17,6 +17,7 @@
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@harb/analytics": "*",
"@harb/web3": "*", "@harb/web3": "*",
"@harb/ui-shared": "*", "@harb/ui-shared": "*",
"@tanstack/vue-query": "^5.92.9", "@tanstack/vue-query": "^5.92.9",

View file

@ -5,9 +5,12 @@ import { WagmiPlugin } from '@wagmi/vue';
// that Wagmi uses internally for its reactive data hooks (useAccount, useConnect, etc.). // that Wagmi uses internally for its reactive data hooks (useAccount, useConnect, etc.).
import { VueQueryPlugin } from '@tanstack/vue-query'; import { VueQueryPlugin } from '@tanstack/vue-query';
import { createHarbConfig } from '@harb/web3'; import { createHarbConfig } from '@harb/web3';
import { initAnalytics } from '@harb/analytics';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';
initAnalytics(import.meta.env.VITE_UMAMI_URL, import.meta.env.VITE_UMAMI_WEBSITE_ID);
const rpcUrl = import.meta.env.VITE_LOCAL_RPC_URL ?? '/api/rpc'; const rpcUrl = import.meta.env.VITE_LOCAL_RPC_URL ?? '/api/rpc';
const config = createHarbConfig({ rpcUrl }); const config = createHarbConfig({ rpcUrl });

View file

@ -10,7 +10,7 @@
$KRK has a price floor backed by real ETH. The protocol adapts automatically. You just hold. $KRK has a price floor backed by real ETH. The protocol adapts automatically. You just hold.
</div> </div>
<div class="header-cta"> <div class="header-cta">
<KButton @click="router.push('/app/get-krk')">Get $KRK</KButton> <KButton @click="navigateCta('/app/get-krk', 'hero_get_krk')">Get $KRK</KButton>
</div> </div>
<div class="blur-effect"></div> <div class="blur-effect"></div>
</div> </div>
@ -38,7 +38,7 @@
</div> </div>
</div> </div>
<div class="centered-cta"> <div class="centered-cta">
<KButton @click="router.push('/app/get-krk')">Get $KRK</KButton> <KButton @click="navigateCta('/app/get-krk', 'how_it_works_get_krk')">Get $KRK</KButton>
</div> </div>
</section> </section>
<section class="protocol-health-section"> <section class="protocol-health-section">
@ -51,7 +51,7 @@
<p> <p>
Watch the protocol in real time. Supply dynamics, ETH reserves, position history all live, no wallet needed. Watch the protocol in real time. Supply dynamics, ETH reserves, position history all live, no wallet needed.
</p> </p>
<KButton @click="router.push('/app')">View Protocol</KButton> <KButton @click="navigateCta('/app', 'view_protocol')">View Protocol</KButton>
</template> </template>
</LeftRightComponent> </LeftRightComponent>
</section> </section>
@ -68,8 +68,8 @@
<li>Hold. The protocol does the rest.</li> <li>Hold. The protocol does the rest.</li>
</ol> </ol>
<div class="button-group"> <div class="button-group">
<KButton @click="router.push('/app/get-krk')">Get $KRK</KButton> <KButton @click="navigateCta('/app/get-krk', 'getting_started_get_krk')">Get $KRK</KButton>
<KButton @click="router.push('/docs/how-it-works')">How It Works </KButton> <KButton @click="navigateCta('/docs/how-it-works', 'how_it_works_docs')">How It Works </KButton>
</div> </div>
</template> </template>
</LeftRightComponent> </LeftRightComponent>
@ -93,11 +93,17 @@ import LiveStats from '@/components/LiveStats.vue';
import SecurityInfo from '@/components/SecurityInfo.vue'; import SecurityInfo from '@/components/SecurityInfo.vue';
import WalletCard from '@/components/WalletCard.vue'; import WalletCard from '@/components/WalletCard.vue';
import { useMobile } from '@/composables/useMobile'; import { useMobile } from '@/composables/useMobile';
import { trackCtaClick } from '@harb/analytics';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
const isMobile = useMobile(); const isMobile = useMobile();
const router = useRouter(); const router = useRouter();
const navigateCta = (path: string, label: string) => {
trackCtaClick(label);
router.push(path);
};
const openExternal = (url: string) => { const openExternal = (url: string) => {
window.open(url, '_blank', 'noopener'); window.open(url, '_blank', 'noopener');
}; };

125
package-lock.json generated
View file

@ -69,6 +69,7 @@
"name": "vue-kraiken", "name": "vue-kraiken",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@harb/analytics": "*",
"@harb/ui-shared": "*", "@harb/ui-shared": "*",
"@harb/web3": "*", "@harb/web3": "*",
"@tanstack/vue-query": "^5.92.9", "@tanstack/vue-query": "^5.92.9",
@ -279,7 +280,6 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.29.0", "@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0", "@babel/generator": "^7.29.0",
@ -938,7 +938,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=20.19.0" "node": ">=20.19.0"
}, },
@ -979,7 +978,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=20.19.0" "node": ">=20.19.0"
} }
@ -3360,6 +3358,10 @@
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
} }
}, },
"node_modules/@harb/analytics": {
"resolved": "packages/analytics",
"link": true
},
"node_modules/@harb/ui-shared": { "node_modules/@harb/ui-shared": {
"resolved": "packages/ui-shared", "resolved": "packages/ui-shared",
"link": true "link": true
@ -4228,7 +4230,6 @@
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz",
"integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": "^14.21.3 || >=16" "node": "^14.21.3 || >=16"
}, },
@ -5291,7 +5292,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@ -5637,7 +5637,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@ -5935,7 +5934,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@ -6430,7 +6428,6 @@
"integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==", "integrity": "sha512-RnO1SaiCFHn666wNz2QfZEFxvmiNRqhzaMXHXxXXKt+MEP7aajlPxUSMIQpKAaJfverpovEYqjBOXDq6dDcaOQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/utils": "^8.13.0", "@typescript-eslint/utils": "^8.13.0",
"eslint-visitor-keys": "^4.2.0", "eslint-visitor-keys": "^4.2.0",
@ -6673,6 +6670,66 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.4.5",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.0.4",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.4.5",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.0.4",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.12",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.10.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.0",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.13", "version": "4.1.13",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz",
@ -6840,7 +6897,6 @@
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/ms": "*" "@types/ms": "*"
} }
@ -6888,7 +6944,6 @@
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/lodash": "*" "@types/lodash": "*"
} }
@ -6966,7 +7021,6 @@
"integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/scope-manager": "8.56.0",
"@typescript-eslint/types": "8.56.0", "@typescript-eslint/types": "8.56.0",
@ -7874,7 +7928,6 @@
"resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.22.1.tgz", "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.22.1.tgz",
"integrity": "sha512-cG/xwQWsBEcKgRTkQVhH29cbpbs/TdcUJVFXCyri3ZknxhMyGv0YEjTcrNpRgt2SaswL1KrvslSNYKKo+5YEAg==", "integrity": "sha512-cG/xwQWsBEcKgRTkQVhH29cbpbs/TdcUJVFXCyri3ZknxhMyGv0YEjTcrNpRgt2SaswL1KrvslSNYKKo+5YEAg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"eventemitter3": "5.0.1", "eventemitter3": "5.0.1",
"mipd": "0.0.7", "mipd": "0.0.7",
@ -8476,7 +8529,6 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@ -8679,7 +8731,6 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@ -9174,7 +9225,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@ -9238,7 +9288,6 @@
"integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"node-gyp-build": "^4.3.0" "node-gyp-build": "^4.3.0"
}, },
@ -9503,7 +9552,6 @@
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@kurkle/color": "^0.3.0" "@kurkle/color": "^0.3.0"
}, },
@ -9943,7 +9991,6 @@
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
"integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"node-fetch": "^2.7.0" "node-fetch": "^2.7.0"
} }
@ -9980,7 +10027,6 @@
"resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz",
"integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"uncrypto": "^0.1.3" "uncrypto": "^0.1.3"
} }
@ -10452,7 +10498,6 @@
"resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.17.tgz", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.17.tgz",
"integrity": "sha512-TOOURki4G7sD1wDCjj7NfLaXZZ49dFOeEb5y39IXpb8p0hRzVvfvzZHOi5JcT+PpyAbi/Y+lxPb8eTag2WYH8w==", "integrity": "sha512-TOOURki4G7sD1wDCjj7NfLaXZZ49dFOeEb5y39IXpb8p0hRzVvfvzZHOi5JcT+PpyAbi/Y+lxPb8eTag2WYH8w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@ecies/ciphers": "^0.2.5", "@ecies/ciphers": "^0.2.5",
"@noble/ciphers": "^1.3.0", "@noble/ciphers": "^1.3.0",
@ -10869,7 +10914,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@ -10946,7 +10990,6 @@
"integrity": "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==", "integrity": "sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -11350,8 +11393,7 @@
"version": "6.4.9", "version": "6.4.9",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/eventemitter3": { "node_modules/eventemitter3": {
"version": "5.0.1", "version": "5.0.1",
@ -11446,7 +11488,6 @@
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"accepts": "^2.0.0", "accepts": "^2.0.0",
"body-parser": "^2.2.0", "body-parser": "^2.2.0",
@ -12223,7 +12264,6 @@
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
"integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
} }
@ -12305,7 +12345,6 @@
"integrity": "sha512-yoLRW+KRlDmnnROdAu7sX77VNLC0bsFoZyGQJLy1cF+X/SkLg/fWkRGrEEYQK8o2cafJ2wmEaMqMEZB3U3DYDg==", "integrity": "sha512-yoLRW+KRlDmnnROdAu7sX77VNLC0bsFoZyGQJLy1cF+X/SkLg/fWkRGrEEYQK8o2cafJ2wmEaMqMEZB3U3DYDg==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },
@ -12359,7 +12398,6 @@
"integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==", "integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"webidl-conversions": "^7.0.0", "webidl-conversions": "^7.0.0",
"whatwg-mimetype": "^3.0.0" "whatwg-mimetype": "^3.0.0"
@ -13382,7 +13420,6 @@
"integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@acemir/cssom": "^0.9.28", "@acemir/cssom": "^0.9.28",
"@asamuzakjp/dom-selector": "^6.7.6", "@asamuzakjp/dom-selector": "^6.7.6",
@ -13685,7 +13722,6 @@
"integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==",
"devOptional": true, "devOptional": true,
"license": "MPL-2.0", "license": "MPL-2.0",
"peer": true,
"dependencies": { "dependencies": {
"detect-libc": "^2.0.3" "detect-libc": "^2.0.3"
}, },
@ -14111,15 +14147,13 @@
"version": "4.17.23", "version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lodash-es": { "node_modules/lodash-es": {
"version": "4.17.23", "version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/lodash-unified": { "node_modules/lodash-unified": {
"version": "1.0.3", "version": "1.0.3",
@ -16286,7 +16320,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -16311,7 +16344,6 @@
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.26.0" "scheduler": "^0.26.0"
}, },
@ -16437,7 +16469,6 @@
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"inherits": "^2.0.3", "inherits": "^2.0.3",
"string_decoder": "^1.1.1", "string_decoder": "^1.1.1",
@ -16604,7 +16635,6 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
"integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.8" "@types/estree": "1.0.8"
}, },
@ -17185,7 +17215,6 @@
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@socket.io/component-emitter": "~3.1.0", "@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1", "debug": "~4.4.1",
@ -17570,8 +17599,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz",
"integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/tailwindcss-animate": { "node_modules/tailwindcss-animate": {
"version": "1.0.7", "version": "1.0.7",
@ -17909,7 +17937,6 @@
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -18281,7 +18308,6 @@
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"node-gyp-build": "^4.3.0" "node-gyp-build": "^4.3.0"
}, },
@ -18322,7 +18348,6 @@
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz",
"integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"derive-valtio": "0.1.0", "derive-valtio": "0.1.0",
"proxy-compare": "2.6.0", "proxy-compare": "2.6.0",
@ -18374,7 +18399,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@noble/curves": "1.9.1", "@noble/curves": "1.9.1",
"@noble/hashes": "1.8.0", "@noble/hashes": "1.8.0",
@ -18522,7 +18546,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",
"fdir": "^6.4.4", "fdir": "^6.4.4",
@ -18721,7 +18744,6 @@
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/chai": "^5.2.2", "@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4", "@vitest/expect": "3.2.4",
@ -18801,7 +18823,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz",
"integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.28", "@vue/compiler-dom": "3.5.28",
"@vue/compiler-sfc": "3.5.28", "@vue/compiler-sfc": "3.5.28",
@ -18830,7 +18851,6 @@
"integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==", "integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"debug": "^4.4.0", "debug": "^4.4.0",
"eslint-scope": "^8.2.0 || ^9.0.0", "eslint-scope": "^8.2.0 || ^9.0.0",
@ -19101,7 +19121,6 @@
"version": "8.17.1", "version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"peer": true,
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
}, },
@ -19289,7 +19308,6 @@
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
@ -19333,6 +19351,10 @@
} }
} }
}, },
"packages/analytics": {
"name": "@harb/analytics",
"version": "0.1.0"
},
"packages/ui-shared": { "packages/ui-shared": {
"name": "@harb/ui-shared", "name": "@harb/ui-shared",
"version": "0.1.0", "version": "0.1.0",
@ -19363,6 +19385,7 @@
"name": "harb-staking", "name": "harb-staking",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@harb/analytics": "*",
"@harb/utils": "*", "@harb/utils": "*",
"@harb/web3": "*", "@harb/web3": "*",
"@tanstack/vue-query": "^5.64.2", "@tanstack/vue-query": "^5.64.2",

View file

@ -0,0 +1,8 @@
{
"name": "@harb/analytics",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "src/index.ts",
"types": "src/index.ts"
}

View file

@ -0,0 +1,69 @@
/**
* Lightweight analytics wrapper for self-hosted Umami.
*
* Call `initAnalytics()` once at app startup to inject the tracker
* script. All other helpers are safe to call at any time if the
* tracker hasn't loaded (ad-blocker, missing config, dev without
* Umami running) every call is a silent no-op.
*/
interface UmamiTracker {
track: (name: string, data?: Record<string, string | number | boolean>) => void;
}
declare global {
interface Window {
umami?: UmamiTracker;
}
}
/**
* Inject the Umami tracker script.
* @param scriptUrl full URL to `script.js`, e.g. `https://analytics.kraiken.org/script.js`
* @param websiteId Umami website ID (UUID)
*/
export function initAnalytics(scriptUrl: string | undefined, websiteId: string | undefined): void {
if (!scriptUrl || !websiteId) return;
if (typeof document === 'undefined') return;
if (document.querySelector(`script[data-website-id="${websiteId}"]`)) return;
const el = document.createElement('script');
el.defer = true;
el.src = scriptUrl;
el.dataset.websiteId = websiteId;
document.head.appendChild(el);
}
function getTracker(): UmamiTracker | undefined {
return typeof window !== 'undefined' ? window.umami : undefined;
}
/** Fire a named event with optional properties. */
export function trackEvent(
name: string,
data?: Record<string, string | number | boolean>,
): void {
getTracker()?.track(name, data);
}
/* ── Funnel events ─────────────────────────────────────────────── */
/** Landing page CTA clicked (e.g. "Get $KRK"). */
export function trackCtaClick(label: string): void {
trackEvent('cta_click', { label });
}
/** Wallet successfully connected. */
export function trackWalletConnect(): void {
trackEvent('wallet_connect');
}
/** Swap (buy/sell) initiated — user submitted the transaction. */
export function trackSwapInitiated(direction: 'buy' | 'sell'): void {
trackEvent('swap_initiated', { direction });
}
/** Stake position created (snatch completed). */
export function trackStakeCreated(): void {
trackEvent('stake_created');
}

View file

@ -33,6 +33,7 @@
"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/analytics": "*",
"@harb/web3": "*", "@harb/web3": "*",
"@harb/utils": "*" "@harb/utils": "*"
}, },

View file

@ -11,6 +11,7 @@ import { createPermitObject, getSignatureRSV } from '@/utils/blockchain';
import { weiToNumber, compactNumber } from 'kraiken-lib/format'; import { weiToNumber, compactNumber } from 'kraiken-lib/format';
import { getTaxRateOptionByIndex } from '@/composables/useAdjustTaxRates'; import { getTaxRateOptionByIndex } from '@/composables/useAdjustTaxRates';
import { useContractToast } from './useContractToast'; import { useContractToast } from './useContractToast';
import { trackStakeCreated } from '@harb/analytics';
const wallet = useWallet(); const wallet = useWallet();
const contractToast = useContractToast(); const contractToast = useContractToast();
const wagmiConfig: Config = config; const wagmiConfig: Config = config;
@ -129,6 +130,7 @@ export function useStake() {
const eventArgs: PositionCreatedArgs = topics.args; const eventArgs: PositionCreatedArgs = topics.args;
const amount = compactNumber(weiToNumber(eventArgs.harbergDeposit, wallet.balance.decimals)); const amount = compactNumber(weiToNumber(eventArgs.harbergDeposit, wallet.balance.decimals));
trackStakeCreated();
contractToast.showSuccessToast(amount, 'Success!', 'You Staked', 'Check your positions on the<br /> Staker Dashboard', '$KRK'); contractToast.showSuccessToast(amount, 'Success!', 'You Staked', 'Check your positions on the<br /> Staker Dashboard', '$KRK');
waiting.value = false; waiting.value = false;

View file

@ -7,6 +7,7 @@ import { config as wagmiConfig } from '@/wagmi';
import { getChain, DEFAULT_CHAIN_ID } from '@/config'; import { getChain, DEFAULT_CHAIN_ID } from '@/config';
import { useWallet } from '@/composables/useWallet'; import { useWallet } from '@/composables/useWallet';
import { getErrorMessage, ensureAddress } from '@harb/utils'; import { getErrorMessage, ensureAddress } from '@harb/utils';
import { trackSwapInitiated } from '@harb/analytics';
export const WETH_ABI = [ export const WETH_ABI = [
{ {
@ -217,6 +218,7 @@ export function useSwapKrk() {
}); });
} }
trackSwapInitiated('buy');
await writeContract(wagmiConfig, { await writeContract(wagmiConfig, {
abi: SWAP_ROUTER_ABI, abi: SWAP_ROUTER_ABI,
address: router, address: router,
@ -327,6 +329,7 @@ export function useSwapKrk() {
} }
sellPhase.value = 'selling'; sellPhase.value = 'selling';
trackSwapInitiated('sell');
await writeContract(wagmiConfig, { await writeContract(wagmiConfig, {
abi: SWAP_ROUTER_ABI, abi: SWAP_ROUTER_ABI,
address: router, address: router,

View file

@ -9,6 +9,7 @@ import { setStakeContract } from '@/contracts/stake';
import { chainsData, DEFAULT_CHAIN_ID } from '@/config'; import { chainsData, DEFAULT_CHAIN_ID } from '@/config';
import { createPublicClient, custom, formatUnits, type EIP1193Provider, type PublicClient, type Transport, type WalletClient } from 'viem'; import { createPublicClient, custom, formatUnits, type EIP1193Provider, type PublicClient, type Transport, type WalletClient } from 'viem';
import { getWalletPublicClient, setWalletPublicClient } from '@/services/walletRpc'; import { getWalletPublicClient, setWalletPublicClient } from '@/services/walletRpc';
import { trackWalletConnect } from '@harb/analytics';
const balance = ref<GetBalanceReturnType>({ const balance = ref<GetBalanceReturnType>({
value: 0n, value: 0n,
@ -118,6 +119,7 @@ export function useWallet() {
}; };
} else if (account.value.address !== data.address || account.value.chainId !== data.chainId) { } else if (account.value.address !== data.address || account.value.chainId !== data.chainId) {
logger.info(`Account changed!:`, data.address); logger.info(`Account changed!:`, data.address);
if (!account.value.address && data.address) trackWalletConnect();
account.value = data; account.value = data;
await syncWalletPublicClient(data); await syncWalletPublicClient(data);
await loadBalance(); await loadBalance();

View file

@ -2,6 +2,7 @@ import { WagmiPlugin } from '@wagmi/vue';
import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'; import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { config } from './wagmi'; import { config } from './wagmi';
import { initAnalytics } from '@harb/analytics';
import ClickOutSide from '@/directives/ClickOutsideDirective'; import ClickOutSide from '@/directives/ClickOutsideDirective';
import router from './router'; import router from './router';
@ -9,6 +10,9 @@ import App from './App.vue';
import './assets/styles/main.sass'; import './assets/styles/main.sass';
import Toast from 'vue-toastification'; import Toast from 'vue-toastification';
import 'vue-toastification/dist/index.css'; import 'vue-toastification/dist/index.css';
initAnalytics(import.meta.env.VITE_UMAMI_URL, import.meta.env.VITE_UMAMI_WEBSITE_ID);
const queryClient = new QueryClient(); const queryClient = new QueryClient();
const app = createApp(App); const app = createApp(App);