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.
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/dist/aio/client.d.ts +93 -0
- package/dist/aio/client.d.ts.map +1 -0
- package/dist/aio/client.js +283 -0
- package/dist/aio/client.js.map +1 -0
- package/dist/aio/index.d.ts +20 -0
- package/dist/aio/index.d.ts.map +1 -0
- package/dist/aio/index.js +19 -0
- package/dist/aio/index.js.map +1 -0
- package/dist/aio/modules/fees.d.ts +19 -0
- package/dist/aio/modules/fees.d.ts.map +1 -0
- package/dist/aio/modules/fees.js +47 -0
- package/dist/aio/modules/fees.js.map +1 -0
- package/dist/aio/modules/liquidity.d.ts +47 -0
- package/dist/aio/modules/liquidity.d.ts.map +1 -0
- package/dist/aio/modules/liquidity.js +115 -0
- package/dist/aio/modules/liquidity.js.map +1 -0
- package/dist/aio/modules/prices.d.ts +18 -0
- package/dist/aio/modules/prices.d.ts.map +1 -0
- package/dist/aio/modules/prices.js +48 -0
- package/dist/aio/modules/prices.js.map +1 -0
- package/dist/aio/modules/swap.d.ts +50 -0
- package/dist/aio/modules/swap.d.ts.map +1 -0
- package/dist/aio/modules/swap.js +267 -0
- package/dist/aio/modules/swap.js.map +1 -0
- package/dist/aio/modules/wallet.d.ts +13 -0
- package/dist/aio/modules/wallet.d.ts.map +1 -0
- package/dist/aio/modules/wallet.js +27 -0
- package/dist/aio/modules/wallet.js.map +1 -0
- package/dist/aio/token.d.ts +19 -0
- package/dist/aio/token.d.ts.map +1 -0
- package/dist/aio/token.js +50 -0
- package/dist/aio/token.js.map +1 -0
- package/dist/client.d.ts +142 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +452 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +36 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +39 -0
- package/dist/constants.js.map +1 -0
- package/dist/data/deployment.json +1 -0
- package/dist/deployment.d.ts +44 -0
- package/dist/deployment.d.ts.map +1 -0
- package/dist/deployment.js +118 -0
- package/dist/deployment.js.map +1 -0
- package/dist/errors.d.ts +57 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +197 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/fees.d.ts +32 -0
- package/dist/modules/fees.d.ts.map +1 -0
- package/dist/modules/fees.js +64 -0
- package/dist/modules/fees.js.map +1 -0
- package/dist/modules/liquidity.d.ts +134 -0
- package/dist/modules/liquidity.d.ts.map +1 -0
- package/dist/modules/liquidity.js +277 -0
- package/dist/modules/liquidity.js.map +1 -0
- package/dist/modules/prices.d.ts +47 -0
- package/dist/modules/prices.d.ts.map +1 -0
- package/dist/modules/prices.js +85 -0
- package/dist/modules/prices.js.map +1 -0
- package/dist/modules/swap.d.ts +102 -0
- package/dist/modules/swap.d.ts.map +1 -0
- package/dist/modules/swap.js +400 -0
- package/dist/modules/swap.js.map +1 -0
- package/dist/modules/wallet.d.ts +16 -0
- package/dist/modules/wallet.d.ts.map +1 -0
- package/dist/modules/wallet.js +30 -0
- package/dist/modules/wallet.js.map +1 -0
- package/dist/permit2.d.ts +97 -0
- package/dist/permit2.d.ts.map +1 -0
- package/dist/permit2.js +130 -0
- package/dist/permit2.js.map +1 -0
- package/dist/previews.d.ts +57 -0
- package/dist/previews.d.ts.map +1 -0
- package/dist/previews.js +69 -0
- package/dist/previews.js.map +1 -0
- package/dist/safety.d.ts +80 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +133 -0
- package/dist/safety.js.map +1 -0
- package/dist/token.d.ts +215 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +239 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +462 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +13 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +22 -0
- package/dist/util.js.map +1 -0
- package/package.json +48 -0
- package/src/aio/client.ts +329 -0
- package/src/aio/index.ts +20 -0
- package/src/aio/modules/fees.ts +60 -0
- package/src/aio/modules/liquidity.ts +181 -0
- package/src/aio/modules/prices.ts +57 -0
- package/src/aio/modules/swap.ts +347 -0
- package/src/aio/modules/wallet.ts +34 -0
- package/src/aio/token.ts +59 -0
- package/src/client.ts +526 -0
- package/src/constants.ts +43 -0
- package/src/data/deployment.json +1 -0
- package/src/deployment.ts +132 -0
- package/src/errors.ts +215 -0
- package/src/index.ts +90 -0
- package/src/modules/fees.ts +78 -0
- package/src/modules/liquidity.ts +446 -0
- package/src/modules/prices.ts +97 -0
- package/src/modules/swap.ts +502 -0
- package/src/modules/wallet.ts +37 -0
- package/src/permit2.ts +169 -0
- package/src/previews.ts +95 -0
- package/src/safety.ts +152 -0
- package/src/token.ts +254 -0
- package/src/types.ts +438 -0
- package/src/util.ts +20 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async entry point: {@link AsyncSallyClient}.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors {@link SallyClient} on top of ethers' provider for non-blocking I/O.
|
|
5
|
+
* Shares all pure machinery (deployment, types, safety, errors, permit signing)
|
|
6
|
+
* with the sync client; only the network calls are awaited.
|
|
7
|
+
*
|
|
8
|
+
* In JS every network call is already async, so this client mainly differs from
|
|
9
|
+
* the main one in that its candidate quoting/simulation fans out concurrently
|
|
10
|
+
* (`Promise.all`), exactly like the Python `aio` client uses `asyncio.gather`.
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { AsyncSallyClient } from "sally-defi-ts-sdk/aio";
|
|
14
|
+
* import { Base } from "sally-defi-ts-sdk";
|
|
15
|
+
*
|
|
16
|
+
* const sally = new AsyncSallyClient("base", RPC, { privateKey: "0x…" });
|
|
17
|
+
* console.log((await sally.prices.usd(Base.USDC)).asFloat);
|
|
18
|
+
* await sally.swap.execute(Base.WETH, Base.USDC, 10n ** 17n, { slippageBps: 50 });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
Contract,
|
|
24
|
+
HDNodeWallet,
|
|
25
|
+
JsonRpcProvider,
|
|
26
|
+
Network,
|
|
27
|
+
Wallet,
|
|
28
|
+
getAddress,
|
|
29
|
+
type Signer,
|
|
30
|
+
} from "ethers";
|
|
31
|
+
|
|
32
|
+
import * as deployment from "../deployment.js";
|
|
33
|
+
import { SallyConfigError, SallyError, SallyRevert, wrapWeb3Error } from "../errors.js";
|
|
34
|
+
import { SafetyConfig } from "../safety.js";
|
|
35
|
+
import { AsyncToken } from "./token.js";
|
|
36
|
+
import { AsyncPrices } from "./modules/prices.js";
|
|
37
|
+
import { AsyncSwap } from "./modules/swap.js";
|
|
38
|
+
import { AsyncWallet } from "./modules/wallet.js";
|
|
39
|
+
import { AsyncLiquidity } from "./modules/liquidity.js";
|
|
40
|
+
import { AsyncFees } from "./modules/fees.js";
|
|
41
|
+
|
|
42
|
+
/** Minimal async mutex: serialize an async critical section. */
|
|
43
|
+
class AsyncMutex {
|
|
44
|
+
private _chain: Promise<void> = Promise.resolve();
|
|
45
|
+
async run<T>(fn: () => Promise<T>): Promise<T> {
|
|
46
|
+
const prev = this._chain;
|
|
47
|
+
let release!: () => void;
|
|
48
|
+
this._chain = new Promise<void>((res) => (release = res));
|
|
49
|
+
await prev;
|
|
50
|
+
try {
|
|
51
|
+
return await fn();
|
|
52
|
+
} finally {
|
|
53
|
+
release();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface AsyncSallyClientOptions {
|
|
59
|
+
privateKey?: string | null;
|
|
60
|
+
mnemonic?: string | null;
|
|
61
|
+
accountIndex?: number;
|
|
62
|
+
account?: Signer | null;
|
|
63
|
+
address?: string | null;
|
|
64
|
+
poa?: boolean | null;
|
|
65
|
+
safety?: SafetyConfig | null;
|
|
66
|
+
privateRpcUrl?: string | null;
|
|
67
|
+
gasMultiplier?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type Fn = any;
|
|
71
|
+
|
|
72
|
+
export class AsyncSallyClient {
|
|
73
|
+
readonly chainKey: string;
|
|
74
|
+
readonly chainId: number;
|
|
75
|
+
safety: SafetyConfig;
|
|
76
|
+
privateRpcUrl: string | null;
|
|
77
|
+
private _privateW3: JsonRpcProvider | null = null;
|
|
78
|
+
gasMultiplier: number;
|
|
79
|
+
w3: JsonRpcProvider;
|
|
80
|
+
account: Signer | null = null;
|
|
81
|
+
private _sender: string | null = null;
|
|
82
|
+
private _sendLock = new AsyncMutex();
|
|
83
|
+
addresses: Record<string, string>;
|
|
84
|
+
private _cache: Record<string, any> = {};
|
|
85
|
+
|
|
86
|
+
constructor(chain: string | number = "base", rpcUrl?: string | null, opts: AsyncSallyClientOptions = {}) {
|
|
87
|
+
this.chainKey = deployment.normalizeChain(chain);
|
|
88
|
+
this.chainId = deployment.chainId(this.chainKey);
|
|
89
|
+
this.safety = opts.safety ?? new SafetyConfig();
|
|
90
|
+
this.privateRpcUrl = opts.privateRpcUrl ?? process.env.SALLY_PRIVATE_RPC_URL ?? null;
|
|
91
|
+
this.gasMultiplier = opts.gasMultiplier ?? 1.25;
|
|
92
|
+
|
|
93
|
+
rpcUrl = rpcUrl ?? process.env.SALLY_RPC_URL ?? null;
|
|
94
|
+
if (!rpcUrl) {
|
|
95
|
+
throw new SallyConfigError("No rpcUrl given and SALLY_RPC_URL is unset.");
|
|
96
|
+
}
|
|
97
|
+
const network = Network.from(this.chainId);
|
|
98
|
+
this.w3 = new JsonRpcProvider(rpcUrl, network, { staticNetwork: network });
|
|
99
|
+
|
|
100
|
+
void (opts.poa ?? this.chainKey === "bsc");
|
|
101
|
+
|
|
102
|
+
const key = opts.privateKey ?? process.env.SALLY_PRIVATE_KEY ?? null;
|
|
103
|
+
const phrase = opts.mnemonic ?? process.env.SALLY_MNEMONIC ?? null;
|
|
104
|
+
if (opts.account != null) {
|
|
105
|
+
this.account = opts.account;
|
|
106
|
+
} else if (key) {
|
|
107
|
+
this.account = new Wallet(key);
|
|
108
|
+
} else if (phrase) {
|
|
109
|
+
const idx = opts.accountIndex ?? 0;
|
|
110
|
+
this.account = HDNodeWallet.fromPhrase(phrase, undefined, `m/44'/60'/0'/0/${idx}`);
|
|
111
|
+
}
|
|
112
|
+
this._sender = this.account
|
|
113
|
+
? getAddress((this.account as any).address)
|
|
114
|
+
: opts.address
|
|
115
|
+
? getAddress(opts.address)
|
|
116
|
+
: null;
|
|
117
|
+
|
|
118
|
+
this.addresses = Object.fromEntries(
|
|
119
|
+
Object.entries(deployment.addresses(this.chainKey)).map(([k, v]) => [
|
|
120
|
+
k,
|
|
121
|
+
v.startsWith("0x") ? getAddress(v) : v,
|
|
122
|
+
]),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// -- identity ---------------------------------------------------------- //
|
|
127
|
+
async isConnected(): Promise<boolean> {
|
|
128
|
+
try {
|
|
129
|
+
await this.w3.getBlockNumber();
|
|
130
|
+
return true;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
get address(): string | null {
|
|
137
|
+
return this._sender;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
get canSign(): boolean {
|
|
141
|
+
return this.account !== null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
requireAddress(): string {
|
|
145
|
+
if (!this._sender) {
|
|
146
|
+
throw new SallyConfigError("This action needs a sender (privateKey=…, mnemonic=…, or address='0x…').");
|
|
147
|
+
}
|
|
148
|
+
return this._sender;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
requireSigner(): Signer {
|
|
152
|
+
if (this.account === null) {
|
|
153
|
+
throw new SallyConfigError("This action signs a tx and needs a key (privateKey=… or mnemonic=…).");
|
|
154
|
+
}
|
|
155
|
+
return this.account;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// -- contracts --------------------------------------------------------- //
|
|
159
|
+
private _contract(address: string, component: string): Contract {
|
|
160
|
+
return new Contract(getAddress(address), deployment.abi(this.chainKey, component) as any, this.w3);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private _cached<T>(key: string, make: () => T): T {
|
|
164
|
+
if (!(key in this._cache)) this._cache[key] = make();
|
|
165
|
+
return this._cache[key];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
get swapContract(): Contract {
|
|
169
|
+
return this._cached("swapContract", () => this._contract(this.addresses["swap"], "swap"));
|
|
170
|
+
}
|
|
171
|
+
get liquidityContract(): Contract {
|
|
172
|
+
return this._cached("liquidityContract", () => this._contract(this.addresses["liquidity"], "liquidity"));
|
|
173
|
+
}
|
|
174
|
+
get lensContract(): Contract {
|
|
175
|
+
return this._cached("lensContract", () => this._contract(this.addresses["lens"], "lens"));
|
|
176
|
+
}
|
|
177
|
+
get sidecarContract(): Contract {
|
|
178
|
+
return this._cached("sidecarContract", () => this._contract(this.addresses["sidecar"], "sidecar"));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
token(address: string): AsyncToken {
|
|
182
|
+
return new AsyncToken(this, address);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// -- call / send ------------------------------------------------------- //
|
|
186
|
+
async call(fn: Fn, args: any[] = [], opts: { value?: bigint; sender?: string | null } = {}): Promise<any> {
|
|
187
|
+
const overrides: Record<string, any> = {};
|
|
188
|
+
if (opts.value) overrides.value = opts.value;
|
|
189
|
+
const sender = opts.sender ?? this.address;
|
|
190
|
+
if (sender) overrides.from = sender;
|
|
191
|
+
try {
|
|
192
|
+
return await fn.staticCall(...args, overrides);
|
|
193
|
+
} catch (exc) {
|
|
194
|
+
throw wrapWeb3Error(this.chainKey, exc);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async buildTx(fn: Fn, args: any[] = [], opts: { value?: bigint; gas?: bigint | null } = {}): Promise<Record<string, any>> {
|
|
199
|
+
const sender = this.requireAddress();
|
|
200
|
+
const overrides: Record<string, any> = { from: sender };
|
|
201
|
+
if (opts.value) overrides.value = opts.value;
|
|
202
|
+
let tx: Record<string, any>;
|
|
203
|
+
try {
|
|
204
|
+
tx = { ...(await fn.populateTransaction(...args, overrides)) };
|
|
205
|
+
} catch (exc) {
|
|
206
|
+
throw wrapWeb3Error(this.chainKey, exc);
|
|
207
|
+
}
|
|
208
|
+
tx.nonce = await this.w3.getTransactionCount(sender, "pending");
|
|
209
|
+
tx.chainId = this.chainId;
|
|
210
|
+
if (opts.value) tx.value = opts.value;
|
|
211
|
+
const latest = await this.w3.getBlock("latest");
|
|
212
|
+
const feeData = await this.w3.getFeeData();
|
|
213
|
+
if (latest && latest.baseFeePerGas != null) {
|
|
214
|
+
let tip = feeData.maxPriorityFeePerGas;
|
|
215
|
+
if (tip == null) tip = 1_000_000_000n;
|
|
216
|
+
tx.maxPriorityFeePerGas = tip;
|
|
217
|
+
tx.maxFeePerGas = tip + 2n * latest.baseFeePerGas;
|
|
218
|
+
tx.type = 2;
|
|
219
|
+
delete tx.gasPrice;
|
|
220
|
+
} else {
|
|
221
|
+
tx.gasPrice = feeData.gasPrice ?? 0n;
|
|
222
|
+
tx.type = 0;
|
|
223
|
+
}
|
|
224
|
+
let gas = opts.gas ?? null;
|
|
225
|
+
if (gas !== null) {
|
|
226
|
+
tx.gasLimit = gas;
|
|
227
|
+
} else {
|
|
228
|
+
const overrides2: Record<string, any> = { from: sender };
|
|
229
|
+
if (opts.value) overrides2.value = opts.value;
|
|
230
|
+
let est = BigInt(await fn.estimateGas(...args, overrides2));
|
|
231
|
+
if (this.gasMultiplier && this.gasMultiplier !== 1.0) {
|
|
232
|
+
est = (est * BigInt(Math.round(this.gasMultiplier * 1000))) / 1000n;
|
|
233
|
+
}
|
|
234
|
+
tx.gasLimit = est;
|
|
235
|
+
}
|
|
236
|
+
delete tx.gas;
|
|
237
|
+
return tx;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async send(
|
|
241
|
+
fn: Fn,
|
|
242
|
+
args: any[] = [],
|
|
243
|
+
opts: {
|
|
244
|
+
value?: bigint;
|
|
245
|
+
gas?: bigint | null;
|
|
246
|
+
wait?: boolean;
|
|
247
|
+
simulate?: boolean;
|
|
248
|
+
private?: boolean | null;
|
|
249
|
+
timeout?: number;
|
|
250
|
+
buildOnly?: boolean;
|
|
251
|
+
} = {},
|
|
252
|
+
): Promise<any> {
|
|
253
|
+
const value = opts.value ?? 0n;
|
|
254
|
+
const wait = opts.wait ?? true;
|
|
255
|
+
const simulate = opts.simulate ?? true;
|
|
256
|
+
const timeout = opts.timeout ?? 180;
|
|
257
|
+
|
|
258
|
+
if (simulate) {
|
|
259
|
+
await this.call(fn, args, { value, sender: this.address });
|
|
260
|
+
}
|
|
261
|
+
if (opts.buildOnly) {
|
|
262
|
+
return this.buildTx(fn, args, { value, gas: opts.gas ?? null });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const account = this.requireSigner();
|
|
266
|
+
const usePrivate = opts.private == null ? this.privateRpcUrl !== null : opts.private;
|
|
267
|
+
const txh: string = await this._sendLock.run(async () => {
|
|
268
|
+
const tx = await this.buildTx(fn, args, { value, gas: opts.gas ?? null });
|
|
269
|
+
const raw = await account.signTransaction(tx as any);
|
|
270
|
+
if (usePrivate) return this._broadcastPrivate(raw);
|
|
271
|
+
const resp = await this.w3.broadcastTransaction(raw);
|
|
272
|
+
return resp.hash;
|
|
273
|
+
});
|
|
274
|
+
if (!wait) return txh;
|
|
275
|
+
|
|
276
|
+
let receipt;
|
|
277
|
+
try {
|
|
278
|
+
receipt = await this.w3.waitForTransaction(txh, 1, timeout * 1000);
|
|
279
|
+
} catch {
|
|
280
|
+
receipt = null;
|
|
281
|
+
}
|
|
282
|
+
if (receipt === null) {
|
|
283
|
+
const where = usePrivate ? "private relay" : "public mempool";
|
|
284
|
+
throw new SallyError(
|
|
285
|
+
`tx ${txh} not mined within ${timeout}s via ${where}. It may have been ` +
|
|
286
|
+
`dropped/not included — check the hash before resubmitting.`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
if (receipt.status !== 1) {
|
|
290
|
+
throw new SallyRevert("Error", [], null, `tx ${txh} reverted on-chain`);
|
|
291
|
+
}
|
|
292
|
+
return receipt;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private async _broadcastPrivate(rawTx: string): Promise<string> {
|
|
296
|
+
if (!this.privateRpcUrl) {
|
|
297
|
+
throw new SallyConfigError("private=true but no privateRpcUrl configured.");
|
|
298
|
+
}
|
|
299
|
+
if (this._privateW3 === null) {
|
|
300
|
+
this._privateW3 = new JsonRpcProvider(this.privateRpcUrl, Network.from(this.chainId), {
|
|
301
|
+
staticNetwork: Network.from(this.chainId),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const resp = await this._privateW3.broadcastTransaction(rawTx);
|
|
305
|
+
return resp.hash;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// -- namespaces -------------------------------------------------------- //
|
|
309
|
+
get prices(): AsyncPrices {
|
|
310
|
+
return this._cached("prices", () => new AsyncPrices(this));
|
|
311
|
+
}
|
|
312
|
+
get swap(): AsyncSwap {
|
|
313
|
+
return this._cached("swap", () => new AsyncSwap(this));
|
|
314
|
+
}
|
|
315
|
+
get wallet(): AsyncWallet {
|
|
316
|
+
return this._cached("wallet", () => new AsyncWallet(this));
|
|
317
|
+
}
|
|
318
|
+
get liquidity(): AsyncLiquidity {
|
|
319
|
+
return this._cached("liquidity", () => new AsyncLiquidity(this));
|
|
320
|
+
}
|
|
321
|
+
get fees(): AsyncFees {
|
|
322
|
+
return this._cached("fees", () => new AsyncFees(this));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
toString(): string {
|
|
326
|
+
const who = this.address ?? "read-only";
|
|
327
|
+
return `<AsyncSallyClient chain=${this.chainKey} id=${this.chainId} signer=${who}>`;
|
|
328
|
+
}
|
|
329
|
+
}
|
package/src/aio/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Sally SDK — {@link AsyncSallyClient}.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { AsyncSallyClient } from "sally-defi-ts-sdk/aio";
|
|
6
|
+
* import { Base } from "sally-defi-ts-sdk";
|
|
7
|
+
*
|
|
8
|
+
* const sally = new AsyncSallyClient("base", "https://mainnet.base.org");
|
|
9
|
+
* console.log((await sally.prices.usd(Base.USDC)).asFloat);
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { AsyncSallyClient } from "./client.js";
|
|
14
|
+
export type { AsyncSallyClientOptions } from "./client.js";
|
|
15
|
+
export { AsyncToken } from "./token.js";
|
|
16
|
+
export { AsyncPrices } from "./modules/prices.js";
|
|
17
|
+
export { AsyncSwap } from "./modules/swap.js";
|
|
18
|
+
export { AsyncWallet } from "./modules/wallet.js";
|
|
19
|
+
export { AsyncLiquidity } from "./modules/liquidity.js";
|
|
20
|
+
export { AsyncFees } from "./modules/fees.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/** Async referral & batch-fee accounting (swap controller). */
|
|
2
|
+
|
|
3
|
+
import { Contract, getAddress } from "ethers";
|
|
4
|
+
import type { AsyncSallyClient } from "../client.js";
|
|
5
|
+
import { ReferralFeeTokens } from "../../types.js";
|
|
6
|
+
|
|
7
|
+
export class AsyncFees {
|
|
8
|
+
private _c: AsyncSallyClient;
|
|
9
|
+
private _swap: Contract;
|
|
10
|
+
|
|
11
|
+
constructor(client: AsyncSallyClient) {
|
|
12
|
+
this._c = client;
|
|
13
|
+
this._swap = client.swapContract;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private _a(x: string): string {
|
|
17
|
+
return getAddress(x);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async referralTokenCount(user: string): Promise<number> {
|
|
21
|
+
return Number(await this._c.call(this._swap.getFunction("getReferralFeeTokenCount"), [this._a(user)]));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async referralTokens(user: string, start = 0, stop = 0): Promise<ReferralFeeTokens> {
|
|
25
|
+
if (stop === 0) {
|
|
26
|
+
stop = Math.max(await this.referralTokenCount(this._a(user)), 1);
|
|
27
|
+
}
|
|
28
|
+
const r = await this._c.call(this._swap.getFunction("getReferralFeeTokens"), [this._a(user), start, stop]);
|
|
29
|
+
return ReferralFeeTokens.fromRaw(r);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async totalReferralOwed(user: string): Promise<bigint> {
|
|
33
|
+
return BigInt(await this._c.call(this._swap.getFunction("totalReferralOwed"), [this._a(user)]));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async referralFee(user: string, token: string): Promise<bigint> {
|
|
37
|
+
return BigInt(await this._c.call(this._swap.getFunction("referralFees"), [this._a(user), this._a(token)]));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async batchFee(token: string): Promise<bigint> {
|
|
41
|
+
return BigInt(await this._c.call(this._swap.getFunction("batchFees"), [this._a(token)]));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async feeBasisPoints(): Promise<bigint> {
|
|
45
|
+
return BigInt(await this._c.call(this._swap.getFunction("feeBasisPoints"), []));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async swapFee(amount: bigint): Promise<[bigint, bigint]> {
|
|
49
|
+
const r = await this._c.call(this._swap.getFunction("getSwapFee"), [amount]);
|
|
50
|
+
return [BigInt(r[0]), BigInt(r[1])];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async claimReferral(tokens: string[], tx: Record<string, any> = {}): Promise<any> {
|
|
54
|
+
return this._c.send(this._swap.getFunction("claimReferralFees"), [tokens.map((t) => this._a(t))], tx);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async claimBatch(tokens: string[], tx: Record<string, any> = {}): Promise<any> {
|
|
58
|
+
return this._c.send(this._swap.getFunction("claimBatchFees"), [tokens.map((t) => this._a(t))], tx);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** Async liquidity: core adds + position/pool/lock reads. */
|
|
2
|
+
|
|
3
|
+
import { Contract, getAddress } from "ethers";
|
|
4
|
+
import type { AsyncSallyClient } from "../client.js";
|
|
5
|
+
import { ZERO, isNative } from "../../token.js";
|
|
6
|
+
import {
|
|
7
|
+
ClaimableFees,
|
|
8
|
+
Lock,
|
|
9
|
+
V2PoolState,
|
|
10
|
+
V2Position,
|
|
11
|
+
V3PoolState,
|
|
12
|
+
V3Position,
|
|
13
|
+
V4Position,
|
|
14
|
+
} from "../../types.js";
|
|
15
|
+
|
|
16
|
+
type Tx = Record<string, any>;
|
|
17
|
+
|
|
18
|
+
export class AsyncLiquidity {
|
|
19
|
+
private _c: AsyncSallyClient;
|
|
20
|
+
private _liq: Contract;
|
|
21
|
+
private _side: Contract;
|
|
22
|
+
|
|
23
|
+
constructor(client: AsyncSallyClient) {
|
|
24
|
+
this._c = client;
|
|
25
|
+
this._liq = client.liquidityContract;
|
|
26
|
+
this._side = client.sidecarContract;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private _a(x: string): string {
|
|
30
|
+
return getAddress(x);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async deadline(secs = 1200): Promise<number> {
|
|
34
|
+
return Math.floor(Date.now() / 1000) + secs;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async _approveAndValue(pairs: [string, bigint][]): Promise<bigint> {
|
|
38
|
+
const spender = this._c.addresses["liquidity"];
|
|
39
|
+
let value = 0n;
|
|
40
|
+
for (const [token, amount] of pairs) {
|
|
41
|
+
if (amount <= 0n) continue;
|
|
42
|
+
if (isNative(token)) value += amount;
|
|
43
|
+
else await this._c.token(token).ensureAllowance(spender, amount);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// -- adds -------------------------------------------------------------- //
|
|
49
|
+
async addV2(
|
|
50
|
+
dexId: number,
|
|
51
|
+
tokenA: string,
|
|
52
|
+
tokenB: string,
|
|
53
|
+
amountA: bigint,
|
|
54
|
+
amountB: bigint,
|
|
55
|
+
opts: {
|
|
56
|
+
amountAMin?: bigint;
|
|
57
|
+
amountBMin?: bigint;
|
|
58
|
+
lockDays?: number;
|
|
59
|
+
referral?: string;
|
|
60
|
+
deadline?: number | null;
|
|
61
|
+
approve?: boolean;
|
|
62
|
+
tx?: Tx;
|
|
63
|
+
} = {},
|
|
64
|
+
): Promise<any> {
|
|
65
|
+
tokenA = this._a(tokenA);
|
|
66
|
+
tokenB = this._a(tokenB);
|
|
67
|
+
const approve = opts.approve ?? true;
|
|
68
|
+
const value = approve ? await this._approveAndValue([[tokenA, amountA], [tokenB, amountB]]) : 0n;
|
|
69
|
+
const p = [
|
|
70
|
+
dexId, tokenA, tokenB, amountA, amountB, opts.amountAMin ?? 0n, opts.amountBMin ?? 0n,
|
|
71
|
+
opts.lockDays ?? 0, this._a(opts.referral ?? ZERO), opts.deadline ?? (await this.deadline()),
|
|
72
|
+
];
|
|
73
|
+
return this._c.send(this._liq.getFunction("addLiquidityV2"), [p], { value, ...(opts.tx ?? {}) });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async addV3(
|
|
77
|
+
dexId: number,
|
|
78
|
+
token0: string,
|
|
79
|
+
token1: string,
|
|
80
|
+
fee: number,
|
|
81
|
+
tickLower: number,
|
|
82
|
+
tickUpper: number,
|
|
83
|
+
amount0: bigint,
|
|
84
|
+
amount1: bigint,
|
|
85
|
+
opts: {
|
|
86
|
+
amount0Min?: bigint;
|
|
87
|
+
amount1Min?: bigint;
|
|
88
|
+
lockDays?: number;
|
|
89
|
+
referral?: string;
|
|
90
|
+
deadline?: number | null;
|
|
91
|
+
approve?: boolean;
|
|
92
|
+
tx?: Tx;
|
|
93
|
+
} = {},
|
|
94
|
+
): Promise<any> {
|
|
95
|
+
token0 = this._a(token0);
|
|
96
|
+
token1 = this._a(token1);
|
|
97
|
+
const approve = opts.approve ?? true;
|
|
98
|
+
const value = approve ? await this._approveAndValue([[token0, amount0], [token1, amount1]]) : 0n;
|
|
99
|
+
const p = [
|
|
100
|
+
dexId, token0, token1, fee, tickLower, tickUpper, amount0, amount1,
|
|
101
|
+
opts.amount0Min ?? 0n, opts.amount1Min ?? 0n, opts.lockDays ?? 0, this._a(opts.referral ?? ZERO),
|
|
102
|
+
opts.deadline ?? (await this.deadline()),
|
|
103
|
+
];
|
|
104
|
+
return this._c.send(this._liq.getFunction("addLiquidityV3"), [p], { value, ...(opts.tx ?? {}) });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// -- claim / lock ------------------------------------------------------ //
|
|
108
|
+
async claimFeesV3(npm: string, tokenId: bigint, recipient: string | null = null, tx: Tx = {}): Promise<any> {
|
|
109
|
+
recipient = recipient ?? this._c.requireAddress();
|
|
110
|
+
return this._c.send(this._liq.getFunction("claimFeesV3"), [this._a(npm), tokenId, this._a(recipient)], tx);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async massClaimFees(npms: string[], tokenIds: bigint[], outputToken: string, tx: Tx = {}): Promise<any> {
|
|
114
|
+
return this._c.send(
|
|
115
|
+
this._liq.getFunction("massClaimFees"),
|
|
116
|
+
[npms.map((a) => this._a(a)), tokenIds, this._a(outputToken)],
|
|
117
|
+
tx,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async lockV3(npm: string, tokenId: bigint, lockDays: number, tx: Tx = {}): Promise<any> {
|
|
122
|
+
return this._c.send(this._liq.getFunction("lockV3Position"), [this._a(npm), tokenId, lockDays], tx);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// -- reads ------------------------------------------------------------- //
|
|
126
|
+
async positionsV2(wallet: string, pairs: string[]): Promise<V2Position[]> {
|
|
127
|
+
const r = await this._c.call(this._side.getFunction("getV2Positions"), [this._a(wallet), pairs.map((p) => this._a(p))]);
|
|
128
|
+
return [...r].map((x) => V2Position.fromRaw(x));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async positionsV3(wallet: string, start = 0, end = 0): Promise<V3Position[]> {
|
|
132
|
+
const r = await this._c.call(this._side.getFunction("getV3Positions"), [this._a(wallet), start, end]);
|
|
133
|
+
return [...r].map((x) => V3Position.fromRaw(x));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async positionsV4(wallet: string, start = 0, end = 0): Promise<V4Position[]> {
|
|
137
|
+
const r = await this._c.call(this._side.getFunction("getV4Positions"), [this._a(wallet), start, end]);
|
|
138
|
+
return [...r].map((x) => V4Position.fromRaw(x));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async poolStateV2(pair: string): Promise<V2PoolState> {
|
|
142
|
+
return V2PoolState.fromRaw(await this._c.call(this._side.getFunction("getV2PoolState"), [this._a(pair)]));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async poolStateV3(pool: string): Promise<V3PoolState> {
|
|
146
|
+
return V3PoolState.fromRaw(await this._c.call(this._side.getFunction("getV3PoolState"), [this._a(pool)]));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async locks(wallet: string): Promise<Lock[]> {
|
|
150
|
+
const r = await this._c.call(this._side.getFunction("getLocks"), [this._a(wallet)]);
|
|
151
|
+
return [...r].map((x) => Lock.fromRaw(x));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async claimableFees(npm: string, tokenId: bigint): Promise<ClaimableFees> {
|
|
155
|
+
return ClaimableFees.fromRaw(await this._c.call(this._side.getFunction("getClaimableFees"), [this._a(npm), tokenId]));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async usdValue(token: string, amount: bigint): Promise<bigint> {
|
|
159
|
+
return BigInt(await this._c.call(this._side.getFunction("usdValue"), [this._a(token), amount]));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async isPaused(): Promise<boolean> {
|
|
163
|
+
return Boolean(await this._c.call(this._liq.getFunction("paused"), []));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async dexCount(version: number): Promise<number> {
|
|
167
|
+
const name = { 2: "v2DexCount", 3: "v3DexCount", 4: "v4DexCount" }[version];
|
|
168
|
+
return Number(await this._c.call(this._liq.getFunction(name!), []));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async dexes(version: number): Promise<any[][]> {
|
|
172
|
+
const name = { 2: "v2Dexes", 3: "v3Dexes", 4: "v4Dexes" }[version];
|
|
173
|
+
const n = await this.dexCount(version);
|
|
174
|
+
const out: any[][] = [];
|
|
175
|
+
for (let i = 0; i < n; i++) {
|
|
176
|
+
const r = await this._c.call(this._liq.getFunction(name!), [i]);
|
|
177
|
+
out.push([...r]);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Async pricing & token-safety reads. */
|
|
2
|
+
|
|
3
|
+
import { Contract, getAddress } from "ethers";
|
|
4
|
+
import type { AsyncSallyClient } from "../client.js";
|
|
5
|
+
import { PriceResult, TokenInfo } from "../../types.js";
|
|
6
|
+
|
|
7
|
+
export class AsyncPrices {
|
|
8
|
+
private _c: AsyncSallyClient;
|
|
9
|
+
private _lens: Contract;
|
|
10
|
+
|
|
11
|
+
constructor(client: AsyncSallyClient) {
|
|
12
|
+
this._c = client;
|
|
13
|
+
this._lens = client.lensContract;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private _a(x: string): string {
|
|
17
|
+
return getAddress(x);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async usd(token: string): Promise<PriceResult> {
|
|
21
|
+
return PriceResult.fromRaw(await this._c.call(this._lens.getFunction("getUsdPrice"), [this._a(token)]));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async usdImpact(token: string): Promise<PriceResult> {
|
|
25
|
+
return PriceResult.fromRaw(await this._c.call(this._lens.getFunction("getUsdPriceImpact"), [this._a(token)]));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async weth(token: string): Promise<PriceResult> {
|
|
29
|
+
return PriceResult.fromRaw(await this._c.call(this._lens.getFunction("getWethPrice"), [this._a(token)]));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async spot(tokenIn: string, tokenOut: string): Promise<bigint> {
|
|
33
|
+
return BigInt(await this._c.call(this._lens.getFunction("getSpotPrice"), [this._a(tokenIn), this._a(tokenOut)]));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async usdLiquidity(token: string, version: number | null = null): Promise<bigint> {
|
|
37
|
+
const name = {
|
|
38
|
+
null: "getUsdLiquidity",
|
|
39
|
+
2: "getUsdLiquidityV2",
|
|
40
|
+
3: "getUsdLiquidityV3",
|
|
41
|
+
4: "getUsdLiquidityV4",
|
|
42
|
+
}[version === null ? "null" : version];
|
|
43
|
+
return BigInt(await this._c.call(this._lens.getFunction(name!), [this._a(token)]));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async tokenInfo(token: string, opts: { probeValue?: bigint | null } = {}): Promise<TokenInfo> {
|
|
47
|
+
const { SafetyConfig } = await import("../../safety.js");
|
|
48
|
+
const { ZERO } = await import("../../token.js");
|
|
49
|
+
let probeValue = opts.probeValue ?? null;
|
|
50
|
+
if (probeValue === null) probeValue = new SafetyConfig().probeValue;
|
|
51
|
+
const raw = await this._lens.getFunction("getTokenInfos").staticCall(this._a(token), {
|
|
52
|
+
from: ZERO,
|
|
53
|
+
value: probeValue,
|
|
54
|
+
});
|
|
55
|
+
return TokenInfo.fromRaw(raw);
|
|
56
|
+
}
|
|
57
|
+
}
|