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. - `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. - `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. - `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. - `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 ## Execution Workflow
1. **Contracts** 1. **Contracts**
- Build/test via `forge build` and `forge test` inside `onchain/`. - Build/test via `forge build` and `forge test` inside `onchain/`.
@ -16,23 +21,25 @@
2. **Indexer (Ponder)** 2. **Indexer (Ponder)**
- Install deps (`npm install`). - 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`. - 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. - Handlers in `src/kraiken.ts` and `src/stake.ts` maintain rolling supply stats, ring-buffered hourly metrics, and position state.
3. **Frontend** 3. **Frontend**
- `npm install` then `npm run dev` in `landing/`. Currently static marketing copy with placeholders for wallet/staking flows. - `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** 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) ## Migration Notes (Subgraph → Ponder)
- Schema parity: ensure any entity changes land in both `ponder.schema.ts` and legacy Graph `schema.graphql` until cutover. - 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. - 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. - 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. - 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. - 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. - 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. 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`. 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. 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. 5. Query GraphQL (`stats`, `positions`) to confirm indexed state.
## Gotchas & Tips ## Gotchas & Tips
@ -50,16 +57,24 @@
- VWAP/ethScarcity logic expects squared price format; do not convert to sqrt unintentionally. - 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. - 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. - 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. - Legacy subgraph is maintained for reference only; all active services now read from Ponder.
- `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/`. - `web-app/` reads from Ponder by default (see `src/config.ts`). Update the environment specific GraphQL URLs when deployments move.
## Useful Commands ## Useful Commands
- `foundryup` / `forge clean` / `forge snapshot` - `foundryup` / `forge clean` / `forge snapshot`
- `anvil --fork-url https://sepolia.base.org` - `anvil --fork-url https://sepolia.base.org`
- `cast call <POOL> "slot0()"` - `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}}"}'` - `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 ## Contacts & Artifacts
- Deployment addresses recorded in `onchain/deployments-local.json` (local) and broadcast traces under `onchain/broadcast/`. - Deployment addresses recorded in `onchain/deployments-local.json` (local) and broadcast traces under `onchain/broadcast/`.

View file

@ -1,10 +1,11 @@
# Network configuration # Network configuration
# Options: local, baseSepolia, base # Options: BASE_SEPOLIA_LOCAL_FORK, BASE_SEPOLIA, BASE
PONDER_NETWORK=local PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
# RPC URLs (optional - defaults provided) # 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=https://base.llamarpc.com
PONDER_RPC_URL_BASE_SEPOLIA=https://sepolia.base.org PONDER_RPC_URL_BASE_SEPOLIA=https://sepolia.base.org
# Database URL (optional - uses SQLite by default) # Database URL (optional - uses SQLite by default)
# DATABASE_URL=postgresql://user:password@localhost:5432/ponder_db # DATABASE_URL=postgresql://user:password@localhost:5432/ponder_db

View file

@ -1,8 +1,8 @@
# Auto-generated by local_env.sh # Auto-generated by local_env.sh
PONDER_NETWORK=local PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
KRAIKEN_ADDRESS=0x56186c1e64ca8043def78d06aff222212ea5df71 KRAIKEN_ADDRESS=0x56186c1e64ca8043def78d06aff222212ea5df71
STAKE_ADDRESS=0x056e4a859558a3975761abd7385506bc4d8a8e60 STAKE_ADDRESS=0x056e4a859558a3975761abd7385506bc4d8a8e60
START_BLOCK=31435585 START_BLOCK=31438430
# Use PostgreSQL connection # Use PostgreSQL connection
DATABASE_URL=postgresql://ponder:ponder_local@localhost/ponder_local 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 anvil --fork-url https://base.llamarpc.com
# Terminal 2: Start Ponder # Terminal 2: Start Ponder
export PONDER_NETWORK=local export PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
npm run dev npm run dev
``` ```
@ -22,7 +22,7 @@ Access GraphQL at: http://localhost:42069/graphql
For integration testing with live testnet. For integration testing with live testnet.
```bash ```bash
export PONDER_NETWORK=baseSepolia export PONDER_NETWORK=BASE_SEPOLIA
export PONDER_RPC_URL_BASE_SEPOLIA=https://sepolia.base.org # or your RPC export PONDER_RPC_URL_BASE_SEPOLIA=https://sepolia.base.org # or your RPC
npm run dev npm run dev
``` ```
@ -32,7 +32,7 @@ npm run dev
For production deployment. For production deployment.
```bash ```bash
export PONDER_NETWORK=base export PONDER_NETWORK=BASE
export PONDER_RPC_URL_BASE=https://base.llamarpc.com # Use paid RPC for production export PONDER_RPC_URL_BASE=https://base.llamarpc.com # Use paid RPC for production
export DATABASE_URL=postgresql://user:pass@host:5432/kraiken_ponder export DATABASE_URL=postgresql://user:pass@host:5432/kraiken_ponder
npm run start npm run start
@ -147,7 +147,7 @@ services:
- "42069:42069" - "42069:42069"
environment: environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/kraiken - DATABASE_URL=postgresql://postgres:password@db:5432/kraiken
- PONDER_NETWORK=base - PONDER_NETWORK=BASE
db: db:
image: postgres:15 image: postgres:15
@ -251,4 +251,4 @@ Compare outputs between subgraph and Ponder:
- Documentation: https://ponder.sh/docs - Documentation: https://ponder.sh/docs
- Discord: https://discord.gg/ponder - Discord: https://discord.gg/ponder
- Issues: https://github.com/yourusername/kraiken-ponder/issues - Issues: https://github.com/yourusername/kraiken-ponder/issues

View file

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

View file

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

View file

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

View file

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

View file

@ -13,10 +13,10 @@ ANVIL_PID=$!
sleep 5 sleep 5
echo "Starting Ponder indexer for local network..." echo "Starting Ponder indexer for local network..."
export PONDER_NETWORK=local export PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
timeout 30 npm run dev timeout 30 npm run dev
# Cleanup # Cleanup
kill $ANVIL_PID 2>/dev/null kill $ANVIL_PID 2>/dev/null
echo "Test completed!" echo "Test completed!"

View file

@ -9,15 +9,19 @@ LOG_DIR="$STATE_DIR/logs"
ANVIL_PID_FILE="$STATE_DIR/anvil.pid" ANVIL_PID_FILE="$STATE_DIR/anvil.pid"
PONDER_PID_FILE="$STATE_DIR/ponder.pid" PONDER_PID_FILE="$STATE_DIR/ponder.pid"
WEBAPP_PID_FILE="$STATE_DIR/webapp.pid" WEBAPP_PID_FILE="$STATE_DIR/webapp.pid"
TXNBOT_PID_FILE="$STATE_DIR/txnBot.pid"
ANVIL_LOG="$LOG_DIR/anvil.log" ANVIL_LOG="$LOG_DIR/anvil.log"
PONDER_LOG="$LOG_DIR/ponder.log" PONDER_LOG="$LOG_DIR/ponder.log"
WEBAPP_LOG="$LOG_DIR/webapp.log" WEBAPP_LOG="$LOG_DIR/webapp.log"
TXNBOT_LOG="$LOG_DIR/txnBot.log"
SETUP_LOG="$LOG_DIR/setup.log" SETUP_LOG="$LOG_DIR/setup.log"
TXNBOT_ENV_FILE="$STATE_DIR/txnBot.env"
FORK_URL=${FORK_URL:-"https://sepolia.base.org"} FORK_URL=${FORK_URL:-"https://sepolia.base.org"}
ANVIL_RPC="http://127.0.0.1:8545" ANVIL_RPC="http://127.0.0.1:8545"
GRAPHQL_HEALTH="http://127.0.0.1:42069/health" 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" FRONTEND_URL="http://127.0.0.1:5173"
FOUNDRY_BIN=${FOUNDRY_BIN:-"$HOME/.foundry/bin"} FOUNDRY_BIN=${FOUNDRY_BIN:-"$HOME/.foundry/bin"}
@ -31,6 +35,13 @@ FEE_DEST="0xf6a3eef9088A255c32b6aD2025f83E57291D9011"
WETH="0x4200000000000000000000000000000000000006" WETH="0x4200000000000000000000000000000000000006"
SWAP_ROUTER="0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4" 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" MAX_UINT="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
SKIP_CLEANUP=false SKIP_CLEANUP=false
@ -39,6 +50,7 @@ COMMAND="start"
cleanup() { cleanup() {
stop_process "Frontend" "$WEBAPP_PID_FILE" stop_process "Frontend" "$WEBAPP_PID_FILE"
stop_process "Ponder" "$PONDER_PID_FILE" stop_process "Ponder" "$PONDER_PID_FILE"
stop_process "TxnBot" "$TXNBOT_PID_FILE"
stop_process "Anvil" "$ANVIL_PID_FILE" stop_process "Anvil" "$ANVIL_PID_FILE"
if [[ "$SKIP_CLEANUP" == true ]]; then if [[ "$SKIP_CLEANUP" == true ]]; then
@ -123,6 +135,13 @@ ensure_dependencies() {
npm install >>"$SETUP_LOG" 2>&1 npm install >>"$SETUP_LOG" 2>&1
popd >/dev/null popd >/dev/null
fi 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() { start_anvil() {
@ -177,7 +196,7 @@ extract_addresses() {
# Create .env.local for Ponder with deployed addresses # Create .env.local for Ponder with deployed addresses
cat > "$ROOT_DIR/ponder/.env.local" <<EOF cat > "$ROOT_DIR/ponder/.env.local" <<EOF
# Auto-generated by local_env.sh # Auto-generated by local_env.sh
PONDER_NETWORK=local PONDER_NETWORK=BASE_SEPOLIA_LOCAL_FORK
KRAIKEN_ADDRESS=$KRAIKEN KRAIKEN_ADDRESS=$KRAIKEN
STAKE_ADDRESS=$STAKE STAKE_ADDRESS=$STAKE
START_BLOCK=$deploy_block START_BLOCK=$deploy_block
@ -231,6 +250,59 @@ prepare_application_state() {
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,10000000000000000,0,0)" >>"$SETUP_LOG" 2>&1 "($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() { start_ponder() {
if [[ -f "$PONDER_PID_FILE" ]]; then if [[ -f "$PONDER_PID_FILE" ]]; then
log "Ponder already running (pid $(cat "$PONDER_PID_FILE"))" log "Ponder already running (pid $(cat "$PONDER_PID_FILE"))"
@ -239,7 +311,7 @@ start_ponder() {
log "Starting Ponder indexer" log "Starting Ponder indexer"
pushd "$ROOT_DIR/ponder" >/dev/null 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 popd >/dev/null
echo $! >"$PONDER_PID_FILE" echo $! >"$PONDER_PID_FILE"
@ -270,7 +342,9 @@ start_environment() {
extract_addresses extract_addresses
bootstrap_liquidity_manager bootstrap_liquidity_manager
prepare_application_state prepare_application_state
prime_chain_for_indexing
start_ponder # Re-enabled with PostgreSQL start_ponder # Re-enabled with PostgreSQL
start_txnbot
start_frontend start_frontend
} }
@ -286,7 +360,7 @@ start_and_wait() {
log " Frontend: $FRONTEND_URL" log " Frontend: $FRONTEND_URL"
log "Press Ctrl+C to shut everything down" 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() { stop_environment() {
@ -343,6 +417,7 @@ status_environment() {
service_status "Anvil" "$ANVIL_PID_FILE" "$ANVIL_LOG" service_status "Anvil" "$ANVIL_PID_FILE" "$ANVIL_LOG"
service_status "Ponder" "$PONDER_PID_FILE" "$PONDER_LOG" service_status "Ponder" "$PONDER_PID_FILE" "$PONDER_LOG"
service_status "Frontend" "$WEBAPP_PID_FILE" "$WEBAPP_LOG" service_status "Frontend" "$WEBAPP_PID_FILE" "$WEBAPP_LOG"
service_status "TxnBot" "$TXNBOT_PID_FILE" "$TXNBOT_LOG"
if [[ -f "$STATE_DIR/contracts.env" ]]; then if [[ -f "$STATE_DIR/contracts.env" ]]; then
printf '\nContracts:\n' 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 # txnBot
yarn
yarn graphclient --fileType json build
node service.js
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", "version": "0.0.1",
"main": "index.js", "main": "index.js",
"license": "GPL3", "license": "GPL3",
"scripts": {
"start": "node service.js"
},
"dependencies": { "dependencies": {
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"ethers": "^6.13.2", "ethers": "^6.13.2",
"express": "^5.0.0", "express": "^5.0.0",
"kraiken-lib": "file:../../kraiken-lib" "kraiken-lib": "file:../../kraiken-lib"
},
"devDependencies": {
"@graphprotocol/client-cli": "^3.0.7"
} }
} }

View file

@ -1,26 +1,48 @@
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 { ethers } = require('ethers');
const express = require('express'); const express = require('express');
const { execute } = require('./.graphclient');
const { decodePositionId, isPositionDelinquent } = require('kraiken-lib'); const { decodePositionId, isPositionDelinquent } = require('kraiken-lib');
const myQuery = ` const ACTIVE_POSITIONS_QUERY = `
query GetPositions { query ActivePositions {
positions(first: 5000, where: {status: "Active"}) { positionss(where: { status: "Active" }, limit: 1000) {
items {
id id
share share
lastTaxTime lastTaxTime
taxRate taxRate
status status
}
} }
} }
` `;
// Load environment variables // Load environment variables
const PROVIDER_URL = process.env.PROVIDER_URL; const PROVIDER_URL = process.env.PROVIDER_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY; const PRIVATE_KEY = process.env.PRIVATE_KEY;
const LM_CONTRACT_ADDRESS = process.env.LM_CONTRACT_ADDRESS; const LM_CONTRACT_ADDRESS = process.env.LM_CONTRACT_ADDRESS;
const STAKE_CONTRACT_ADDRESS = process.env.STAKE_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 = [ const LM_ABI = [
{"type":"function","name":"recenter","inputs":[],"outputs":[],"stateMutability":"nonpayable"} {"type":"function","name":"recenter","inputs":[],"outputs":[],"stateMutability":"nonpayable"}
@ -40,6 +62,26 @@ let startTime = new Date();
let lastRecenterTime = null; let lastRecenterTime = null;
let lastLiquidationTime = 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() { async function checkFunds() {
const balance = await provider.getBalance(wallet.address); const balance = await provider.getBalance(wallet.address);
return ethers.formatEther(balance); return ethers.formatEther(balance);
@ -102,20 +144,20 @@ async function liquidityLoop() {
async function liquidationLoop() { async function liquidationLoop() {
let counter = 0; let counter = 0;
try { try {
const result = await execute(myQuery, {}); const positions = await fetchActivePositions();
for (const position of result.data.positions) { for (const position of positions) {
counter = counter + await checkPosition(position); counter = counter + await checkPosition(position);
} }
if (counter == 0) { if (counter == 0) {
console.log(`No tax can be claimed at the moment. - ${(new Date()).toISOString()}`); console.log(`No tax can be claimed at the moment. - ${(new Date()).toISOString()}`);
} }
} catch (error) { } catch (error) {
console.error('Error in liquididation loop:', error); console.error('Error in liquidation loop:', error);
} }
} }
async function main() { async function main() {
console.log('Service started...'); console.log(`txnBot service started for environment ${ENVIRONMENT}`);
await liquidityLoop(); await liquidityLoop();
await liquidationLoop(); 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); 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 //war das mal so, wurde das geändert --> funktioniert nicht mehr
// const minStake = computed(() => stakeAbleHarbAmount.value / 600n); // const minStake = computed(() => stakeAbleHarbAmount.value / 600n);
const tokenIssuance = computed(() => { const tokenIssuance = computed(() => {
if (statCollection.harbTotalSupply === 0n) { if (statCollection.kraikenTotalSupply === 0n) {
return 0n; return 0n;
} }
return (statCollection.nettoToken7d / statCollection.harbTotalSupply) * 100n; return (statCollection.nettoToken7d / statCollection.kraikenTotalSupply) * 100n;
}); });
function getMinFloorTax() { function getMinFloorTax() {
@ -280,7 +281,7 @@ onMounted(async () => {
} }
}); });
// async function getGraphData(): Promise<Array<any>> { // 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}", // 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; // return res.data.data.positions;

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import { getAllowance, HarbContract, getNonce } from "@/contracts/harb";
import logger from "@/utils/logger"; import logger from "@/utils/logger";
import { setHarbContract } from "@/contracts/harb"; import { setHarbContract } from "@/contracts/harb";
import { setStakeContract } from "@/contracts/stake"; import { setStakeContract } from "@/contracts/stake";
import {chainsData} from "@/config" import {chainsData, DEFAULT_CHAIN_ID} from "@/config"
const balance = ref<GetBalanceReturnType>({ const balance = ref<GetBalanceReturnType>({
value: 0n, value: 0n,
@ -28,8 +28,10 @@ const account = ref<GetAccountReturnType>({
status: "disconnected", status: "disconnected",
}); });
const selectedChainId = computed(() => account.value.chainId ?? DEFAULT_CHAIN_ID);
export const chainData = computed(() => { 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; 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 = [ export const chainsData = [
{ {
//sepolia // sepolia
id: 11155111, id: 11155111,
thegraph: "https://api.studio.thegraph.com/query/71271/harb/v0.0.50", graphql: import.meta.env.VITE_PONDER_SEPOLIA ?? "",
path: "sepolia", path: "sepolia",
stake: "0xCd21a41a137BCAf8743E47D048F57D92398f7Da9", stake: "0xCd21a41a137BCAf8743E47D048F57D92398f7Da9",
harb: "0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9", harb: "0x087F256D11fe533b0c7d372e44Ee0F9e47C89dF9",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE" uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE"
}, { }, {
//base-sepolia // base-sepolia (local dev default)
id: 84532, 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", path: "sepoliabase",
stake: "0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2", stake: "0xe28020BCdEeAf2779dd47c670A8eFC2973316EE2",
harb: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8", harb: "0x22c264Ecf8D4E49D1E3CabD8DD39b7C4Ab51C1B8",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE" uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE"
}, },
{ {
//base // base mainnet
id: 8453, id: 8453,
thegraph: import.meta.env.VITE_BASE_URL, graphql: import.meta.env.VITE_PONDER_BASE ?? "",
path: "base", path: "base",
stake: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc", stake: "0xed70707fab05d973ad41eae8d17e2bcd36192cfc",
harb: "0x45caa5929f6ee038039984205bdecf968b954820", harb: "0x45caa5929f6ee038039984205bdecf968b954820",
uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE" uniswap: "https://app.uniswap.org/swap?chain=mainnet&inputCurrency=NATIVE"
}, },
] ];
export function getChain(id:number){ export function getChain(id:number){
return chainsData.find((obj) => obj.id === id) return chainsData.find((obj) => obj.id === id)

View file

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