sally-defi-ts-sdk 0.3.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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +263 -0
  3. package/dist/aio/client.d.ts +93 -0
  4. package/dist/aio/client.d.ts.map +1 -0
  5. package/dist/aio/client.js +283 -0
  6. package/dist/aio/client.js.map +1 -0
  7. package/dist/aio/index.d.ts +20 -0
  8. package/dist/aio/index.d.ts.map +1 -0
  9. package/dist/aio/index.js +19 -0
  10. package/dist/aio/index.js.map +1 -0
  11. package/dist/aio/modules/fees.d.ts +19 -0
  12. package/dist/aio/modules/fees.d.ts.map +1 -0
  13. package/dist/aio/modules/fees.js +47 -0
  14. package/dist/aio/modules/fees.js.map +1 -0
  15. package/dist/aio/modules/liquidity.d.ts +47 -0
  16. package/dist/aio/modules/liquidity.d.ts.map +1 -0
  17. package/dist/aio/modules/liquidity.js +115 -0
  18. package/dist/aio/modules/liquidity.js.map +1 -0
  19. package/dist/aio/modules/prices.d.ts +18 -0
  20. package/dist/aio/modules/prices.d.ts.map +1 -0
  21. package/dist/aio/modules/prices.js +48 -0
  22. package/dist/aio/modules/prices.js.map +1 -0
  23. package/dist/aio/modules/swap.d.ts +50 -0
  24. package/dist/aio/modules/swap.d.ts.map +1 -0
  25. package/dist/aio/modules/swap.js +267 -0
  26. package/dist/aio/modules/swap.js.map +1 -0
  27. package/dist/aio/modules/wallet.d.ts +13 -0
  28. package/dist/aio/modules/wallet.d.ts.map +1 -0
  29. package/dist/aio/modules/wallet.js +27 -0
  30. package/dist/aio/modules/wallet.js.map +1 -0
  31. package/dist/aio/token.d.ts +19 -0
  32. package/dist/aio/token.d.ts.map +1 -0
  33. package/dist/aio/token.js +50 -0
  34. package/dist/aio/token.js.map +1 -0
  35. package/dist/client.d.ts +142 -0
  36. package/dist/client.d.ts.map +1 -0
  37. package/dist/client.js +452 -0
  38. package/dist/client.js.map +1 -0
  39. package/dist/constants.d.ts +36 -0
  40. package/dist/constants.d.ts.map +1 -0
  41. package/dist/constants.js +39 -0
  42. package/dist/constants.js.map +1 -0
  43. package/dist/data/deployment.json +1 -0
  44. package/dist/deployment.d.ts +44 -0
  45. package/dist/deployment.d.ts.map +1 -0
  46. package/dist/deployment.js +118 -0
  47. package/dist/deployment.js.map +1 -0
  48. package/dist/errors.d.ts +57 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +197 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/index.d.ts +48 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +47 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/modules/fees.d.ts +32 -0
  57. package/dist/modules/fees.d.ts.map +1 -0
  58. package/dist/modules/fees.js +64 -0
  59. package/dist/modules/fees.js.map +1 -0
  60. package/dist/modules/liquidity.d.ts +134 -0
  61. package/dist/modules/liquidity.d.ts.map +1 -0
  62. package/dist/modules/liquidity.js +277 -0
  63. package/dist/modules/liquidity.js.map +1 -0
  64. package/dist/modules/prices.d.ts +47 -0
  65. package/dist/modules/prices.d.ts.map +1 -0
  66. package/dist/modules/prices.js +85 -0
  67. package/dist/modules/prices.js.map +1 -0
  68. package/dist/modules/swap.d.ts +102 -0
  69. package/dist/modules/swap.d.ts.map +1 -0
  70. package/dist/modules/swap.js +400 -0
  71. package/dist/modules/swap.js.map +1 -0
  72. package/dist/modules/wallet.d.ts +16 -0
  73. package/dist/modules/wallet.d.ts.map +1 -0
  74. package/dist/modules/wallet.js +30 -0
  75. package/dist/modules/wallet.js.map +1 -0
  76. package/dist/permit2.d.ts +97 -0
  77. package/dist/permit2.d.ts.map +1 -0
  78. package/dist/permit2.js +130 -0
  79. package/dist/permit2.js.map +1 -0
  80. package/dist/previews.d.ts +57 -0
  81. package/dist/previews.d.ts.map +1 -0
  82. package/dist/previews.js +69 -0
  83. package/dist/previews.js.map +1 -0
  84. package/dist/safety.d.ts +80 -0
  85. package/dist/safety.d.ts.map +1 -0
  86. package/dist/safety.js +133 -0
  87. package/dist/safety.js.map +1 -0
  88. package/dist/token.d.ts +215 -0
  89. package/dist/token.d.ts.map +1 -0
  90. package/dist/token.js +239 -0
  91. package/dist/token.js.map +1 -0
  92. package/dist/types.d.ts +229 -0
  93. package/dist/types.d.ts.map +1 -0
  94. package/dist/types.js +462 -0
  95. package/dist/types.js.map +1 -0
  96. package/dist/util.d.ts +13 -0
  97. package/dist/util.d.ts.map +1 -0
  98. package/dist/util.js +22 -0
  99. package/dist/util.js.map +1 -0
  100. package/package.json +48 -0
  101. package/src/aio/client.ts +329 -0
  102. package/src/aio/index.ts +20 -0
  103. package/src/aio/modules/fees.ts +60 -0
  104. package/src/aio/modules/liquidity.ts +181 -0
  105. package/src/aio/modules/prices.ts +57 -0
  106. package/src/aio/modules/swap.ts +347 -0
  107. package/src/aio/modules/wallet.ts +34 -0
  108. package/src/aio/token.ts +59 -0
  109. package/src/client.ts +526 -0
  110. package/src/constants.ts +43 -0
  111. package/src/data/deployment.json +1 -0
  112. package/src/deployment.ts +132 -0
  113. package/src/errors.ts +215 -0
  114. package/src/index.ts +90 -0
  115. package/src/modules/fees.ts +78 -0
  116. package/src/modules/liquidity.ts +446 -0
  117. package/src/modules/prices.ts +97 -0
  118. package/src/modules/swap.ts +502 -0
  119. package/src/modules/wallet.ts +37 -0
  120. package/src/permit2.ts +169 -0
  121. package/src/previews.ts +95 -0
  122. package/src/safety.ts +152 -0
  123. package/src/token.ts +254 -0
  124. package/src/types.ts +438 -0
  125. package/src/util.ts +20 -0
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Single source of truth loader.
3
+ *
4
+ * All addresses and ABIs come from the bundled `deployment.json` (addresses +
5
+ * ABIs only, derived from the contracts' deployment artifact). Nothing is
6
+ * hard-coded here — if the deployment changes, drop in a new JSON and everything
7
+ * downstream follows.
8
+ *
9
+ * The `_guide` header of that file documents the v2.0.2 routing model:
10
+ *
11
+ * - The frontend (and this SDK) only ever calls the **two proxies**.
12
+ * - Every *Lens* view is callable through the **SwapController proxy** via a
13
+ * read-only fallback, and every *Sidecar* view through the **LiquidityController
14
+ * proxy**. So we bind the Lens ABI to the swap-proxy address and the Sidecar ABI
15
+ * to the liq-proxy address — no separate Lens/Sidecar address needed at runtime.
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ export type AbiItem = Record<string, any>;
22
+ export type Abi = AbiItem[];
23
+
24
+ export const DEPLOYMENT_FILE = "deployment.json";
25
+
26
+ // Friendly chain aliases -> the key used inside the deployment JSON.
27
+ const CHAIN_ALIASES: Record<string, string> = {
28
+ base: "base",
29
+ "8453": "base",
30
+ bsc: "bsc",
31
+ binance: "bsc",
32
+ bnb: "bsc",
33
+ "56": "bsc",
34
+ };
35
+
36
+ let _rawCache: Record<string, any> | null = null;
37
+
38
+ /** Parse and cache the bundled deployment JSON. */
39
+ function raw(): Record<string, any> {
40
+ if (_rawCache === null) {
41
+ const path = fileURLToPath(new URL("./data/deployment.json", import.meta.url));
42
+ _rawCache = JSON.parse(readFileSync(path, "utf-8")) as Record<string, any>;
43
+ }
44
+ return _rawCache;
45
+ }
46
+
47
+ /** Release tag of the bundled deployment, e.g. `v2.0.2`. */
48
+ export function release(): string {
49
+ return raw()["_guide"]["release"];
50
+ }
51
+
52
+ /** Canonical chain keys present in the deployment (`base`, `bsc`). */
53
+ export function supportedChains(): string[] {
54
+ return Object.keys(raw()["chains"]);
55
+ }
56
+
57
+ /** Map an alias / chain-id to the canonical deployment key. */
58
+ export function normalizeChain(chain: string | number): string {
59
+ const key = CHAIN_ALIASES[String(chain).toLowerCase()];
60
+ if (key === undefined) {
61
+ const supported = [...new Set(Object.keys(CHAIN_ALIASES))].sort();
62
+ throw new Error(`Unknown chain ${JSON.stringify(chain)}. Supported: ${JSON.stringify(supported)}`);
63
+ }
64
+ return key;
65
+ }
66
+
67
+ /** Raw deployment node for one chain. */
68
+ export function chain(chainKey: string | number): Record<string, any> {
69
+ return raw()["chains"][normalizeChain(chainKey)];
70
+ }
71
+
72
+ export function chainId(chainKey: string | number): number {
73
+ return Number(chain(chainKey)["chainId"]);
74
+ }
75
+
76
+ // --------------------------------------------------------------------------- //
77
+ // Addresses
78
+ // --------------------------------------------------------------------------- //
79
+ /**
80
+ * All well-known addresses for a chain, checksummed by the caller.
81
+ *
82
+ * `lens` and `sidecar` map to the *proxy* addresses on purpose: in v2.0.2 their
83
+ * views are served through the proxies via fallback. The dedicated Lens/Sidecar
84
+ * addresses are exposed separately as `lens_direct` / `sidecar_direct` for the
85
+ * rare case you want to bypass the proxy.
86
+ */
87
+ export function addresses(chainKey: string | number): Record<string, string> {
88
+ const c = chain(chainKey);
89
+ const swapProxy: string = c["swapController"]["proxy"];
90
+ const liqProxy: string = c["liquidityController"]["proxy"];
91
+ const out: Record<string, string> = {
92
+ swap: swapProxy,
93
+ liquidity: liqProxy,
94
+ lens: swapProxy, // Lens views via swap proxy fallback
95
+ sidecar: liqProxy, // Sidecar views via liq proxy fallback
96
+ treasury: c["treasury"],
97
+ lens_direct: c["lens"]["address"],
98
+ sidecar_direct: c["liquidityController"]["liqUtilsSidecar"],
99
+ swap_impl: c["swapController"]["impl"],
100
+ liquidity_impl: c["liquidityController"]["impl"],
101
+ };
102
+ if ("utilsV4" in c) {
103
+ out["utils_v4"] = c["utilsV4"];
104
+ }
105
+ return out;
106
+ }
107
+
108
+ // --------------------------------------------------------------------------- //
109
+ // ABIs
110
+ // --------------------------------------------------------------------------- //
111
+ /**
112
+ * Return the ABI for a logical component.
113
+ *
114
+ * Components: `swap`, `liquidity`, `lens`, `sidecar`, `treasury`.
115
+ */
116
+ export function abi(chainKey: string | number, component: string): Abi {
117
+ const c = chain(chainKey);
118
+ switch (component) {
119
+ case "swap":
120
+ return c["swapController"]["abi"];
121
+ case "liquidity":
122
+ return c["liquidityController"]["abi"];
123
+ case "lens":
124
+ return c["lens"]["abi"];
125
+ case "sidecar":
126
+ return c["liquidityController"]["liqUtilsSidecarAbi"];
127
+ case "treasury":
128
+ return c["treasuryAbi"];
129
+ default:
130
+ throw new Error(`Unknown ABI component ${JSON.stringify(component)}`);
131
+ }
132
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Typed errors and on-chain revert decoding.
3
+ *
4
+ * ethers surfaces a reverting call as a `CALL_EXCEPTION` carrying the raw revert
5
+ * `data` (4-byte selector + payload) but does not always decode custom Solidity
6
+ * errors against an ABI it doesn't know. We build a `selector -> error-ABI` table
7
+ * from every Sally ABI so a revert turns into a readable {@link SallyRevert} with
8
+ * the error name and decoded args instead of an opaque hex blob.
9
+ */
10
+
11
+ import { AbiCoder, ErrorFragment } from "ethers";
12
+ import * as deployment from "./deployment.js";
13
+
14
+ export class SallyError extends Error {
15
+ constructor(message?: string) {
16
+ super(message);
17
+ this.name = "SallyError";
18
+ }
19
+ }
20
+
21
+ /** Bad/missing configuration (no signer, unknown chain, …). */
22
+ export class SallyConfigError extends SallyError {
23
+ constructor(message?: string) {
24
+ super(message);
25
+ this.name = "SallyConfigError";
26
+ }
27
+ }
28
+
29
+ /**
30
+ * A swap route failed an integrity check (mismatched/wrong-pool path).
31
+ *
32
+ * Raised *before* any funds move when a quoted route would send tokens through a
33
+ * path that does not start at `tokenIn` / end at `tokenOut` or whose hops do not
34
+ * chain — i.e. the guard against funds landing in the wrong pool.
35
+ */
36
+ export class SallyRouteError extends SallyError {
37
+ constructor(message?: string) {
38
+ super(message);
39
+ this.name = "SallyRouteError";
40
+ }
41
+ }
42
+
43
+ /**
44
+ * A safety preflight blocked the swap (honeypot, excessive tax, impact).
45
+ */
46
+ export class SallySafetyError extends SallyError {
47
+ readonly reasons: string[];
48
+
49
+ constructor(message: string, reasons: string[] | null = null) {
50
+ super(message);
51
+ this.name = "SallySafetyError";
52
+ this.reasons = reasons ?? [];
53
+ }
54
+ }
55
+
56
+ /**
57
+ * An on-chain call/transaction reverted.
58
+ *
59
+ * Public fields mirror the Python `SallyRevert` 1:1:
60
+ * - `name`: decoded custom-error name (or `Error` / `Panic` / `"SallyRevert"`
61
+ * when unknown — Python uses `None` there; JS keeps a string for branding).
62
+ * - `argsDecoded`: decoded error arguments (Python `args_decoded`).
63
+ * - `selector`: the 4-byte selector hex.
64
+ * - `message`: the raw `Error(string)` / panic message (Python `.message`).
65
+ *
66
+ * `toString()` produces Python's `str(exc)` form: `"<name>: <detail>"`.
67
+ */
68
+ export class SallyRevert extends SallyError {
69
+ readonly argsDecoded: readonly any[];
70
+ readonly selector: string | null;
71
+ private readonly _label: string;
72
+
73
+ constructor(
74
+ name: string | null,
75
+ args: readonly any[] = [],
76
+ selector: string | null = null,
77
+ message: string | null = null,
78
+ ) {
79
+ // Error.message holds the raw revert message (Python `.message`), or "".
80
+ super(message ?? "");
81
+ // Python sets `self.name` to the decoded error name; keep a string for JS.
82
+ this.name = name ?? "SallyRevert";
83
+ this.argsDecoded = args;
84
+ this.selector = selector;
85
+ this._label = name || "revert";
86
+ }
87
+
88
+ override toString(): string {
89
+ const detail = this.message ? this.message : this.argsDecoded.length ? formatArgs(this.argsDecoded) : "";
90
+ return `${this._label}${detail ? ": " + detail : ""}`;
91
+ }
92
+ }
93
+
94
+ function formatArgs(args: readonly any[]): string {
95
+ return `(${args.map((a) => (typeof a === "bigint" ? a.toString() : String(a))).join(", ")})`;
96
+ }
97
+
98
+ // Standard selectors defined by Solidity itself.
99
+ const ERROR_STRING = "0x08c379a0"; // Error(string)
100
+ const PANIC = "0x4e487b71"; // Panic(uint256)
101
+
102
+ const PANIC_CODES: Record<number, string> = {
103
+ 0x01: "assert(false)",
104
+ 0x11: "arithmetic overflow/underflow",
105
+ 0x12: "division or modulo by zero",
106
+ 0x21: "invalid enum conversion",
107
+ 0x22: "incorrectly encoded storage byte array",
108
+ 0x31: "pop on empty array",
109
+ 0x32: "array index out of bounds",
110
+ 0x41: "out of memory",
111
+ 0x51: "call to invalid internal function",
112
+ };
113
+
114
+ const _tableCache = new Map<string, Map<string, Record<string, any>>>();
115
+
116
+ /** Map `0x<selector>` -> error ABI across all components of a chain. */
117
+ function selectorTable(chainKey: string): Map<string, Record<string, any>> {
118
+ const cached = _tableCache.get(chainKey);
119
+ if (cached) return cached;
120
+ const table = new Map<string, Record<string, any>>();
121
+ for (const component of ["swap", "liquidity", "lens", "sidecar", "treasury"]) {
122
+ for (const item of deployment.abi(chainKey, component)) {
123
+ if (item["type"] !== "error") continue;
124
+ let sel: string;
125
+ try {
126
+ sel = ErrorFragment.from(item).selector.toLowerCase();
127
+ } catch {
128
+ continue;
129
+ }
130
+ if (!table.has(sel)) table.set(sel, item);
131
+ }
132
+ }
133
+ _tableCache.set(chainKey, table);
134
+ return table;
135
+ }
136
+
137
+ const coder = AbiCoder.defaultAbiCoder();
138
+
139
+ /** Turn raw revert `data` (0x-hex) into a typed {@link SallyRevert}. */
140
+ export function decodeRevert(chainKey: string, data: string | null | undefined): SallyRevert {
141
+ if (!data || !data.startsWith("0x") || data.length < 10) {
142
+ return new SallyRevert(null, [], data ?? null);
143
+ }
144
+
145
+ const selector = data.slice(0, 10).toLowerCase();
146
+ const payload = "0x" + data.slice(10);
147
+
148
+ if (selector === ERROR_STRING) {
149
+ let msg: string | null = null;
150
+ try {
151
+ msg = String(coder.decode(["string"], payload)[0]);
152
+ } catch {
153
+ msg = null;
154
+ }
155
+ return new SallyRevert("Error", [], selector, msg);
156
+ }
157
+
158
+ if (selector === PANIC) {
159
+ let msg: string | null = null;
160
+ try {
161
+ const code = Number(coder.decode(["uint256"], payload)[0]);
162
+ msg = PANIC_CODES[code] ?? `panic 0x${code.toString(16).padStart(2, "0")}`;
163
+ } catch {
164
+ msg = null;
165
+ }
166
+ return new SallyRevert("Panic", [], selector, msg);
167
+ }
168
+
169
+ const err = selectorTable(chainKey).get(selector);
170
+ if (err !== undefined) {
171
+ const types = (err["inputs"] ?? []).map((i: Record<string, any>) => i["type"]);
172
+ let args: any[] = [];
173
+ if (types.length) {
174
+ try {
175
+ args = [...coder.decode(types, payload)];
176
+ } catch {
177
+ args = [];
178
+ }
179
+ }
180
+ return new SallyRevert(err["name"], args, selector);
181
+ }
182
+
183
+ return new SallyRevert(null, [], selector);
184
+ }
185
+
186
+ /** Extract raw revert data (0x-hex) from the many shapes ethers throws. */
187
+ function extractRevertData(exc: any): string | null {
188
+ if (exc == null) return null;
189
+ const candidates = [
190
+ exc.data,
191
+ exc.info?.error?.data,
192
+ exc.error?.data,
193
+ exc.error?.error?.data,
194
+ exc.revert?.data,
195
+ ];
196
+ for (const c of candidates) {
197
+ if (typeof c === "string" && c.startsWith("0x")) return c;
198
+ }
199
+ return null;
200
+ }
201
+
202
+ /** Convert an ethers contract error into a {@link SallyRevert} if possible. */
203
+ export function wrapWeb3Error(chainKey: string, exc: any): SallyError {
204
+ const data = extractRevertData(exc);
205
+ if (data) {
206
+ return decodeRevert(chainKey, data);
207
+ }
208
+ // CALL_EXCEPTION without decodable data, or a plain require-string revert.
209
+ const code = exc?.code;
210
+ if (code === "CALL_EXCEPTION") {
211
+ const msg = exc?.reason ?? exc?.shortMessage ?? exc?.message ?? String(exc);
212
+ return new SallyRevert("Error", [], null, msg);
213
+ }
214
+ return new SallyError(exc?.message ?? String(exc));
215
+ }
package/src/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Sally DeFi SDK — one client for CrossHybrid swaps & liquidity on Base + BSC.
3
+ *
4
+ * Fully on-chain: every read (prices, routes, positions, balances, honeypot
5
+ * screen) is a direct `eth_call` to the contracts. No Sally API, no Graph Node /
6
+ * subgraph, no indexer, no backend — the only thing in the data path is your RPC
7
+ * and the chain.
8
+ *
9
+ * Quickstart:
10
+ * ```ts
11
+ * import { SallyClient, Base } from "sally-defi-ts-sdk";
12
+ * const sally = new SallyClient("base", "https://mainnet.base.org");
13
+ * (await sally.prices.usd(Base.USDC)).asFloat; // display price
14
+ * const quote = await sally.swap.quote(Base.WETH, Base.USDC, 10n ** 18n);
15
+ * quote.estimatedAmountOut;
16
+ * ```
17
+ *
18
+ * To send transactions, give the client a signer:
19
+ * ```ts
20
+ * const sally = new SallyClient("base", RPC, { privateKey: "0x…" });
21
+ * await sally.swap.execute(Base.WETH, Base.USDC, 10n ** 17n, { slippageBps: 50 });
22
+ * ```
23
+ *
24
+ * Everything reads from the bundled `deployment.json` (addresses + ABIs only,
25
+ * derived from the contracts' deployment artifact). Addresses/ABIs are never
26
+ * hard-coded.
27
+ */
28
+
29
+ export const __version__ = "0.3.2";
30
+
31
+ import * as constants from "./constants.js";
32
+ import * as deployment from "./deployment.js";
33
+
34
+ export { constants, deployment };
35
+
36
+ export { SallyClient } from "./client.js";
37
+ export type { SallyClientOptions } from "./client.js";
38
+ export {
39
+ SallyConfigError,
40
+ SallyError,
41
+ SallyRevert,
42
+ SallyRouteError,
43
+ SallySafetyError,
44
+ decodeRevert,
45
+ wrapWeb3Error,
46
+ } from "./errors.js";
47
+ export { PERMIT2_ADDRESS, Permit2, signPermit2612, signPermitSingle } from "./permit2.js";
48
+ export { SwapBuild, TxPreview } from "./previews.js";
49
+ export { RouteCandidate, SafetyConfig, SwapPlan } from "./safety.js";
50
+ export type { SafetyConfigFields } from "./safety.js";
51
+ export {
52
+ ERC20_ABI,
53
+ MAX_UINT256,
54
+ NATIVE,
55
+ Token,
56
+ TokenAmount,
57
+ ZERO,
58
+ humanToRaw,
59
+ isNative,
60
+ rawToHuman,
61
+ } from "./token.js";
62
+ export {
63
+ ClaimableFees,
64
+ Lock,
65
+ LockKind,
66
+ PoolVersion,
67
+ PriceResult,
68
+ ReferralFeeTokens,
69
+ SwapPath,
70
+ SwapStep,
71
+ TokenInfo,
72
+ V2PoolState,
73
+ V2Position,
74
+ V3PoolState,
75
+ V3Position,
76
+ V4PoolState,
77
+ V4Position,
78
+ WalletBalance,
79
+ } from "./types.js";
80
+
81
+ // Convenience re-exports of the well-known token tables.
82
+ export { Base, Bsc, NATIVE_TOKEN, PrivateRelays, ZERO_ADDRESS } from "./constants.js";
83
+
84
+ // Module namespace classes (so callers can type their handles if they wish).
85
+ export { Prices } from "./modules/prices.js";
86
+ export { Swap } from "./modules/swap.js";
87
+ export { Wallet } from "./modules/wallet.js";
88
+ export { Liquidity } from "./modules/liquidity.js";
89
+ export { Fees } from "./modules/fees.js";
90
+ export type { ExecuteOptions } from "./modules/swap.js";
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Referral & batch-fee accounting (SwapController side).
3
+ *
4
+ * Both controllers track referral/batch fees independently. This namespace reads
5
+ * and claims on the **swap** controller; the liquidity controller's equivalents
6
+ * live on `client.liquidity` (`claimReferralFees` / `claimBatchFees`).
7
+ */
8
+
9
+ import { Contract, getAddress } from "ethers";
10
+ import type { SallyClient } from "../client.js";
11
+ import { ReferralFeeTokens } from "../types.js";
12
+
13
+ export class Fees {
14
+ private _c: SallyClient;
15
+ private _swap: Contract;
16
+
17
+ constructor(client: SallyClient) {
18
+ this._c = client;
19
+ this._swap = client.swapContract;
20
+ }
21
+
22
+ private _a(x: string): string {
23
+ return getAddress(x);
24
+ }
25
+
26
+ // -- reads ------------------------------------------------------------- //
27
+ async referralTokenCount(user: string): Promise<number> {
28
+ return Number(await this._c.call(this._swap.getFunction("getReferralFeeTokenCount"), [this._a(user)]));
29
+ }
30
+
31
+ /**
32
+ * Paged referral-fee balances. `getReferralFeeTokens(user, start, stop)`.
33
+ *
34
+ * `stop=0` auto-resolves to the current max index.
35
+ */
36
+ async referralTokens(user: string, start = 0, stop = 0): Promise<ReferralFeeTokens> {
37
+ if (stop === 0) {
38
+ stop = Math.max(await this.referralTokenCount(this._a(user)), 1);
39
+ }
40
+ const r = await this._c.call(this._swap.getFunction("getReferralFeeTokens"), [this._a(user), start, stop]);
41
+ return ReferralFeeTokens.fromRaw(r);
42
+ }
43
+
44
+ async totalReferralOwed(user: string): Promise<bigint> {
45
+ return BigInt(await this._c.call(this._swap.getFunction("totalReferralOwed"), [this._a(user)]));
46
+ }
47
+
48
+ async referralFee(user: string, token: string): Promise<bigint> {
49
+ return BigInt(await this._c.call(this._swap.getFunction("referralFees"), [this._a(user), this._a(token)]));
50
+ }
51
+
52
+ async batchFee(token: string): Promise<bigint> {
53
+ return BigInt(await this._c.call(this._swap.getFunction("batchFees"), [this._a(token)]));
54
+ }
55
+
56
+ async feeBasisPoints(): Promise<bigint> {
57
+ return BigInt(await this._c.call(this._swap.getFunction("feeBasisPoints"), []));
58
+ }
59
+
60
+ async feeThreshold(): Promise<bigint> {
61
+ return BigInt(await this._c.call(this._swap.getFunction("feeThreshold"), []));
62
+ }
63
+
64
+ /** Fee split for an amount. `getSwapFee(amount) -> [fee, net]`. */
65
+ async swapFee(amount: bigint): Promise<[bigint, bigint]> {
66
+ const r = await this._c.call(this._swap.getFunction("getSwapFee"), [amount]);
67
+ return [BigInt(r[0]), BigInt(r[1])];
68
+ }
69
+
70
+ // -- claims ------------------------------------------------------------ //
71
+ async claimReferral(tokens: string[], tx: Record<string, any> = {}): Promise<any> {
72
+ return this._c.send(this._swap.getFunction("claimReferralFees"), [tokens.map((t) => this._a(t))], tx);
73
+ }
74
+
75
+ async claimBatch(tokens: string[], tx: Record<string, any> = {}): Promise<any> {
76
+ return this._c.send(this._swap.getFunction("claimBatchFees"), [tokens.map((t) => this._a(t))], tx);
77
+ }
78
+ }