@aegis-engine/core 0.4.0 → 0.6.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/README.md CHANGED
@@ -31,8 +31,9 @@ the published Core package; consumers do not need GitHub Packages credentials.
31
31
  bounded session caches, and explicit indexed freshness.
32
32
  - Engine 12-hour market-metric history for optional rate, utilization, and
33
33
  liquidity charts.
34
- - Engine lend and borrow quotes, exact rounded LP-shaped debt/carry
35
- projections, collateral sizing, Permit2 preparation, and guarded execution.
34
+ - Engine lend, borrow, and directional Long/Short quotes; exact rounded
35
+ LP-shaped debt/carry projections; collateral sizing; Permit2 preparation;
36
+ and guarded execution.
36
37
 
37
38
  Direct pool swaps support `exactIn` and `exactOut`. KyberSwap routes currently
38
39
  support `exactIn` only because Kyber's route/build flow is exact-input.
@@ -46,8 +47,10 @@ Engine discovery and wallet positions default to partner-safe REST:
46
47
 
47
48
  ```ts
48
49
  import {
50
+ deriveEngineTradePositionStrategy,
49
51
  fetchEngineMarketHistory,
50
52
  fetchEngineMarkets,
53
+ fetchEnginePositionActivitySnapshot,
51
54
  fetchEnginePositionsSnapshot
52
55
  } from '@aegis-engine/core'
53
56
 
@@ -63,6 +66,16 @@ const positions = await fetchEnginePositionsSnapshot({
63
66
  chainId: 1,
64
67
  account
65
68
  })
69
+ const activity = await fetchEnginePositionActivitySnapshot({
70
+ source,
71
+ chainId: 1,
72
+ account
73
+ })
74
+ const position = positions.positions[0]
75
+ const vault = activity?.vaults.find((entry) => entry.vaultId === position?.vaultId)
76
+ const strategy = position && vault
77
+ ? deriveEngineTradePositionStrategy(position, vault)
78
+ : null
66
79
  const marketHistory = await fetchEngineMarketHistory({
67
80
  source,
68
81
  marketId: markets[0].id,
@@ -71,7 +84,10 @@ const marketHistory = await fetchEngineMarketHistory({
71
84
  ```
72
85
 
73
86
  Hosts that already own deterministic market/position metadata can instead use
74
- the typed `static` source. Engine market history remains a REST capability.
87
+ the typed `static` source. Supply `vaultActivity` when an offline fixture must
88
+ reconstruct Long/Short identity; position rows alone are ordinary borrow-vault
89
+ facts and are not enough to classify a trade. Engine market history remains a
90
+ REST capability.
75
91
 
76
92
  The REST service supplies facts and provenance. Core owns live RPC reads,
77
93
  collateral/debt math, quote freshness, recipe construction, simulation,
@@ -106,6 +122,124 @@ remaining collateral stays in the vault until a separate withdrawal. A
106
122
  one-token lend is a zap into a two-sided full-range LP, not single-token
107
123
  lending.
108
124
 
125
+ ### Simple-yield position summaries
126
+
127
+ Use `summarizeEngineSimpleYieldPosition` when a custom UI needs one honest,
128
+ React-free summary of an Engine lender-share position. The helper values live
129
+ shares first, falls back to trusted indexed shares, projects an optional added
130
+ USD amount, and converts realized share-price growth into a weekly estimate.
131
+
132
+ ```ts
133
+ import {
134
+ readEnginePositionsHydration,
135
+ summarizeEngineSimpleYieldPosition
136
+ } from '@aegis-engine/core'
137
+
138
+ const market = markets[0]
139
+ const positionsHydration = await readEnginePositionsHydration({
140
+ publicClient,
141
+ chainId: market.chainId,
142
+ expectedAccount: account,
143
+ positions: positions.positions,
144
+ lendPositions: positions.lendPositions
145
+ })
146
+ const position = positions.lendPositions.find(
147
+ (entry) =>
148
+ entry.chainId === market.chainId &&
149
+ entry.poolId.toLowerCase() === market.poolId.toLowerCase()
150
+ ) ?? null
151
+ const live = position
152
+ ? positionsHydration.lendPositions.find(
153
+ (entry) => entry.positionId === position.id
154
+ )
155
+ : undefined
156
+
157
+ const summary = summarizeEngineSimpleYieldPosition({
158
+ market,
159
+ position,
160
+ hydration: live,
161
+ history: marketHistory,
162
+ additionalValueUsd: 100
163
+ })
164
+ ```
165
+
166
+ `summary.valuation` identifies whether live or indexed shares produced the
167
+ current USD value. `summary.projection` is null when an existing position cannot
168
+ be valued, so an add-funds UI cannot mislabel it as a new deposit.
169
+ `summary.growth.weeklyEarningsPer100Usd` is a historical share-growth estimate,
170
+ not a promise. `market.metrics.lendApyPct` is the current modeled lender rate;
171
+ it is a different metric and is never substituted for realized history.
172
+
173
+ ### Directional Engine positions
174
+
175
+ `quoteEngineTradeOpen` sizes a fresh-vault Long or Short against one
176
+ block-pinned Engine and Uniswap v4 frame. It returns the selected average and
177
+ live leverage, exact opening LTV after the swap minimum, interest carry, risk
178
+ thresholds, gross return at the configured target, actionable solver limits,
179
+ and atomic router calldata when all execution gates pass.
180
+
181
+ Successful and leverage-bound quotes expose `limits.minLeverage` and
182
+ `limits.maxLeverage`. Treat these as live, request-specific bounds rather than
183
+ a protocol-wide constant.
184
+
185
+ `deriveEngineTradePositionStrategy` recognizes the indexed Tap opening action
186
+ shape and recovers its Long/Short direction and executed entry price after a
187
+ reload. The selected target and average-to-target leverage are not transaction
188
+ fields. A host can pass persisted intent records to augment those fields only
189
+ when chain, account, market, pool, transaction, and direction all match.
190
+ A no-swap opening has no indexed direction signal, so this helper deliberately
191
+ leaves it unclassified instead of trusting off-chain intent alone.
192
+
193
+ ```ts
194
+ import { quoteEngineTradeOpen } from '@aegis-engine/core'
195
+
196
+ const quote = await quoteEngineTradeOpen({
197
+ publicClient,
198
+ market,
199
+ owner: account,
200
+ direction: 'long',
201
+ collateralIn: 1_000_000n,
202
+ inputIsToken0: false,
203
+ leverage: 1.5,
204
+ horizonSeconds: 30 * 24 * 60 * 60,
205
+ priceMove: 0.2,
206
+ slippageBps: 100
207
+ })
208
+ ```
209
+
210
+ Treat `feasible` and `executable` separately. A feasible quote can still need
211
+ the trusted router's vault-operator grant or Permit2 authorization. After an
212
+ authorization transaction, request the submit quote with
213
+ `forceExecutionEvidenceRefresh: true`; then pass its fresh `tx` and `expiresAt`
214
+ to `sendEngineTransaction`. The prebuilt `AegisLeverageWidget` performs this
215
+ sequence for frontend integrations.
216
+
217
+ Use `readEngineAuthorizationPlan` to present the connected account's current
218
+ wallet sequence before submission:
219
+
220
+ ```ts
221
+ import { readEngineAuthorizationPlan } from '@aegis-engine/core'
222
+
223
+ const plan = await readEngineAuthorizationPlan({
224
+ publicClient,
225
+ market,
226
+ owner: account,
227
+ walletChainId,
228
+ token: market.token0,
229
+ amount: 1_000_000n,
230
+ approvalMode: 'exact'
231
+ })
232
+ ```
233
+
234
+ `missingPermissionCount` excludes the final open and any zero-first reset.
235
+ `walletPromptCount` includes every required sequential transaction.
236
+ `walletChainSwitchRequired` reports the network switch separately and is null
237
+ until the wallet chain is known. The plan is a read-only presentation snapshot, not authorization
238
+ evidence. Submission must still re-read permissions and obtain a fresh
239
+ executable quote. Exact ERC-20 and Permit2 limits are consumed by the successful
240
+ opening transfer; maximum limits and the VaultRegistry operator grant remain
241
+ active until revoked.
242
+
109
243
  ## Historical token prices
110
244
 
111
245
  Historical prices are intentionally separate from Engine market and
@@ -113,10 +247,7 @@ transaction truth. A host reads one exact token through the public REST
113
247
  backend without placing private data-service credentials in the client:
114
248
 
115
249
  ```ts
116
- import {
117
- AEGIS_BACKEND_PROD_BASE_URL,
118
- fetchTokenPriceHistory
119
- } from '@aegis-engine/core'
250
+ import { AEGIS_BACKEND_PROD_BASE_URL, fetchTokenPriceHistory } from '@aegis-engine/core'
120
251
 
121
252
  const history = await fetchTokenPriceHistory({
122
253
  baseUrl: AEGIS_BACKEND_PROD_BASE_URL,
@@ -189,10 +320,7 @@ const wallet = await resolveWalletSession({
189
320
  Create a read-side viem client from an Aegis chain config:
190
321
 
191
322
  ```ts
192
- import {
193
- AEGIS_SWAP_MAINNET_CHAIN,
194
- createSwapPublicClient
195
- } from '@aegis-engine/core'
323
+ import { AEGIS_SWAP_MAINNET_CHAIN, createSwapPublicClient } from '@aegis-engine/core'
196
324
 
197
325
  const publicClient = createSwapPublicClient(
198
326
  AEGIS_SWAP_MAINNET_CHAIN.chain,
@@ -212,10 +340,7 @@ metadata, market discovery, allowlists, and fee policy come from REST. Swap
212
340
  execution remains client-side.
213
341
 
214
342
  ```ts
215
- import {
216
- AEGIS_BACKEND_PROD_BASE_URL,
217
- createAegisBackendApiClient
218
- } from '@aegis-engine/core'
343
+ import { AEGIS_BACKEND_PROD_BASE_URL, createAegisBackendApiClient } from '@aegis-engine/core'
219
344
 
220
345
  const aegisApi = createAegisBackendApiClient({
221
346
  baseUrl: AEGIS_BACKEND_PROD_BASE_URL
@@ -281,10 +406,7 @@ provided, core verifies it matches the key.
281
406
  ## Direct Pool Quote
282
407
 
283
408
  ```ts
284
- import {
285
- AEGIS_SWAP_MAINNET_CHAIN,
286
- fetchDirectPoolSwapQuote
287
- } from '@aegis-engine/core'
409
+ import { AEGIS_SWAP_MAINNET_CHAIN, fetchDirectPoolSwapQuote } from '@aegis-engine/core'
288
410
 
289
411
  const quote = await fetchDirectPoolSwapQuote({
290
412
  publicClient,
@@ -339,10 +461,7 @@ protection the user saw.
339
461
  ## KyberSwap Quote
340
462
 
341
463
  ```ts
342
- import {
343
- AGGREGATED_SWAP_DEFAULT_TOKENS,
344
- fetchAggregatedSwapQuote
345
- } from '@aegis-engine/core'
464
+ import { AGGREGATED_SWAP_DEFAULT_TOKENS, fetchAggregatedSwapQuote } from '@aegis-engine/core'
346
465
 
347
466
  const quote = await fetchAggregatedSwapQuote(
348
467
  AGGREGATED_SWAP_DEFAULT_TOKENS.USDC,
@@ -454,9 +573,12 @@ Execution returns:
454
573
 
455
574
  ## Publishing
456
575
 
457
- This package is configured with `prepack`, so `npm publish` builds `dist` before
458
- creating the tarball.
576
+ Releases use Changesets and npm trusted publishing. The release workflow builds
577
+ all publishable packages once, validates their tarballs, then publishes the
578
+ prebuilt artifacts with package lifecycle scripts disabled. This prevents
579
+ concurrent package publishes from rebuilding shared workspace artifacts.
459
580
 
460
581
  ```sh
461
- npm publish --access public
582
+ pnpm build
583
+ pnpm pack:check
462
584
  ```
@@ -2,7 +2,7 @@
2
2
 
3
3
  The lazy Engine implementation includes code from these MIT-licensed works:
4
4
 
5
- - `@labs-solo/aegis-engine-sdk` 0.30.0 — Copyright (c) 2026 Solo Labs
5
+ - `@labs-solo/aegis-engine-sdk` 0.33.0 — Copyright (c) 2026 Solo Labs
6
6
  - `@uniswap/sdk-core` 6.1.1 and 7.18.0 — Copyright (c) 2021 Uniswap Labs
7
7
  - `@uniswap/v3-sdk` 3.27.0 — Copyright (c) 2021 Uniswap Labs
8
8
  - `tiny-invariant` 1.3.3 — Copyright (c) 2019 Alexander Reardon
@@ -32,6 +32,7 @@ export declare function approveDirectPoolSwapInput(args: {
32
32
  amount: bigint;
33
33
  approvalMode?: AegisSwapApprovalMode;
34
34
  onStatus?: AegisSwapActionCallbacks['onStatus'];
35
+ onApprovalProgress?: AegisSwapActionCallbacks['onApprovalProgress'];
35
36
  }): Promise<DirectPoolSwapApprovalResult>;
36
37
  export declare function executeDirectPoolSwap(args: {
37
38
  wallet: WalletSession;
@@ -3,10 +3,65 @@ import type { DirectSwapQuote } from '../quote.js';
3
3
  import type { AegisSwapReceiptSummary, SubmitStatus } from '../transactionProgress.js';
4
4
  import type { AggregatedSwapQuote } from '../types/aggregatedSwap.js';
5
5
  import type { AegisSwapSubmitDetails } from '../swap/types.js';
6
+ /**
7
+ * The grant the approval prompt asks for, as the executor resolved it from the
8
+ * sizing rule. It travels with the progress report so a surface renders the
9
+ * number that will actually be signed instead of re-deriving one from form
10
+ * state — the input amount and the requested amount are the same only under
11
+ * 'exact' sizing. There is no `formatted` field: the unlimited case has no
12
+ * honest formatting and has to be rendered as a word.
13
+ */
14
+ export type AegisSwapApprovalRequest = {
15
+ /** The exact value passed to `approve()`. */
16
+ value: bigint;
17
+ /** Decimals of the approved token, so `value` is renderable on its own. */
18
+ decimals: number;
19
+ /** True when `value` reads as no limit at all; render a word, not `value`. */
20
+ unlimited: boolean;
21
+ };
22
+ /**
23
+ * The approval step's wallet prompts: how many it will open, how many the
24
+ * wallet has already confirmed, and what they ask for. Only the executor can
25
+ * answer any of it — its allowance read decides whether a USDT-style zero-reset
26
+ * adds a second prompt, and only it sees that reset's receipt.
27
+ */
28
+ export type AegisSwapApprovalProgress = {
29
+ /** Prompts the approval step opens: 0, 1, or 2 when a zero-reset precedes it. */
30
+ planned: number;
31
+ /** How many of `planned` have landed on-chain. */
32
+ completed: number;
33
+ /** Absent exactly when `planned` is 0 — nothing will be approved. */
34
+ request?: AegisSwapApprovalRequest;
35
+ };
36
+ /**
37
+ * Wallet prompts used to authorize Permit2 for the router. Usually this is one
38
+ * typed-data signature. A wallet that explicitly reports typed-data as
39
+ * unsupported uses one on-chain spending-cap prompt instead. Returned invalid
40
+ * signatures fail closed, so this is a count rather than a boolean.
41
+ */
42
+ export type AegisSwapAuthorizationProgress = {
43
+ /** Prompts this authorization step will open from the executor's live state. */
44
+ planned: number;
45
+ /** Prompts already completed by the wallet/user. */
46
+ completed: number;
47
+ };
6
48
  export type AegisSwapActionCallbacks<TSwapDetails = AegisSwapSubmitDetails> = {
7
49
  onStatus?: (status: SubmitStatus) => void;
8
50
  onTxHash?: (hash: Hex | null) => void;
9
51
  onSubmitted?: (hash: Hex, swap?: TSwapDetails) => void;
52
+ /**
53
+ * Fires with the plan before the first approval prompt opens, then again
54
+ * after each approval transaction mines. Once allowance resolution is
55
+ * reached, no required approval reports `{ planned: 0, completed: 0 }`.
56
+ * Earlier chain-validation or quote failures can stop before this callback.
57
+ */
58
+ onApprovalProgress?: (progress: AegisSwapApprovalProgress) => void;
59
+ /**
60
+ * Fires before the first Permit2 authorization prompt, whenever the plan
61
+ * changes, and after each completed prompt. A reusable standing cap reports
62
+ * `{ planned: 0, completed: 0 }`.
63
+ */
64
+ onAuthorizationProgress?: (progress: AegisSwapAuthorizationProgress) => void;
10
65
  };
11
66
  export type AegisSwapActionResult<TQuote = DirectSwapQuote | AggregatedSwapQuote> = {
12
67
  hash: Hex;