added harb-lib
This commit is contained in:
parent
ea1b007283
commit
5a8c80e838
15 changed files with 4876 additions and 48 deletions
2
harb-lib/.gitignore
vendored
Normal file
2
harb-lib/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
dist
|
||||
node_modules
|
||||
22
harb-lib/README.md
Normal file
22
harb-lib/README.md
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
|
||||
## Test
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
|
||||
## Import
|
||||
|
||||
```
|
||||
yarn add harb-lib
|
||||
```
|
||||
|
||||
then
|
||||
|
||||
```
|
||||
import {bytesToUint256LittleEndian, uint256ToBytesLittleEndian} from 'harb-lib'
|
||||
|
||||
uint256ToBytesLittleEndian(3n);
|
||||
```
|
||||
8
harb-lib/codegen.yml
Normal file
8
harb-lib/codegen.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
overwrite: true
|
||||
schema: "../subgraph/harb/schema.graphql" # Or an endpoint if your schema is remote
|
||||
documents: "../subgraph/harb/src/**/*.graphql"
|
||||
generates:
|
||||
src/__generated__/graphql.ts:
|
||||
plugins:
|
||||
- "typescript"
|
||||
- "typescript-operations"
|
||||
4
harb-lib/jest.config.js
Normal file
4
harb-lib/jest.config.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
};
|
||||
32
harb-lib/package.json
Normal file
32
harb-lib/package.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "harb-lib",
|
||||
"version": "0.1.0",
|
||||
"description": "helper functions and snatch selection",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"/dist"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"compile": "graphql-codegen",
|
||||
"watch": "graphql-codegen -w"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.9.10",
|
||||
"apollo-link-http": "^1.5.17",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-tag": "^2.12.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/client-preset": "^4.2.5",
|
||||
"@graphql-codegen/typescript": "^4.0.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.2.0",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.4.3"
|
||||
}
|
||||
}
|
||||
52
harb-lib/src/__generated__/graphql.ts
generated
Normal file
52
harb-lib/src/__generated__/graphql.ts
generated
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never };
|
||||
export type Incremental<T> = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never };
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: { input: string; output: string; }
|
||||
String: { input: string; output: string; }
|
||||
Boolean: { input: boolean; output: boolean; }
|
||||
Int: { input: number; output: number; }
|
||||
Float: { input: number; output: number; }
|
||||
BigDecimal: { input: any; output: any; }
|
||||
BigInt: { input: any; output: any; }
|
||||
Bytes: { input: any; output: any; }
|
||||
};
|
||||
|
||||
export type Position = {
|
||||
__typename?: 'Position';
|
||||
creationTime: Scalars['Int']['output'];
|
||||
id: Scalars['Bytes']['output'];
|
||||
lastTaxTime?: Maybe<Scalars['Int']['output']>;
|
||||
owner: Scalars['Bytes']['output'];
|
||||
share: Scalars['BigDecimal']['output'];
|
||||
status: PositionStatus;
|
||||
taxRate: Scalars['BigDecimal']['output'];
|
||||
};
|
||||
|
||||
export enum PositionStatus {
|
||||
Active = 'Active',
|
||||
Closed = 'Closed'
|
||||
}
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
positions?: Maybe<Array<Maybe<Position>>>;
|
||||
stats?: Maybe<Array<Maybe<Stats>>>;
|
||||
};
|
||||
|
||||
export type Stats = {
|
||||
__typename?: 'Stats';
|
||||
activeSupply: Scalars['BigInt']['output'];
|
||||
id: Scalars['Bytes']['output'];
|
||||
outstandingSupply: Scalars['BigInt']['output'];
|
||||
};
|
||||
|
||||
export type GetPositionsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type GetPositionsQuery = { __typename?: 'Query', positions?: Array<{ __typename?: 'Position', id: any, owner: any, share: any, creationTime: number, lastTaxTime?: number | null, taxRate: any, status: PositionStatus } | null> | null };
|
||||
1
harb-lib/src/index.ts
Normal file
1
harb-lib/src/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export {bytesToUint256LittleEndian, uint256ToBytesLittleEndian} from './subgraph'
|
||||
32
harb-lib/src/subgraph.ts
Normal file
32
harb-lib/src/subgraph.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
export function bytesToUint256LittleEndian(bytes: Uint8Array): bigint {
|
||||
// console.log(hexString);
|
||||
// const cleanHexString = hexString.startsWith('0x') ? hexString.substring(2) : hexString;
|
||||
// const bytes = new Uint8Array(Math.ceil(cleanHexString.length / 2));
|
||||
// for (let i = 0, j = 0; i < cleanHexString.length; i += 2, j++) {
|
||||
// bytes[j] = parseInt(cleanHexString.slice(i, i + 2), 16);
|
||||
// }
|
||||
let value: bigint = 0n;
|
||||
|
||||
for (let i = bytes.length - 1; i >= 0; i--) {
|
||||
value = (value << 8n) | BigInt(bytes[i]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function uint256ToBytesLittleEndian(value: bigint): Uint8Array {
|
||||
const bytes = new Uint8Array(4);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
bytes[i] = Number((value >> (8n * BigInt(i))) & 0xFFn);
|
||||
}
|
||||
return bytes;
|
||||
// let hexString = '0x';
|
||||
|
||||
// for (let i = 0; i < bytes.length; i++) {
|
||||
// // Convert each byte to a hexadecimal string and pad with zero if needed
|
||||
// hexString += bytes[i].toString(16).padStart(2, '0');
|
||||
// }
|
||||
|
||||
// return hexString;
|
||||
}
|
||||
29
harb-lib/src/tests/functions.test.ts
Normal file
29
harb-lib/src/tests/functions.test.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { bytesToUint256LittleEndian, uint256ToBytesLittleEndian } from "../subgraph"; // Adjust the import path to where your functions are defined
|
||||
import { Position, PositionStatus } from '../__generated__/graphql';
|
||||
|
||||
|
||||
describe('BigInt Conversion Functions', () => {
|
||||
test('converts uint256 to bytes and back (little endian)', async () => {
|
||||
|
||||
const mockPos: Position = {
|
||||
__typename: 'Position',
|
||||
id: uint256ToBytesLittleEndian(3n),
|
||||
owner: '0x8db6b632d743aef641146dc943acb64957155388',
|
||||
share: '0.000001',
|
||||
creationTime: 1610000000,
|
||||
taxRate: '0.01',
|
||||
status: PositionStatus.Active, // Enum usage
|
||||
};
|
||||
let hexString = '0x';
|
||||
|
||||
for (let i = 0; i < mockPos.id.length; i++) {
|
||||
// Convert each byte to a hexadecimal string and pad with zero if needed
|
||||
hexString += mockPos.id[i].toString(16).padStart(2, '0');
|
||||
}
|
||||
expect(hexString).toEqual('0x03000000');
|
||||
|
||||
// return hexString;
|
||||
expect(bytesToUint256LittleEndian(mockPos.id)).toEqual(3n);
|
||||
});
|
||||
|
||||
});
|
||||
11
harb-lib/tsconfig.json
Normal file
11
harb-lib/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2020",
|
||||
"declaration": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
4565
harb-lib/yarn.lock
Normal file
4565
harb-lib/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -212,46 +212,7 @@ contract BaseLineLP {
|
|||
});
|
||||
}
|
||||
|
||||
// call this function when price has moved up 15%
|
||||
function shift() external {
|
||||
// Fetch the current tick from the Uniswap V3 pool
|
||||
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
|
||||
// TODO: check slippage with oracle
|
||||
|
||||
// ## check price moved up
|
||||
{
|
||||
// Check if current tick is within the specified range
|
||||
int24 anchorTickLower = positions[Stage.ANCHOR].tickLower;
|
||||
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
|
||||
// center tick can be calculated positive and negative numbers the same
|
||||
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
|
||||
int24 amplitudeTick = anchorTickLower + (anchorTickUpper - anchorTickLower) * 3 / 20;
|
||||
|
||||
// Determine the correct comparison direction based on token0isWeth
|
||||
bool isUp = token0isWeth ? currentTick > centerTick : currentTick < centerTick;
|
||||
bool isEnough = token0isWeth ? currentTick > amplitudeTick : currentTick < amplitudeTick;
|
||||
|
||||
// Check Conditions
|
||||
require(isUp, "call slide(), not shift()");
|
||||
require(isEnough, "amplitude not reached, come back later");
|
||||
}
|
||||
|
||||
// ## scrape positions
|
||||
uint256 ethInAnchor;
|
||||
for (uint256 i=uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
|
||||
TokenPosition storage position = positions[Stage(i)];
|
||||
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
|
||||
if (i == uint256(Stage.ANCHOR)) {
|
||||
ethInAnchor = token0isWeth ? amount0 : amount1;
|
||||
}
|
||||
}
|
||||
// TODO: handle fees
|
||||
|
||||
// ## set new positions
|
||||
// reduce Anchor by 10% of new ETH. It will be moved into Floor
|
||||
uint256 initialEthInAnchor = ethIn(Stage.ANCHOR);
|
||||
ethInAnchor -= (ethInAnchor - initialEthInAnchor) * 10 / LIQUIDITY_RATIO_DIVISOR;
|
||||
|
||||
function _set(uint160 sqrtPriceX96, int24 currentTick, uint256 ethInNewAnchor) internal {
|
||||
// ### set Anchor position
|
||||
uint128 anchorLiquidity;
|
||||
{
|
||||
|
|
@ -261,11 +222,11 @@ contract BaseLineLP {
|
|||
uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper);
|
||||
if (token0isWeth) {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount0(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInAnchor
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
|
||||
);
|
||||
} else {
|
||||
anchorLiquidity = LiquidityAmounts.getLiquidityForAmount1(
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInAnchor
|
||||
sqrtRatioAX96, sqrtRatioBX96, ethInNewAnchor
|
||||
);
|
||||
}
|
||||
_mint(tickLower, tickUpper, anchorLiquidity);
|
||||
|
|
@ -322,16 +283,103 @@ contract BaseLineLP {
|
|||
}
|
||||
_mint(tickLower, tickUpper, liquidity);
|
||||
}
|
||||
}
|
||||
|
||||
// call this function when price has moved up 15%
|
||||
function shift() external {
|
||||
require(positions[Stage.ANCHOR].liquidity > 0, "Not initialized");
|
||||
// Fetch the current tick from the Uniswap V3 pool
|
||||
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
|
||||
// TODO: check slippage with oracle
|
||||
|
||||
// ## check price moved up
|
||||
{
|
||||
// Check if current tick is within the specified range
|
||||
int24 anchorTickLower = positions[Stage.ANCHOR].tickLower;
|
||||
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
|
||||
// center tick can be calculated positive and negative numbers the same
|
||||
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
|
||||
int24 amplitudeTick = anchorTickLower + (anchorTickUpper - anchorTickLower) * 3 / 20;
|
||||
|
||||
// Determine the correct comparison direction based on token0isWeth
|
||||
bool isUp = token0isWeth ? currentTick > centerTick : currentTick < centerTick;
|
||||
bool isEnough = token0isWeth ? currentTick > amplitudeTick : currentTick < amplitudeTick;
|
||||
|
||||
// Check Conditions
|
||||
require(isUp, "call slide(), not shift()");
|
||||
require(isEnough, "amplitude not reached, come back later!");
|
||||
}
|
||||
|
||||
// ## scrape positions
|
||||
uint256 ethInAnchor;
|
||||
for (uint256 i=uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
|
||||
TokenPosition storage position = positions[Stage(i)];
|
||||
(uint256 amount0, uint256 amount1) = pool.burn(position.tickLower, position.tickUpper, position.liquidity);
|
||||
if (i == uint256(Stage.ANCHOR)) {
|
||||
ethInAnchor = token0isWeth ? amount0 : amount1;
|
||||
}
|
||||
}
|
||||
// TODO: handle fees
|
||||
|
||||
// ## set new positions
|
||||
// reduce Anchor by 10% of new ETH. It will be moved into Floor
|
||||
uint256 initialEthInAnchor = ethIn(Stage.ANCHOR);
|
||||
ethInAnchor -= (ethInAnchor - initialEthInAnchor) * 10 / LIQUIDITY_RATIO_DIVISOR;
|
||||
|
||||
// cap anchor size at 10 % of total ETH
|
||||
uint256 ethBalance = address(this).balance;
|
||||
ethInAnchor = (ethInAnchor > ethBalance / 10) ? ethBalance / 10 : ethInAnchor;
|
||||
|
||||
_set(sqrtPriceX96, currentTick, ethInAnchor);
|
||||
}
|
||||
|
||||
function slide() external {
|
||||
// check price moved down
|
||||
// check price moved down enough
|
||||
// scrape positions
|
||||
// get outstanding upply and capacity
|
||||
// set new positions
|
||||
// burn harb
|
||||
// Fetch the current tick from the Uniswap V3 pool
|
||||
(uint160 sqrtPriceX96, int24 currentTick, , , , , ) = pool.slot0();
|
||||
// TODO: check slippage with oracle
|
||||
|
||||
// ## check price moved down
|
||||
if (positions[Stage.ANCHOR].liquidity > 0) {
|
||||
// Check if current tick is within the specified range
|
||||
int24 anchorTickLower = positions[Stage.ANCHOR].tickLower;
|
||||
int24 anchorTickUpper = positions[Stage.ANCHOR].tickUpper;
|
||||
// center tick can be calculated positive and negative numbers the same
|
||||
int24 centerTick = anchorTickLower + ((anchorTickUpper - anchorTickLower) / 2);
|
||||
int24 amplitudeTick = anchorTickLower + (anchorTickUpper - anchorTickLower) * 3 / 20;
|
||||
|
||||
// Determine the correct comparison direction based on token0isWeth
|
||||
bool isDown = token0isWeth ? currentTick < centerTick : currentTick > centerTick;
|
||||
bool isEnough = token0isWeth ? currentTick < amplitudeTick : currentTick > amplitudeTick;
|
||||
|
||||
// Check Conditions
|
||||
require(isDown, "call shift(), not slide()");
|
||||
require(isEnough, "amplitude not reached, diamond hands!");
|
||||
}
|
||||
|
||||
// ## scrape positions
|
||||
for (uint256 i=uint256(Stage.FLOOR); i <= uint256(Stage.DISCOVERY); i++) {
|
||||
TokenPosition storage position = positions[Stage(i)];
|
||||
if (position.liquidity > 0) {
|
||||
pool.burn(position.tickLower, position.tickUpper, position.liquidity);
|
||||
// TODO: handle fees
|
||||
}
|
||||
}
|
||||
|
||||
uint256 ethBalance = address(this).balance;
|
||||
if (ethBalance == 0) {
|
||||
// TODO: set only discovery
|
||||
return;
|
||||
}
|
||||
uint256 ethInAnchor = ethIn(Stage.ANCHOR);
|
||||
uint256 ethInFloor = ethIn(Stage.FLOOR);
|
||||
|
||||
// use previous ration of Floor to Anchor
|
||||
uint256 ethInNewAnchor = ethBalance * ethInAnchor / (ethInAnchor + ethInFloor);
|
||||
|
||||
// but cap anchor size at 10 % of total ETH
|
||||
ethInNewAnchor = (ethInNewAnchor > ethBalance / 10) ? ethBalance / 10 : ethInNewAnchor;
|
||||
|
||||
_set(sqrtPriceX96, currentTick, ethInNewAnchor);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ contract Stake is IStake {
|
|||
if (sharesWanted < minStake) {
|
||||
revert SharesTooLow(receiver, assets, sharesWanted, minStake);
|
||||
}
|
||||
// TODO: check that position size is multiple of minStake
|
||||
|
||||
// run through all suggested positions to snatch
|
||||
for (uint256 i = 0; i < positionsToSnatch.length; i++) {
|
||||
|
|
@ -104,6 +105,8 @@ contract Stake is IStake {
|
|||
// TODO: what if someone calls payTax and exitPosition in the same transaction?
|
||||
_payTax(pos, 0);
|
||||
_exitPosition(positionsToSnatch[i], pos);
|
||||
// TODO: exit positions partially, if needed
|
||||
// TODO: avoid greeving where more positions are freed than needed.
|
||||
}
|
||||
|
||||
// try to make a new position in the free space and hope it is big enough
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
scalar Bytes
|
||||
scalar BigInt
|
||||
scalar BigDecimal
|
||||
|
||||
type Stats @entity {
|
||||
id: Bytes!
|
||||
|
|
@ -21,3 +24,8 @@ type Position @entity {
|
|||
taxRate: BigDecimal!
|
||||
status: PositionStatus!
|
||||
}
|
||||
|
||||
type Query {
|
||||
stats: [Stats]
|
||||
positions: [Position]
|
||||
}
|
||||
11
subgraph/harb/src/queries/getPositions.graphql
Normal file
11
subgraph/harb/src/queries/getPositions.graphql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
query GetPositions {
|
||||
positions {
|
||||
id
|
||||
owner
|
||||
share
|
||||
creationTime
|
||||
lastTaxTime
|
||||
taxRate
|
||||
status
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue