@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.
@@ -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
+ }