@aegis-engine/core 0.1.0 → 0.1.2

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
@@ -2,41 +2,370 @@
2
2
 
3
3
  React-free swap operations for Aegis integrations.
4
4
 
5
- This package exposes the operational pieces that the widget uses internally:
6
- direct pool quotes and execution, KyberSwap quotes and execution, Permit2
7
- authorization helpers, chain/wallet helpers, token and market config, and
8
- transaction progress types.
5
+ `@aegis-engine/core` is the operations layer behind the Aegis swap experience.
6
+ Use it when you want Aegis direct-pool swaps or KyberSwap aggregated routes
7
+ inside your own UI instead of using a prebuilt widget.
8
+
9
+ ## Install
10
+
11
+ ```sh
12
+ npm install @aegis-engine/core viem
13
+ ```
14
+
15
+ `viem` is a peer dependency because hosts usually already own their wallet and
16
+ RPC setup.
17
+
18
+ ## What It Supports
19
+
20
+ - Direct Aegis pool quotes and execution against host-supplied Uniswap v4 pool
21
+ configs.
22
+ - KyberSwap aggregated quotes and execution through the Kyber aggregator API.
23
+ - ERC-20 approvals, Permit2 signing, Permit2 on-chain fallback, wallet chain
24
+ switching, transaction submission, replacement tracking, and receipt summaries.
25
+ - Token, market, chain, metadata, polling, and progress helpers for custom UIs.
26
+
27
+ Direct pool swaps support `exactIn` and `exactOut`. KyberSwap routes currently
28
+ support `exactIn` only because Kyber's route/build flow is exact-input.
29
+
30
+ Core does not ship a hardcoded pool list. The widget or host app should own pool
31
+ selection and pass the selected market into the operation functions.
32
+
33
+ ## Wallet Sessions
34
+
35
+ Execution functions take a `WalletSession`:
36
+
37
+ ```ts
38
+ type WalletSession = {
39
+ provider: Eip1193Provider
40
+ account: Address
41
+ }
42
+ ```
43
+
44
+ If your app already uses wagmi, RainbowKit, Privy, Dynamic, or another wallet
45
+ manager, you can build this object yourself and skip `resolveWalletSession`.
46
+
47
+ `resolveWalletSession` is a convenience for simple browser integrations. It:
48
+
49
+ - uses a supplied `provider` and `account` when provided,
50
+ - otherwise calls `getProvider`,
51
+ - otherwise falls back to `window.ethereum`,
52
+ - optionally calls your `connectWallet` callback,
53
+ - requests an account by default when one is missing.
54
+
55
+ Call it from a user action, not during render, because it may open the wallet
56
+ connection prompt.
57
+
58
+ ```ts
59
+ import { resolveWalletSession } from '@aegis-engine/core'
60
+
61
+ const wallet = await resolveWalletSession()
62
+ ```
63
+
64
+ With a host wallet manager:
65
+
66
+ ```ts
67
+ const wallet = await resolveWalletSession({
68
+ account: address,
69
+ getProvider: () => connector.getProvider()
70
+ })
71
+ ```
72
+
73
+ To only read an already connected account without prompting:
74
+
75
+ ```ts
76
+ const wallet = await resolveWalletSession({
77
+ provider,
78
+ requestAccount: false
79
+ })
80
+ ```
81
+
82
+ ## Public Client
83
+
84
+ Create a read-side viem client from an Aegis chain config:
9
85
 
10
86
  ```ts
11
87
  import {
12
88
  AEGIS_SWAP_MAINNET_CHAIN,
13
- AEGIS_SWAP_MARKETS,
14
- AEGIS_SWAP_TOKENS,
15
- createSwapPublicClient,
16
- executeDirectPoolSwap,
17
- resolveWalletSession
89
+ createSwapPublicClient
18
90
  } from '@aegis-engine/core'
19
91
 
20
92
  const publicClient = createSwapPublicClient(
21
93
  AEGIS_SWAP_MAINNET_CHAIN.chain,
22
94
  AEGIS_SWAP_MAINNET_CHAIN.rpcUrls
23
95
  )
96
+ ```
97
+
98
+ For production integrations, prefer passing your own viem `PublicClient` backed
99
+ by your RPC provider. `createSwapPublicClient` is a convenience fallback for
100
+ simple integrations and demos.
101
+
102
+ ## Backend V1 API
103
+
104
+ The V1 backend/indexer contract is exposed as typed REST helpers. This is the
105
+ public boundary for widget/core integrations once the backend service is live:
106
+ AppSync, DynamoDB, indexer reads, provider adapters, quote storage, route hashes,
107
+ allowlists, and final simulation stay behind the backend.
108
+
109
+ ```ts
110
+ import { createAegisBackendApiClient } from '@aegis-engine/core'
111
+
112
+ const aegisApi = createAegisBackendApiClient({
113
+ baseUrl: 'https://api.aegis.markets'
114
+ })
115
+
116
+ const config = await aegisApi.config()
117
+ const tokens = await aegisApi.tokens({ chainId: 1 })
118
+ const markets = await aegisApi.markets({ chainId: 1 })
119
+
120
+ const quote = await aegisApi.quote({
121
+ chainId: 1,
122
+ provider: 'aegis-direct-v4',
123
+ marketId: 'WATER_USDC',
124
+ mode: 'exactIn',
125
+ tokenIn: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
126
+ tokenOut: '0x828B22c9ea1c20B551EeFC6198174C4bb7a8CBF6',
127
+ amountIn: '1000000',
128
+ slippageBps: config.quote.defaultSlippageBps,
129
+ account
130
+ })
131
+
132
+ const build = await aegisApi.build({
133
+ quoteId: quote.quoteId,
134
+ account,
135
+ recipient: account
136
+ })
137
+ ```
138
+
139
+ The backend endpoints are:
140
+
141
+ - `GET /v1/health`
142
+ - `GET /v1/config`
143
+ - `GET /v1/tokens?chainId=1`
144
+ - `GET /v1/tokens/by-address?chainId=1&addresses=0x...,0x...`
145
+ - `GET /v1/markets?chainId=1`
146
+ - `POST /v1/swap/quote`
147
+ - `POST /v1/swap/build`
148
+ - `GET /v1/status/tx/:chainId/:txHash`
149
+
150
+ `quote` returns a short-lived `quoteId`, `quoteHash`, `routeHash`, expiry, gas,
151
+ approval, and normalized fee data. `build` accepts only `quoteId`, `account`,
152
+ `recipient`, and an optional Permit2 signature; token amounts, slippage, provider
153
+ route data, router addresses, and spender addresses are not accepted in the
154
+ build request. The backend must reject expired or tampered quotes, validate the
155
+ chain/router/spender/pool allowlists, and simulate the unsigned transaction
156
+ before returning wallet-ready `tx` data.
157
+
158
+ Core still includes direct-pool and Kyber client-side helpers for the current
159
+ widget flow. Those helpers are transitional for swap execution: once the backend
160
+ service is available, production widget paths should fetch config, tokens,
161
+ markets, quotes, builds, and transaction status through this REST client while
162
+ wallet signing and transaction sending remain client-side.
163
+
164
+ ## Direct Pool Market Config
165
+
166
+ The direct pool operations take an `AegisSwapMarket`. Hosts can build one from
167
+ their own pool config:
168
+
169
+ ```ts
170
+ import { createAegisSwapPoolMarket } from '@aegis-engine/core'
171
+
172
+ const market = createAegisSwapPoolMarket({
173
+ id: 'HOST_POOL_ID',
174
+ token: targetToken,
175
+ counterToken,
176
+ poolKey: {
177
+ currency0,
178
+ currency1,
179
+ fee,
180
+ tickSpacing,
181
+ hooks
182
+ },
183
+ poolId
184
+ })
185
+ ```
186
+
187
+ The pool key must use Uniswap v4 canonical currency ordering. If `poolId` is
188
+ provided, core verifies it matches the key.
189
+
190
+ ## Direct Pool Quote
191
+
192
+ ```ts
193
+ import {
194
+ AEGIS_SWAP_MAINNET_CHAIN,
195
+ fetchDirectPoolSwapQuote
196
+ } from '@aegis-engine/core'
197
+
198
+ const quote = await fetchDirectPoolSwapQuote({
199
+ publicClient,
200
+ account,
201
+ activeChain: AEGIS_SWAP_MAINNET_CHAIN,
202
+ market,
203
+ inputToken,
204
+ outputToken,
205
+ mode: 'exactIn',
206
+ amount: 1_000_000n,
207
+ slippageBps: 100
208
+ })
209
+ ```
210
+
211
+ Amounts are raw token base units. `slippageBps: 100` means 1%.
212
+ The Aegis 0.5% output fee is applied by the high-level quote and execution
213
+ helpers.
214
+
215
+ ## Direct Pool Execution
216
+
217
+ ```ts
218
+ import {
219
+ AEGIS_SWAP_MAINNET_CHAIN,
220
+ executeDirectPoolSwap,
221
+ resolveWalletSession
222
+ } from '@aegis-engine/core'
223
+
24
224
  const wallet = await resolveWalletSession()
25
225
 
26
226
  const result = await executeDirectPoolSwap({
27
227
  wallet,
28
228
  publicClient,
29
229
  activeChain: AEGIS_SWAP_MAINNET_CHAIN,
30
- market: AEGIS_SWAP_MARKETS.WATER_USDC,
31
- inputToken: AEGIS_SWAP_TOKENS.USDC,
32
- outputToken: AEGIS_SWAP_TOKENS.WATER,
230
+ market,
231
+ inputToken,
232
+ outputToken,
33
233
  mode: 'exactIn',
34
234
  amount: 1_000_000n,
35
- slippageBps: 100
235
+ slippageBps: 100,
236
+ displayedQuote: quote,
237
+ approvalMode: 'exact',
238
+ onStatus: (status) => setStatus(status),
239
+ onTxHash: (hash) => setTxHash(hash),
240
+ onSubmitted: (hash, swap) => trackSwapSubmitted(hash, swap)
36
241
  })
37
242
  ```
38
243
 
39
- For Kyber routes, call `executeKyberSwap` with an input token, output token,
40
- amount, and slippage. Both execution actions accept callbacks for status,
41
- transaction hash, and submitted swap details so a host UI can render its own
42
- progress surfaces.
244
+ Passing `displayedQuote` is recommended. Core requotes immediately before
245
+ submission and refuses to submit if the fresh quote no longer satisfies the
246
+ protection the user saw.
247
+
248
+ ## KyberSwap Quote
249
+
250
+ ```ts
251
+ import {
252
+ AGGREGATED_SWAP_DEFAULT_TOKENS,
253
+ fetchAggregatedSwapQuote
254
+ } from '@aegis-engine/core'
255
+
256
+ const quote = await fetchAggregatedSwapQuote(
257
+ AGGREGATED_SWAP_DEFAULT_TOKENS.USDC,
258
+ AGGREGATED_SWAP_DEFAULT_TOKENS.USDT,
259
+ 1_000_000n,
260
+ 100,
261
+ 'exactIn'
262
+ )
263
+ ```
264
+
265
+ For a display-only quote, omit the account. For executable calldata, pass the
266
+ user account:
267
+
268
+ ```ts
269
+ const executableQuote = await fetchAggregatedSwapQuote(
270
+ inputToken,
271
+ outputToken,
272
+ amount,
273
+ slippageBps,
274
+ 'exactIn',
275
+ wallet.account
276
+ )
277
+ ```
278
+
279
+ ## KyberSwap Execution
280
+
281
+ ```ts
282
+ import {
283
+ AEGIS_SWAP_MAINNET_CHAIN,
284
+ AGGREGATED_SWAP_DEFAULT_TOKENS,
285
+ executeKyberSwap,
286
+ resolveWalletSession
287
+ } from '@aegis-engine/core'
288
+
289
+ const wallet = await resolveWalletSession()
290
+
291
+ const result = await executeKyberSwap({
292
+ wallet,
293
+ publicClient,
294
+ activeChain: AEGIS_SWAP_MAINNET_CHAIN,
295
+ inputToken: AGGREGATED_SWAP_DEFAULT_TOKENS.USDC,
296
+ outputToken: AGGREGATED_SWAP_DEFAULT_TOKENS.USDT,
297
+ amount: 1_000_000n,
298
+ slippageBps: 100,
299
+ displayedQuote: quote,
300
+ approvalMode: 'exact',
301
+ onStatus: (status) => setStatus(status),
302
+ onTxHash: (hash) => setTxHash(hash)
303
+ })
304
+ ```
305
+
306
+ Kyber execution will:
307
+
308
+ - switch the wallet to the active chain when needed,
309
+ - approve the Kyber router for ERC-20 input tokens,
310
+ - skip approval for native ETH-style input tokens,
311
+ - rebuild the route with executable calldata for the connected account,
312
+ - reject submission if the route moved below the displayed minimum,
313
+ - send the transaction and wait for a receipt.
314
+
315
+ ## Approval Modes
316
+
317
+ `approvalMode` controls ERC-20 approval sizing.
318
+
319
+ - `exact`: approve only the current quote spend.
320
+ - `max`: approve the maximum spender allowance to reduce future approval prompts.
321
+
322
+ Direct pool swaps also use Permit2. Core first attempts an off-chain Permit2
323
+ signature and falls back to an on-chain Permit2 approval if the wallet cannot
324
+ produce a compatible typed-data signature.
325
+
326
+ ## Result Shape
327
+
328
+ Execution returns:
329
+
330
+ ```ts
331
+ {
332
+ hash: Hex
333
+ explorerUrl: string
334
+ quote: DirectSwapQuote | AggregatedSwapQuote
335
+ receiptSummary: {
336
+ transactionHash: Hex
337
+ blockNumber: bigint
338
+ gasUsed: bigint
339
+ effectiveGasPrice: bigint
340
+ feeWei: bigint
341
+ }
342
+ swap: {
343
+ mode: 'exactIn' | 'exactOut'
344
+ inputSymbol: string
345
+ outputSymbol: string
346
+ amountIn: bigint
347
+ amountOut: bigint
348
+ feeAmount: bigint
349
+ feeBps: number
350
+ }
351
+ }
352
+ ```
353
+
354
+ ## Errors To Handle
355
+
356
+ - `DirectPoolSwapPriceMovedError`: direct pool submit-time quote moved beyond
357
+ the displayed tolerance. The error includes the fresh `quote`.
358
+ - `KyberSwapPriceMovedError`: Kyber executable route moved below the displayed
359
+ minimum. The error includes the fresh `quote`.
360
+ - User rejection errors normalize through `errorMessage(error, fallback)`.
361
+ - Kyber temporary failures can be mapped with
362
+ `aggregatedSwapErrorMessage(error, fallback)`.
363
+
364
+ ## Publishing
365
+
366
+ This package is configured with `prepack`, so `npm publish` builds `dist` before
367
+ creating the tarball.
368
+
369
+ ```sh
370
+ npm publish --access public
371
+ ```
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Polls an ERC-20 allowance read until it covers `minimum`, then asserts it did.
3
+ * Shared by the direct-pool and KyberSwap approval flows after they submit an
4
+ * approval transaction: a confirmed approval whose allowance is still short means
5
+ * the wallet approved a smaller amount, so the swap must not proceed.
6
+ */
7
+ export declare function confirmAllowanceReached(read: () => Promise<bigint>, minimum: bigint): Promise<bigint>;
@@ -1,5 +1,5 @@
1
1
  import { type Hex, type PublicClient } from 'viem';
2
- import type { AegisSwapChainConfig, AegisSwapFeeConfig, AegisSwapMarket, AegisSwapToken } from '../config';
2
+ import type { AegisSwapChainConfig, AegisSwapMarket, AegisSwapToken } from '../config';
3
3
  import { type DirectSwapQuote, type SwapQuoteMode } from '../quote';
4
4
  import type { AegisSwapApprovalMode, WalletSession } from '../swap/types';
5
5
  import type { AegisSwapActionCallbacks, AegisSwapActionResult } from './types';
@@ -11,6 +11,18 @@ export type DirectPoolSwapApprovalResult = {
11
11
  approvalHash: Hex | null;
12
12
  allowance: bigint;
13
13
  };
14
+ export declare function fetchDirectPoolSwapQuote(args: {
15
+ publicClient: PublicClient;
16
+ account: WalletSession['account'];
17
+ activeChain: AegisSwapChainConfig;
18
+ market: AegisSwapMarket;
19
+ inputToken: AegisSwapToken;
20
+ outputToken: AegisSwapToken;
21
+ mode: SwapQuoteMode;
22
+ amount: bigint;
23
+ slippageBps: number;
24
+ signal?: AbortSignal;
25
+ }): Promise<DirectSwapQuote>;
14
26
  export declare function approveDirectPoolSwapInput(args: {
15
27
  wallet: WalletSession;
16
28
  publicClient: PublicClient;
@@ -31,7 +43,6 @@ export declare function executeDirectPoolSwap(args: {
31
43
  mode: SwapQuoteMode;
32
44
  amount: bigint;
33
45
  slippageBps: number;
34
- fee?: AegisSwapFeeConfig | null;
35
46
  displayedQuote?: DirectSwapQuote | null;
36
47
  approvalMode?: AegisSwapApprovalMode;
37
48
  signal?: AbortSignal;