@kehtus/proportion-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SDK_IMPROVEMENT_PLAN.md +197 -0
- package/arch.md +448 -0
- package/dist/__tests__/account.test.d.ts +1 -0
- package/dist/__tests__/account.test.js +36 -0
- package/dist/__tests__/indexer.test.d.ts +1 -0
- package/dist/__tests__/indexer.test.js +230 -0
- package/dist/__tests__/lifecycle.test.d.ts +1 -0
- package/dist/__tests__/lifecycle.test.js +186 -0
- package/dist/__tests__/reads.test.d.ts +1 -0
- package/dist/__tests__/reads.test.js +185 -0
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +185 -0
- package/dist/abi/factory.d.ts +308 -0
- package/dist/abi/factory.js +399 -0
- package/dist/abi/fundedAccount.d.ts +1074 -0
- package/dist/abi/fundedAccount.js +1373 -0
- package/dist/abi/mainVault.d.ts +1448 -0
- package/dist/abi/mainVault.js +1865 -0
- package/dist/account.d.ts +55 -0
- package/dist/account.js +290 -0
- package/dist/constants.d.ts +48 -0
- package/dist/constants.js +42 -0
- package/dist/factory.d.ts +23 -0
- package/dist/factory.js +139 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +14 -0
- package/dist/indexer.d.ts +57 -0
- package/dist/indexer.js +540 -0
- package/dist/sdk.d.ts +22 -0
- package/dist/sdk.js +89 -0
- package/dist/types.d.ts +384 -0
- package/dist/types.js +11 -0
- package/dist/utils.d.ts +21 -0
- package/dist/utils.js +89 -0
- package/dist/vault.d.ts +60 -0
- package/dist/vault.js +429 -0
- package/package.json +32 -0
- package/src/__tests__/account.test.ts +40 -0
- package/src/__tests__/indexer.test.ts +255 -0
- package/src/__tests__/lifecycle.test.ts +231 -0
- package/src/__tests__/reads.test.ts +209 -0
- package/src/__tests__/utils.test.ts +245 -0
- package/src/abi/factory.ts +399 -0
- package/src/abi/fundedAccount.ts +1373 -0
- package/src/abi/mainVault.ts +1865 -0
- package/src/account.ts +339 -0
- package/src/constants.ts +50 -0
- package/src/factory.ts +157 -0
- package/src/index.ts +16 -0
- package/src/indexer.ts +636 -0
- package/src/sdk.ts +93 -0
- package/src/types.ts +426 -0
- package/src/utils.ts +143 -0
- package/src/vault.ts +479 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# План улучшения Proportion SDK
|
|
2
|
+
|
|
3
|
+
## Статус после повторной сверки
|
|
4
|
+
|
|
5
|
+
Этот документ обновлён после повторной проверки against:
|
|
6
|
+
|
|
7
|
+
- `contracts/MainVault.sol`
|
|
8
|
+
- `contracts/FundedAccount.sol`
|
|
9
|
+
- `contracts/FundedAccountFactory.sol`
|
|
10
|
+
- `prop-sdk/src/*`
|
|
11
|
+
- критичные indexer-файлы `envio-funded/schema.graphql` и `envio-funded/src/EventHandlers.ts`
|
|
12
|
+
|
|
13
|
+
Если этот план и Solidity расходятся, Solidity остаётся source of truth.
|
|
14
|
+
|
|
15
|
+
## Что уже закрыто
|
|
16
|
+
|
|
17
|
+
### Контрактная синхронизация
|
|
18
|
+
|
|
19
|
+
- ABI в SDK синхронизированы с текущими локальными контрактами.
|
|
20
|
+
- Формулы `activationFee`, `subscriptionFee`, `splitProfit`, `trailingFloor`, `dailyFloor` приведены к актуальной Solidity-логике.
|
|
21
|
+
- `arch.md` обновлён и теперь описывает текущий surface SDK, а не старую версию контрактов.
|
|
22
|
+
|
|
23
|
+
### VaultModule
|
|
24
|
+
|
|
25
|
+
- Добавлены admin setters для текущих mutable параметров vault:
|
|
26
|
+
- `setFeeMultiplier`
|
|
27
|
+
- `setSubscriptionFeeMultiplier`
|
|
28
|
+
- `setHypeForSpot`
|
|
29
|
+
- `setMaxCap`
|
|
30
|
+
- `setMinBuilderShares`
|
|
31
|
+
- `setBuilderLeverage`
|
|
32
|
+
- `setProtocolFeeBps`
|
|
33
|
+
- `setMinAccountSize`
|
|
34
|
+
- `setMaxAccountSize`
|
|
35
|
+
- `setMinDrawdownBps`
|
|
36
|
+
- `setMaxDrawdownBps`
|
|
37
|
+
- `setMaxUtilizationBps`
|
|
38
|
+
- `setMaxSingleAccountBps`
|
|
39
|
+
- `getStats()` расширен и читает текущий конфиг и runtime state vault.
|
|
40
|
+
- Добавлены utility getters:
|
|
41
|
+
- `isFundedAccount`
|
|
42
|
+
- `getDelegateBuilder`
|
|
43
|
+
- `getRemainingCap`
|
|
44
|
+
- `activateFunded()` больше не полагается на stale native fee default и читает `hypeForSpot()` из vault.
|
|
45
|
+
|
|
46
|
+
### AccountModule
|
|
47
|
+
|
|
48
|
+
- Добавлен `initiateCloseAdmin`.
|
|
49
|
+
- Добавлен `getAccountConfig()`.
|
|
50
|
+
- `getDetails()` теперь возвращает `subscriptionFeeMultiplier`, `checkpointIndex` и нормализованные checkpoints вместо сырого ring-buffer порядка.
|
|
51
|
+
- Текущий write surface выровнен с локальным `FundedAccount.sol`.
|
|
52
|
+
|
|
53
|
+
### FactoryModule
|
|
54
|
+
|
|
55
|
+
- SDK переведён на реальный on-chain surface новой фабрики:
|
|
56
|
+
- `allAccounts(index)`
|
|
57
|
+
- `allAccountsIndex(account)`
|
|
58
|
+
- `traderBuilderNonce(owner, builder)`
|
|
59
|
+
- `predictFundedAccountAddress(owner, builder)`
|
|
60
|
+
- Сохранены совместимые helper-методы `getAccountsCount()` и `getAccounts()`, но теперь они реализованы поверх probing `allAccounts(index)`.
|
|
61
|
+
- Добавлены safe helpers:
|
|
62
|
+
- `hasAccount`
|
|
63
|
+
- `findAccountIndex`
|
|
64
|
+
|
|
65
|
+
### Indexer Layer
|
|
66
|
+
|
|
67
|
+
- GraphQL-типы приведены к реальной `schema.graphql`.
|
|
68
|
+
- Разделены on-chain state labels и indexer state labels.
|
|
69
|
+
- Учтён `POSITION_LEVERAGE`.
|
|
70
|
+
- Учтены дополнительные индексируемые поля вроде `subscriptionFeeMultiplier`, `lastWithdrawalId`, `totalAllocated`, `totalShares`.
|
|
71
|
+
- Vault analytics больше не должны опираться на жёстко зашитый 90/10 split: актуальны `protocolFeeReceiver`, mutable `PROTOCOL_FEE_BPS` и фактический amount, остающийся в vault.
|
|
72
|
+
- Для service/backend use cases добавлена инъекция `fetchFn` и `webSocketCtor`, чтобы SDK не зависел жёстко от глобальных runtime объектов.
|
|
73
|
+
|
|
74
|
+
## Что в старом плане устарело
|
|
75
|
+
|
|
76
|
+
### Устаревшие статусы
|
|
77
|
+
|
|
78
|
+
Следующие пункты были верны для прошлой версии SDK, но уже закрыты:
|
|
79
|
+
|
|
80
|
+
- отсутствие `initiateCloseAdmin`
|
|
81
|
+
- отсутствие `getAccountConfig`
|
|
82
|
+
- отсутствие большинства admin setters в `VaultModule`
|
|
83
|
+
- неполный `VaultStats`
|
|
84
|
+
- отсутствие `isFundedAccount` и `getDelegateBuilder`
|
|
85
|
+
|
|
86
|
+
### Устаревшие константы
|
|
87
|
+
|
|
88
|
+
В старом плане были значения, которые больше не совпадают с текущими контрактами:
|
|
89
|
+
|
|
90
|
+
- `MIN_PROFIT_BPS` указан как `150`, но в текущем `FundedAccount.sol` он равен `50`
|
|
91
|
+
- важные экспортируемые значения нужно сверять с локальной Solidity, а не со старым plan draft
|
|
92
|
+
|
|
93
|
+
### Необязательная рекомендация про отдельный `AdminModule`
|
|
94
|
+
|
|
95
|
+
Идея отдельного `AdminModule` сама по себе нормальная, но сейчас это не must-have.
|
|
96
|
+
Текущий `VaultModule` уже покрывает актуальный owner admin surface без архитектурного долга, который требовал бы срочного выделения нового модуля.
|
|
97
|
+
|
|
98
|
+
## Что остаётся актуальным
|
|
99
|
+
|
|
100
|
+
### Phase 2. Улучшения API и DX
|
|
101
|
+
|
|
102
|
+
1. Выровнять числовую модель SDK для service use cases.
|
|
103
|
+
Сейчас часть helper-ов (`fromUsdc`, `from8Dec`, `formatUsd`) переводит `bigint` в `number`, что удобно для UI, но не идеально для финансовой серверной логики.
|
|
104
|
+
|
|
105
|
+
2. Выровнять timestamp-модель.
|
|
106
|
+
Сейчас часть временных полей в `AccountDetails` хранится как `bigint`, часть как `number`. Лучше выбрать одну ясную стратегию API.
|
|
107
|
+
|
|
108
|
+
3. Добавить типы для параметризуемого admin-конфига.
|
|
109
|
+
Практичный следующий шаг:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
export interface VaultParameters {
|
|
113
|
+
feeMultiplier: number;
|
|
114
|
+
subscriptionFeeMultiplier: number;
|
|
115
|
+
minBuilderShares: bigint;
|
|
116
|
+
builderLeverage: bigint;
|
|
117
|
+
protocolFeeBps: bigint;
|
|
118
|
+
minAccountSize: bigint;
|
|
119
|
+
maxAccountSize: bigint;
|
|
120
|
+
minDrawdownBps: number;
|
|
121
|
+
maxDrawdownBps: number;
|
|
122
|
+
maxUtilizationBps: bigint;
|
|
123
|
+
maxSingleAccountBps: bigint;
|
|
124
|
+
hypeForSpot: bigint;
|
|
125
|
+
maxCap: bigint;
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
4. Добавить validation helpers поверх текущего `VaultStats`.
|
|
130
|
+
Это уже полезный SDK-level слой, который не зависит от деплоя:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
export function validateAccountSize(size: bigint, vaultStats: VaultStats): {
|
|
134
|
+
valid: boolean;
|
|
135
|
+
reason?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function validateDrawdownBps(bps: number, vaultStats: VaultStats): {
|
|
139
|
+
valid: boolean;
|
|
140
|
+
reason?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function canBuilderActivate(
|
|
144
|
+
builderInfo: BuilderInfo,
|
|
145
|
+
accountSize: bigint,
|
|
146
|
+
vaultStats: VaultStats
|
|
147
|
+
): {
|
|
148
|
+
canActivate: boolean;
|
|
149
|
+
reason?: string;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Phase 3. Дополнительные helper-обёртки
|
|
154
|
+
|
|
155
|
+
1. Batch reads:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
async getMultipleLPPositions(addresses: Address[]): Promise<LPPosition[]>
|
|
159
|
+
async getMultipleBuilderInfo(addresses: Address[]): Promise<BuilderInfo[]>
|
|
160
|
+
async getMultipleAccountDetails(addresses: Address[]): Promise<AccountDetails[]>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
2. Дополнительные indexer helpers:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
async getEventsByDateRange(
|
|
167
|
+
accountAddress: Address,
|
|
168
|
+
fromDate: Date,
|
|
169
|
+
toDate: Date
|
|
170
|
+
): Promise<GqlAccountEvent[]>
|
|
171
|
+
|
|
172
|
+
async getCheckpointHistory(
|
|
173
|
+
accountAddress: Address,
|
|
174
|
+
limit?: number
|
|
175
|
+
): Promise<GqlCheckpoint[]>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
3. Higher-level admin ergonomics:
|
|
179
|
+
|
|
180
|
+
- `getCurrentParameters()`
|
|
181
|
+
- `validateParameters()`
|
|
182
|
+
- опциональный bulk-update helper для owner workflows
|
|
183
|
+
|
|
184
|
+
## Рекомендуемый следующий порядок работ
|
|
185
|
+
|
|
186
|
+
1. Сначала привести numeric/timestamp API к более строгой и единообразной модели.
|
|
187
|
+
2. Затем добавить validation helpers поверх уже актуального `VaultStats`.
|
|
188
|
+
3. После этого расширять batch helpers и high-level admin ergonomics.
|
|
189
|
+
|
|
190
|
+
## Проверка
|
|
191
|
+
|
|
192
|
+
После последних изменений:
|
|
193
|
+
|
|
194
|
+
- `npm run check` проходит
|
|
195
|
+
- `npm run test:unit` проходит
|
|
196
|
+
|
|
197
|
+
RPC/GraphQL-зависимые тесты не считаются источником истины, пока локальные контракты не задеплоены.
|
package/arch.md
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# Proportion SDK
|
|
2
|
+
|
|
3
|
+
TypeScript SDK over the current local Proportion Solidity contracts.
|
|
4
|
+
|
|
5
|
+
This document is intentionally aligned to the code in:
|
|
6
|
+
|
|
7
|
+
- `contracts/MainVault.sol`
|
|
8
|
+
- `contracts/FundedAccount.sol`
|
|
9
|
+
- `contracts/FundedAccountFactory.sol`
|
|
10
|
+
- `prop-sdk/src/*`
|
|
11
|
+
|
|
12
|
+
If documentation and Solidity diverge, Solidity is the source of truth.
|
|
13
|
+
|
|
14
|
+
## Architecture
|
|
15
|
+
|
|
16
|
+
The SDK has four runtime modules:
|
|
17
|
+
|
|
18
|
+
- `vault`: `MainVault` reads, LP flows, builder flows, and owner admin writes.
|
|
19
|
+
- `account`: `FundedAccount` reads and writes for a specific funded account clone.
|
|
20
|
+
- `factory`: `FundedAccountFactory` helpers around deterministic account addresses and factory state.
|
|
21
|
+
- `indexer`: optional Envio GraphQL/WebSocket client for off-chain querying and subscriptions.
|
|
22
|
+
|
|
23
|
+
`ProportionSDK` itself is a thin composition layer over `viem`.
|
|
24
|
+
|
|
25
|
+
## Initialization
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { ProportionSDK, hyperEvm } from "@proportion/sdk";
|
|
29
|
+
import { createWalletClient, http } from "viem";
|
|
30
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
31
|
+
|
|
32
|
+
const wallet = createWalletClient({
|
|
33
|
+
account: privateKeyToAccount("0x..."),
|
|
34
|
+
chain: hyperEvm,
|
|
35
|
+
transport: http(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const sdk = new ProportionSDK({
|
|
39
|
+
graphqlUrl: "https://your-envio-endpoint/graphql",
|
|
40
|
+
walletClient: wallet,
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
There are two construction modes:
|
|
45
|
+
|
|
46
|
+
- `new ProportionSDK(config)`: uses addresses from `config.addresses` or `src/constants.ts`.
|
|
47
|
+
- `await ProportionSDK.create(config)`: resolves addresses in this order:
|
|
48
|
+
1. `config.addresses`
|
|
49
|
+
2. `PROPORTION_VAULT_ADDRESS`, `PROPORTION_FACTORY_ADDRESS`, `PROPORTION_USDC_ADDRESS`
|
|
50
|
+
3. `indexer.getProtocolConfig()`
|
|
51
|
+
4. if vault is already known but `USDC` is missing, `usdc()` is read from `MainVault`
|
|
52
|
+
5. hardcoded SDK defaults
|
|
53
|
+
|
|
54
|
+
Best practice:
|
|
55
|
+
|
|
56
|
+
- Prefer explicit `config.addresses` or ENV in applications and services.
|
|
57
|
+
- Treat `indexer.getProtocolConfig()` as a convenience fallback, not as the primary source of deployment addresses.
|
|
58
|
+
- Use `new ProportionSDK(config)` when you already know the full address set and want direct construction without async resolution.
|
|
59
|
+
|
|
60
|
+
## Config
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
interface SDKConfig {
|
|
64
|
+
graphqlUrl: string;
|
|
65
|
+
graphqlWsUrl?: string;
|
|
66
|
+
rpcUrl?: string;
|
|
67
|
+
walletClient?: WalletClient;
|
|
68
|
+
fetchFn?: typeof fetch;
|
|
69
|
+
webSocketCtor?: WebSocketConstructor;
|
|
70
|
+
addresses?: {
|
|
71
|
+
MAIN_VAULT?: string;
|
|
72
|
+
FACTORY?: string;
|
|
73
|
+
USDC?: string;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
For service or server integrations, `fetchFn` and `webSocketCtor` can be injected explicitly instead of relying on global runtime objects.
|
|
79
|
+
|
|
80
|
+
The exported addresses in `src/constants.ts` are SDK defaults for the current environment, not a guarantee of future deployments.
|
|
81
|
+
|
|
82
|
+
## Modules
|
|
83
|
+
|
|
84
|
+
### `prop.vault`
|
|
85
|
+
|
|
86
|
+
Reads:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const stats = await prop.vault.getStats();
|
|
90
|
+
const position = await prop.vault.getLPPosition("0x...");
|
|
91
|
+
const builder = await prop.vault.getBuilderInfo("0x...");
|
|
92
|
+
const perps = await prop.vault.getAllowedPerps();
|
|
93
|
+
const allowed = await prop.vault.isAllowedPerp(0);
|
|
94
|
+
const fee = await prop.vault.calculateFee(toUsdc(5000), 500, false);
|
|
95
|
+
const paused = await prop.vault.isPaused();
|
|
96
|
+
const remainingCap = await prop.vault.getRemainingCap();
|
|
97
|
+
const isFa = await prop.vault.isFundedAccount("0x...");
|
|
98
|
+
const delegatedTo = await prop.vault.getDelegateBuilder("0x...");
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`getStats()` returns both operational metrics and current mutable vault config:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
type VaultStats = {
|
|
105
|
+
totalAssets: bigint;
|
|
106
|
+
sharePrice: bigint;
|
|
107
|
+
utilizationBps: bigint;
|
|
108
|
+
availableForAllocation: bigint;
|
|
109
|
+
maxAccountSize: bigint;
|
|
110
|
+
remainingCap: bigint;
|
|
111
|
+
paused: boolean;
|
|
112
|
+
feeMultiplier: number;
|
|
113
|
+
subscriptionFeeMultiplier: number;
|
|
114
|
+
totalShares: bigint;
|
|
115
|
+
totalAllocated: bigint;
|
|
116
|
+
owner: Address;
|
|
117
|
+
pendingOwner: Address;
|
|
118
|
+
relayer: Address;
|
|
119
|
+
protocolFeeReceiver: Address;
|
|
120
|
+
maxCap: bigint;
|
|
121
|
+
hypeForSpot: bigint;
|
|
122
|
+
factory: Address;
|
|
123
|
+
minBuilderShares: bigint;
|
|
124
|
+
builderLeverage: bigint;
|
|
125
|
+
protocolFeeBps: bigint;
|
|
126
|
+
minAccountSize: bigint;
|
|
127
|
+
maxAccountSizeLimit: bigint;
|
|
128
|
+
minDrawdownBps: number;
|
|
129
|
+
maxDrawdownBps: number;
|
|
130
|
+
maxUtilizationBps: bigint;
|
|
131
|
+
maxSingleAccountBps: bigint;
|
|
132
|
+
dailyDdDiscountBps: number;
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Writes:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
await prop.vault.approveUsdc(toUsdc(1000));
|
|
140
|
+
await prop.vault.deposit(toUsdc(1000));
|
|
141
|
+
await prop.vault.withdraw(shareAmount);
|
|
142
|
+
|
|
143
|
+
await prop.vault.registerBuilder();
|
|
144
|
+
await prop.vault.setDelegate("0x...");
|
|
145
|
+
await prop.vault.removeDelegate("0x...");
|
|
146
|
+
|
|
147
|
+
await prop.vault.activateFunded({
|
|
148
|
+
trader: "0x...",
|
|
149
|
+
accountSize: toUsdc(5000),
|
|
150
|
+
drawdownBps: 500,
|
|
151
|
+
dailyDrawdownEnabled: false,
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`activateFunded()` now reads `hypeForSpot()` from the vault when `hypeValue` is not supplied, so the SDK follows the current contract config instead of hardcoding a stale native value.
|
|
156
|
+
|
|
157
|
+
Admin writes covered by the SDK:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
await prop.vault.pause();
|
|
161
|
+
await prop.vault.unpause();
|
|
162
|
+
await prop.vault.setRelayer("0x...");
|
|
163
|
+
await prop.vault.setFactory("0x...");
|
|
164
|
+
await prop.vault.setProtocolFeeReceiver("0x...");
|
|
165
|
+
await prop.vault.setFeeMultiplier(120);
|
|
166
|
+
await prop.vault.setSubscriptionFeeMultiplier(150);
|
|
167
|
+
await prop.vault.setHypeForSpot(1_000_000_000_000_000n);
|
|
168
|
+
await prop.vault.setMaxCap(0n);
|
|
169
|
+
await prop.vault.setMinBuilderShares(toUsdc(10));
|
|
170
|
+
await prop.vault.setBuilderLeverage(10n);
|
|
171
|
+
await prop.vault.setProtocolFeeBps(1000n);
|
|
172
|
+
await prop.vault.setMinAccountSize(toUsdc(10));
|
|
173
|
+
await prop.vault.setMaxAccountSize(toUsdc(50_000));
|
|
174
|
+
await prop.vault.setMinDrawdownBps(500);
|
|
175
|
+
await prop.vault.setMaxDrawdownBps(2500);
|
|
176
|
+
await prop.vault.setMaxUtilizationBps(8000n);
|
|
177
|
+
await prop.vault.setMaxSingleAccountBps(1000n);
|
|
178
|
+
await prop.vault.setDailyDdDiscountBps(2000);
|
|
179
|
+
await prop.vault.addAllowedPerp(4);
|
|
180
|
+
await prop.vault.removeAllowedPerp(4);
|
|
181
|
+
await prop.vault.transferOwnership("0x...");
|
|
182
|
+
await prop.vault.acceptOwnership();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `prop.account`
|
|
186
|
+
|
|
187
|
+
Reads:
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
const details = await prop.account.getDetails("0xAccount");
|
|
191
|
+
const state = await prop.account.getState("0xAccount");
|
|
192
|
+
const trailing = await prop.account.getTrailingFloor("0xAccount");
|
|
193
|
+
const daily = await prop.account.getDailyFloor("0xAccount");
|
|
194
|
+
const canWithdraw = await prop.account.canWithdrawProfit("0xAccount");
|
|
195
|
+
const subActive = await prop.account.isSubscriptionActive("0xAccount");
|
|
196
|
+
const checkpoints = await prop.account.getCheckpoints("0xAccount");
|
|
197
|
+
const pending = await prop.account.getPendingWithdrawal("0xAccount");
|
|
198
|
+
const config = await prop.account.getAccountConfig("0xAccount");
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`getDetails()` includes the current per-account `subscriptionFeeMultiplier`, exposes `checkpointIndex`, and normalizes the on-chain checkpoint ring buffer into chronological order.
|
|
202
|
+
|
|
203
|
+
Writes:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
await prop.account.setApiWallet("0xAccount", "0xWallet");
|
|
207
|
+
await prop.account.setRiskEngineWallet("0xAccount", "0xWallet");
|
|
208
|
+
await prop.account.requestWithdrawProfit("0xAccount");
|
|
209
|
+
await prop.account.cancelSubscription("0xAccount");
|
|
210
|
+
await prop.account.cancelStuckWithdrawal("0xAccount");
|
|
211
|
+
|
|
212
|
+
await prop.account.checkpoint("0xAccount");
|
|
213
|
+
await prop.account.initiateClose("0xAccount");
|
|
214
|
+
await prop.account.initiateCloseForPerp("0xAccount", 0);
|
|
215
|
+
await prop.account.initiateCloseAdmin("0xAccount");
|
|
216
|
+
await prop.account.closeAndReturn("0xAccount");
|
|
217
|
+
await prop.account.finalizeReturn("0xAccount");
|
|
218
|
+
await prop.account.claimWithdrawal("0xAccount");
|
|
219
|
+
await prop.account.claimSubscriptionFee("0xAccount");
|
|
220
|
+
await prop.account.rescueStuckFunds("0xAccount");
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`setRiskEngineWallet()` maps to the current contract behavior in which the same method is used by the protocol relayer and by the builder; on-chain access control decides which of `riskEngineWallet` or `builderRiskEngineWallet` is updated.
|
|
224
|
+
|
|
225
|
+
### `prop.factory`
|
|
226
|
+
|
|
227
|
+
Canonical on-chain surface of the current factory:
|
|
228
|
+
|
|
229
|
+
- `predictFundedAccountAddress(owner, builder)`
|
|
230
|
+
- `allAccounts(index)`
|
|
231
|
+
- `allAccountsIndex(account)`
|
|
232
|
+
- `traderBuilderNonce(owner, builder)`
|
|
233
|
+
- public config getters such as `implementation`, `owner`, `pendingOwner`, `usdc`, and `vault`
|
|
234
|
+
|
|
235
|
+
SDK helpers:
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
const predicted = await prop.factory.predictAddress(owner, builder);
|
|
239
|
+
const nonce = await prop.factory.getTraderBuilderNonce(owner, builder);
|
|
240
|
+
const account0 = await prop.factory.getAccountAt(0n);
|
|
241
|
+
const index = await prop.factory.getAccountIndex(account0);
|
|
242
|
+
const exists = await prop.factory.hasAccount(account0);
|
|
243
|
+
const safeIndex = await prop.factory.findAccountIndex(account0);
|
|
244
|
+
const cfg = await prop.factory.getConfig();
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
For backwards compatibility, the SDK still exposes:
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const count = await prop.factory.getAccountsCount();
|
|
251
|
+
const page = await prop.factory.getAccounts(0n, 10n);
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
These are now implemented in the SDK by probing `allAccounts(index)` because the current Solidity factory does not expose explicit `getAccountsCount()` or paginated `getAccounts()` functions.
|
|
255
|
+
|
|
256
|
+
### `prop.indexer`
|
|
257
|
+
|
|
258
|
+
GraphQL queries:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
await prop.indexer.getVault();
|
|
262
|
+
await prop.indexer.getLP("0x...");
|
|
263
|
+
await prop.indexer.getLPDeposits("0x...", { limit: 20, offset: 0 });
|
|
264
|
+
await prop.indexer.getLPWithdrawals("0x...");
|
|
265
|
+
await prop.indexer.getBuilder("0x...");
|
|
266
|
+
await prop.indexer.getBuilder("0x...", "0xVault...");
|
|
267
|
+
await prop.indexer.getDelegates("0x...");
|
|
268
|
+
await prop.indexer.getDelegates("0x...", "0xVault...");
|
|
269
|
+
await prop.indexer.getAccount("0x...");
|
|
270
|
+
await prop.indexer.getAccountsByOwner("0x...");
|
|
271
|
+
await prop.indexer.getAccountsByBuilder("0x...");
|
|
272
|
+
await prop.indexer.getAccountsByBuilder("0x...", "0xVault...");
|
|
273
|
+
await prop.indexer.getAccountsByState("ACTIVE");
|
|
274
|
+
await prop.indexer.getAccountsByStates(["ACTIVE", "CLOSING"]);
|
|
275
|
+
await prop.indexer.getAccountsUpdatedSince("1709500000");
|
|
276
|
+
await prop.indexer.getCheckpoints("0x...");
|
|
277
|
+
await prop.indexer.getWithdrawals("0x...");
|
|
278
|
+
await prop.indexer.getSubscriptionPayments("0x...");
|
|
279
|
+
await prop.indexer.getEvents("0x...");
|
|
280
|
+
await prop.indexer.getEventsByType("Initialized", 5);
|
|
281
|
+
await prop.indexer.getProtocolConfig();
|
|
282
|
+
await prop.indexer.getMainVaultConfig();
|
|
283
|
+
await prop.indexer.getFactoryConfig();
|
|
284
|
+
await prop.indexer.getConfigChanges({ field: "protocolFeeBps", limit: 20 });
|
|
285
|
+
await prop.indexer.getVaultDayData();
|
|
286
|
+
await prop.indexer.getVaultDayData("0xVault...", { limit: 30 });
|
|
287
|
+
await prop.indexer.getLPDayData("0x...");
|
|
288
|
+
await prop.indexer.getAllowedPerps();
|
|
289
|
+
await prop.indexer.getAllowedPerps("0xVault...");
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
When `vaultAddress` is omitted, `getVault()`, `getMainVaultConfig()`, `getBuilder()`, `getDelegates()`, `getAccountsByBuilder()`, `getVaultDayData()`, and `getAllowedPerps()` default to the active vault from `ProtocolConfig`.
|
|
293
|
+
This is a convenience default only; production callers can pass `vaultAddress` explicitly to avoid hidden address resolution.
|
|
294
|
+
|
|
295
|
+
When `factoryAddress` is omitted, `getFactoryConfig()` defaults to the active factory from `ProtocolConfig`.
|
|
296
|
+
This is a convenience default only; production callers can pass `factoryAddress` explicitly to avoid hidden address resolution.
|
|
297
|
+
|
|
298
|
+
The SDK indexer layer follows `envio-funded/schema.graphql`, not the on-chain enum surface directly. That means:
|
|
299
|
+
|
|
300
|
+
- indexer account states start at `INITIALIZED`, not `UNINITIALIZED`
|
|
301
|
+
- `breachType` includes `POSITION_LEVERAGE`
|
|
302
|
+
- the SDK exposes address-keyed config/history entities like `MainVaultConfig`, `FactoryConfig`, `ConfigChange`, and `VaultDayData`
|
|
303
|
+
- `Vault` and `FundedAccount` GraphQL objects expose extra indexed fields such as `totalAllocated`, `totalShares`, `subscriptionFeeMultiplier`, `lastWithdrawalId`, `closePendingSubFee`, `closePendingProfit`, `cumulativeActivationFeesToVault`, `cumulativeCancelledProfit`, `cumulativeProtocolFeesRouted`, `cumulativeHypercoreFees`, `cumulativeRescuedFunds`, `protocolFeeBps`, and `protocolFeeReceiver`
|
|
304
|
+
- `LiquidityProviderDayData` exists in the current schema and is wrapped by the SDK, but the current indexer handlers do not populate it yet, so queries may legitimately return empty arrays
|
|
305
|
+
|
|
306
|
+
Subscriptions:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
const unsubActive = prop.indexer.subscribeActiveAccounts((accounts) => {});
|
|
310
|
+
const unsubAccount = prop.indexer.subscribeAccount("0x...", (account) => {});
|
|
311
|
+
const unsubEvents = prop.indexer.subscribeAccountEvents("0x...", (events) => {});
|
|
312
|
+
const unsubStates = prop.indexer.subscribeStateChanges((accounts) => {});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Call `prop.disconnect()` to close all WebSocket state.
|
|
316
|
+
|
|
317
|
+
## Utility Math
|
|
318
|
+
|
|
319
|
+
The utility layer mirrors the current contract formulas.
|
|
320
|
+
|
|
321
|
+
Decimals:
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
toUsdc(100); // 100_000_000n
|
|
325
|
+
fromUsdc(100_000_000n); // 100
|
|
326
|
+
to8Dec(100_000_000n); // 10_000_000_000n
|
|
327
|
+
toUsdc6(10_000_000_000n); // 100_000_000n
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Activation fee:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
activationFee(toUsdc(5000), 500, false, 120); // 300 USDC
|
|
334
|
+
activationFee(toUsdc(5000), 500, true, 120, 2000); // 240 USDC
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Formula:
|
|
338
|
+
|
|
339
|
+
```text
|
|
340
|
+
accountSize * drawdownBps * feeMultiplier / 1_000_000
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
The helper then rounds up to whole USDC units, matching the current contract behavior.
|
|
344
|
+
|
|
345
|
+
If daily drawdown is enabled, the helper applies `dailyDdDiscountBps`:
|
|
346
|
+
|
|
347
|
+
```text
|
|
348
|
+
fee * (10000 - dailyDdDiscountBps) / 10000
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
`activationFee()` defaults `dailyDdDiscountBps` from `src/constants.ts`.
|
|
352
|
+
For exact live parity with a deployed vault, pass `vault.getStats().dailyDdDiscountBps`.
|
|
353
|
+
|
|
354
|
+
Subscription fee:
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
subscriptionFee(toUsdc(5000), 150); // 75 USDC
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Canonical formula:
|
|
361
|
+
|
|
362
|
+
```text
|
|
363
|
+
accountSize * subscriptionFeeMultiplier / 10000
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The helper rounds the result up to whole USDC units.
|
|
367
|
+
|
|
368
|
+
The helper still accepts the legacy 4-argument signature for compatibility, but drawdown and daily-DD no longer affect the result because the current contracts do not use them in subscription pricing.
|
|
369
|
+
|
|
370
|
+
Profit split:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
const { owner, vault, protocol } = splitProfit(toUsdc(1000));
|
|
374
|
+
// owner = 800
|
|
375
|
+
// vault = 130
|
|
376
|
+
// protocol = 70
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
State labels:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
stateToLabel(AccountState.Uninitialized); // "UNINITIALIZED"
|
|
383
|
+
labelToState("CLOSING"); // AccountState.Closing
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Defaults vs Dynamic Config
|
|
387
|
+
|
|
388
|
+
Two categories exist in the SDK:
|
|
389
|
+
|
|
390
|
+
- Static source defaults exported from `src/constants.ts`.
|
|
391
|
+
- Mutable on-chain config read from `MainVault`.
|
|
392
|
+
|
|
393
|
+
Examples of values that can change on-chain through admin setters:
|
|
394
|
+
|
|
395
|
+
- `feeMultiplier`
|
|
396
|
+
- `subscriptionFeeMultiplier`
|
|
397
|
+
- `MIN_BUILDER_SHARES`
|
|
398
|
+
- `BUILDER_LEVERAGE`
|
|
399
|
+
- `PROTOCOL_FEE_BPS`
|
|
400
|
+
- `MIN_ACCOUNT_SIZE`
|
|
401
|
+
- `MAX_ACCOUNT_SIZE`
|
|
402
|
+
- `MIN_DRAWDOWN_BPS`
|
|
403
|
+
- `MAX_DRAWDOWN_BPS`
|
|
404
|
+
- `MAX_UTILIZATION_BPS`
|
|
405
|
+
- `MAX_SINGLE_ACCOUNT_BPS`
|
|
406
|
+
- `dailyDdDiscountBps`
|
|
407
|
+
- `hypeForSpot`
|
|
408
|
+
- `maxCap`
|
|
409
|
+
|
|
410
|
+
Best practice:
|
|
411
|
+
|
|
412
|
+
- Use exported constants for local validation defaults and tests.
|
|
413
|
+
- Use `vault.getStats()` before showing risk, pricing, or activation constraints to users.
|
|
414
|
+
|
|
415
|
+
## Testing
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
npm run check
|
|
419
|
+
npm run test:unit
|
|
420
|
+
npm run test:reads
|
|
421
|
+
npm run test:lifecycle
|
|
422
|
+
npm run test:indexer
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Notes:
|
|
426
|
+
|
|
427
|
+
- `test:unit` is fully local.
|
|
428
|
+
- `test:reads`, `test:lifecycle`, and `test:indexer` require reachable RPC/GraphQL endpoints.
|
|
429
|
+
- Current Foundry tests in `/test` are still on an older contract API and should not be treated as SDK source of truth until updated.
|
|
430
|
+
|
|
431
|
+
## File Layout
|
|
432
|
+
|
|
433
|
+
```text
|
|
434
|
+
src/
|
|
435
|
+
sdk.ts
|
|
436
|
+
vault.ts
|
|
437
|
+
account.ts
|
|
438
|
+
factory.ts
|
|
439
|
+
indexer.ts
|
|
440
|
+
types.ts
|
|
441
|
+
constants.ts
|
|
442
|
+
utils.ts
|
|
443
|
+
index.ts
|
|
444
|
+
abi/
|
|
445
|
+
mainVault.ts
|
|
446
|
+
fundedAccount.ts
|
|
447
|
+
factory.ts
|
|
448
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { normalizeCheckpoints } from "../account.js";
|
|
3
|
+
describe("normalizeCheckpoints", () => {
|
|
4
|
+
it("keeps linear order before the ring buffer wraps", () => {
|
|
5
|
+
const checkpoints = [
|
|
6
|
+
{ accountValue: 100n, timestamp: 1n, profitable: false },
|
|
7
|
+
{ accountValue: 110n, timestamp: 2n, profitable: true },
|
|
8
|
+
{ accountValue: 120n, timestamp: 3n, profitable: true },
|
|
9
|
+
{ accountValue: 0n, timestamp: 0n, profitable: false },
|
|
10
|
+
{ accountValue: 0n, timestamp: 0n, profitable: false },
|
|
11
|
+
{ accountValue: 0n, timestamp: 0n, profitable: false },
|
|
12
|
+
{ accountValue: 0n, timestamp: 0n, profitable: false },
|
|
13
|
+
];
|
|
14
|
+
expect(normalizeCheckpoints(checkpoints, 3, 3).map((cp) => cp.accountValue)).toEqual([100n, 110n, 120n]);
|
|
15
|
+
});
|
|
16
|
+
it("returns chronological order after the ring buffer wraps", () => {
|
|
17
|
+
const checkpoints = [
|
|
18
|
+
{ accountValue: 800n, timestamp: 8n, profitable: true },
|
|
19
|
+
{ accountValue: 200n, timestamp: 2n, profitable: false },
|
|
20
|
+
{ accountValue: 300n, timestamp: 3n, profitable: false },
|
|
21
|
+
{ accountValue: 400n, timestamp: 4n, profitable: false },
|
|
22
|
+
{ accountValue: 500n, timestamp: 5n, profitable: true },
|
|
23
|
+
{ accountValue: 600n, timestamp: 6n, profitable: true },
|
|
24
|
+
{ accountValue: 700n, timestamp: 7n, profitable: true },
|
|
25
|
+
];
|
|
26
|
+
expect(normalizeCheckpoints(checkpoints, 7, 1).map((cp) => cp.accountValue)).toEqual([
|
|
27
|
+
200n,
|
|
28
|
+
300n,
|
|
29
|
+
400n,
|
|
30
|
+
500n,
|
|
31
|
+
600n,
|
|
32
|
+
700n,
|
|
33
|
+
800n,
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "dotenv/config";
|