@alephium/powfi-sdk 0.0.1-rc.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 +4 -0
- package/clmm/artifacts/BitmapWord.ral.json +125 -0
- package/clmm/artifacts/BitmapWordDeployer.ral.json +31 -0
- package/clmm/artifacts/CreateConfig.ral.json +37 -0
- package/clmm/artifacts/CreateLiquidPool.ral.json +55 -0
- package/clmm/artifacts/DexAccount.ral.json +110 -0
- package/clmm/artifacts/LiquidityAmountsTest.ral.json +161 -0
- package/clmm/artifacts/LiquidityManagmentTest.ral.json +384 -0
- package/clmm/artifacts/Pool.ral.json +1530 -0
- package/clmm/artifacts/PoolConfig.ral.json +31 -0
- package/clmm/artifacts/PoolFactory.ral.json +300 -0
- package/clmm/artifacts/PoolRouterDemo.ral.json +49 -0
- package/clmm/artifacts/PoolUser.ral.json +89 -0
- package/clmm/artifacts/Position.ral.json +183 -0
- package/clmm/artifacts/PositionManager.ral.json +416 -0
- package/clmm/artifacts/SwapWithoutAccount.ral.json +46 -0
- package/clmm/artifacts/TestToken.ral.json +68 -0
- package/clmm/artifacts/Tick.ral.json +161 -0
- package/clmm/artifacts/TickBitmapTest.ral.json +220 -0
- package/clmm/artifacts/constants.ral.json +81 -0
- package/clmm/artifacts/structs.ral.json +335 -0
- package/clmm/artifacts/ts/BitmapWord.ts +337 -0
- package/clmm/artifacts/ts/BitmapWordDeployer.ts +164 -0
- package/clmm/artifacts/ts/DexAccount.ts +330 -0
- package/clmm/artifacts/ts/LiquidityAmountsTest.ts +464 -0
- package/clmm/artifacts/ts/LiquidityManagmentTest.ts +859 -0
- package/clmm/artifacts/ts/Pool.ts +2535 -0
- package/clmm/artifacts/ts/PoolConfig.ts +179 -0
- package/clmm/artifacts/ts/PoolFactory.ts +640 -0
- package/clmm/artifacts/ts/PoolUser.ts +237 -0
- package/clmm/artifacts/ts/Position.ts +440 -0
- package/clmm/artifacts/ts/PositionManager.ts +929 -0
- package/clmm/artifacts/ts/TestToken.ts +277 -0
- package/clmm/artifacts/ts/Tick.ts +351 -0
- package/clmm/artifacts/ts/TickBitmapTest.ts +512 -0
- package/clmm/artifacts/ts/constants.ts +17 -0
- package/clmm/artifacts/ts/contracts.ts +26 -0
- package/clmm/artifacts/ts/deployments.ts +160 -0
- package/clmm/artifacts/ts/index.ts +20 -0
- package/clmm/artifacts/ts/scripts.ts +76 -0
- package/clmm/artifacts/ts/types.ts +105 -0
- package/clmm/deployments/.deployments.devnet.json +350 -0
- package/clmm/deployments/.deployments.testnet.json +350 -0
- package/cpmm/artifacts/dex/DexAccount.ral.json +110 -0
- package/cpmm/artifacts/dex/Router.ral.json +361 -0
- package/cpmm/artifacts/dex/TokenPair.ral.json +512 -0
- package/cpmm/artifacts/dex/TokenPairFactory.ral.json +297 -0
- package/cpmm/artifacts/examples/ExampleOracleSimple.ral.json +192 -0
- package/cpmm/artifacts/examples/FeeCollectorFactoryImpl.ral.json +185 -0
- package/cpmm/artifacts/examples/FeeCollectorPerTokenPairImpl.ral.json +216 -0
- package/cpmm/artifacts/examples/FullMathTest.ral.json +123 -0
- package/cpmm/artifacts/scripts/AddLiquidity.ral.json +46 -0
- package/cpmm/artifacts/scripts/Burn.ral.json +31 -0
- package/cpmm/artifacts/scripts/CollectFee.ral.json +25 -0
- package/cpmm/artifacts/scripts/CreatePair.ral.json +37 -0
- package/cpmm/artifacts/scripts/CreatePairAndAddLiquidity.ral.json +43 -0
- package/cpmm/artifacts/scripts/EnableFeeCollector.ral.json +28 -0
- package/cpmm/artifacts/scripts/Mint.ral.json +34 -0
- package/cpmm/artifacts/scripts/RemoveLiquidity.ral.json +43 -0
- package/cpmm/artifacts/scripts/SetFeeCollectorFactory.ral.json +28 -0
- package/cpmm/artifacts/scripts/Swap.ral.json +46 -0
- package/cpmm/artifacts/scripts/SwapMaxIn.ral.json +46 -0
- package/cpmm/artifacts/scripts/SwapMinOut.ral.json +46 -0
- package/cpmm/artifacts/test/GetToken.ral.json +31 -0
- package/cpmm/artifacts/test/MathTest.ral.json +49 -0
- package/cpmm/artifacts/test/TestToken.ral.json +87 -0
- package/cpmm/artifacts/ts/DexAccount.ts +329 -0
- package/cpmm/artifacts/ts/ExampleOracleSimple.ts +383 -0
- package/cpmm/artifacts/ts/FeeCollectorFactoryImpl.ts +227 -0
- package/cpmm/artifacts/ts/FeeCollectorPerTokenPairImpl.ts +327 -0
- package/cpmm/artifacts/ts/FullMathTest.ts +251 -0
- package/cpmm/artifacts/ts/MathTest.ts +183 -0
- package/cpmm/artifacts/ts/Router.ts +554 -0
- package/cpmm/artifacts/ts/TestToken.ts +312 -0
- package/cpmm/artifacts/ts/TokenPair.ts +947 -0
- package/cpmm/artifacts/ts/TokenPairFactory.ts +501 -0
- package/cpmm/artifacts/ts/contracts.ts +26 -0
- package/cpmm/artifacts/ts/deployments.ts +109 -0
- package/cpmm/artifacts/ts/index.ts +16 -0
- package/cpmm/artifacts/ts/scripts.ts +142 -0
- package/cpmm/deployments/.deployments.devnet.json +77 -0
- package/cpmm/deployments/.deployments.testnet.json +79 -0
- package/lib/index.d.mts +8800 -0
- package/lib/index.d.ts +8800 -0
- package/lib/index.js +21769 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +22118 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +80 -0
- package/src/clmm/clmm.ts +607 -0
- package/src/clmm/constants.ts +7 -0
- package/src/clmm/index.ts +6 -0
- package/src/clmm/liquidity.ts +163 -0
- package/src/clmm/pool.ts +154 -0
- package/src/clmm/tick.ts +335 -0
- package/src/clmm/types.ts +155 -0
- package/src/common/constants.ts +1 -0
- package/src/common/error.ts +46 -0
- package/src/common/index.ts +7 -0
- package/src/common/logger.ts +82 -0
- package/src/common/math.ts +88 -0
- package/src/common/numeric.ts +64 -0
- package/src/common/types.ts +49 -0
- package/src/common/utils.ts +3 -0
- package/src/cpmm/constants.ts +2 -0
- package/src/cpmm/cpmm.ts +631 -0
- package/src/cpmm/index.ts +3 -0
- package/src/cpmm/types.ts +113 -0
- package/src/index.ts +25 -0
- package/src/moduleBase.ts +64 -0
- package/src/staking/index.ts +4 -0
- package/src/staking/settings.ts +38 -0
- package/src/staking/staking.ts +277 -0
- package/src/staking/types.ts +15 -0
- package/src/staking/utils.ts +25 -0
- package/src/token/index.ts +1 -0
- package/src/token/token.ts +163 -0
- package/src/zeta.ts +105 -0
- package/staking/artifacts/AlphStakeAndLock.ral.json +31 -0
- package/staking/artifacts/AlphUnstakeVault.ral.json +151 -0
- package/staking/artifacts/XAlphStakeVault.ral.json +559 -0
- package/staking/artifacts/XAlphToken.ral.json +404 -0
- package/staking/artifacts/XAlphUnlockAndStartUnstake.ral.json +31 -0
- package/staking/artifacts/examples/GovernanceDemo.ral.json +282 -0
- package/staking/artifacts/examples/RewardSharingVault.ral.json +253 -0
- package/staking/artifacts/structs.ral.json +47 -0
- package/staking/artifacts/ts/AlphUnstakeVault.ts +354 -0
- package/staking/artifacts/ts/FullMathTest.ts +175 -0
- package/staking/artifacts/ts/GovernanceDemo.ts +726 -0
- package/staking/artifacts/ts/RewardSharingVault.ts +559 -0
- package/staking/artifacts/ts/TestDynamicArrayByteVec32.ts +431 -0
- package/staking/artifacts/ts/TestDynamicSortedArrayForU256.ts +516 -0
- package/staking/artifacts/ts/TestMerkleProof.ts +343 -0
- package/staking/artifacts/ts/XAlphStakeVault.ts +1120 -0
- package/staking/artifacts/ts/XAlphToken.ts +835 -0
- package/staking/artifacts/ts/contracts.ts +26 -0
- package/staking/artifacts/ts/deployments.ts +109 -0
- package/staking/artifacts/ts/index.ts +15 -0
- package/staking/artifacts/ts/scripts.ts +35 -0
- package/staking/artifacts/ts/types.ts +19 -0
- package/staking/artifacts/utils/FullMathTest.ral.json +57 -0
- package/staking/artifacts/utils/TestDynamicArrayByteVec32.ral.json +165 -0
- package/staking/artifacts/utils/TestDynamicSortedArrayForU256.ral.json +189 -0
- package/staking/artifacts/utils/TestMerkleProof.ral.json +134 -0
- package/staking/deployments/.deployments.devnet.json +77 -0
- package/staking/deployments/.deployments.testnet.json +78 -0
package/src/cpmm/cpmm.ts
ADDED
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
import type { ExecuteScriptResult } from '@alephium/web3';
|
|
2
|
+
import {
|
|
3
|
+
DUST_AMOUNT,
|
|
4
|
+
addressFromContractId,
|
|
5
|
+
subContractId,
|
|
6
|
+
ALPH_TOKEN_ID,
|
|
7
|
+
ONE_ALPH,
|
|
8
|
+
prettifyTokenAmount,
|
|
9
|
+
} from '@alephium/web3';
|
|
10
|
+
import {
|
|
11
|
+
TokenPair as TokenPairContract,
|
|
12
|
+
SwapMaxIn,
|
|
13
|
+
SwapMinOut,
|
|
14
|
+
AddLiquidity,
|
|
15
|
+
RemoveLiquidity,
|
|
16
|
+
CreatePair,
|
|
17
|
+
CreatePairAndAddLiquidity,
|
|
18
|
+
} from 'cpmm/artifacts/ts';
|
|
19
|
+
import { loadDeployments } from 'cpmm/artifacts/ts/deployments';
|
|
20
|
+
import type { CpmmPoolContractState } from './types';
|
|
21
|
+
import { sortTokens } from '../common/utils';
|
|
22
|
+
import { MAX_PRICE_IMPACT } from './constants';
|
|
23
|
+
import {
|
|
24
|
+
InsufficientBalanceError,
|
|
25
|
+
PriceImpactTooHighError,
|
|
26
|
+
PoolNotFoundError,
|
|
27
|
+
} from '../common/error';
|
|
28
|
+
import type {
|
|
29
|
+
SwapParams,
|
|
30
|
+
SwapDetails,
|
|
31
|
+
AddLiquidityParams,
|
|
32
|
+
AddLiquidityDetails,
|
|
33
|
+
RemoveLiquidityParams,
|
|
34
|
+
RemoveLiquidityDetails,
|
|
35
|
+
ClaimableAmounts,
|
|
36
|
+
CreatePoolParams,
|
|
37
|
+
ComputeSwapParams,
|
|
38
|
+
ComputeLiquidityParams,
|
|
39
|
+
CpmmConfig,
|
|
40
|
+
} from './types';
|
|
41
|
+
import type { Zeta } from '../zeta';
|
|
42
|
+
import ModuleBase from '../moduleBase';
|
|
43
|
+
import BigNumber from 'bignumber.js';
|
|
44
|
+
import { MathUtil } from '../common/math';
|
|
45
|
+
import { MINIMUM_LIQUIDITY } from './constants';
|
|
46
|
+
import { BPS } from '../common/constants';
|
|
47
|
+
import { InsufficientLiquidityError } from '../common/error';
|
|
48
|
+
|
|
49
|
+
export class CpmmModule extends ModuleBase {
|
|
50
|
+
private config: CpmmConfig;
|
|
51
|
+
|
|
52
|
+
constructor(scope: Zeta) {
|
|
53
|
+
super({ scope, moduleName: 'CpmmModule' });
|
|
54
|
+
|
|
55
|
+
this.config = this.getCpmmConfig();
|
|
56
|
+
this.scope = scope;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getPoolId(tokenA: string, tokenB: string): string {
|
|
60
|
+
const [token0Id, token1Id] = sortTokens(tokenA, tokenB);
|
|
61
|
+
const path = token0Id + token1Id;
|
|
62
|
+
return subContractId(this.config.factoryId, path, this.config.groupIndex);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getPoolAddress(tokenA: string, tokenB: string): string {
|
|
66
|
+
return addressFromContractId(this.getPoolId(tokenA, tokenB));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async getPoolState(tokenA: string, tokenB: string): Promise<CpmmPoolContractState> {
|
|
70
|
+
const [token0Id, token1Id] = sortTokens(tokenA, tokenB);
|
|
71
|
+
const token0Info = await this.scope.token.getTokenById(token0Id);
|
|
72
|
+
const token1Info = await this.scope.token.getTokenById(token1Id);
|
|
73
|
+
|
|
74
|
+
const poolId = this.getPoolId(tokenA, tokenB);
|
|
75
|
+
const contractAddress = addressFromContractId(poolId);
|
|
76
|
+
const pool = TokenPairContract.at(contractAddress);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const state = await pool.fetchState();
|
|
80
|
+
return {
|
|
81
|
+
poolId,
|
|
82
|
+
reserve0: state.fields.reserve0,
|
|
83
|
+
reserve1: state.fields.reserve1,
|
|
84
|
+
token0Info,
|
|
85
|
+
token1Info,
|
|
86
|
+
totalSupply: state.fields.totalSupply,
|
|
87
|
+
dexAccount: state.fields.dexAccount0,
|
|
88
|
+
};
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error instanceof Error && error.message.includes('not found')) {
|
|
91
|
+
throw new PoolNotFoundError(poolId);
|
|
92
|
+
}
|
|
93
|
+
this.logAndThrowError(`Failed to fetch pool state on ${poolId}`, error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async poolExists(tokenA: string, tokenB: string): Promise<boolean> {
|
|
98
|
+
const address = this.getPoolAddress(tokenA, tokenB);
|
|
99
|
+
|
|
100
|
+
return this.scope.nodeProvider.addresses
|
|
101
|
+
.getAddressesAddressGroup(address)
|
|
102
|
+
.then((_) => true)
|
|
103
|
+
.catch((e: any) => {
|
|
104
|
+
if (e instanceof Error && e.message.indexOf('Group not found') !== -1) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
throw e;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async swap(params: SwapParams, balances?: Map<string, bigint>): Promise<ExecuteScriptResult> {
|
|
112
|
+
if (!this.scope.signer) {
|
|
113
|
+
throw new Error('Signer is required for swap operation');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const poolState = await this.getPoolState(params.tokenIn.id, params.tokenOut.id);
|
|
117
|
+
const swapDetails = CpmmModule.computeSwapAmount({
|
|
118
|
+
state: poolState,
|
|
119
|
+
tokenIn: params.tokenIn,
|
|
120
|
+
tokenOut: params.tokenOut,
|
|
121
|
+
amountIn: params.amountIn,
|
|
122
|
+
amountOut: params.amountOut,
|
|
123
|
+
slippage: params.slippage,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (swapDetails.priceImpact >= MAX_PRICE_IMPACT) {
|
|
127
|
+
throw new PriceImpactTooHighError(swapDetails.priceImpact, MAX_PRICE_IMPACT);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (balances) {
|
|
131
|
+
const available = balances.get(swapDetails.tokenInInfo.id) ?? 0n;
|
|
132
|
+
if (available < swapDetails.tokenInAmount) {
|
|
133
|
+
throw new InsufficientBalanceError(
|
|
134
|
+
swapDetails.tokenInInfo.symbol,
|
|
135
|
+
prettifyTokenAmount(swapDetails.tokenInAmount, swapDetails.tokenInInfo.decimals) ??
|
|
136
|
+
`${swapDetails.tokenInAmount}`,
|
|
137
|
+
prettifyTokenAmount(available, swapDetails.tokenInInfo.decimals) ?? `${available}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const ttl = params.ttl ?? 60;
|
|
143
|
+
|
|
144
|
+
if (swapDetails.swapType === 'ExactIn') {
|
|
145
|
+
let attoAlphAmount = this.getExtraAlphAmount(
|
|
146
|
+
swapDetails.state.token0Info.id,
|
|
147
|
+
swapDetails.state.token1Info.id,
|
|
148
|
+
);
|
|
149
|
+
const tokens: Array<{ id: string; amount: bigint }> = [];
|
|
150
|
+
|
|
151
|
+
if (swapDetails.tokenInInfo.id === ALPH_TOKEN_ID) {
|
|
152
|
+
attoAlphAmount += swapDetails.tokenInAmount;
|
|
153
|
+
} else {
|
|
154
|
+
tokens.push({ id: swapDetails.tokenInInfo.id, amount: swapDetails.tokenInAmount });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const result = await SwapMinOut.execute({
|
|
158
|
+
signer: this.scope.signer,
|
|
159
|
+
initialFields: {
|
|
160
|
+
dexAccount: poolState.dexAccount,
|
|
161
|
+
sender: params.sender,
|
|
162
|
+
router: this.config.routerId,
|
|
163
|
+
pair: swapDetails.state.poolId,
|
|
164
|
+
tokenInId: swapDetails.tokenInInfo.id,
|
|
165
|
+
amountIn: swapDetails.tokenInAmount,
|
|
166
|
+
amountOutMin: swapDetails.minimalTokenOutAmount!,
|
|
167
|
+
deadline: deadline(ttl),
|
|
168
|
+
},
|
|
169
|
+
attoAlphAmount,
|
|
170
|
+
tokens,
|
|
171
|
+
});
|
|
172
|
+
return result;
|
|
173
|
+
} else {
|
|
174
|
+
let attoAlphAmount = this.getExtraAlphAmount(
|
|
175
|
+
swapDetails.state.token0Info.id,
|
|
176
|
+
swapDetails.state.token1Info.id,
|
|
177
|
+
);
|
|
178
|
+
const tokens: Array<{ id: string; amount: bigint }> = [];
|
|
179
|
+
|
|
180
|
+
if (swapDetails.tokenInInfo.id === ALPH_TOKEN_ID) {
|
|
181
|
+
attoAlphAmount += swapDetails.maximalTokenInAmount!;
|
|
182
|
+
} else {
|
|
183
|
+
tokens.push({ id: swapDetails.tokenInInfo.id, amount: swapDetails.maximalTokenInAmount! });
|
|
184
|
+
}
|
|
185
|
+
const result = await SwapMaxIn.execute({
|
|
186
|
+
signer: this.scope.signer,
|
|
187
|
+
initialFields: {
|
|
188
|
+
dexAccount: poolState.dexAccount,
|
|
189
|
+
sender: params.sender,
|
|
190
|
+
router: this.config.routerId,
|
|
191
|
+
pair: swapDetails.state.poolId,
|
|
192
|
+
tokenInId: swapDetails.tokenInInfo.id,
|
|
193
|
+
amountInMax: swapDetails.maximalTokenInAmount!,
|
|
194
|
+
amountOut: swapDetails.tokenOutAmount,
|
|
195
|
+
deadline: deadline(ttl),
|
|
196
|
+
},
|
|
197
|
+
attoAlphAmount,
|
|
198
|
+
tokens,
|
|
199
|
+
});
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async addLiquidity(
|
|
205
|
+
params: AddLiquidityParams,
|
|
206
|
+
balances?: Map<string, bigint>,
|
|
207
|
+
): Promise<ExecuteScriptResult> {
|
|
208
|
+
if (!this.scope.signer) {
|
|
209
|
+
throw new Error('Signer is required for addLiquidity operation');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const { cpmmPoolState, tokenA, tokenB, amountA, amountB, slippage, sender, ttl = 60 } = params;
|
|
213
|
+
|
|
214
|
+
if (amountA === 0n || amountB === 0n) {
|
|
215
|
+
throw new Error('The input amount must be greater than 0');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (balances) {
|
|
219
|
+
const tokenAAvailable = balances.get(tokenA.id) ?? 0n;
|
|
220
|
+
if (tokenAAvailable < amountA) {
|
|
221
|
+
throw new InsufficientBalanceError(
|
|
222
|
+
tokenA.symbol,
|
|
223
|
+
prettifyTokenAmount(amountA, tokenA.decimals) ?? `${amountA}`,
|
|
224
|
+
prettifyTokenAmount(tokenAAvailable, tokenA.decimals) ?? `${tokenAAvailable}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const tokenBAvailable = balances.get(tokenB.id) ?? 0n;
|
|
229
|
+
if (tokenBAvailable < amountB) {
|
|
230
|
+
throw new InsufficientBalanceError(
|
|
231
|
+
tokenB.symbol,
|
|
232
|
+
prettifyTokenAmount(amountB, tokenB.decimals) ?? `${amountB}`,
|
|
233
|
+
prettifyTokenAmount(tokenBAvailable, tokenB.decimals) ?? `${tokenBAvailable}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const isInitial = cpmmPoolState.reserve0 === 0n && cpmmPoolState.reserve1 === 0n;
|
|
239
|
+
const amountAMin = isInitial ? amountA : CpmmModule.minimalAmount(amountA, slippage);
|
|
240
|
+
const amountBMin = isInitial ? amountB : CpmmModule.minimalAmount(amountB, slippage);
|
|
241
|
+
|
|
242
|
+
const [amount0Desired, amount1Desired, amount0Min, amount1Min] =
|
|
243
|
+
tokenA.id === cpmmPoolState.token0Info.id
|
|
244
|
+
? [amountA, amountB, amountAMin, amountBMin]
|
|
245
|
+
: [amountB, amountA, amountBMin, amountAMin];
|
|
246
|
+
|
|
247
|
+
// Calculate ALPH amounts properly
|
|
248
|
+
const extraAlph = this.getExtraAlphAmount(tokenA.id, tokenB.id);
|
|
249
|
+
let attoAlphAmount = extraAlph + DUST_AMOUNT;
|
|
250
|
+
const tokens: Array<{ id: string; amount: bigint }> = [];
|
|
251
|
+
|
|
252
|
+
// Handle ALPH token properly - don't double count it
|
|
253
|
+
if (tokenA.id === ALPH_TOKEN_ID) {
|
|
254
|
+
attoAlphAmount += amountA;
|
|
255
|
+
tokens.push({ id: tokenB.id, amount: amountB });
|
|
256
|
+
} else if (tokenB.id === ALPH_TOKEN_ID) {
|
|
257
|
+
attoAlphAmount += amountB;
|
|
258
|
+
tokens.push({ id: tokenA.id, amount: amountA });
|
|
259
|
+
} else {
|
|
260
|
+
tokens.push({ id: tokenA.id, amount: amountA }, { id: tokenB.id, amount: amountB });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const result = await AddLiquidity.execute({
|
|
264
|
+
signer: this.scope.signer,
|
|
265
|
+
initialFields: {
|
|
266
|
+
sender,
|
|
267
|
+
router: this.config.routerId,
|
|
268
|
+
pair: cpmmPoolState.poolId,
|
|
269
|
+
amount0Desired,
|
|
270
|
+
amount1Desired,
|
|
271
|
+
amount0Min,
|
|
272
|
+
amount1Min,
|
|
273
|
+
deadline: deadline(ttl),
|
|
274
|
+
},
|
|
275
|
+
attoAlphAmount,
|
|
276
|
+
tokens,
|
|
277
|
+
});
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async removeLiquidity(params: RemoveLiquidityParams): Promise<ExecuteScriptResult> {
|
|
282
|
+
if (!this.scope.signer) {
|
|
283
|
+
throw new Error('Signer is required for removeLiquidity operation');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const { state, liquidity, totalLiquidityAmount, slippage, sender, ttl = 60 } = params;
|
|
287
|
+
const ownedLiquidity = totalLiquidityAmount ?? state.totalSupply;
|
|
288
|
+
const details = CpmmModule.computeRemoveLiquidityAmounts(state, ownedLiquidity, liquidity);
|
|
289
|
+
|
|
290
|
+
const amount0Min = CpmmModule.minimalAmount(details.amount0, slippage);
|
|
291
|
+
const amount1Min = CpmmModule.minimalAmount(details.amount1, slippage);
|
|
292
|
+
|
|
293
|
+
const result = await RemoveLiquidity.execute({
|
|
294
|
+
signer: this.scope.signer,
|
|
295
|
+
initialFields: {
|
|
296
|
+
sender,
|
|
297
|
+
router: this.config.routerId,
|
|
298
|
+
pairId: state.poolId,
|
|
299
|
+
liquidity,
|
|
300
|
+
amount0Min,
|
|
301
|
+
amount1Min,
|
|
302
|
+
deadline: deadline(ttl),
|
|
303
|
+
},
|
|
304
|
+
attoAlphAmount:
|
|
305
|
+
this.getExtraAlphAmount(state.token0Info.id, state.token1Info.id) + DUST_AMOUNT,
|
|
306
|
+
tokens: [{ id: state.poolId, amount: liquidity }],
|
|
307
|
+
});
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async computeClaimableAmounts(
|
|
312
|
+
tokenAId: string,
|
|
313
|
+
tokenBId: string,
|
|
314
|
+
liquidityBalance: bigint,
|
|
315
|
+
): Promise<ClaimableAmounts> {
|
|
316
|
+
const state = await this.getPoolState(tokenAId, tokenBId);
|
|
317
|
+
const details = CpmmModule.computeClaimableAmounts(state, liquidityBalance);
|
|
318
|
+
return {
|
|
319
|
+
token0: details.token0,
|
|
320
|
+
amount0: details.amount0,
|
|
321
|
+
token1: details.token1,
|
|
322
|
+
amount1: details.amount1,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async createPool(params: CreatePoolParams): Promise<ExecuteScriptResult & { poolId: string }> {
|
|
327
|
+
if (!this.scope.signer) {
|
|
328
|
+
throw new Error('Signer is required for createPool operation');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const { tokenA, tokenB, sender, tokenAAmount, tokenBAmount } = params;
|
|
332
|
+
const poolId = this.getPoolId(tokenA.id, tokenB.id);
|
|
333
|
+
|
|
334
|
+
if (tokenAAmount !== undefined && tokenBAmount !== undefined) {
|
|
335
|
+
const [token0Id, token1Id] = sortTokens(tokenA.id, tokenB.id);
|
|
336
|
+
const [amount0, amount1] =
|
|
337
|
+
token0Id === tokenA.id ? [tokenAAmount, tokenBAmount] : [tokenBAmount, tokenAAmount];
|
|
338
|
+
const result = await CreatePairAndAddLiquidity.execute({
|
|
339
|
+
signer: this.scope.signer,
|
|
340
|
+
initialFields: {
|
|
341
|
+
payer: sender,
|
|
342
|
+
factory: this.config.factoryId,
|
|
343
|
+
alphAmount: ONE_ALPH,
|
|
344
|
+
token0Id,
|
|
345
|
+
token1Id,
|
|
346
|
+
amount0,
|
|
347
|
+
amount1,
|
|
348
|
+
},
|
|
349
|
+
attoAlphAmount: ONE_ALPH + this.getExtraAlphAmount(tokenA.id, tokenB.id),
|
|
350
|
+
tokens: [
|
|
351
|
+
{ id: token0Id, amount: amount0 },
|
|
352
|
+
{ id: token1Id, amount: amount1 },
|
|
353
|
+
],
|
|
354
|
+
});
|
|
355
|
+
return { ...result, poolId };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const result = await CreatePair.execute({
|
|
359
|
+
signer: this.scope.signer,
|
|
360
|
+
initialFields: {
|
|
361
|
+
payer: sender,
|
|
362
|
+
factory: this.config.factoryId,
|
|
363
|
+
alphAmount: ONE_ALPH,
|
|
364
|
+
tokenAId: tokenA.id,
|
|
365
|
+
tokenBId: tokenB.id,
|
|
366
|
+
},
|
|
367
|
+
attoAlphAmount: ONE_ALPH + this.getExtraAlphAmount(tokenA.id, tokenB.id),
|
|
368
|
+
tokens: [
|
|
369
|
+
{ id: tokenA.id, amount: 1n },
|
|
370
|
+
{ id: tokenB.id, amount: 1n },
|
|
371
|
+
],
|
|
372
|
+
});
|
|
373
|
+
return { ...result, poolId };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
getCpmmConfig(): CpmmConfig {
|
|
377
|
+
const networkId = this.scope.network.id;
|
|
378
|
+
try {
|
|
379
|
+
const deployments = loadDeployments(networkId);
|
|
380
|
+
return {
|
|
381
|
+
groupIndex: deployments.contracts.Router.contractInstance.groupIndex,
|
|
382
|
+
factoryId: deployments.contracts.TokenPairFactory.contractInstance.contractId,
|
|
383
|
+
routerId: deployments.contracts.Router.contractInstance.contractId,
|
|
384
|
+
};
|
|
385
|
+
} catch (error) {
|
|
386
|
+
this.logAndThrowError(`Failed to load deployments on ${networkId}`, error);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
static computeSwapAmount(params: ComputeSwapParams): SwapDetails {
|
|
391
|
+
const { state, tokenIn, tokenOut, amountIn, amountOut, slippage } = params;
|
|
392
|
+
|
|
393
|
+
let swapType: 'ExactIn' | 'ExactOut';
|
|
394
|
+
let tokenInAmount: bigint;
|
|
395
|
+
let tokenOutAmount: bigint;
|
|
396
|
+
|
|
397
|
+
if (amountIn !== undefined) {
|
|
398
|
+
swapType = 'ExactIn';
|
|
399
|
+
tokenInAmount = amountIn;
|
|
400
|
+
tokenOutAmount = CpmmModule.getAmountOut(state, tokenIn.id, amountIn);
|
|
401
|
+
} else if (amountOut !== undefined) {
|
|
402
|
+
swapType = 'ExactOut';
|
|
403
|
+
tokenInAmount = CpmmModule.getAmountIn(state, tokenOut.id, amountOut);
|
|
404
|
+
tokenOutAmount = amountOut;
|
|
405
|
+
} else {
|
|
406
|
+
throw new Error('Either amountIn or amountOut must be specified');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const priceImpact = this.calcPriceImpact(
|
|
410
|
+
state.reserve0,
|
|
411
|
+
state.reserve1,
|
|
412
|
+
tokenIn.id,
|
|
413
|
+
state.token0Info.id,
|
|
414
|
+
tokenInAmount,
|
|
415
|
+
tokenOutAmount,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
swapType,
|
|
420
|
+
state,
|
|
421
|
+
tokenInInfo: tokenIn,
|
|
422
|
+
tokenOutInfo: tokenOut,
|
|
423
|
+
tokenInAmount,
|
|
424
|
+
tokenOutAmount,
|
|
425
|
+
priceImpact,
|
|
426
|
+
minimalTokenOutAmount:
|
|
427
|
+
swapType === 'ExactIn' ? this.minimalAmount(tokenOutAmount, slippage) : undefined,
|
|
428
|
+
maximalTokenInAmount:
|
|
429
|
+
swapType === 'ExactOut' ? this.maximalAmount(tokenInAmount, slippage) : undefined,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
static computeLiquidityAmounts(params: ComputeLiquidityParams): AddLiquidityDetails {
|
|
434
|
+
const { state, tokenA, tokenB, amountA, amountB, inputType = 'TokenA' } = params;
|
|
435
|
+
|
|
436
|
+
if (!state) {
|
|
437
|
+
// Initial liquidity
|
|
438
|
+
if (!amountA || !amountB) {
|
|
439
|
+
throw new Error('Both amountA and amountB are required for initial liquidity');
|
|
440
|
+
}
|
|
441
|
+
return this.getInitLiquidityDetails(tokenA.id, tokenB.id, amountA, amountB);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Adding to existing pool
|
|
445
|
+
const inputTokenId = inputType === 'TokenA' ? tokenA.id : tokenB.id;
|
|
446
|
+
const inputAmount = inputType === 'TokenA' ? amountA : amountB;
|
|
447
|
+
|
|
448
|
+
if (!inputAmount) {
|
|
449
|
+
throw new Error(`Amount for ${inputType} is required`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return this.getLiquidityDetails(state, inputTokenId, inputAmount, inputType);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
static computeRemoveLiquidityAmounts(
|
|
456
|
+
state: CpmmPoolContractState,
|
|
457
|
+
totalLiquidity: bigint,
|
|
458
|
+
liquidityToRemove: bigint,
|
|
459
|
+
): RemoveLiquidityDetails {
|
|
460
|
+
if (liquidityToRemove > totalLiquidity) {
|
|
461
|
+
throw new Error('Liquidity exceeds total liquidity amount');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const amount0 = (liquidityToRemove * state.reserve0) / state.totalSupply;
|
|
465
|
+
const amount1 = (liquidityToRemove * state.reserve1) / state.totalSupply;
|
|
466
|
+
const remainShareAmount = totalLiquidity - liquidityToRemove;
|
|
467
|
+
const remainPoolLiquidity = state.totalSupply - liquidityToRemove;
|
|
468
|
+
const remainSharePercentage = BigNumber((100n * remainShareAmount).toString())
|
|
469
|
+
.div(BigNumber(remainPoolLiquidity.toString()))
|
|
470
|
+
.toFixed(5);
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
state: state,
|
|
474
|
+
token0: state.token0Info,
|
|
475
|
+
amount0,
|
|
476
|
+
token1: state.token1Info,
|
|
477
|
+
amount1,
|
|
478
|
+
remainShareAmount,
|
|
479
|
+
remainSharePercentage: parseFloat(remainSharePercentage),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
static computeClaimableAmounts(
|
|
484
|
+
state: CpmmPoolContractState,
|
|
485
|
+
liquidityBalance: bigint,
|
|
486
|
+
): RemoveLiquidityDetails {
|
|
487
|
+
return this.computeRemoveLiquidityAmounts(state, liquidityBalance, liquidityBalance);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
static minimalAmount(amount: bigint, slippage: bigint): bigint {
|
|
491
|
+
this.assertSlippageInRange(slippage);
|
|
492
|
+
return (amount * BPS) / (BPS + slippage);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
static maximalAmount(amount: bigint, slippage: bigint): bigint {
|
|
496
|
+
this.assertSlippageInRange(slippage);
|
|
497
|
+
return (amount * (BPS + slippage) + (BPS - 1n)) / BPS;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
private static assertSlippageInRange(slippage: bigint): void {
|
|
501
|
+
if (slippage < 0n || slippage >= BPS) {
|
|
502
|
+
throw new Error(`Slippage must satisfy 0 <= slippage < ${BPS}, received ${slippage}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
static calcPriceImpact(
|
|
507
|
+
reserve0: bigint,
|
|
508
|
+
reserve1: bigint,
|
|
509
|
+
tokenInId: string,
|
|
510
|
+
token0Id: string,
|
|
511
|
+
amountIn: bigint,
|
|
512
|
+
amountOut: bigint,
|
|
513
|
+
): number {
|
|
514
|
+
const [reserveIn, reserveOut] =
|
|
515
|
+
token0Id === tokenInId ? [reserve0, reserve1] : [reserve1, reserve0];
|
|
516
|
+
const numerator =
|
|
517
|
+
(reserveOut * (reserveIn + amountIn) - (reserveOut - amountOut) * reserveIn) * 100n;
|
|
518
|
+
const denumerator = reserveIn * (reserveOut - amountOut);
|
|
519
|
+
const impact = new BigNumber(numerator.toString())
|
|
520
|
+
.div(new BigNumber(denumerator.toString()))
|
|
521
|
+
.toFixed();
|
|
522
|
+
return parseFloat(impact);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
static getLiquidityDetails(
|
|
526
|
+
state: CpmmPoolContractState,
|
|
527
|
+
inputTokenId: string,
|
|
528
|
+
inputAmount: bigint,
|
|
529
|
+
inputType: 'TokenA' | 'TokenB', // First or second token in the token input box
|
|
530
|
+
): {
|
|
531
|
+
state: CpmmPoolContractState;
|
|
532
|
+
tokenAId: string;
|
|
533
|
+
tokenBId: string;
|
|
534
|
+
amountA: bigint;
|
|
535
|
+
amountB: bigint;
|
|
536
|
+
shareAmount: bigint;
|
|
537
|
+
sharePercentage: number;
|
|
538
|
+
} {
|
|
539
|
+
const isInputToken0 = inputTokenId === state.token0Info.id;
|
|
540
|
+
const [reserveA, reserveB] = isInputToken0
|
|
541
|
+
? [state.reserve0, state.reserve1]
|
|
542
|
+
: [state.reserve1, state.reserve0];
|
|
543
|
+
|
|
544
|
+
const outputAmount = (inputAmount * reserveB) / reserveA;
|
|
545
|
+
const liquidityA = (inputAmount * state.totalSupply) / reserveA;
|
|
546
|
+
const liquidityB = (outputAmount * state.totalSupply) / reserveB;
|
|
547
|
+
const liquidity = liquidityA < liquidityB ? liquidityA : liquidityB;
|
|
548
|
+
const totalSupply = state.totalSupply + liquidity;
|
|
549
|
+
const percentage = BigNumber((100n * liquidity).toString())
|
|
550
|
+
.div(BigNumber(totalSupply.toString()))
|
|
551
|
+
.toFixed(5);
|
|
552
|
+
const sharePercentage = parseFloat(percentage);
|
|
553
|
+
const outputTokenId = isInputToken0 ? state.token1Info.id : state.token0Info.id;
|
|
554
|
+
|
|
555
|
+
const [tokenAId, tokenBId] =
|
|
556
|
+
inputType === 'TokenA' ? [inputTokenId, outputTokenId] : [outputTokenId, inputTokenId];
|
|
557
|
+
const [amountA, amountB] =
|
|
558
|
+
inputType === 'TokenA' ? [inputAmount, outputAmount] : [outputAmount, inputAmount];
|
|
559
|
+
return { state, tokenAId, tokenBId, amountA, amountB, shareAmount: liquidity, sharePercentage };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
static getAmountIn(state: CpmmPoolContractState, tokenOutId: string, amountOut: bigint): bigint {
|
|
563
|
+
const [tokenOutInfo, reserveIn, reserveOut] =
|
|
564
|
+
tokenOutId === state.token0Info.id
|
|
565
|
+
? [state.token0Info, state.reserve1, state.reserve0]
|
|
566
|
+
: [state.token1Info, state.reserve0, state.reserve1];
|
|
567
|
+
|
|
568
|
+
if (amountOut >= reserveOut) {
|
|
569
|
+
throw new InsufficientLiquidityError(
|
|
570
|
+
`Amount must be less than reserve, amount: ${prettifyTokenAmount(amountOut, tokenOutInfo.decimals)}, reserve: ${prettifyTokenAmount(reserveOut, tokenOutInfo.decimals)}`,
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
const numerator = reserveIn * amountOut * 1000n;
|
|
574
|
+
const denominator = (reserveOut - amountOut) * 997n;
|
|
575
|
+
return numerator / denominator + 1n;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
static getAmountOut(state: CpmmPoolContractState, tokenInId: string, amountIn: bigint): bigint {
|
|
579
|
+
if (tokenInId === state.token0Info.id) {
|
|
580
|
+
return this._getAmountOut(amountIn, state.reserve0, state.reserve1);
|
|
581
|
+
} else {
|
|
582
|
+
return this._getAmountOut(amountIn, state.reserve1, state.reserve0);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
static getInitLiquidityDetails(
|
|
587
|
+
tokenAId: string,
|
|
588
|
+
tokenBId: string,
|
|
589
|
+
amountA: bigint,
|
|
590
|
+
amountB: bigint,
|
|
591
|
+
): {
|
|
592
|
+
tokenAId: string;
|
|
593
|
+
tokenBId: string;
|
|
594
|
+
amountA: bigint;
|
|
595
|
+
amountB: bigint;
|
|
596
|
+
shareAmount: bigint;
|
|
597
|
+
sharePercentage: number;
|
|
598
|
+
} {
|
|
599
|
+
const liquidity = MathUtil.sqrt(amountA * amountB);
|
|
600
|
+
if (liquidity <= MINIMUM_LIQUIDITY) {
|
|
601
|
+
throw new InsufficientLiquidityError('Insufficient initial liquidity');
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
tokenAId,
|
|
605
|
+
tokenBId,
|
|
606
|
+
amountA,
|
|
607
|
+
amountB,
|
|
608
|
+
shareAmount: liquidity - MINIMUM_LIQUIDITY,
|
|
609
|
+
sharePercentage: 100,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private static _getAmountOut(amountIn: bigint, reserveIn: bigint, reserveOut: bigint): bigint {
|
|
614
|
+
const amountInExcludeFee = 997n * amountIn;
|
|
615
|
+
const numerator = amountInExcludeFee * reserveOut;
|
|
616
|
+
const denominator = amountInExcludeFee + 1000n * reserveIn;
|
|
617
|
+
return numerator / denominator;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// TODO: This might not be needed
|
|
621
|
+
private getExtraAlphAmount(tokenAId: string, tokenBId: string): bigint {
|
|
622
|
+
if (tokenAId === ALPH_TOKEN_ID || tokenBId === ALPH_TOKEN_ID) {
|
|
623
|
+
return DUST_AMOUNT * 2n;
|
|
624
|
+
}
|
|
625
|
+
return DUST_AMOUNT * 3n;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function deadline(ttlInMinutes: number): bigint {
|
|
630
|
+
return BigInt(Date.now() + ttlInMinutes * 60 * 1000);
|
|
631
|
+
}
|