@leofcoin/endpoint-clients 0.3.16 → 0.3.17
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/exports/dex.js +137 -0
- package/exports/index.js +1 -0
- package/exports/types/dex.d.ts +45 -0
- package/exports/types/index.d.ts +2 -0
- package/package.json +7 -2
package/exports/dex.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const BASIS_POINTS = 10000n;
|
|
2
|
+
const address = (value, label) => {
|
|
3
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
4
|
+
throw new Error(`${label} address required`);
|
|
5
|
+
return value;
|
|
6
|
+
};
|
|
7
|
+
const amount = (value, label, allowZero = false) => {
|
|
8
|
+
const result = BigInt(value);
|
|
9
|
+
if (allowZero ? result < 0n : result <= 0n)
|
|
10
|
+
throw new Error(`${label} must be ${allowZero ? 'non-negative' : 'positive'}`);
|
|
11
|
+
return result;
|
|
12
|
+
};
|
|
13
|
+
const safeInteger = (value, label) => {
|
|
14
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
15
|
+
throw new Error(`${label} must be a non-negative safe integer`);
|
|
16
|
+
return value;
|
|
17
|
+
};
|
|
18
|
+
class DexClient {
|
|
19
|
+
transport;
|
|
20
|
+
factory;
|
|
21
|
+
constructor(transport, factory) {
|
|
22
|
+
if (!transport || typeof transport.staticCall !== 'function')
|
|
23
|
+
throw new Error('DEX transport with staticCall required');
|
|
24
|
+
this.transport = transport;
|
|
25
|
+
this.factory = address(factory, 'factory');
|
|
26
|
+
}
|
|
27
|
+
async poolFor(tokenA, tokenB) {
|
|
28
|
+
tokenA = address(tokenA, 'token A');
|
|
29
|
+
tokenB = address(tokenB, 'token B');
|
|
30
|
+
if (tokenA === tokenB)
|
|
31
|
+
throw new Error('pool tokens must differ');
|
|
32
|
+
const pool = await this.transport.staticCall(this.factory, 'poolFor', [tokenA, tokenB]);
|
|
33
|
+
return pool === undefined || pool === null || pool === '' ? undefined : String(pool);
|
|
34
|
+
}
|
|
35
|
+
async pools() {
|
|
36
|
+
const pools = await this.transport.staticCall(this.factory, 'pools', []);
|
|
37
|
+
if (!Array.isArray(pools))
|
|
38
|
+
throw new Error('factory returned an invalid pool list');
|
|
39
|
+
return pools.map(String);
|
|
40
|
+
}
|
|
41
|
+
async poolState(pool) {
|
|
42
|
+
pool = address(pool, 'pool');
|
|
43
|
+
const [token0, token1, feeBasisPoints, reserve0, reserve1, totalLiquidity] = await Promise.all([
|
|
44
|
+
this.transport.staticCall(pool, 'token0', []),
|
|
45
|
+
this.transport.staticCall(pool, 'token1', []),
|
|
46
|
+
this.transport.staticCall(pool, 'feeBasisPoints', []),
|
|
47
|
+
this.transport.staticCall(pool, 'reserve0', []),
|
|
48
|
+
this.transport.staticCall(pool, 'reserve1', []),
|
|
49
|
+
this.transport.staticCall(pool, 'totalLiquidity', [])
|
|
50
|
+
]);
|
|
51
|
+
return {
|
|
52
|
+
address: pool,
|
|
53
|
+
token0: address(String(token0 ?? ''), 'token 0'),
|
|
54
|
+
token1: address(String(token1 ?? ''), 'token 1'),
|
|
55
|
+
feeBasisPoints: BigInt(feeBasisPoints),
|
|
56
|
+
reserve0: BigInt(reserve0),
|
|
57
|
+
reserve1: BigInt(reserve1),
|
|
58
|
+
totalLiquidity: BigInt(totalLiquidity)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async liquidityOf(pool, provider) {
|
|
62
|
+
pool = address(pool, 'pool');
|
|
63
|
+
provider = address(provider, 'provider');
|
|
64
|
+
return BigInt((await this.transport.staticCall(pool, 'liquidityOf', [provider])));
|
|
65
|
+
}
|
|
66
|
+
async quoteExactInput(pool, tokenIn, amountIn) {
|
|
67
|
+
pool = address(pool, 'pool');
|
|
68
|
+
tokenIn = address(tokenIn, 'input token');
|
|
69
|
+
const input = amount(amountIn, 'input amount');
|
|
70
|
+
return BigInt((await this.transport.staticCall(pool, 'getAmountOut', [tokenIn, input])));
|
|
71
|
+
}
|
|
72
|
+
minimumAmountOut(quote, slippageBasisPoints) {
|
|
73
|
+
const output = amount(quote, 'quote');
|
|
74
|
+
const slippage = amount(slippageBasisPoints, 'slippage', true);
|
|
75
|
+
if (slippage >= BASIS_POINTS)
|
|
76
|
+
throw new Error('slippage must be below 10000 basis points');
|
|
77
|
+
return (output * (BASIS_POINTS - slippage)) / BASIS_POINTS;
|
|
78
|
+
}
|
|
79
|
+
approve(from, token, pool, value, options = {}) {
|
|
80
|
+
return this.#transaction(from, token, 'approve', [address(pool, 'pool'), amount(value, 'approval amount')], options);
|
|
81
|
+
}
|
|
82
|
+
addLiquidity(from, pool, amount0Desired, amount1Desired, minimumLiquidity = 0n, options = {}) {
|
|
83
|
+
return this.#transaction(from, pool, 'addLiquidity', [
|
|
84
|
+
amount(amount0Desired, 'token 0 amount'),
|
|
85
|
+
amount(amount1Desired, 'token 1 amount'),
|
|
86
|
+
amount(minimumLiquidity, 'minimum liquidity', true)
|
|
87
|
+
], options);
|
|
88
|
+
}
|
|
89
|
+
removeLiquidity(from, pool, liquidity, minimumAmount0 = 0n, minimumAmount1 = 0n, receiver = from, options = {}) {
|
|
90
|
+
return this.#transaction(from, pool, 'removeLiquidity', [
|
|
91
|
+
amount(liquidity, 'liquidity'),
|
|
92
|
+
amount(minimumAmount0, 'minimum token 0 amount', true),
|
|
93
|
+
amount(minimumAmount1, 'minimum token 1 amount', true),
|
|
94
|
+
address(receiver, 'receiver')
|
|
95
|
+
], options);
|
|
96
|
+
}
|
|
97
|
+
swapExactInput(from, pool, tokenIn, amountIn, minimumAmountOut, receiver = from, deadline = Date.now() + 20 * 60 * 1000, options = {}) {
|
|
98
|
+
return this.#transaction(from, pool, 'swapExactTokensForTokens', [
|
|
99
|
+
address(tokenIn, 'input token'),
|
|
100
|
+
amount(amountIn, 'input amount'),
|
|
101
|
+
amount(minimumAmountOut, 'minimum output amount', true),
|
|
102
|
+
address(receiver, 'receiver'),
|
|
103
|
+
safeInteger(deadline, 'deadline')
|
|
104
|
+
], options);
|
|
105
|
+
}
|
|
106
|
+
registerPool(from, pool, options = {}) {
|
|
107
|
+
return this.#transaction(from, this.factory, 'registerPool', [address(pool, 'pool')], options);
|
|
108
|
+
}
|
|
109
|
+
async signAndSend(transaction, signer) {
|
|
110
|
+
if (typeof signer !== 'function')
|
|
111
|
+
throw new Error('transaction signer required');
|
|
112
|
+
if (typeof this.transport.sendTransaction !== 'function')
|
|
113
|
+
throw new Error('DEX transport does not support sendTransaction');
|
|
114
|
+
const signed = await signer(transaction);
|
|
115
|
+
return this.transport.sendTransaction(signed);
|
|
116
|
+
}
|
|
117
|
+
async #transaction(from, to, method, params, options) {
|
|
118
|
+
from = address(from, 'sender');
|
|
119
|
+
to = address(to, 'target');
|
|
120
|
+
let nonce = options.nonce;
|
|
121
|
+
if (nonce === undefined) {
|
|
122
|
+
if (typeof this.transport.getNonce !== 'function')
|
|
123
|
+
throw new Error('nonce required when transport has no getNonce');
|
|
124
|
+
nonce = Number(await this.transport.getNonce(from)) + 1;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
from,
|
|
128
|
+
to,
|
|
129
|
+
method,
|
|
130
|
+
params,
|
|
131
|
+
nonce: safeInteger(nonce, 'nonce'),
|
|
132
|
+
timestamp: safeInteger(options.timestamp ?? Date.now(), 'timestamp')
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { DexClient as default };
|
package/exports/index.js
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type DexTransaction = {
|
|
2
|
+
from: string;
|
|
3
|
+
to: string;
|
|
4
|
+
method: string;
|
|
5
|
+
params: unknown[];
|
|
6
|
+
nonce: number;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
};
|
|
9
|
+
export type DexTransport = {
|
|
10
|
+
staticCall: (contract: string, method: string, params?: unknown[]) => Promise<unknown>;
|
|
11
|
+
getNonce?: (address: string) => Promise<number | string | bigint>;
|
|
12
|
+
sendTransaction?: (transaction: unknown) => Promise<unknown>;
|
|
13
|
+
};
|
|
14
|
+
export type DexSigner = (transaction: DexTransaction) => Promise<unknown> | unknown;
|
|
15
|
+
export type TransactionOptions = {
|
|
16
|
+
nonce?: number;
|
|
17
|
+
timestamp?: number;
|
|
18
|
+
};
|
|
19
|
+
export type PoolState = {
|
|
20
|
+
address: string;
|
|
21
|
+
token0: string;
|
|
22
|
+
token1: string;
|
|
23
|
+
feeBasisPoints: bigint;
|
|
24
|
+
reserve0: bigint;
|
|
25
|
+
reserve1: bigint;
|
|
26
|
+
totalLiquidity: bigint;
|
|
27
|
+
};
|
|
28
|
+
export default class DexClient {
|
|
29
|
+
#private;
|
|
30
|
+
readonly transport: DexTransport;
|
|
31
|
+
readonly factory: string;
|
|
32
|
+
constructor(transport: DexTransport, factory: string);
|
|
33
|
+
poolFor(tokenA: string, tokenB: string): Promise<string | undefined>;
|
|
34
|
+
pools(): Promise<string[]>;
|
|
35
|
+
poolState(pool: string): Promise<PoolState>;
|
|
36
|
+
liquidityOf(pool: string, provider: string): Promise<bigint>;
|
|
37
|
+
quoteExactInput(pool: string, tokenIn: string, amountIn: bigint | number | string): Promise<bigint>;
|
|
38
|
+
minimumAmountOut(quote: bigint | number | string, slippageBasisPoints: bigint | number | string): bigint;
|
|
39
|
+
approve(from: string, token: string, pool: string, value: bigint | number | string, options?: TransactionOptions): Promise<DexTransaction>;
|
|
40
|
+
addLiquidity(from: string, pool: string, amount0Desired: bigint | number | string, amount1Desired: bigint | number | string, minimumLiquidity?: bigint | number | string, options?: TransactionOptions): Promise<DexTransaction>;
|
|
41
|
+
removeLiquidity(from: string, pool: string, liquidity: bigint | number | string, minimumAmount0?: bigint | number | string, minimumAmount1?: bigint | number | string, receiver?: string, options?: TransactionOptions): Promise<DexTransaction>;
|
|
42
|
+
swapExactInput(from: string, pool: string, tokenIn: string, amountIn: bigint | number | string, minimumAmountOut: bigint | number | string, receiver?: string, deadline?: number, options?: TransactionOptions): Promise<DexTransaction>;
|
|
43
|
+
registerPool(from: string, pool: string, options?: TransactionOptions): Promise<DexTransaction>;
|
|
44
|
+
signAndSend(transaction: DexTransaction, signer: DexSigner): Promise<unknown>;
|
|
45
|
+
}
|
package/exports/types/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { default as ws } from './ws.js';
|
|
2
2
|
export { default as http } from './http.js';
|
|
3
3
|
export { default as direct } from './direct.js';
|
|
4
|
+
export { default as DexClient } from './dex.js';
|
|
5
|
+
export type { DexSigner, DexTransaction, DexTransport, PoolState, TransactionOptions } from './dex.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leofcoin/endpoint-clients",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,9 +19,14 @@
|
|
|
19
19
|
"import": "./exports/direct.js",
|
|
20
20
|
"types": "./exports/types/direct.d.ts"
|
|
21
21
|
},
|
|
22
|
+
"./dex": {
|
|
23
|
+
"import": "./exports/dex.js",
|
|
24
|
+
"types": "./exports/types/dex.d.ts"
|
|
25
|
+
},
|
|
22
26
|
"./ws.js": "./exports/ws.js",
|
|
23
27
|
"./http.js": "./exports/http.js",
|
|
24
|
-
"./direct.js": "./exports/direct.js"
|
|
28
|
+
"./direct.js": "./exports/direct.js",
|
|
29
|
+
"./dex.js": "./exports/dex.js"
|
|
25
30
|
},
|
|
26
31
|
"files": [
|
|
27
32
|
"exports"
|