@runesx/api-client 0.0.1
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 +47 -0
- package/package.json +58 -0
- package/src/api.mjs +50 -0
- package/src/config.mjs +8 -0
- package/src/index.mjs +80 -0
- package/src/socket.mjs +247 -0
- package/src/store/coinStore.mjs +113 -0
- package/src/store/poolStore.mjs +136 -0
- package/src/store/userSharesStore.mjs +90 -0
- package/src/store/walletStore.mjs +96 -0
- package/src/utils/liquidityUtils.mjs +213 -0
- package/src/utils/swapUtils.mjs +447 -0
- package/src/waitForStores.mjs +167 -0
- package/src/workers/swapWorker.mjs +14 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { BigNumber } from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
const poolStore = {
|
|
4
|
+
pools: new Map(), // Store pool data by pool ID
|
|
5
|
+
isInitialReceived: false, // Track if initial pool data is received
|
|
6
|
+
pendingUpdates: [], // Buffer for updates before initial data
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Initialize pools with initial data
|
|
10
|
+
const setInitialPools = (pools) => {
|
|
11
|
+
poolStore.pools.clear();
|
|
12
|
+
pools.forEach(pool => {
|
|
13
|
+
poolStore.pools.set(pool.id, {
|
|
14
|
+
id: pool.id,
|
|
15
|
+
reserveA: pool.reserveA,
|
|
16
|
+
reserveB: pool.reserveB,
|
|
17
|
+
totalShares: pool.totalShares,
|
|
18
|
+
activeLiquidityProviders: pool.activeLiquidityProviders || 0,
|
|
19
|
+
runesCompliant: pool.runesCompliant || false,
|
|
20
|
+
lpFeeRate: pool.lpFeeRate || 30,
|
|
21
|
+
treasuryFeeRate: pool.treasuryFeeRate || 5,
|
|
22
|
+
coinA: pool.coinA,
|
|
23
|
+
coinB: pool.coinB,
|
|
24
|
+
liquidityShares: pool.liquidityShares || [],
|
|
25
|
+
updatedAt: pool.updatedAt,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
poolStore.isInitialReceived = true;
|
|
29
|
+
console.log(`Initialized with ${pools.length} pools`);
|
|
30
|
+
|
|
31
|
+
// Process buffered updates
|
|
32
|
+
if (poolStore.pendingUpdates.length > 0) {
|
|
33
|
+
console.log(`Processing ${poolStore.pendingUpdates.length} buffered pool updates`);
|
|
34
|
+
poolStore.pendingUpdates.forEach(({ pools }) => {
|
|
35
|
+
pools.forEach(pool => updatePool(pool));
|
|
36
|
+
});
|
|
37
|
+
poolStore.pendingUpdates = [];
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Update a single pool
|
|
42
|
+
const updatePool = (pool) => {
|
|
43
|
+
if (!poolStore.isInitialReceived) {
|
|
44
|
+
console.log('Buffering pool update, initial data not yet received:', pool);
|
|
45
|
+
poolStore.pendingUpdates.push({ pools: [pool] });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const existingPool = poolStore.pools.get(pool.id);
|
|
50
|
+
const incomingUpdatedAt = new Date(pool.updatedAt).getTime();
|
|
51
|
+
|
|
52
|
+
if (existingPool) {
|
|
53
|
+
const existingUpdatedAt = new Date(existingPool.updatedAt).getTime();
|
|
54
|
+
if (incomingUpdatedAt <= existingUpdatedAt) {
|
|
55
|
+
console.log(`Skipping stale update for pool ${pool.id}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`Updating pool ${pool.id}:`, {
|
|
60
|
+
reserveA: pool.reserveA,
|
|
61
|
+
reserveB: pool.reserveB,
|
|
62
|
+
totalShares: pool.totalShares,
|
|
63
|
+
activeLiquidityProviders: pool.activeLiquidityProviders,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
Object.assign(existingPool, {
|
|
67
|
+
reserveA: pool.reserveA,
|
|
68
|
+
reserveB: pool.reserveB,
|
|
69
|
+
updatedAt: pool.updatedAt,
|
|
70
|
+
...(pool.totalShares !== undefined && { totalShares: pool.totalShares }),
|
|
71
|
+
...(pool.activeLiquidityProviders !== undefined && { activeLiquidityProviders: pool.activeLiquidityProviders }),
|
|
72
|
+
...(pool.runesCompliant !== undefined && { runesCompliant: pool.runesCompliant }),
|
|
73
|
+
...(pool.lpFeeRate !== undefined && { lpFeeRate: pool.lpFeeRate }),
|
|
74
|
+
...(pool.treasuryFeeRate !== undefined && { treasuryFeeRate: pool.treasuryFeeRate }),
|
|
75
|
+
...(pool.coinA && { coinA: pool.coinA }),
|
|
76
|
+
...(pool.coinB && { coinB: pool.coinB }),
|
|
77
|
+
...(pool.liquidityShares && {
|
|
78
|
+
liquidityShares: pool.liquidityShares.reduce((acc, newShare) => {
|
|
79
|
+
if (new BigNumber(newShare.shares).isZero()) {
|
|
80
|
+
return acc.filter(share => share.id !== newShare.id);
|
|
81
|
+
}
|
|
82
|
+
const existingShareIndex = acc.findIndex(share => share.id === newShare.id);
|
|
83
|
+
if (existingShareIndex >= 0) {
|
|
84
|
+
acc[existingShareIndex] = newShare;
|
|
85
|
+
} else {
|
|
86
|
+
acc.push(newShare);
|
|
87
|
+
}
|
|
88
|
+
return acc;
|
|
89
|
+
}, [...(existingPool.liquidityShares || [])]),
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (new BigNumber(existingPool.totalShares).isZero()) {
|
|
94
|
+
console.log(`Removing pool ${pool.id} with zero totalShares`);
|
|
95
|
+
poolStore.pools.delete(pool.id);
|
|
96
|
+
}
|
|
97
|
+
} else if (pool.coinA && pool.coinB && new BigNumber(pool.totalShares).gt(0)) {
|
|
98
|
+
console.log(`Adding new pool ${pool.id}:`, pool);
|
|
99
|
+
poolStore.pools.set(pool.id, {
|
|
100
|
+
id: pool.id,
|
|
101
|
+
reserveA: pool.reserveA,
|
|
102
|
+
reserveB: pool.reserveB,
|
|
103
|
+
totalShares: pool.totalShares,
|
|
104
|
+
runesCompliant: pool.runesCompliant || false,
|
|
105
|
+
lpFeeRate: pool.lpFeeRate || 30,
|
|
106
|
+
treasuryFeeRate: pool.treasuryFeeRate || 5,
|
|
107
|
+
coinA: pool.coinA,
|
|
108
|
+
coinB: pool.coinB,
|
|
109
|
+
activeLiquidityProviders: pool.activeLiquidityProviders || 0,
|
|
110
|
+
liquidityShares: pool.liquidityShares || [],
|
|
111
|
+
updatedAt: pool.updatedAt,
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
console.warn(`Ignoring update for unknown pool ${pool.id} without full data or zero totalShares`);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Get all pools
|
|
119
|
+
const getPools = () => {
|
|
120
|
+
return Array.from(poolStore.pools.values());
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Get a specific pool by ID
|
|
124
|
+
const getPool = (poolId) => {
|
|
125
|
+
return poolStore.pools.get(poolId);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Reset store on disconnect or error
|
|
129
|
+
const resetPools = () => {
|
|
130
|
+
poolStore.pools.clear();
|
|
131
|
+
poolStore.isInitialReceived = false;
|
|
132
|
+
poolStore.pendingUpdates = [];
|
|
133
|
+
console.log('Reset pool state due to disconnect or error');
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export { poolStore, setInitialPools, updatePool, getPools, getPool, resetPools };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/store/userSharesStore.mjs
|
|
2
|
+
import { BigNumber } from 'bignumber.js';
|
|
3
|
+
|
|
4
|
+
const userSharesStore = {
|
|
5
|
+
userShares: new Map(),
|
|
6
|
+
isInitialReceived: false,
|
|
7
|
+
pendingUpdates: [],
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const setInitialUserShares = (userShares) => {
|
|
11
|
+
userSharesStore.userShares.clear();
|
|
12
|
+
userShares.forEach(share => {
|
|
13
|
+
userSharesStore.userShares.set(share.poolId, {
|
|
14
|
+
poolId: share.poolId,
|
|
15
|
+
shares: new BigNumber(share.shares).toString(),
|
|
16
|
+
updatedAt: share.updatedAt,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
userSharesStore.isInitialReceived = true;
|
|
20
|
+
console.log(`Initialized with ${userShares.length} user shares`);
|
|
21
|
+
|
|
22
|
+
if (userSharesStore.pendingUpdates.length > 0) {
|
|
23
|
+
console.log(`Processing ${userSharesStore.pendingUpdates.length} buffered user share updates`);
|
|
24
|
+
userSharesStore.pendingUpdates.forEach(({ userShares }) => {
|
|
25
|
+
userShares.forEach(share => updateUserShare(share));
|
|
26
|
+
});
|
|
27
|
+
userSharesStore.pendingUpdates = [];
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const updateUserShare = (share) => {
|
|
32
|
+
if (!userSharesStore.isInitialReceived) {
|
|
33
|
+
console.log('Buffering user share update, initial data not yet received:', share);
|
|
34
|
+
userSharesStore.pendingUpdates.push({ userShares: [share] });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const existingShare = userSharesStore.userShares.get(share.poolId);
|
|
39
|
+
const incomingUpdatedAt = new Date(share.updatedAt).getTime();
|
|
40
|
+
|
|
41
|
+
if (existingShare) {
|
|
42
|
+
const existingUpdatedAt = new Date(existingShare.updatedAt).getTime();
|
|
43
|
+
if (incomingUpdatedAt <= existingUpdatedAt) {
|
|
44
|
+
console.log(`Skipping stale update for user share in pool ${share.poolId}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(`Updating user share for pool ${share.poolId}:`, {
|
|
49
|
+
shares: share.shares,
|
|
50
|
+
updatedAt: share.updatedAt,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
userSharesStore.userShares.set(share.poolId, {
|
|
54
|
+
poolId: share.poolId,
|
|
55
|
+
shares: new BigNumber(share.shares).toString(),
|
|
56
|
+
updatedAt: share.updatedAt,
|
|
57
|
+
});
|
|
58
|
+
} else if (share.poolId && new BigNumber(share.shares).gt(0)) {
|
|
59
|
+
console.log(`Adding new user share for pool ${share.poolId}:`, share);
|
|
60
|
+
userSharesStore.userShares.set(share.poolId, {
|
|
61
|
+
poolId: share.poolId,
|
|
62
|
+
shares: new BigNumber(share.shares).toString(),
|
|
63
|
+
updatedAt: share.updatedAt,
|
|
64
|
+
});
|
|
65
|
+
} else {
|
|
66
|
+
console.warn(`Ignoring update for user share in pool ${share.poolId} with zero or negative shares`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (existingShare && new BigNumber(share.shares).isZero()) {
|
|
70
|
+
console.log(`Removing user share for pool ${share.poolId} with zero shares`);
|
|
71
|
+
userSharesStore.userShares.delete(share.poolId);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const getUserShares = () => {
|
|
76
|
+
return Array.from(userSharesStore.userShares.values());
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const getUserShareByPoolId = (poolId) => {
|
|
80
|
+
return userSharesStore.userShares.get(poolId);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const resetUserShares = () => {
|
|
84
|
+
userSharesStore.userShares.clear();
|
|
85
|
+
userSharesStore.isInitialReceived = false;
|
|
86
|
+
userSharesStore.pendingUpdates = [];
|
|
87
|
+
console.log('Reset user shares state due to disconnect or error');
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export { userSharesStore, setInitialUserShares, updateUserShare, getUserShares, getUserShareByPoolId, resetUserShares };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { BigNumber } from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
const walletStore = {
|
|
4
|
+
wallets: new Map(), // Store wallet data by ticker
|
|
5
|
+
isInitialReceived: false, // Track if initial wallet data is received
|
|
6
|
+
pendingUpdates: [], // Buffer for updates before initial data
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Initialize wallets with initial data
|
|
10
|
+
const setInitialWallets = (wallets) => {
|
|
11
|
+
walletStore.wallets.clear();
|
|
12
|
+
wallets.forEach(wallet => {
|
|
13
|
+
walletStore.wallets.set(wallet.ticker, {
|
|
14
|
+
id: wallet.id,
|
|
15
|
+
ticker: wallet.ticker,
|
|
16
|
+
available: new BigNumber(wallet.available).toString(),
|
|
17
|
+
locked: new BigNumber(wallet.locked).toString(),
|
|
18
|
+
updatedAt: wallet.updatedAt,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
walletStore.isInitialReceived = true;
|
|
22
|
+
console.log(`Initialized with ${wallets.length} wallets`);
|
|
23
|
+
|
|
24
|
+
// Process buffered updates
|
|
25
|
+
if (walletStore.pendingUpdates.length > 0) {
|
|
26
|
+
console.log(`Processing ${walletStore.pendingUpdates.length} buffered wallet updates`);
|
|
27
|
+
walletStore.pendingUpdates.forEach(({ wallets }) => {
|
|
28
|
+
wallets.forEach(wallet => updateWallet(wallet));
|
|
29
|
+
});
|
|
30
|
+
walletStore.pendingUpdates = [];
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Update a single wallet
|
|
35
|
+
const updateWallet = (wallet) => {
|
|
36
|
+
if (!walletStore.isInitialReceived) {
|
|
37
|
+
console.log('Buffering wallet update, initial data not yet received:', wallet);
|
|
38
|
+
walletStore.pendingUpdates.push({ wallets: [wallet] });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const existingWallet = walletStore.wallets.get(wallet.ticker);
|
|
43
|
+
const incomingUpdatedAt = new Date(wallet.updatedAt).getTime();
|
|
44
|
+
|
|
45
|
+
if (existingWallet) {
|
|
46
|
+
const existingUpdatedAt = new Date(existingWallet.updatedAt).getTime();
|
|
47
|
+
if (incomingUpdatedAt <= existingUpdatedAt) {
|
|
48
|
+
console.log(`Skipping stale update for wallet ${wallet.ticker}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(`Updating wallet ${wallet.ticker}:`, {
|
|
53
|
+
available: wallet.available,
|
|
54
|
+
locked: wallet.locked,
|
|
55
|
+
updatedAt: wallet.updatedAt,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
walletStore.wallets.set(wallet.ticker, {
|
|
59
|
+
...existingWallet,
|
|
60
|
+
available: new BigNumber(wallet.available).toString(),
|
|
61
|
+
locked: new BigNumber(wallet.locked).toString(),
|
|
62
|
+
updatedAt: wallet.updatedAt,
|
|
63
|
+
});
|
|
64
|
+
} else if (wallet.ticker && new BigNumber(wallet.available).gte(0) && new BigNumber(wallet.locked).gte(0)) {
|
|
65
|
+
console.log(`Adding new wallet ${wallet.ticker}:`, wallet);
|
|
66
|
+
walletStore.wallets.set(wallet.ticker, {
|
|
67
|
+
id: wallet.id,
|
|
68
|
+
ticker: wallet.ticker,
|
|
69
|
+
available: new BigNumber(wallet.available).toString(),
|
|
70
|
+
locked: new BigNumber(wallet.locked).toString(),
|
|
71
|
+
updatedAt: wallet.updatedAt,
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
console.warn(`Ignoring update for unknown wallet ${wallet.ticker} with incomplete data`);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Get all wallets
|
|
79
|
+
const getWallets = () => {
|
|
80
|
+
return Array.from(walletStore.wallets.values());
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Get a specific wallet by ticker
|
|
84
|
+
const getWalletByTicker = (ticker) => {
|
|
85
|
+
return walletStore.wallets.get(ticker);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Reset store on disconnect or error
|
|
89
|
+
const resetWallets = () => {
|
|
90
|
+
walletStore.wallets.clear();
|
|
91
|
+
walletStore.isInitialReceived = false;
|
|
92
|
+
walletStore.pendingUpdates = [];
|
|
93
|
+
console.log('Reset wallet state due to disconnect or error');
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { walletStore, setInitialWallets, updateWallet, getWallets, getWalletByTicker, resetWallets };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// src/utils/liquidityUtils.mjs
|
|
2
|
+
import { BigNumber } from 'bignumber.js';
|
|
3
|
+
|
|
4
|
+
export function normalizeTokenPairFrontend(coinA, coinB, pools) {
|
|
5
|
+
if (!coinA || !coinB || !coinA.ticker || !coinB.ticker) {
|
|
6
|
+
throw new Error('Invalid token objects');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (coinA.ticker === 'RUNES') {
|
|
10
|
+
return { tokenA: coinA, tokenB: coinB, flipped: false };
|
|
11
|
+
}
|
|
12
|
+
if (coinB.ticker === 'RUNES') {
|
|
13
|
+
return { tokenA: coinB, tokenB: coinA, flipped: true };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pool = pools.find(
|
|
17
|
+
(p) =>
|
|
18
|
+
(p.coinA.ticker === coinA.ticker && p.coinB.ticker === coinB.ticker) ||
|
|
19
|
+
(p.coinB.ticker === coinA.ticker && p.coinA.ticker === coinB.ticker)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (pool) {
|
|
23
|
+
if (pool.coinA.ticker === coinA.ticker && pool.coinB.ticker === coinB.ticker) {
|
|
24
|
+
return { tokenA: coinA, tokenB: coinB, flipped: false };
|
|
25
|
+
}
|
|
26
|
+
return { tokenA: coinB, tokenB: coinA, flipped: true };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { tokenA: coinA, tokenB: coinB, flipped: false };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function checkRunesLiquidityFrontend(tokenA, tokenB, pools, coins) {
|
|
33
|
+
if (tokenA.ticker === 'RUNES' || tokenB.ticker === 'RUNES') {
|
|
34
|
+
return { isCompliant: true, warnings: [] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const runesCoin = coins.find((c) => c.ticker === 'RUNES');
|
|
38
|
+
if (!runesCoin) {
|
|
39
|
+
return {
|
|
40
|
+
isCompliant: false,
|
|
41
|
+
warnings: [{ message: 'RUNES coin not found', isListItem: true }],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const tokensToCheck = [
|
|
46
|
+
{ ticker: tokenA.ticker, coin: coins.find((c) => c.ticker === tokenA.ticker) },
|
|
47
|
+
{ ticker: tokenB.ticker, coin: coins.find((c) => c.ticker === tokenB.ticker) },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const warnings = [];
|
|
51
|
+
|
|
52
|
+
for (const { ticker, coin } of tokensToCheck) {
|
|
53
|
+
if (!coin) {
|
|
54
|
+
warnings.push({ message: `Token ${ticker} not found`, isListItem: true });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const runesPool = pools.find(
|
|
59
|
+
(p) => p.coinA.ticker === 'RUNES' && p.coinB.ticker === ticker
|
|
60
|
+
);
|
|
61
|
+
const requiredRunes = new BigNumber(coin.runesComplianceRequirement || '100000000000');
|
|
62
|
+
if (!runesPool || new BigNumber(runesPool.reserveA).isZero()) {
|
|
63
|
+
warnings.push({
|
|
64
|
+
message: `A RUNES/${ticker} pool with at least ${requiredRunes
|
|
65
|
+
.shiftedBy(-runesCoin.dp)
|
|
66
|
+
.dp(runesCoin.dp)
|
|
67
|
+
.toString()} RUNES liquidity is required`,
|
|
68
|
+
isListItem: true,
|
|
69
|
+
});
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const actualRunes = new BigNumber(runesPool.reserveA);
|
|
74
|
+
if (actualRunes.lt(requiredRunes)) {
|
|
75
|
+
warnings.push({
|
|
76
|
+
message: `A RUNES/${ticker} pool with at least ${requiredRunes
|
|
77
|
+
.shiftedBy(-runesCoin.dp)
|
|
78
|
+
.dp(runesCoin.dp)
|
|
79
|
+
.toString()} RUNES liquidity is required`,
|
|
80
|
+
isListItem: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (warnings.length > 0) {
|
|
86
|
+
warnings.push({
|
|
87
|
+
message: 'Pools are periodically checked, and non-compliant pools will be disabled for trading.',
|
|
88
|
+
isListItem: false,
|
|
89
|
+
});
|
|
90
|
+
return { isCompliant: false, warnings };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return { isCompliant: true, warnings: [] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getPoolRatioFrontend(pool) {
|
|
97
|
+
if (!pool || !pool.reserveA || !pool.reserveB || !pool.coinA || !pool.coinB) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const reserveABN = new BigNumber(pool.reserveA);
|
|
102
|
+
const reserveBBN = new BigNumber(pool.reserveB);
|
|
103
|
+
if (!reserveABN.isFinite() || !reserveBBN.isFinite() || reserveABN.isZero() || reserveBBN.isZero()) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const reserveADecimal = reserveABN.shiftedBy(-pool.coinA.dp);
|
|
108
|
+
const reserveBDecimal = reserveBBN.shiftedBy(-pool.coinB.dp);
|
|
109
|
+
return reserveADecimal.div(reserveBDecimal);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function estimateLiquidityFrontend({ coinA, coinB, amountA, amountB, pools, coins }) {
|
|
113
|
+
if ((amountA === null && amountB === null) || (amountA !== null && amountB !== null)) {
|
|
114
|
+
throw new Error('Provide either amountA or amountB, but not both or neither');
|
|
115
|
+
}
|
|
116
|
+
if (amountA !== null && new BigNumber(amountA).lte(0)) {
|
|
117
|
+
throw new Error(`${coinA.ticker} amount must be positive`);
|
|
118
|
+
}
|
|
119
|
+
if (amountB !== null && new BigNumber(amountB).lte(0)) {
|
|
120
|
+
throw new Error(`${coinB.ticker} amount must be positive`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { tokenA, tokenB, flipped } = normalizeTokenPairFrontend(coinA, coinB, pools);
|
|
124
|
+
let adjustedAmountA = amountA;
|
|
125
|
+
let adjustedAmountB = amountB;
|
|
126
|
+
if (flipped) {
|
|
127
|
+
adjustedAmountA = amountB;
|
|
128
|
+
adjustedAmountB = amountA;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const pool = pools.find(
|
|
132
|
+
(p) =>
|
|
133
|
+
(p.coinA.ticker === tokenA.ticker && p.coinB.ticker === tokenB.ticker) ||
|
|
134
|
+
(p.coinB.ticker === tokenA.ticker && p.coinA.ticker === coinB.ticker)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
let amountAOut;
|
|
138
|
+
let amountBOut;
|
|
139
|
+
|
|
140
|
+
if (!pool || (new BigNumber(pool.reserveA).isZero() && new BigNumber(pool.reserveB).isZero())) {
|
|
141
|
+
if (adjustedAmountA === null || adjustedAmountB === null) {
|
|
142
|
+
return {
|
|
143
|
+
coinA: { ticker: tokenA.ticker, dp: tokenA.dp, projectName: tokenA.projectName },
|
|
144
|
+
coinB: { ticker: tokenB.ticker, dp: tokenB.dp, projectName: tokenB.projectName },
|
|
145
|
+
isPoolEmpty: true,
|
|
146
|
+
flipped,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
amountAOut = new BigNumber(adjustedAmountA).decimalPlaces(tokenA.dp, BigNumber.ROUND_DOWN);
|
|
150
|
+
amountBOut = new BigNumber(adjustedAmountB).decimalPlaces(tokenB.dp, BigNumber.ROUND_DOWN);
|
|
151
|
+
} else {
|
|
152
|
+
const currentRatio = getPoolRatioFrontend(pool);
|
|
153
|
+
if (!currentRatio || currentRatio.isZero() || !currentRatio.isFinite()) {
|
|
154
|
+
throw new Error('Invalid pool ratio');
|
|
155
|
+
}
|
|
156
|
+
if (adjustedAmountA !== null) {
|
|
157
|
+
amountAOut = new BigNumber(adjustedAmountA).decimalPlaces(tokenA.dp, BigNumber.ROUND_DOWN);
|
|
158
|
+
amountBOut = amountAOut.div(currentRatio).decimalPlaces(tokenB.dp, BigNumber.ROUND_DOWN);
|
|
159
|
+
} else {
|
|
160
|
+
amountBOut = new BigNumber(adjustedAmountB).decimalPlaces(tokenB.dp, BigNumber.ROUND_DOWN);
|
|
161
|
+
amountAOut = amountBOut.times(currentRatio).decimalPlaces(tokenA.dp, BigNumber.ROUND_DOWN);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
amountA: amountAOut.toString(),
|
|
167
|
+
amountB: amountBOut.toString(),
|
|
168
|
+
coinA: { ticker: tokenA.ticker, dp: tokenA.dp, projectName: tokenA.projectName },
|
|
169
|
+
coinB: { ticker: tokenB.ticker, dp: tokenB.dp, projectName: tokenB.projectName },
|
|
170
|
+
isPoolEmpty: !pool || (new BigNumber(pool.reserveA).isZero() && new BigNumber(pool.reserveB).isZero()),
|
|
171
|
+
flipped,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function calculateShareAmounts({ userShares, pools }) {
|
|
176
|
+
return userShares.map(share => {
|
|
177
|
+
const pool = pools.find(p => p.id === share.poolId);
|
|
178
|
+
if (!pool || !pool.totalShares || new BigNumber(pool.totalShares).isZero()) {
|
|
179
|
+
return {
|
|
180
|
+
...share,
|
|
181
|
+
amountA: '0',
|
|
182
|
+
amountB: '0',
|
|
183
|
+
totalShares: '0',
|
|
184
|
+
reserveA: '0',
|
|
185
|
+
reserveB: '0',
|
|
186
|
+
coinA: { ticker: 'Unknown', dp: 0, projectName: 'Unknown' },
|
|
187
|
+
coinB: { ticker: 'Unknown', dp: 0, projectName: 'Unknown' },
|
|
188
|
+
pair: 'Unknown/Unknown',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const sharesBN = new BigNumber(share.shares);
|
|
193
|
+
const totalSharesBN = new BigNumber(pool.totalShares);
|
|
194
|
+
const reserveABN = new BigNumber(pool.reserveA);
|
|
195
|
+
const reserveBBN = new BigNumber(pool.reserveB);
|
|
196
|
+
|
|
197
|
+
const shareRatio = sharesBN.div(totalSharesBN);
|
|
198
|
+
const amountA = shareRatio.times(reserveABN).decimalPlaces(pool.coinA.dp, BigNumber.ROUND_DOWN).toString();
|
|
199
|
+
const amountB = shareRatio.times(reserveBBN).decimalPlaces(pool.coinB.dp, BigNumber.ROUND_DOWN).toString();
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
...share,
|
|
203
|
+
amountA,
|
|
204
|
+
amountB,
|
|
205
|
+
totalShares: pool.totalShares,
|
|
206
|
+
reserveA: pool.reserveA,
|
|
207
|
+
reserveB: pool.reserveB,
|
|
208
|
+
coinA: pool.coinA,
|
|
209
|
+
coinB: pool.coinB,
|
|
210
|
+
pair: `${pool.coinA.ticker}/${pool.coinB.ticker}`,
|
|
211
|
+
};
|
|
212
|
+
});
|
|
213
|
+
}
|