replaced subgraph with ponder

This commit is contained in:
johba 2025-09-23 19:24:05 +02:00
parent 5e6e21878c
commit ba64e725dc
45 changed files with 386 additions and 7275 deletions

View file

@ -5,9 +5,14 @@
- `ponder/`: New indexer replacing The Graph. `ponder.config.ts` selects network/contracts, `src/` holds TypeScript event handlers, `ponder.schema.ts` defines tables, `README.md` documents usage. Generated artifacts in `generated/` are auto-created by Ponder.
- `subgraph/base_sepolia/`: Legacy AssemblyScript implementation kept for reference during migration. Do not modify unless syncing schema changes between stacks.
- `landing/`: Vue 3 app (Vite + TypeScript) for the public launch site and forthcoming staking UI. See `src/views/` for pages, `env.d.ts` for injected globals.
- `services/txnBot/`: Node service that consumes the GraphQL API (`.graphclient`) to trigger `recenter()` and `payTax()` on-chain when profitable.
- `services/txnBot/`: Node service that consumes the Ponder GraphQL API to trigger `recenter()` and `payTax()` on-chain when profitable.
- `kraiken-lib/`: Shared TypeScript helpers (e.g., `bytesToUint256`) consumed by the landing app + bots.
## Environment Profiles
- `BASE_SEPOLIA_LOCAL_FORK`: Anvil fork bootstrapped by `scripts/local_env.sh`
- `BASE_SEPOLIA`: Public Base Sepolia testnet
- `BASE`: Base mainnet
## Execution Workflow
1. **Contracts**
- Build/test via `forge build` and `forge test` inside `onchain/`.
@ -16,23 +21,25 @@
2. **Indexer (Ponder)**
- Install deps (`npm install`).
- Configure environment via `PONDER_NETWORK` (`local`, `baseSepolia`, or `base`). Update `ponder.config.ts` addresses if deployments change.
- Configure environment via `PONDER_NETWORK` (`BASE_SEPOLIA_LOCAL_FORK`, `BASE_SEPOLIA`, or `BASE`). Update `ponder.config.ts` addresses if deployments change.
- Run dev mode with `npm run dev`; GraphQL served at `http://localhost:42069/graphql`.
- Handlers in `src/kraiken.ts` and `src/stake.ts` maintain rolling supply stats, ring-buffered hourly metrics, and position state.
3. **Frontend**
- `npm install` then `npm run dev` in `landing/`. Currently static marketing copy with placeholders for wallet/staking flows.
- Configure GraphQL endpoints via `VITE_PONDER_BASE_SEPOLIA`, `VITE_PONDER_BASE`, and optionally override `VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK` (defaults to `http://127.0.0.1:42069/graphql`).
4. **Automation Bot**
- Requires `.env` with RPC + key. Queries indexer (Graph or Ponder) to decide when to pay tax or recenter liquidity.
- Requires `.env` with RPC + key. Queries the Ponder GraphQL endpoint to decide when to pay tax or recenter liquidity.
- `scripts/local_env.sh start` provisions and launches the bot automatically when running against the local fork.
## Migration Notes (Subgraph → Ponder)
- Schema parity: ensure any entity changes land in both `ponder.schema.ts` and legacy Graph `schema.graphql` until cutover.
- Event coverage: `Stake` events (`PositionCreated/Removed/Shrunk/TaxPaid/RateHiked`) mirrored from AssemblyScript handlers. Kraiken `Transfer` powers mint/burn/tax/UBI tracking.
- Ring buffer logic in `kraiken.ts` depends on block timestamps being monotonic; gaps >168 hours zero out the buffer. Verify `startBlock` in `ponder.config.ts` to avoid reprocessing genesis noise.
- Local deployment requires updating `ponder.config.ts` local contract addresses after each run or injecting via env overrides.
- Local deployment requires updating `ponder.config.ts` local contract addresses after each run or injecting via env overrides. The `BASE_SEPOLIA_LOCAL_FORK` profile assumes addresses generated by `DeployLocal.sol`.
- Ponder v0.13 exposes helpers via `context.client` / `context.contracts`; use these to seed stats from on-chain `totalSupply` and refresh `outstandingStake` straight from the `Stake` contract. All hourly projections now run off a JSON ring buffer on the `stats` row.
- Subgraph naming differs from the new schema (`stats_collection`, decimal shares). Until the web-app switches to Ponders GraphQL endpoint, keep the legacy entity shape in mind. Plan to provide either a compatibility layer or update `web-app/src/composables/useStatCollection.ts` to hit the new schema.
- Subgraph naming differs from the new schema (`stats_collection`, decimal shares). The web-app and txn bot now target the Ponder schema directly; keep legacy Graph compatibility only until the AssemblyScript stack is fully retired.
- Tax accounting must listen to `PositionTaxPaid` instead of guessing via hard-coded addresses. The old subgraph missed this; Ponder now increments the ring buffer segment on each tax payment.
- Liquidity bootstrap still depends on the Uniswap pool stepping through `recenter()`. On current Base-Sepolia forks the LM reverts with `LOK`. Investigate before relying on `scripts/local_env.sh start` for an unattended setup.
@ -42,7 +49,7 @@
1. `anvil --fork-url https://sepolia.base.org` (or base mainnet RPC) in terminal 1.
2. From `onchain/`, deploy with `forge script script/DeployLocal.sol --fork-url http://127.0.0.1:8545 --broadcast`.
3. Fund LiquidityManager, call `recenter()`, and execute sample trades (KRK buy, stake, unstake) using `cast` or Foundry scripts.
4. Start Ponder (`PONDER_NETWORK=local npm run dev`) and watch logs for handler errors.
4. Start Ponder (`PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK npm run dev`) and watch logs for handler errors.
5. Query GraphQL (`stats`, `positions`) to confirm indexed state.
## Gotchas & Tips
@ -50,16 +57,24 @@
- VWAP/ethScarcity logic expects squared price format; do not convert to sqrt unintentionally.
- LiquidityManager `recenter()` reverts unless funded with WETH (Base WETH address `0x4200...006`). Use `cast send` with sufficient ETH when testing locally.
- Ponders SQLite store lives in `.ponder/` (gitignored). Delete between runs if schema changes.
- Legacy subgraph still powers `services/txnBot` until cutover—coordinate endpoint switch when Ponder is production-ready.
- `web-app/` currently points at The Graph Studio URLs (see `src/config.ts`). When Ponder replaces the subgraph, update those envs and mirror the expected GraphQL shape (the app queries `stats_collection` with camelCase fields). Consider adding an adapter layer or bumping the frontend to the new schema before deleting `subgraph/`.
- Legacy subgraph is maintained for reference only; all active services now read from Ponder.
- `web-app/` reads from Ponder by default (see `src/config.ts`). Update the environment specific GraphQL URLs when deployments move.
## Useful Commands
- `foundryup` / `forge clean` / `forge snapshot`
- `anvil --fork-url https://sepolia.base.org`
- `cast call <POOL> "slot0()"`
- `PONDER_NETWORK=local npm run dev`
- `PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK npm run dev`
- `curl -X POST http://localhost:42069/graphql -d '{"query":"{ stats(id:\"0x01\"){kraikenTotalSupply}}"}'`
- `./scripts/local_env.sh start` boots Anvil+contracts+ponder+frontend; stop with Ctrl+C or `./scripts/local_env.sh stop`.
- `curl http://127.0.0.1:43069/status`
- `./scripts/local_env.sh start` boots Anvil+contracts+ponder+frontend+txnBot; stop with Ctrl+C or `./scripts/local_env.sh stop`.
## Refactor Backlog
- Replace the temporary `any` shims in `ponder/src/kraiken.ts` and `ponder/src/stake.ts` by importing the official 0.13 handler types instead of stubbing `ponder-env.d.ts`.
- Drop the custom `ponder:api` / `ponder:registry` module declarations once the generator emits them; if it cannot, declare precise interfaces rather than `any` to keep autocomplete and future upgrades safe.
- Convert the JSON ABI imports in `ponder/ponder.config.ts` to typed loaders (e.g., `satisfies Abi`) so config drift is caught at compile time instead of via `as Abi` casts.
- Move the snatch-selection logic out of `web-app/src/components/StakeHolder.vue` into a dedicated composable that both the stake form and charts can reuse, and memoise the `assetsToShares` conversions there.
- Split `kraiken-lib/src/helpers.ts` into focused modules (ids, tax rates, snatch selection) so consumers can tree-shake and each helper stays small and testable.
## Contacts & Artifacts
- Deployment addresses recorded in `onchain/deployments-local.json` (local) and broadcast traces under `onchain/broadcast/`.

View file

@ -1,8 +1,9 @@
# Network configuration
# Options: local, baseSepolia, base
PONDER_NETWORK=local
# Options: BASE_SEPOLIA_LOCAL_FORK, BASE_SEPOLIA, BASE
PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
# RPC URLs (optional - defaults provided)
PONDER_RPC_URL_BASE_SEPOLIA_LOCAL_FORK=http://127.0.0.1:8545
PONDER_RPC_URL_BASE=https://base.llamarpc.com
PONDER_RPC_URL_BASE_SEPOLIA=https://sepolia.base.org

View file

@ -1,8 +1,8 @@
# Auto-generated by local_env.sh
PONDER_NETWORK=local
PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
KRAIKEN_ADDRESS=0x56186c1e64ca8043def78d06aff222212ea5df71
STAKE_ADDRESS=0x056e4a859558a3975761abd7385506bc4d8a8e60
START_BLOCK=31435585
START_BLOCK=31438430
# Use PostgreSQL connection
DATABASE_URL=postgresql://ponder:ponder_local@localhost/ponder_local
DATABASE_SCHEMA=ponder_local_31435585
DATABASE_SCHEMA=ponder_local_31438430

View file

@ -11,7 +11,7 @@ Perfect for testing with mainnet state without spending gas.
anvil --fork-url https://base.llamarpc.com
# Terminal 2: Start Ponder
export PONDER_NETWORK=local
export PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
npm run dev
```
@ -22,7 +22,7 @@ Access GraphQL at: http://localhost:42069/graphql
For integration testing with live testnet.
```bash
export PONDER_NETWORK=baseSepolia
export PONDER_NETWORK=BASE_SEPOLIA
export PONDER_RPC_URL_BASE_SEPOLIA=https://sepolia.base.org # or your RPC
npm run dev
```
@ -32,7 +32,7 @@ npm run dev
For production deployment.
```bash
export PONDER_NETWORK=base
export PONDER_NETWORK=BASE
export PONDER_RPC_URL_BASE=https://base.llamarpc.com # Use paid RPC for production
export DATABASE_URL=postgresql://user:pass@host:5432/kraiken_ponder
npm run start
@ -147,7 +147,7 @@ services:
- "42069:42069"
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/kraiken
- PONDER_NETWORK=base
- PONDER_NETWORK=BASE
db:
image: postgres:15

View file

@ -26,9 +26,9 @@ cp .env.example .env
```
Edit `.env` to select your network:
- `PONDER_NETWORK=local` - Local Anvil fork
- `PONDER_NETWORK=baseSepolia` - Base Sepolia testnet
- `PONDER_NETWORK=base` - Base mainnet
- `PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK` - Local Anvil fork managed by `scripts/local_env.sh`
- `PONDER_NETWORK=BASE_SEPOLIA` - Base Sepolia testnet
- `PONDER_NETWORK=BASE` - Base mainnet
### 3. Local Development (Anvil Fork)
@ -37,19 +37,19 @@ Edit `.env` to select your network:
anvil --fork-url https://base.llamarpc.com
# Terminal 2: Start Ponder indexer
PONDER_NETWORK=local npm run dev
PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK npm run dev
```
### 4. Testnet Deployment (Base Sepolia)
```bash
PONDER_NETWORK=baseSepolia npm run dev
PONDER_NETWORK=BASE_SEPOLIA npm run dev
```
### 5. Production Deployment (Base Mainnet)
```bash
PONDER_NETWORK=base npm run start
PONDER_NETWORK=BASE npm run start
```
## GraphQL Queries
@ -157,7 +157,7 @@ WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV PONDER_NETWORK=base
ENV PONDER_NETWORK=BASE
CMD ["npm", "run", "start"]
```

View file

@ -1905,23 +1905,6 @@
],
"license": "BSD-3-Clause"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
@ -2556,18 +2539,6 @@
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pino": {
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-8.21.0.tgz",
@ -3139,22 +3110,6 @@
"real-require": "^0.2.0"
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tsconfck": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz",
@ -3284,23 +3239,20 @@
}
},
"node_modules/vite": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz",
"integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==",
"version": "5.4.20",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
"integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.43.0",
"tinyglobby": "^0.2.15"
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
"rollup": "^4.20.0"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
@ -3309,25 +3261,19 @@
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"@types/node": "^18.0.0 || >=20.0.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
@ -3348,12 +3294,6 @@
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
},

View file

@ -21,7 +21,7 @@
},
"overrides": {
"esbuild": "^0.25.10",
"vite": "^7.1.7"
"vite": "^5.4.11"
},
"engines": {
"node": ">=18.0.0"

View file

@ -9,17 +9,6 @@ declare module "ponder:schema" {
export * from "./ponder.schema.ts";
}
declare module "ponder:registry" {
const ponder: any;
export { ponder };
}
declare module "ponder:api" {
export const db: any;
const api: any;
export default api;
}
// This file enables type checking and editor autocomplete for this Ponder project.
// After upgrading, you may find that changes have been made to this file.
// If this happens, please commit the changes. Do not manually edit this file.

View file

@ -3,20 +3,18 @@ import type { Abi } from "viem";
import KraikenAbi from "./abis/Kraiken.json";
import StakeAbi from "./abis/Stake.json";
// Network configurations
// Network configurations keyed by canonical environment name
const networks = {
// Local development (Anvil fork)
local: {
BASE_SEPOLIA_LOCAL_FORK: {
chainId: 31337,
rpc: "http://127.0.0.1:8545",
rpc: process.env.PONDER_RPC_URL_BASE_SEPOLIA_LOCAL_FORK || "http://127.0.0.1:8545",
contracts: {
kraiken: process.env.KRAIKEN_ADDRESS || "0x56186c1E64cA8043dEF78d06AfF222212eA5df71",
stake: process.env.STAKE_ADDRESS || "0x056E4a859558A3975761ABd7385506BC4D8A8E60",
startBlock: parseInt(process.env.START_BLOCK || "31425917"),
},
},
// Base Sepolia testnet
baseSepolia: {
BASE_SEPOLIA: {
chainId: 84532,
rpc: process.env.PONDER_RPC_URL_BASE_SEPOLIA || "https://sepolia.base.org",
contracts: {
@ -25,8 +23,7 @@ const networks = {
startBlock: 20940337,
},
},
// Base mainnet
base: {
BASE: {
chainId: 8453,
rpc: process.env.PONDER_RPC_URL_BASE || "https://base.llamarpc.com",
contracts: {
@ -35,16 +32,20 @@ const networks = {
startBlock: 26038614,
},
},
};
} as const;
// Select network based on environment variable
const NETWORK = process.env.PONDER_NETWORK || "local";
const NETWORK = (process.env.PONDER_NETWORK as keyof typeof networks) || "BASE_SEPOLIA_LOCAL_FORK";
const selectedNetwork = networks[NETWORK as keyof typeof networks];
if (!selectedNetwork) {
throw new Error(`Invalid network: ${NETWORK}. Valid options: ${Object.keys(networks).join(", ")}`);
}
console.log(
`[ponder.config] Network=${NETWORK}, chainId=${selectedNetwork.chainId}, startBlock=${selectedNetwork.contracts.startBlock}`,
);
export default createConfig({
// Use PostgreSQL if DATABASE_URL is set, otherwise use PGlite
database: process.env.DATABASE_URL ? {

View file

@ -13,7 +13,7 @@ ANVIL_PID=$!
sleep 5
echo "Starting Ponder indexer for local network..."
export PONDER_NETWORK=local
export PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
timeout 30 npm run dev
# Cleanup

View file

@ -9,15 +9,19 @@ LOG_DIR="$STATE_DIR/logs"
ANVIL_PID_FILE="$STATE_DIR/anvil.pid"
PONDER_PID_FILE="$STATE_DIR/ponder.pid"
WEBAPP_PID_FILE="$STATE_DIR/webapp.pid"
TXNBOT_PID_FILE="$STATE_DIR/txnBot.pid"
ANVIL_LOG="$LOG_DIR/anvil.log"
PONDER_LOG="$LOG_DIR/ponder.log"
WEBAPP_LOG="$LOG_DIR/webapp.log"
TXNBOT_LOG="$LOG_DIR/txnBot.log"
SETUP_LOG="$LOG_DIR/setup.log"
TXNBOT_ENV_FILE="$STATE_DIR/txnBot.env"
FORK_URL=${FORK_URL:-"https://sepolia.base.org"}
ANVIL_RPC="http://127.0.0.1:8545"
GRAPHQL_HEALTH="http://127.0.0.1:42069/health"
GRAPHQL_ENDPOINT="http://127.0.0.1:42069/graphql"
FRONTEND_URL="http://127.0.0.1:5173"
FOUNDRY_BIN=${FOUNDRY_BIN:-"$HOME/.foundry/bin"}
@ -31,6 +35,13 @@ FEE_DEST="0xf6a3eef9088A255c32b6aD2025f83E57291D9011"
WETH="0x4200000000000000000000000000000000000006"
SWAP_ROUTER="0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4"
DEFAULT_TXNBOT_PRIVATE_KEY="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
DEFAULT_TXNBOT_ADDRESS="0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
TXNBOT_PRIVATE_KEY=${TXNBOT_PRIVATE_KEY:-$DEFAULT_TXNBOT_PRIVATE_KEY}
TXNBOT_ADDRESS=${TXNBOT_ADDRESS:-$DEFAULT_TXNBOT_ADDRESS}
TXNBOT_FUND_VALUE=${TXNBOT_FUND_VALUE:-1ether}
MAX_UINT="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
SKIP_CLEANUP=false
@ -39,6 +50,7 @@ COMMAND="start"
cleanup() {
stop_process "Frontend" "$WEBAPP_PID_FILE"
stop_process "Ponder" "$PONDER_PID_FILE"
stop_process "TxnBot" "$TXNBOT_PID_FILE"
stop_process "Anvil" "$ANVIL_PID_FILE"
if [[ "$SKIP_CLEANUP" == true ]]; then
@ -123,6 +135,13 @@ ensure_dependencies() {
npm install >>"$SETUP_LOG" 2>&1
popd >/dev/null
fi
if [[ ! -d "$ROOT_DIR/services/txnBot/node_modules" ]]; then
log "Installing txnBot dependencies"
pushd "$ROOT_DIR/services/txnBot" >/dev/null
npm install >>"$SETUP_LOG" 2>&1
popd >/dev/null
fi
}
start_anvil() {
@ -177,7 +196,7 @@ extract_addresses() {
# Create .env.local for Ponder with deployed addresses
cat > "$ROOT_DIR/ponder/.env.local" <<EOF
# Auto-generated by local_env.sh
PONDER_NETWORK=local
PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
KRAIKEN_ADDRESS=$KRAIKEN
STAKE_ADDRESS=$STAKE
START_BLOCK=$deploy_block
@ -231,6 +250,59 @@ prepare_application_state() {
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,10000000000000000,0,0)" >>"$SETUP_LOG" 2>&1
}
prime_chain_for_indexing() {
log "Pre-mining blocks for indexer stability"
for _ in {1..1200}; do
"$CAST" rpc --rpc-url "$ANVIL_RPC" evm_mine > /dev/null 2>&1 || true
done
}
write_txnbot_env() {
source_addresses || { log "Contract addresses not found"; exit 1; }
cat > "$TXNBOT_ENV_FILE" <<EOF
ENVIRONMENT=BASE_SEPOLIA_LOCAL_FORK
PROVIDER_URL=$ANVIL_RPC
PRIVATE_KEY=$TXNBOT_PRIVATE_KEY
LM_CONTRACT_ADDRESS=$LIQUIDITY_MANAGER
STAKE_CONTRACT_ADDRESS=$STAKE
GRAPHQL_ENDPOINT=$GRAPHQL_ENDPOINT
WALLET_ADDRESS=$TXNBOT_ADDRESS
# Expose a non-conflicting status port for local dev
PORT=43069
EOF
}
fund_txnbot_wallet() {
source_addresses || { log "Contract addresses not found"; exit 1; }
if [[ -z "$TXNBOT_ADDRESS" ]]; then
log "TxnBot wallet address not provided; skipping funding"
return
fi
log "Funding txnBot wallet $TXNBOT_ADDRESS with $TXNBOT_FUND_VALUE"
"$CAST" send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
"$TXNBOT_ADDRESS" --value "$TXNBOT_FUND_VALUE" >>"$SETUP_LOG" 2>&1 || \
log "Funding txnBot wallet failed (see setup log)"
}
start_txnbot() {
if [[ -f "$TXNBOT_PID_FILE" ]]; then
log "txnBot already running (pid $(cat \"$TXNBOT_PID_FILE\"))"
return
fi
write_txnbot_env
fund_txnbot_wallet
log "Starting txnBot automation"
pushd "$ROOT_DIR/services/txnBot" >/dev/null
TXN_BOT_ENV_FILE="$TXNBOT_ENV_FILE" node service.js >"$TXNBOT_LOG" 2>&1 &
popd >/dev/null
echo $! >"$TXNBOT_PID_FILE"
}
start_ponder() {
if [[ -f "$PONDER_PID_FILE" ]]; then
log "Ponder already running (pid $(cat "$PONDER_PID_FILE"))"
@ -239,7 +311,7 @@ start_ponder() {
log "Starting Ponder indexer"
pushd "$ROOT_DIR/ponder" >/dev/null
PONDER_NETWORK=local npm run dev >"$PONDER_LOG" 2>&1 &
PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK npm run dev >"$PONDER_LOG" 2>&1 &
popd >/dev/null
echo $! >"$PONDER_PID_FILE"
@ -270,7 +342,9 @@ start_environment() {
extract_addresses
bootstrap_liquidity_manager
prepare_application_state
prime_chain_for_indexing
start_ponder # Re-enabled with PostgreSQL
start_txnbot
start_frontend
}
@ -286,7 +360,7 @@ start_and_wait() {
log " Frontend: $FRONTEND_URL"
log "Press Ctrl+C to shut everything down"
wait "$(cat "$ANVIL_PID_FILE")" "$(cat "$PONDER_PID_FILE")" "$(cat "$WEBAPP_PID_FILE")"
wait "$(cat "$ANVIL_PID_FILE")" "$(cat "$PONDER_PID_FILE")" "$(cat "$WEBAPP_PID_FILE")" "$(cat "$TXNBOT_PID_FILE")"
}
stop_environment() {
@ -343,6 +417,7 @@ status_environment() {
service_status "Anvil" "$ANVIL_PID_FILE" "$ANVIL_LOG"
service_status "Ponder" "$PONDER_PID_FILE" "$PONDER_LOG"
service_status "Frontend" "$WEBAPP_PID_FILE" "$WEBAPP_LOG"
service_status "TxnBot" "$TXNBOT_PID_FILE" "$TXNBOT_LOG"
if [[ -f "$STATE_DIR/contracts.env" ]]; then
printf '\nContracts:\n'

View file

@ -1,13 +0,0 @@
# .graphclientrc.yml
sources:
- name: harberg
handler:
graphql:
endpoint: https://api.studio.thegraph.com/query/47986/harberg-base-sepolia/version/latest
transforms:
- autoPagination:
# You might want to disable schema validation for faster startup
validateSchema: true
documents:
- ./.graphql

View file

@ -1,5 +1,38 @@
## RUN
yarn
yarn graphclient --fileType json build
node service.js
# txnBot
Automation worker that monitors staking positions and calls `recenter()` / `payTax()` when profitable.
## Environments
The bot supports three environments shared across the stack:
- `BASE_SEPOLIA_LOCAL_FORK` Anvil fork started by `scripts/local_env.sh`
- `BASE_SEPOLIA` Public Base Sepolia testnet
- `BASE` Base mainnet
Set `ENVIRONMENT` alongside the other variables listed below to target the desired chain.
## Configuration
Create an environment file (e.g. `.env`) with:
```
ENVIRONMENT=BASE_SEPOLIA
PROVIDER_URL=<rpc-url>
PRIVATE_KEY=<hex-private-key>
LM_CONTRACT_ADDRESS=<liquidity-manager-address>
STAKE_CONTRACT_ADDRESS=<stake-contract-address>
GRAPHQL_ENDPOINT=<ponder-graphql-url>
# Optional: PORT=43069
```
`scripts/local_env.sh start` generates these values automatically for the local fork, writes them to a temporary file, and keeps the process running in the background.
## Local Run
```
npm install
npm start
```
The service exposes a lightweight status endpoint at `GET /status` reporting uptime, balances, and the most recent automation activity.

View file

@ -3,13 +3,13 @@
"version": "0.0.1",
"main": "index.js",
"license": "GPL3",
"scripts": {
"start": "node service.js"
},
"dependencies": {
"dotenv": "^16.4.5",
"ethers": "^6.13.2",
"express": "^5.0.0",
"kraiken-lib": "file:../../kraiken-lib"
},
"devDependencies": {
"@graphprotocol/client-cli": "^3.0.7"
}
}

View file

@ -1,12 +1,17 @@
require('dotenv').config();
const path = require('path');
const dotenvPath = process.env.TXN_BOT_ENV_FILE
? path.resolve(process.env.TXN_BOT_ENV_FILE)
: path.resolve(__dirname, '.env');
require('dotenv').config({ path: dotenvPath });
const { ethers } = require('ethers');
const express = require('express');
const { execute } = require('./.graphclient');
const { decodePositionId, isPositionDelinquent } = require('kraiken-lib');
const myQuery = `
query GetPositions {
positions(first: 5000, where: {status: "Active"}) {
const ACTIVE_POSITIONS_QUERY = `
query ActivePositions {
positionss(where: { status: "Active" }, limit: 1000) {
items {
id
share
lastTaxTime
@ -14,13 +19,30 @@ const myQuery = `
status
}
}
`
}
`;
// Load environment variables
const PROVIDER_URL = process.env.PROVIDER_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const LM_CONTRACT_ADDRESS = process.env.LM_CONTRACT_ADDRESS;
const STAKE_CONTRACT_ADDRESS = process.env.STAKE_CONTRACT_ADDRESS;
const GRAPHQL_ENDPOINT = process.env.GRAPHQL_ENDPOINT;
const ENVIRONMENT = process.env.ENVIRONMENT || 'UNSPECIFIED';
const requiredEnv = {
PROVIDER_URL,
PRIVATE_KEY,
LM_CONTRACT_ADDRESS,
STAKE_CONTRACT_ADDRESS,
GRAPHQL_ENDPOINT,
};
for (const [key, value] of Object.entries(requiredEnv)) {
if (!value) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
const LM_ABI = [
{"type":"function","name":"recenter","inputs":[],"outputs":[],"stateMutability":"nonpayable"}
@ -40,6 +62,26 @@ let startTime = new Date();
let lastRecenterTime = null;
let lastLiquidationTime = null;
async function fetchActivePositions() {
const response = await fetch(GRAPHQL_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: ACTIVE_POSITIONS_QUERY }),
});
if (!response.ok) {
throw new Error(`GraphQL request failed with status ${response.status}`);
}
const payload = await response.json();
if (payload.errors && payload.errors.length > 0) {
const messages = payload.errors.map((error) => error.message).join('; ');
throw new Error(`GraphQL responded with errors: ${messages}`);
}
return payload.data?.positionss?.items ?? [];
}
async function checkFunds() {
const balance = await provider.getBalance(wallet.address);
return ethers.formatEther(balance);
@ -102,20 +144,20 @@ async function liquidityLoop() {
async function liquidationLoop() {
let counter = 0;
try {
const result = await execute(myQuery, {});
for (const position of result.data.positions) {
const positions = await fetchActivePositions();
for (const position of positions) {
counter = counter + await checkPosition(position);
}
if (counter == 0) {
console.log(`No tax can be claimed at the moment. - ${(new Date()).toISOString()}`);
}
} catch (error) {
console.error('Error in liquididation loop:', error);
console.error('Error in liquidation loop:', error);
}
}
async function main() {
console.log('Service started...');
console.log(`txnBot service started for environment ${ENVIRONMENT}`);
await liquidityLoop();
await liquidationLoop();

View file

@ -1,4 +0,0 @@
build/
node_modules/
generated/
yarn.lock

View file

@ -1,104 +0,0 @@
# Subgraph - CLAUDE.md
The Graph Protocol subgraph for indexing KRAIKEN protocol events and state.
## Overview
This subgraph indexes:
- Token transfers and supply changes
- Staking positions and Harberger tax events
- Liquidity manager recentering events
- User statistics and protocol metrics
## Schema Design
### Core Entities
**Stats** - Global protocol statistics
- Token supplies (KRAIKEN and STAKE)
- Minting/burning metrics with hourly projections
- Tax collection statistics
- Staking percentages and average rates
**Position** - Individual staking positions
- Holder address and staked amount
- Self-assessed tax rate
- Last update timestamp
**Snatch** - Harberger tax position takeover events
- Old and new holder details
- Tax rate changes
- Transaction metadata
**User** - Aggregated user statistics
- Total taxes paid/received
- Staking history
- Minting/burning activity
## Development Commands
```bash
# Install dependencies
npm install
# Generate AssemblyScript types from schema
npm run codegen
# Build the subgraph
npm run build
# Deploy to The Graph Studio
npm run deploy
# Local development (requires graph-node)
npm run create-local
npm run deploy-local
```
## Event Handlers
### Kraiken Contract Events
- `Transfer` - Track token movements and supply changes
- `TaxPaid` - Record tax collection events
- `Mint/Burn` - Update supply statistics
### Stake Contract Events
- `Staked` - Create/update position entities
- `Snatched` - Record position takeovers
- `TaxRateChanged` - Update position tax rates
## Query Examples
```graphql
# Get current protocol statistics
query GetStats {
stats(id: "0x00") {
kraikenTotalSupply
percentageStaked
averageTaxRate
mintedLastDay
}
}
# Get top staking positions
query TopStakers {
positions(first: 10, orderBy: amount, orderDirection: desc) {
holder
amount
rate
}
}
```
## Deployment Notes
- **Network**: Base Sepolia (testnet)
- **Start Block**: Configured in subgraph.yaml
- **API Endpoint**: Available after deployment to Studio
## Code Quality
- All handlers must be idempotent
- Use BigInt for all numeric values
- Handle null cases explicitly
- Keep entity updates minimal for performance

View file

@ -1,39 +0,0 @@
## deployment
```
yarn codegen
yarn build
yarn deploy
```
## deployment-script
networks are defined in deploy.js
This solution overwrites the subgraph.yaml with the template and enables the possible to upgrade the same contract on different chains
```
node deploy.js base
```
## queries
for stats
```
{
stats(id:"0x01") {
harbTotalSupply
stakeTotalSupply
outstandingStake
totalMinted
mintedLastWeek
mintedLastDay
mintNextHourProjected
totalBurned
burnedLastWeek
burnedLastDay
burnNextHourProjected
}
}
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,102 +0,0 @@
const fs = require('fs');
const { exec } = require("child_process");
const readline = require("readline");
// Netzwerkspezifische Informationen
const networks = {
'base-sepolia': {
NETWORK: 'base-sepolia',
CONTRACT_ADDRESS_HARB: '0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8',
CONTRACT_ADDRESS_STAKE: '0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2',
START_BLOCK: 20940337,
SUBGRAPH_NAME: "harb-base-sepolia"
},
'sepolia': {
NETWORK: 'sepolia',
CONTRACT_ADDRESS_HARB: '0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9',
CONTRACT_ADDRESS_STAKE: '0xCd21a41a137BCAf8743E47D048F57D92398f7Da9',
START_BLOCK: 6746241,
SUBGRAPH_NAME: "harb"
},
'base': {
NETWORK: 'base',
CONTRACT_ADDRESS_HARB: '0x45caa5929f6ee038039984205bdecf968b954820',
CONTRACT_ADDRESS_STAKE: '0xed70707fab05d973ad41eae8d17e2bcd36192cfc',
START_BLOCK: 26038614,
// START_BLOCK: 25895309,
SUBGRAPH_NAME: "kraiken-base"
},
'base2': {
NETWORK: 'base',
CONTRACT_ADDRESS_HARB: '0x45caa5929f6ee038039984205bdecf968b954820',
CONTRACT_ADDRESS_STAKE: '0xed70707fab05d973ad41eae8d17e2bcd36192cfc',
START_BLOCK: 26038614,
// START_BLOCK: 25895309,
SUBGRAPH_NAME: "kraiken-base-test"
},
};
// Netzwerk über Kommandozeilenargument auswählen
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Kein Netzwerk angegeben, z. B.: node deploy.js sepolia');
process.exit(1);
}
const selectedNetwork = networks[args[0]];
if (!selectedNetwork) {
console.error(`Kein gülitges Netzwerk angegeben: ${args[0]}. Verfügbare Netzwerke: ${Object.keys(networks).join(', ')}`);
process.exit(1);
}
// Template-Datei laden
const subgraphTemplate = fs.readFileSync('subgraph_template.yaml', 'utf-8');
// Platzhalter ersetzen
let subgraphConfig = subgraphTemplate.replace(/{{NETWORK}}/g, selectedNetwork.NETWORK);
subgraphConfig = subgraphConfig.replace(/{{CONTRACT_ADDRESS_STAKE}}/g, selectedNetwork.CONTRACT_ADDRESS_STAKE);
subgraphConfig = subgraphConfig.replace(/{{CONTRACT_ADDRESS_HARB}}/g, selectedNetwork.CONTRACT_ADDRESS_HARB);
subgraphConfig = subgraphConfig.replace(/{{START_BLOCK}}/g, selectedNetwork.START_BLOCK);
// Neue Datei generieren
fs.writeFileSync('subgraph.yaml', subgraphConfig);
console.log(`subgraph.yaml für das Netzwerk ${selectedNetwork.NETWORK} wurde erfolgreich generiert.`);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Which version label to use1? (e.g. "v0.0.1"): ', (versionLabel) => {
if (!versionLabel) {
console.error("Kein Versionslabel angegeben. Abbruch.");
rl.close();
process.exit(1);
}
const serverAdress = "https://api.studio.thegraph.com/deploy/";
console.log(`Starte Deployment mit Versionslabel "${versionLabel}"...`);
// Spawn-Prozess starten
// const deployProcess = spawn("graph", ["deploy", "--node", serverAdress, selectedNetwork.SUBGRAPH_NAME]);
const command = `graph deploy --node ${serverAdress} ${selectedNetwork.SUBGRAPH_NAME} --version-label ${versionLabel}`;
console.log(`Auszuführender Befehl: ${command}`);
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Fehler: ${error.message}`);
return;
}
if (stderr) {
console.error(`stderr: ${stderr}`);
}
console.log(`stdout: ${stdout}`);
});
});

File diff suppressed because it is too large Load diff

View file

@ -1,24 +0,0 @@
{
"name": "kraiken",
"license": "UNLICENSED",
"scripts": {
"codegen": "graph codegen",
"build": "graph build",
"deploy": "graph deploy --node https://api.studio.thegraph.com/deploy/ kraiken-base-sepolia",
"create-local": "graph create --node http://localhost:8020/ kraiken",
"remove-local": "graph remove --node http://localhost:8020/ kraiken",
"deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 kraiken",
"test": "graph test"
},
"dependencies": {
"@graphprotocol/graph-cli": "0.69.0",
"@graphprotocol/graph-ts": "0.34.0",
"assemblyscript": "0.27.25"
},
"devDependencies": {
"@types/node": "^20.11.30",
"matchstick-as": "0.5.0",
"typescript": "^5.4.3"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View file

@ -1,51 +0,0 @@
# scalar Bytes
# scalar BigInt
# scalar BigDecimal
type Stats @entity {
id: Bytes!
kraikenTotalSupply: BigInt! # uint256
stakeTotalSupply: BigInt! # uint256
outstandingStake: BigInt! # uint256
ringBuffer: [BigInt!]! # Ring buffer to store daily totals
ringBufferPointer: Int! # Pointer to the current day in the ring buffer
lastUpdatedHour: Int! # The last updated day boundary (timestamp in days)
totalMinted: BigInt!
mintedLastWeek: BigInt!
mintedLastDay: BigInt!
mintNextHourProjected: BigInt!
totalBurned: BigInt!
burnedLastWeek: BigInt!
burnedLastDay: BigInt!
burnNextHourProjected: BigInt!
}
enum PositionStatus {
Active
Closed
}
type Position @entity {
id: Bytes!
owner: Bytes! # address
share: BigDecimal!
# harb at start
# current harb value
creationTime: Int!
lastTaxTime: Int
taxRate: BigDecimal!
taxPaid: BigInt!
kraikenDeposit: BigInt!
snatched: Int!
totalSupplyInit: BigInt!
totalSupplyEnd: BigInt
status: PositionStatus!
payout: BigInt!
}
# type Query {
# stats: [Stats!]
# positions: [Position!]
#}

View file

@ -1,38 +0,0 @@
import { Address, dataSource } from '@graphprotocol/graph-ts'
export enum ChainId {
BASE = 8453,
BASE_SEPOLIA = 84532,
}
const BASE_NETWORK_NAME = 'base'
const BASE_SEPOLIA_NETWORK_NAME = 'base-sepolia'
export class SubgraphConfig {
kraikenAddress: Address
stakeAddress: Address
}
export function getSubgraphConfig(): SubgraphConfig {
// Update this value to the corresponding chain you want to deploy
const selectedNetwork = dataSource.network()
// subgraph does not support case switch with strings, hence this if else block
if (selectedNetwork == BASE_NETWORK_NAME) {
return {
kraikenAddress: Address.fromString('0x45caa5929f6ee038039984205bdecf968b954820'),
stakeAddress: Address.fromString('0xed70707fab05d973ad41eae8d17e2bcd36192cfc'),
}
} else if (selectedNetwork == BASE_SEPOLIA_NETWORK_NAME) {
return {
kraikenAddress: Address.fromString('0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8'),
stakeAddress: Address.fromString('0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2'),
}
} else {
throw new Error('Unsupported Network')
}
}

View file

@ -1,189 +0,0 @@
import {
Approval as ApprovalEvent,
EIP712DomainChanged as EIP712DomainChangedEvent,
Transfer as TransferEvent,
Kraiken
} from "../generated/Kraiken/Kraiken";
import { BigInt, Bytes, ethereum, Address, log } from "@graphprotocol/graph-ts";
import { Stats } from "../generated/schema";
import { getSubgraphConfig } from './chains';
let stakeTotalSupply: BigInt = BigInt.fromString("10000000000000000000000000");
// Helper function to get or create Stats entity
function getOrCreateStats(): Stats {
let stats = Stats.load(Bytes.fromHexString("0x01") as Bytes);
if (stats == null) {
log.info("Stats not found, creating a new one", []);
stats = new Stats(Bytes.fromHexString("0x01") as Bytes);
let kraiken = Kraiken.bind(getSubgraphConfig().kraikenAddress);
stats.kraikenTotalSupply = kraiken.totalSupply();
stats.stakeTotalSupply = stakeTotalSupply;
stats.outstandingStake = BigInt.zero();
// Minted
stats.totalMinted = BigInt.zero();
stats.mintedLastWeek = BigInt.zero();
stats.mintedLastDay = BigInt.zero();
stats.mintNextHourProjected = BigInt.zero();
// Burned
stats.totalBurned = BigInt.zero();
stats.burnedLastWeek = BigInt.zero();
stats.burnedLastDay = BigInt.zero();
stats.burnNextHourProjected = BigInt.zero();
stats.ringBuffer = new Array<BigInt>(168 * 4).fill(BigInt.zero());
stats.ringBufferPointer = 0;
stats.lastUpdatedHour = 0;
}
return stats;
}
export function handleTransfer(event: TransferEvent): void {
let ZERO_ADDRESS = Address.fromString("0x0000000000000000000000000000000000000000");
let TAX_POOL_ADDR = Address.fromString("0x0000000000000000000000000000000000000002");
let stats = getOrCreateStats();
// Get a copy of the ring buffer
let ringBuffer = stats.ringBuffer;
if (event.params.from == TAX_POOL_ADDR) {
// Add the UBI amount to the current hour's total in the ring buffer
let ubiBufferIndex = (stats.ringBufferPointer * 4) + 0; // UBI is at index 0
ringBuffer[ubiBufferIndex] = ringBuffer[ubiBufferIndex].plus(event.params.value);
} else if (event.params.from == ZERO_ADDRESS) {
// Mint event
stats.totalMinted = stats.totalMinted.plus(event.params.value);
// Add the minted amount to the current hour's total in the ring buffer
let mintBufferIndex = (stats.ringBufferPointer * 4) + 1; // Minted tokens are at index 1
ringBuffer[mintBufferIndex] = ringBuffer[mintBufferIndex].plus(event.params.value);
stats.kraikenTotalSupply = stats.kraikenTotalSupply.plus(event.params.value);
} else if (event.params.to == ZERO_ADDRESS) {
// Burn event
stats.totalBurned = stats.totalBurned.plus(event.params.value);
// Add the burned amount to the current hour's total in the ring buffer
let burnBufferIndex = (stats.ringBufferPointer * 4) + 2; // Burned tokens are at index 2
ringBuffer[burnBufferIndex] = ringBuffer[burnBufferIndex].plus(event.params.value);
stats.kraikenTotalSupply = stats.kraikenTotalSupply.minus(event.params.value);
} else if (event.params.to == TAX_POOL_ADDR) {
// Add the burned amount to the current hour's total in the ring buffer
let taxBufferIndex = (stats.ringBufferPointer * 4) + 3; // tax paid is at index 3
ringBuffer[taxBufferIndex] = ringBuffer[taxBufferIndex].plus(event.params.value);
}
// Update the ring buffer in the stats entity
stats.ringBuffer = ringBuffer;
// Save the updated Stats entity
stats.save();
}
// Block handler to aggregate stats
export function handleBlock(block: ethereum.Block): void {
// Update Stats entity
let stats = getOrCreateStats();
// Get a copy of the ring buffer
let ringBuffer = stats.ringBuffer;
// Calculate the current hour
let currentHour = block.timestamp.toI32() / 3600;
// Check if we've moved to a new hour
if (currentHour > stats.lastUpdatedHour) {
// Move the ring buffer pointer forward
stats.ringBufferPointer = (stats.ringBufferPointer + 1) % 168;
// Reset the new current hour in the ring buffer
let baseIndex = stats.ringBufferPointer * 4;
ringBuffer[baseIndex] = BigInt.zero(); // UBI claimed
ringBuffer[baseIndex + 1] = BigInt.zero(); // Minted tokens
ringBuffer[baseIndex + 2] = BigInt.zero(); // Burned tokens
// Update the last updated hour
stats.lastUpdatedHour = currentHour;
// Recalculate the sum of the last 7 days and 24 hours
let mintedLastWeek = BigInt.zero();
let burnedLastWeek = BigInt.zero();
let mintedLastDay = BigInt.zero();
let burnedLastDay = BigInt.zero();
for (let i = 0; i < 168; i++) {
let index = ((stats.ringBufferPointer - i + 168) % 168) * 4;
mintedLastWeek = mintedLastWeek.plus(ringBuffer[index + 1]);
burnedLastWeek = burnedLastWeek.plus(ringBuffer[index + 2]);
if (i < 24) {
mintedLastDay = mintedLastDay.plus(ringBuffer[index + 1]);
burnedLastDay = burnedLastDay.plus(ringBuffer[index + 2]);
}
}
stats.mintedLastWeek = mintedLastWeek;
stats.burnedLastWeek = burnedLastWeek;
stats.mintedLastDay = mintedLastDay;
stats.burnedLastDay = burnedLastDay;
// Update the ring buffer in the stats entity
stats.ringBuffer = ringBuffer;
} else {
// update projected stats with every block
// Calculate the elapsed time in the current hour
let currentTimestamp = block.timestamp.toI32();
let startOfHour = (currentTimestamp / 3600) * 3600;
let elapsedSeconds = currentTimestamp - startOfHour;
for (var i = 0; i <= 3; i++) {
let bufferIndex = (stats.ringBufferPointer * 4) + i;
// Project the current hour's total based on the average rate of burned tokens in the current hour
let projectedTotal = ringBuffer[bufferIndex].times(BigInt.fromI32(3600)).div(BigInt.fromI32(elapsedSeconds));
// Calculate the medium between the previous hour and the projection
let previousHourTotal = ringBuffer[((stats.ringBufferPointer - 1 + 168) % 168) * 4 + i];
log.info("projecting stats : {} projected total: {}. previous hour Total: {}. medium: {}", [
i.toString(),
projectedTotal.toString(),
previousHourTotal.toString(),
previousHourTotal.plus(projectedTotal).div(BigInt.fromI32(2)).toString(),
]);
let medium = previousHourTotal.plus(projectedTotal).div(BigInt.fromI32(2));
if (i == 1) {
stats.mintNextHourProjected = (medium > BigInt.zero()) ? medium : stats.mintedLastWeek.div(BigInt.fromI32(7));
} else if (i == 2) {
stats.burnNextHourProjected = (medium > BigInt.zero()) ? medium : stats.burnedLastWeek.div(BigInt.fromI32(7));
}
}
}
// Save the updated Stats entity
stats.save();
}

View file

@ -1,11 +0,0 @@
query GetPositions {
positions {
id
owner
share
creationTime
lastTaxTime
taxRate
status
}
}

View file

@ -1,86 +0,0 @@
import {
PositionCreated as PositionCreatedEvent,
PositionRemoved as PositionRemovedEvent,
PositionTaxPaid as PositionTaxPaidEvent,
PositionShrunk as PositionShrunkEvent,
PositionRateHiked as PositionRateHikedEvent,
} from "../generated/Stake/Stake";
import { Kraiken } from "../generated/Kraiken/Kraiken";
import { Stake } from "../generated/Stake/Stake";
import { getSubgraphConfig, SubgraphConfig } from './chains';
import { BigDecimal, BigInt, Bytes } from "@graphprotocol/graph-ts";
import { Position, Stats } from "../generated/schema";
let decimals: BigDecimal = BigDecimal.fromString("1000000000000000000");
let totalSupply: BigDecimal = BigDecimal.fromString("10000000000000000000000000");
let taxRates: Array<string> = ["0.01", "0.03", "0.05", "0.08", "0.12", "0.18", "0.24", "0.30", "0.40", "0.50", "0.60", "0.80", "1", "1.3", "1.8", "2.5", "3.2", "4.2", "5.4", "7", "9.2", "12", "16", "20", "26", "34", "44", "57", "75", "97"];
export function handlePositionCreated(event: PositionCreatedEvent): void {
let position = new Position(Bytes.fromI32(event.params.positionId.toI32()));
position.owner = event.params.owner;
position.share = event.params.share.toBigDecimal().div(totalSupply);
position.creationTime = event.block.timestamp.toI32();
position.lastTaxTime = event.block.timestamp.toI32();
position.taxRate = BigDecimal.fromString(taxRates[event.params.taxRate.toI32()]);
position.kraikenDeposit = event.params.kraikenDeposit;
position.status = "Active";
position.snatched = 0;
position.payout = BigInt.fromString("0");
position.taxPaid = BigInt.fromString("0");
let kraiken = Kraiken.bind(getSubgraphConfig().kraikenAddress);
position.totalSupplyInit = kraiken.totalSupply();
position.save();
let stake = Stake.bind(event.address);
let stats = Stats.load(Bytes.fromHexString("0x01") as Bytes);
if (stats != null) {
stats.outstandingStake = stake.outstandingStake();
stats.save();
}
}
export function handlePositionRemoved(event: PositionRemovedEvent): void {
let position = Position.load(Bytes.fromI32(event.params.positionId.toI32()));
if (position != null) {
position.status = "Closed";
let kraiken = Kraiken.bind(getSubgraphConfig().kraikenAddress);
position.totalSupplyEnd = kraiken.totalSupply();
// position.payout = position.payout.plus(event.params.kraikenPayout);
position.save();
}
let stake = Stake.bind(event.address);
let stats = Stats.load(Bytes.fromHexString("0x01") as Bytes);
if (stats != null) {
stats.outstandingStake = stake.outstandingStake();
stats.save();
}
}
export function handlePositionShrunk(event: PositionShrunkEvent): void {
let position = Position.load(Bytes.fromI32(event.params.positionId.toI32()));
if (position != null) {
position.share = event.params.newShares.toBigDecimal().div(totalSupply);
position.kraikenDeposit = position.kraikenDeposit.minus(event.params.kraikenPayout);
position.snatched = position.snatched++;
// position.payout = position.payout.plus(event.params.kraikenPayout);
position.save();
}
}
export function handleTaxPaid(event: PositionTaxPaidEvent): void {
let position = Position.load(Bytes.fromI32(event.params.positionId.toI32()));
if (position != null) {
position.taxPaid = position.taxPaid.plus(event.params.taxPaid);
position.taxRate = BigDecimal.fromString(taxRates[event.params.taxRate.toI32()]);
position.lastTaxTime = event.block.timestamp.toI32();
position.save();
}
}
export function handlePositionRateHiked(event: PositionRateHikedEvent): void {
let position = Position.load(Bytes.fromI32(event.params.positionId.toI32()));
if (position != null) {
position.taxRate = BigDecimal.fromString(taxRates[event.params.newTaxRate.toI32()]);
position.save();
}
}

View file

@ -1,59 +0,0 @@
specVersion: 0.0.4
repository: http://gitea.loseyourip.com:4000/dark-meme-society/harb.git
description: Kraiken Token
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: Kraiken
network: base
source:
address: "0x45caa5929f6ee038039984205bdecf968b954820"
abi: Kraiken
startBlock: 26038614
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Stats
- UbiClaim
abis:
- name: Kraiken
file: ./abis/Kraiken.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
blockHandlers:
- handler: handleBlock
file: ./src/kraiken.ts
- kind: ethereum
name: Stake
network: base
source:
address: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc"
abi: Stake
startBlock: 26038614
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Position
abis:
- name: Stake
file: ./abis/Stake.json
- name: Kraiken
file: ./abis/Kraiken.json
eventHandlers:
- event: PositionCreated(indexed uint256,indexed address,uint256,uint256,uint32)
handler: handlePositionCreated
- event: PositionRemoved(indexed uint256,indexed address,uint256)
handler: handlePositionRemoved
- event: PositionShrunk(indexed uint256,indexed address,uint256,uint256)
handler: handlePositionShrunk
- event: PositionTaxPaid(indexed uint256,indexed address,uint256,uint256,uint256)
handler: handleTaxPaid
- event: PositionRateHiked(indexed uint256,indexed address,uint256)
handler: handlePositionRateHiked
file: ./src/stake.ts

View file

@ -1,59 +0,0 @@
specVersion: 0.0.4
repository: http://gitea.loseyourip.com:4000/dark-meme-society/harb.git
description: Harberger Tax Token
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: Harb
network: {{NETWORK}}
source:
address: "{{CONTRACT_ADDRESS_HARB}}"
abi: Harb
startBlock: {{START_BLOCK}}
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Stats
- UbiClaim
abis:
- name: Harb
file: ./abis/Harb.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
blockHandlers:
- handler: handleBlock
file: ./src/harb.ts
- kind: ethereum
name: Stake
network: {{NETWORK}}
source:
address: "{{CONTRACT_ADDRESS_STAKE}}"
abi: Stake
startBlock: {{START_BLOCK}}
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Position
abis:
- name: Stake
file: ./abis/Stake.json
- name: Harb
file: ./abis/Harb.json
eventHandlers:
- event: PositionCreated(indexed uint256,indexed address,uint256,uint256,uint32)
handler: handlePositionCreated
- event: PositionRemoved(indexed uint256,indexed address,uint256)
handler: handlePositionRemoved
- event: PositionShrunk(indexed uint256,indexed address,uint256,uint256)
handler: handlePositionShrunk
- event: PositionTaxPaid(indexed uint256,indexed address,uint256,uint256,uint256)
handler: handleTaxPaid
- event: PositionRateHiked(indexed uint256,indexed address,uint256)
handler: handlePositionRateHiked
file: ./src/stake.ts

View file

@ -1,59 +0,0 @@
specVersion: 0.0.4
repository: http://gitea.loseyourip.com:4000/dark-meme-society/harb.git
description: Harberger Tax Token
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: Harb
network: base-sepolia
source:
address: "0x54838DC097E7fC4736B801bF1c1FCf1597348265"
abi: Harb
startBlock: 15399960
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Stats
- UbiClaim
abis:
- name: Harb
file: ./abis/Harb.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
blockHandlers:
- handler: handleBlock
file: ./src/harb.ts
- kind: ethereum
name: Stake
network: base-sepolia
source:
address: "0xd7728173F73C748944d29EA77b56f09b8FEc8F33"
abi: Stake
startBlock: 15399960
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Position
abis:
- name: Stake
file: ./abis/Stake.json
- name: Harb
file: ./abis/Harb.json
eventHandlers:
- event: PositionCreated(indexed uint256,indexed address,uint256,uint256,uint32)
handler: handlePositionCreated
- event: PositionRemoved(indexed uint256,indexed address,uint256)
handler: handlePositionRemoved
- event: PositionShrunk(indexed uint256,indexed address,uint256,uint256)
handler: handlePositionShrunk
- event: PositionTaxPaid(indexed uint256,indexed address,uint256,uint256,uint256)
handler: handleTaxPaid
- event: PositionRateHiked(indexed uint256,indexed address,uint256)
handler: handlePositionRateHiked
file: ./src/stake.ts

View file

@ -1,55 +0,0 @@
import { newMockEvent } from "matchstick-as"
import { ethereum, Address, BigInt } from "@graphprotocol/graph-ts"
import { Approval, EIP712DomainChanged, Transfer } from "../generated/Harb/Harb"
export function createApprovalEvent(
owner: Address,
spender: Address,
value: BigInt
): Approval {
let approvalEvent = changetype<Approval>(newMockEvent())
approvalEvent.parameters = new Array()
approvalEvent.parameters.push(
new ethereum.EventParam("owner", ethereum.Value.fromAddress(owner))
)
approvalEvent.parameters.push(
new ethereum.EventParam("spender", ethereum.Value.fromAddress(spender))
)
approvalEvent.parameters.push(
new ethereum.EventParam("value", ethereum.Value.fromUnsignedBigInt(value))
)
return approvalEvent
}
export function createEIP712DomainChangedEvent(): EIP712DomainChanged {
let eip712DomainChangedEvent = changetype<EIP712DomainChanged>(newMockEvent())
eip712DomainChangedEvent.parameters = new Array()
return eip712DomainChangedEvent
}
export function createTransferEvent(
from: Address,
to: Address,
value: BigInt
): Transfer {
let transferEvent = changetype<Transfer>(newMockEvent())
transferEvent.parameters = new Array()
transferEvent.parameters.push(
new ethereum.EventParam("from", ethereum.Value.fromAddress(from))
)
transferEvent.parameters.push(
new ethereum.EventParam("to", ethereum.Value.fromAddress(to))
)
transferEvent.parameters.push(
new ethereum.EventParam("value", ethereum.Value.fromUnsignedBigInt(value))
)
return transferEvent
}

View file

@ -1,62 +0,0 @@
import {
assert,
describe,
test,
clearStore,
beforeAll,
afterAll
} from "matchstick-as/assembly/index"
import { Address, BigInt } from "@graphprotocol/graph-ts"
import { Approval } from "../generated/schema"
import { Approval as ApprovalEvent } from "../generated/Harb/Harb"
import { handleApproval } from "../src/harb"
import { createApprovalEvent } from "./harb-utils"
// Tests structure (matchstick-as >=0.5.0)
// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0
describe("Describe entity assertions", () => {
beforeAll(() => {
let owner = Address.fromString("0x0000000000000000000000000000000000000001")
let spender = Address.fromString(
"0x0000000000000000000000000000000000000001"
)
let value = BigInt.fromI32(234)
let newApprovalEvent = createApprovalEvent(owner, spender, value)
handleApproval(newApprovalEvent)
})
afterAll(() => {
clearStore()
})
// For more test scenarios, see:
// https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test
test("Approval created and stored", () => {
assert.entityCount("Approval", 1)
// 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function
assert.fieldEquals(
"Approval",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"owner",
"0x0000000000000000000000000000000000000001"
)
assert.fieldEquals(
"Approval",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"spender",
"0x0000000000000000000000000000000000000001"
)
assert.fieldEquals(
"Approval",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"value",
"234"
)
// More assert options:
// https://thegraph.com/docs/en/developer/matchstick/#asserts
})
})

View file

@ -1,70 +0,0 @@
import { newMockEvent } from "matchstick-as"
import { ethereum, BigInt, Address } from "@graphprotocol/graph-ts"
import { PositionCreated, PositionRemoved } from "../generated/Stake/Stake"
export function createPositionCreatedEvent(
positionId: BigInt,
owner: Address,
share: BigInt,
creationTime: BigInt,
taxRate: BigInt
): PositionCreated {
let positionCreatedEvent = changetype<PositionCreated>(newMockEvent())
positionCreatedEvent.parameters = new Array()
positionCreatedEvent.parameters.push(
new ethereum.EventParam(
"positionId",
ethereum.Value.fromUnsignedBigInt(positionId)
)
)
positionCreatedEvent.parameters.push(
new ethereum.EventParam("owner", ethereum.Value.fromAddress(owner))
)
positionCreatedEvent.parameters.push(
new ethereum.EventParam("share", ethereum.Value.fromUnsignedBigInt(share))
)
positionCreatedEvent.parameters.push(
new ethereum.EventParam(
"creationTime",
ethereum.Value.fromUnsignedBigInt(creationTime)
)
)
positionCreatedEvent.parameters.push(
new ethereum.EventParam(
"taxRate",
ethereum.Value.fromUnsignedBigInt(taxRate)
)
)
return positionCreatedEvent
}
export function createPositionRemovedEvent(
positionId: BigInt,
share: BigInt,
lastTaxTime: BigInt
): PositionRemoved {
let positionRemovedEvent = changetype<PositionRemoved>(newMockEvent())
positionRemovedEvent.parameters = new Array()
positionRemovedEvent.parameters.push(
new ethereum.EventParam(
"positionId",
ethereum.Value.fromUnsignedBigInt(positionId)
)
)
positionRemovedEvent.parameters.push(
new ethereum.EventParam("share", ethereum.Value.fromUnsignedBigInt(share))
)
positionRemovedEvent.parameters.push(
new ethereum.EventParam(
"lastTaxTime",
ethereum.Value.fromUnsignedBigInt(lastTaxTime)
)
)
return positionRemovedEvent
}

View file

@ -1,80 +0,0 @@
import {
assert,
describe,
test,
clearStore,
beforeAll,
afterAll
} from "matchstick-as/assembly/index"
import { BigInt, Address } from "@graphprotocol/graph-ts"
import { PositionCreated } from "../generated/schema"
import { PositionCreated as PositionCreatedEvent } from "../generated/Stake/Stake"
import { handlePositionCreated } from "../src/stake"
import { createPositionCreatedEvent } from "./stake-utils"
// Tests structure (matchstick-as >=0.5.0)
// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0
describe("Describe entity assertions", () => {
beforeAll(() => {
let positionId = BigInt.fromI32(234)
let owner = Address.fromString("0x0000000000000000000000000000000000000001")
let share = BigInt.fromI32(234)
let creationTime = BigInt.fromI32(234)
let taxRate = BigInt.fromI32(234)
let newPositionCreatedEvent = createPositionCreatedEvent(
positionId,
owner,
share,
creationTime,
taxRate
)
handlePositionCreated(newPositionCreatedEvent)
})
afterAll(() => {
clearStore()
})
// For more test scenarios, see:
// https://thegraph.com/docs/en/developer/matchstick/#write-a-unit-test
test("PositionCreated created and stored", () => {
assert.entityCount("PositionCreated", 1)
// 0xa16081f360e3847006db660bae1c6d1b2e17ec2a is the default address used in newMockEvent() function
assert.fieldEquals(
"PositionCreated",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"positionId",
"234"
)
assert.fieldEquals(
"PositionCreated",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"owner",
"0x0000000000000000000000000000000000000001"
)
assert.fieldEquals(
"PositionCreated",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"share",
"234"
)
assert.fieldEquals(
"PositionCreated",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"creationTime",
"234"
)
assert.fieldEquals(
"PositionCreated",
"0xa16081f360e3847006db660bae1c6d1b2e17ec2a-1",
"taxRate",
"234"
)
// More assert options:
// https://thegraph.com/docs/en/developer/matchstick/#asserts
})
})

View file

@ -1,4 +0,0 @@
{
"extends": "@graphprotocol/graph-ts/types/tsconfig.base.json",
"include": ["src", "tests"]
}

View file

@ -211,16 +211,17 @@ watchEffect(() => {
stakeSlots.value = (supplyFreeze.value * 1000)?.toFixed(2);
});
// const stakeAbleHarbAmount = computed(() => statCollection.harbTotalSupply / 5n);
// const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
//war das mal so, wurde das geändert --> funktioniert nicht mehr
// const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
const tokenIssuance = computed(() => {
if (statCollection.harbTotalSupply === 0n) {
if (statCollection.kraikenTotalSupply === 0n) {
return 0n;
}
return (statCollection.nettoToken7d / statCollection.harbTotalSupply) * 100n;
return (statCollection.nettoToken7d / statCollection.kraikenTotalSupply) * 100n;
});
function getMinFloorTax() {
@ -280,7 +281,7 @@ onMounted(async () => {
}
});
// async function getGraphData(): Promise<Array<any>> {
// let res = await axios.post("https://api.studio.thegraph.com/query/47986/harb/version/latest", {
// let res = await axios.post("http://127.0.0.1:42069/graphql", {
// query: "query MyQuery {\n positions {\n id\n lastTaxTime\n owner\n share\n status\n taxRate\n creationTime\n }\n}",
// });
// return res.data.data.positions;

View file

@ -27,7 +27,7 @@ console.log("minStake", minStake.value);
return formatBigIntDivision(minStake.value, 10n ** 18n);
});
const stakeAbleHarbAmount = computed(() => statCollection.harbTotalSupply / 5n);
const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
@ -36,7 +36,7 @@ const statCollection = useStatCollection();
const snatchPositions = computed(() => {
if (
bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <=
bigInt2Number(statCollection.harbTotalSupply, 18) * 0.2
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2
) {
return [];
}
@ -44,7 +44,7 @@ if (
const difference =
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.harbTotalSupply, 18) * 0.2;
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten

View file

@ -162,14 +162,14 @@ async function loadActivePositionData() {
const multiplier =
Number(formatUnits(props.position.totalSupplyInit, 18)) /
Number(formatUnits(statCollection.harbTotalSupply, 18));
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
console.log("props.position.totalSupplyInit", props.position.totalSupplyInit);
console.log("multiplier", multiplier);
profit.value =
Number(formatUnits(statCollection.harbTotalSupply, 18)) * multiplier -
Number(formatUnits(statCollection.harbTotalSupply, 18));
Number(formatUnits(statCollection.kraikenTotalSupply, 18)) * multiplier -
Number(formatUnits(statCollection.kraikenTotalSupply, 18));
}
onMounted(() => {

View file

@ -108,20 +108,21 @@ const tresholdValue = computed(() => {
export async function loadActivePositions() {
logger.info(`loadActivePositions for chain: ${chainData.value?.path}`);
if (!chainData.value?.thegraph) {
if (!chainData.value?.graphql) {
return [];
}
console.log("chainData.value?.thegraph", chainData.value?.thegraph);
console.log("chainData.value?.graphql", chainData.value?.graphql);
const res = await axios.post(chainData.value?.thegraph, {
query: `query MyQuery {
positions(where: {status: Active}, orderBy: taxRate, orderDirection: asc) {
const res = await axios.post(chainData.value?.graphql, {
query: `query ActivePositions {
positionss(where: { status: "Active" }, orderBy: "taxRate", orderDirection: "asc", limit: 1000) {
items {
id
lastTaxTime
owner
payout
share
harbDeposit
kraikenDeposit
snatched
status
taxPaid
@ -129,11 +130,15 @@ export async function loadActivePositions() {
totalSupplyEnd
totalSupplyInit
}
}
}`,
});
console.log("res", res.data);
return res.data.data.positions as Position[];
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: any) => ({
...item,
harbDeposit: item.kraikenDeposit ?? "0",
})) as Position[];
}
function formatId(id: Hex) {
@ -144,18 +149,19 @@ function formatId(id: Hex) {
export async function loadMyClosedPositions(account: GetAccountReturnType) {
logger.info(`loadMyClosedPositions for chain: ${chainData.value?.path}`);
if (!chainData.value?.thegraph) {
if (!chainData.value?.graphql) {
return [];
}
const res = await axios.post(chainData.value?.thegraph, {
query: `query MyQuery {
positions(where: {status: Closed, owner: "${account.address?.toLowerCase()}"}) {
const res = await axios.post(chainData.value?.graphql, {
query: `query ClosedPositions {
positionss(where: { status: "Closed", owner: "${account.address?.toLowerCase()}" }, limit: 1000) {
items {
id
lastTaxTime
owner
payout
share
harbDeposit
kraikenDeposit
snatched
status
taxPaid
@ -163,14 +169,18 @@ export async function loadMyClosedPositions(account: GetAccountReturnType) {
totalSupplyEnd
totalSupplyInit
}
}
}`,
});
if (res.data.errors?.length > 0) {
console.error("todo nur laden, wenn eingeloggt");
return [];
}
const positions: Position[] = res.data.data.positions;
return positions;
const items = res.data?.data?.positionss?.items ?? [];
return items.map((item: any) => ({
...item,
harbDeposit: item.kraikenDeposit ?? "0",
})) as Position[];
}
export async function loadPositions() {

View file

@ -8,46 +8,44 @@ import type { WatchBlocksReturnType } from "viem";
import { bigInt2Number } from "@/utils/helper";
const demo = sessionStorage.getItem("demo") === "true";
interface statsCollection {
burnNextHourProjected: bigint;
burnedLastDay: bigint;
burnedLastWeek: bigint;
interface StatsRecord {
burnNextHourProjected: string;
burnedLastDay: string;
burnedLastWeek: string;
id: string;
lastUpdatedHour: number;
mintNextHourProjected: bigint;
mintedLastDay: bigint;
mintedLastWeek: bigint;
outstandingStake: bigint;
harbTotalSupply: bigint;
stakeTotalSupply: bigint;
mintNextHourProjected: string;
mintedLastDay: string;
mintedLastWeek: string;
outstandingStake: string;
kraikenTotalSupply: string;
stakeTotalSupply: string;
ringBufferPointer: number;
totalBurned: bigint;
totalMinted: bigint;
totalBurned: string;
totalMinted: string;
}
const rawStatsCollections = ref<Array<statsCollection>>([]);
const rawStatsCollections = ref<Array<StatsRecord>>([]);
const loading = ref(false);
const initialized = ref(false);
export async function loadStatsCollection() {
logger.info(`loadStatsCollection for chain: ${chainData.value?.path}`);
if (!chainData.value?.thegraph) {
if (!chainData.value?.graphql) {
return [];
}
const res = await axios.post(chainData.value?.thegraph, {
query: `query MyQuery {
stats_collection {
const res = await axios.post(chainData.value?.graphql, {
query: `query StatsQuery {
stats(id: "0x01") {
burnNextHourProjected
burnedLastDay
burnedLastWeek
id
lastUpdatedHour
mintNextHourProjected
mintedLastDay
mintedLastWeek
outstandingStake
harbTotalSupply
kraikenTotalSupply
stakeTotalSupply
ringBufferPointer
totalBurned
@ -55,25 +53,35 @@ export async function loadStatsCollection() {
}
}`,
});
console.groupEnd();
return res.data?.data?.stats_collection;
const stats = res.data?.data?.stats as StatsRecord | undefined;
if (!stats) {
return [];
}
return [{ ...stats, kraikenTotalSupply: stats.kraikenTotalSupply }];
}
const profit7d = computed(() => {
if (!statsCollection.value) {
return 0n;
}
return (
(BigInt(statsCollection.value?.mintedLastWeek) - BigInt(statsCollection.value?.burnedLastWeek)) /
(BigInt(statsCollection.value?.totalMinted) - BigInt(statsCollection.value?.totalBurned))
);
const mintedLastWeek = BigInt(statsCollection.value.mintedLastWeek);
const burnedLastWeek = BigInt(statsCollection.value.burnedLastWeek);
const totalMinted = BigInt(statsCollection.value.totalMinted);
const totalBurned = BigInt(statsCollection.value.totalBurned);
const denominator = totalMinted - totalBurned;
if (denominator === 0n) {
return 0n;
}
return (mintedLastWeek - burnedLastWeek) / denominator;
});
const nettoToken7d = computed(() => {
if (!statsCollection.value) {
return 0n;
}
return BigInt(statsCollection.value?.mintedLastWeek) - BigInt(statsCollection.value.burnedLastWeek);
return BigInt(statsCollection.value.mintedLastWeek) - BigInt(statsCollection.value.burnedLastWeek);
});
const statsCollection = computed(() => {
@ -96,9 +104,9 @@ const outstandingStake = computed(() => {
}
});
const harbTotalSupply = computed(() => {
const kraikenTotalSupply = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].harbTotalSupply);
return BigInt(rawStatsCollections.value[0].kraikenTotalSupply);
} else {
return 0n;
}
@ -115,7 +123,10 @@ const stakeTotalSupply = computed(() => {
//Total Supply Change / 7d=mintedLastWeekburnedLastWeek
const totalSupplyChange7d = computed(() => {
if (rawStatsCollections.value?.length > 0) {
return BigInt(rawStatsCollections.value[0].mintedLastWeek - rawStatsCollections.value[0].burnedLastWeek);
return (
BigInt(rawStatsCollections.value[0].mintedLastWeek) -
BigInt(rawStatsCollections.value[0].burnedLastWeek)
);
} else {
return 0n;
}
@ -123,15 +134,18 @@ const totalSupplyChange7d = computed(() => {
//totalsupply Change7d / harbtotalsupply
const inflation7d = computed(() => {
if (rawStatsCollections.value?.length > 0 && rawStatsCollections.value[0].harbTotalSupply > 0) {
return BigInt(rawStatsCollections.value[0].mintedLastWeek - rawStatsCollections.value[0].burnedLastWeek);
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
return (
BigInt(rawStatsCollections.value[0].mintedLastWeek) -
BigInt(rawStatsCollections.value[0].burnedLastWeek)
);
} else {
return 0n;
}
});
const stakeableSupply = computed(() => {
if (rawStatsCollections.value?.length > 0 && rawStatsCollections.value[0].harbTotalSupply > 0) {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
return stakeTotalSupply.value / 5n;
@ -142,7 +156,7 @@ const stakeableSupply = computed(() => {
//maxSlots
const maxSlots = computed(() => {
if (rawStatsCollections.value?.length > 0 && rawStatsCollections.value[0].harbTotalSupply > 0) {
if (rawStatsCollections.value?.length > 0 && BigInt(rawStatsCollections.value[0].kraikenTotalSupply) > 0n) {
console.log("rawStatsCollections.value[0]", rawStatsCollections.value[0]);
return (bigInt2Number(stakeTotalSupply.value, 18) * 0.2) / 100;
@ -206,7 +220,7 @@ export function useStatCollection() {
nettoToken7d,
inflation7d,
outstandingStake,
harbTotalSupply,
kraikenTotalSupply,
stakeTotalSupply,
totalSupplyChange7d,
initialized,

View file

@ -7,7 +7,7 @@ import { getAllowance, HarbContract, getNonce } from "@/contracts/harb";
import logger from "@/utils/logger";
import { setHarbContract } from "@/contracts/harb";
import { setStakeContract } from "@/contracts/stake";
import {chainsData} from "@/config"
import {chainsData, DEFAULT_CHAIN_ID} from "@/config"
const balance = ref<GetBalanceReturnType>({
value: 0n,
@ -28,8 +28,10 @@ const account = ref<GetAccountReturnType>({
status: "disconnected",
});
const selectedChainId = computed(() => account.value.chainId ?? DEFAULT_CHAIN_ID);
export const chainData = computed(() => {
return chainsData.find((obj) => obj.id === account.value.chainId)
return chainsData.find((obj) => obj.id === selectedChainId.value)
})
let unwatch: any = null;

View file

@ -1,31 +1,35 @@
const LOCAL_PONDER_URL = "http://127.0.0.1:42069/graphql";
export const DEFAULT_CHAIN_ID = Number(import.meta.env.VITE_DEFAULT_CHAIN_ID ?? 84532);
export const chainsData = [
{
// sepolia
id: 11155111,
thegraph: "https://api.studio.thegraph.com/query/71271/harb/v0.0.50",
graphql: import.meta.env.VITE_PONDER_SEPOLIA ?? "",
path: "sepolia",
stake: "0xCd21a41a137BCAf8743E47D048F57D92398f7Da9",
harb: "0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE"
}, {
//base-sepolia
// base-sepolia (local dev default)
id: 84532,
thegraph: "https://api.studio.thegraph.com/query/71271/harb-base-sepolia/v0.0.12",
graphql: import.meta.env.VITE_PONDER_BASE_SEPOLIA_LOCAL_FORK ?? LOCAL_PONDER_URL,
path: "sepoliabase",
stake: "0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2",
harb: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE"
},
{
//base
// base mainnet
id: 8453,
thegraph: import.meta.env.VITE_BASE_URL,
graphql: import.meta.env.VITE_PONDER_BASE ?? "",
path: "base",
stake: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc",
harb: "0x45caa5929f6ee038039984205bdecf968b954820",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE"
},
]
];
export function getChain(id:number){
return chainsData.find((obj) => obj.id === id)

View file

@ -27,7 +27,7 @@ const minStakeAmount = computed(() => {
return formatBigIntDivision(minStake.value, 10n ** 18n);
});
const stakeAbleHarbAmount = computed(() => statCollection.harbTotalSupply / 5n);
const stakeAbleHarbAmount = computed(() => statCollection.kraikenTotalSupply / 5n);
const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
@ -36,7 +36,7 @@ const statCollection = useStatCollection();
const snatchPositions = computed(() => {
if (
bigInt2Number(statCollection.outstandingStake, 18) + stake.stakingAmountNumber <=
bigInt2Number(statCollection.harbTotalSupply, 18) * 0.2
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2
) {
return [];
}
@ -44,7 +44,7 @@ const snatchPositions = computed(() => {
const difference =
bigInt2Number(statCollection.outstandingStake, 18) +
stake.stakingAmountNumber -
bigInt2Number(statCollection.harbTotalSupply, 18) * 0.2;
bigInt2Number(statCollection.kraikenTotalSupply, 18) * 0.2;
console.log("difference", difference);
//Division ohne Rest, um zu schauen wie viele Positionen gesnatched werden könnten