@mania-labs/mania-sdk 1.0.0
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 +397 -0
- package/dist/index.d.mts +1116 -0
- package/dist/index.d.ts +1116 -0
- package/dist/index.js +1220 -0
- package/dist/index.mjs +1165 -0
- package/package.json +56 -0
- package/src/.claude/settings.local.json +9 -0
- package/src/abi/ManiaFactoryUpgradeable.json +2183 -0
- package/src/abi.ts +268 -0
- package/src/bondingCurve.ts +319 -0
- package/src/constants.ts +72 -0
- package/src/index.ts +71 -0
- package/src/mania.ts +652 -0
- package/src/types.ts +238 -0
- package/src/utils.ts +154 -0
package/src/abi.ts
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
export const MANIA_FACTORY_ABI = [
|
|
2
|
+
{
|
|
3
|
+
type: "function",
|
|
4
|
+
name: "bondingCurves",
|
|
5
|
+
inputs: [{ name: "mint", type: "address" }],
|
|
6
|
+
outputs: [
|
|
7
|
+
{ name: "virtualTokenReserves", type: "uint256" },
|
|
8
|
+
{ name: "virtualEthReserves", type: "uint64" },
|
|
9
|
+
{ name: "realTokenReserves", type: "uint256" },
|
|
10
|
+
{ name: "realEthReserves", type: "uint64" },
|
|
11
|
+
{ name: "tokenTotalSupply", type: "uint256" },
|
|
12
|
+
{ name: "complete", type: "bool" },
|
|
13
|
+
{ name: "trackVolume", type: "bool" },
|
|
14
|
+
],
|
|
15
|
+
stateMutability: "view",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: "function",
|
|
19
|
+
name: "buy",
|
|
20
|
+
inputs: [
|
|
21
|
+
{ name: "token", type: "address" },
|
|
22
|
+
{ name: "minTokensOut", type: "uint256" },
|
|
23
|
+
{ name: "recipient", type: "address" },
|
|
24
|
+
],
|
|
25
|
+
outputs: [],
|
|
26
|
+
stateMutability: "payable",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "function",
|
|
30
|
+
name: "create",
|
|
31
|
+
inputs: [
|
|
32
|
+
{ name: "name", type: "string" },
|
|
33
|
+
{ name: "symbol", type: "string" },
|
|
34
|
+
{ name: "uri", type: "string" },
|
|
35
|
+
{ name: "creator", type: "address" },
|
|
36
|
+
],
|
|
37
|
+
outputs: [{ name: "token", type: "address" }],
|
|
38
|
+
stateMutability: "nonpayable",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "function",
|
|
42
|
+
name: "createAndBuy",
|
|
43
|
+
inputs: [
|
|
44
|
+
{ name: "name", type: "string" },
|
|
45
|
+
{ name: "symbol", type: "string" },
|
|
46
|
+
{ name: "uri", type: "string" },
|
|
47
|
+
{ name: "creator", type: "address" },
|
|
48
|
+
{ name: "minTokensOut", type: "uint256" },
|
|
49
|
+
],
|
|
50
|
+
outputs: [{ name: "token", type: "address" }],
|
|
51
|
+
stateMutability: "payable",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
type: "function",
|
|
55
|
+
name: "getBondingCurve",
|
|
56
|
+
inputs: [{ name: "token", type: "address" }],
|
|
57
|
+
outputs: [
|
|
58
|
+
{
|
|
59
|
+
name: "curve",
|
|
60
|
+
type: "tuple",
|
|
61
|
+
components: [
|
|
62
|
+
{ name: "virtualTokenReserves", type: "uint256" },
|
|
63
|
+
{ name: "virtualEthReserves", type: "uint64" },
|
|
64
|
+
{ name: "realTokenReserves", type: "uint256" },
|
|
65
|
+
{ name: "realEthReserves", type: "uint64" },
|
|
66
|
+
{ name: "tokenTotalSupply", type: "uint256" },
|
|
67
|
+
{ name: "complete", type: "bool" },
|
|
68
|
+
{ name: "trackVolume", type: "bool" },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
stateMutability: "view",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
type: "function",
|
|
76
|
+
name: "getBuyQuote",
|
|
77
|
+
inputs: [
|
|
78
|
+
{ name: "token", type: "address" },
|
|
79
|
+
{ name: "ethAmount", type: "uint64" },
|
|
80
|
+
],
|
|
81
|
+
outputs: [{ name: "tokenAmount", type: "uint256" }],
|
|
82
|
+
stateMutability: "view",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "function",
|
|
86
|
+
name: "getFee",
|
|
87
|
+
inputs: [{ name: "amount", type: "uint64" }],
|
|
88
|
+
outputs: [{ name: "fee", type: "uint128" }],
|
|
89
|
+
stateMutability: "view",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: "function",
|
|
93
|
+
name: "getSellQuote",
|
|
94
|
+
inputs: [
|
|
95
|
+
{ name: "token", type: "address" },
|
|
96
|
+
{ name: "amount", type: "uint256" },
|
|
97
|
+
],
|
|
98
|
+
outputs: [{ name: "ethOutput", type: "uint128" }],
|
|
99
|
+
stateMutability: "view",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: "function",
|
|
103
|
+
name: "global",
|
|
104
|
+
inputs: [],
|
|
105
|
+
outputs: [
|
|
106
|
+
{ name: "initialized", type: "bool" },
|
|
107
|
+
{ name: "authority", type: "address" },
|
|
108
|
+
{ name: "feeRecipient", type: "address" },
|
|
109
|
+
{ name: "initialVirtualTokenReserves", type: "uint256" },
|
|
110
|
+
{ name: "initialVirtualEthReserves", type: "uint64" },
|
|
111
|
+
{ name: "initialRealTokenReserves", type: "uint256" },
|
|
112
|
+
{ name: "tokenTotalSupply", type: "uint256" },
|
|
113
|
+
{ name: "feeBasisPoints", type: "uint64" },
|
|
114
|
+
{ name: "withdrawAuthority", type: "address" },
|
|
115
|
+
{ name: "enableMigrate", type: "bool" },
|
|
116
|
+
{ name: "poolMigrationFee", type: "uint64" },
|
|
117
|
+
],
|
|
118
|
+
stateMutability: "view",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: "function",
|
|
122
|
+
name: "migrate",
|
|
123
|
+
inputs: [{ name: "token", type: "address" }],
|
|
124
|
+
outputs: [],
|
|
125
|
+
stateMutability: "payable",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: "function",
|
|
129
|
+
name: "sell",
|
|
130
|
+
inputs: [
|
|
131
|
+
{ name: "token", type: "address" },
|
|
132
|
+
{ name: "amount", type: "uint256" },
|
|
133
|
+
{ name: "minEthOutput", type: "uint64" },
|
|
134
|
+
],
|
|
135
|
+
outputs: [],
|
|
136
|
+
stateMutability: "nonpayable",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: "function",
|
|
140
|
+
name: "userVolume",
|
|
141
|
+
inputs: [{ name: "user", type: "address" }],
|
|
142
|
+
outputs: [{ name: "volume", type: "uint256" }],
|
|
143
|
+
stateMutability: "view",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: "event",
|
|
147
|
+
name: "CreateEvent",
|
|
148
|
+
inputs: [
|
|
149
|
+
{ name: "name", type: "string", indexed: false },
|
|
150
|
+
{ name: "symbol", type: "string", indexed: false },
|
|
151
|
+
{ name: "uri", type: "string", indexed: false },
|
|
152
|
+
{ name: "mint", type: "address", indexed: true },
|
|
153
|
+
{ name: "user", type: "address", indexed: true },
|
|
154
|
+
{ name: "creator", type: "address", indexed: true },
|
|
155
|
+
{ name: "timestamp", type: "uint256", indexed: false },
|
|
156
|
+
],
|
|
157
|
+
anonymous: false,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
type: "event",
|
|
161
|
+
name: "TradeEvent",
|
|
162
|
+
inputs: [
|
|
163
|
+
{ name: "mint", type: "address", indexed: true },
|
|
164
|
+
{ name: "ethAmount", type: "uint64", indexed: false },
|
|
165
|
+
{ name: "tokenAmount", type: "uint256", indexed: false },
|
|
166
|
+
{ name: "isBuy", type: "bool", indexed: false },
|
|
167
|
+
{ name: "user", type: "address", indexed: true },
|
|
168
|
+
{ name: "timestamp", type: "uint256", indexed: false },
|
|
169
|
+
{ name: "virtualEthReserves", type: "uint64", indexed: false },
|
|
170
|
+
{ name: "virtualTokenReserves", type: "uint256", indexed: false },
|
|
171
|
+
{ name: "realEthReserves", type: "uint64", indexed: false },
|
|
172
|
+
{ name: "realTokenReserves", type: "uint256", indexed: false },
|
|
173
|
+
],
|
|
174
|
+
anonymous: false,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
type: "event",
|
|
178
|
+
name: "CompleteEvent",
|
|
179
|
+
inputs: [
|
|
180
|
+
{ name: "user", type: "address", indexed: true },
|
|
181
|
+
{ name: "mint", type: "address", indexed: true },
|
|
182
|
+
{ name: "timestamp", type: "uint256", indexed: false },
|
|
183
|
+
],
|
|
184
|
+
anonymous: false,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: "event",
|
|
188
|
+
name: "CompleteManiaAmmMigrationEvent",
|
|
189
|
+
inputs: [
|
|
190
|
+
{ name: "user", type: "address", indexed: true },
|
|
191
|
+
{ name: "mint", type: "address", indexed: true },
|
|
192
|
+
{ name: "mintAmount", type: "uint256", indexed: false },
|
|
193
|
+
{ name: "ethAmount", type: "uint64", indexed: false },
|
|
194
|
+
{ name: "poolMigrationFee", type: "uint64", indexed: false },
|
|
195
|
+
{ name: "timestamp", type: "uint256", indexed: false },
|
|
196
|
+
{ name: "pool", type: "address", indexed: true },
|
|
197
|
+
],
|
|
198
|
+
anonymous: false,
|
|
199
|
+
},
|
|
200
|
+
] as const;
|
|
201
|
+
|
|
202
|
+
export const ERC20_ABI = [
|
|
203
|
+
{
|
|
204
|
+
type: "function",
|
|
205
|
+
name: "name",
|
|
206
|
+
inputs: [],
|
|
207
|
+
outputs: [{ type: "string" }],
|
|
208
|
+
stateMutability: "view",
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
type: "function",
|
|
212
|
+
name: "symbol",
|
|
213
|
+
inputs: [],
|
|
214
|
+
outputs: [{ type: "string" }],
|
|
215
|
+
stateMutability: "view",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: "function",
|
|
219
|
+
name: "decimals",
|
|
220
|
+
inputs: [],
|
|
221
|
+
outputs: [{ type: "uint8" }],
|
|
222
|
+
stateMutability: "view",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
type: "function",
|
|
226
|
+
name: "totalSupply",
|
|
227
|
+
inputs: [],
|
|
228
|
+
outputs: [{ type: "uint256" }],
|
|
229
|
+
stateMutability: "view",
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: "function",
|
|
233
|
+
name: "balanceOf",
|
|
234
|
+
inputs: [{ name: "account", type: "address" }],
|
|
235
|
+
outputs: [{ type: "uint256" }],
|
|
236
|
+
stateMutability: "view",
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
type: "function",
|
|
240
|
+
name: "allowance",
|
|
241
|
+
inputs: [
|
|
242
|
+
{ name: "owner", type: "address" },
|
|
243
|
+
{ name: "spender", type: "address" },
|
|
244
|
+
],
|
|
245
|
+
outputs: [{ type: "uint256" }],
|
|
246
|
+
stateMutability: "view",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
type: "function",
|
|
250
|
+
name: "approve",
|
|
251
|
+
inputs: [
|
|
252
|
+
{ name: "spender", type: "address" },
|
|
253
|
+
{ name: "amount", type: "uint256" },
|
|
254
|
+
],
|
|
255
|
+
outputs: [{ type: "bool" }],
|
|
256
|
+
stateMutability: "nonpayable",
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
type: "function",
|
|
260
|
+
name: "transfer",
|
|
261
|
+
inputs: [
|
|
262
|
+
{ name: "to", type: "address" },
|
|
263
|
+
{ name: "amount", type: "uint256" },
|
|
264
|
+
],
|
|
265
|
+
outputs: [{ type: "bool" }],
|
|
266
|
+
stateMutability: "nonpayable",
|
|
267
|
+
},
|
|
268
|
+
] as const;
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import type { BondingCurveState, BuyQuote, SellQuote } from "./types.js";
|
|
2
|
+
import {
|
|
3
|
+
MIGRATION_THRESHOLD,
|
|
4
|
+
BPS_DENOMINATOR,
|
|
5
|
+
} from "./constants.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* BondingCurve class for handling bonding curve calculations and state
|
|
9
|
+
*
|
|
10
|
+
* The bonding curve uses a constant product formula (x * y = k) similar to Uniswap V2,
|
|
11
|
+
* but with virtual reserves to provide initial liquidity.
|
|
12
|
+
*/
|
|
13
|
+
export class BondingCurve {
|
|
14
|
+
private state: BondingCurveState;
|
|
15
|
+
private feeBasisPoints: bigint;
|
|
16
|
+
|
|
17
|
+
constructor(state: BondingCurveState, feeBasisPoints: bigint) {
|
|
18
|
+
this.state = state;
|
|
19
|
+
this.feeBasisPoints = feeBasisPoints;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get the current bonding curve state
|
|
24
|
+
*/
|
|
25
|
+
getState(): BondingCurveState {
|
|
26
|
+
return { ...this.state };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if the bonding curve is complete (reached migration threshold)
|
|
31
|
+
*/
|
|
32
|
+
isComplete(): boolean {
|
|
33
|
+
return this.state.complete;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if the bonding curve has been migrated (all reserves are 0)
|
|
38
|
+
*/
|
|
39
|
+
isMigrated(): boolean {
|
|
40
|
+
return (
|
|
41
|
+
this.state.realEthReserves === 0n &&
|
|
42
|
+
this.state.virtualEthReserves === 0n &&
|
|
43
|
+
this.state.realTokenReserves === 0n &&
|
|
44
|
+
this.state.virtualTokenReserves === 0n
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get the current token price in ETH (wei per token)
|
|
50
|
+
*
|
|
51
|
+
* Price is calculated as: virtualEthReserves / virtualTokenReserves
|
|
52
|
+
*/
|
|
53
|
+
getCurrentPrice(): bigint {
|
|
54
|
+
if (this.state.virtualTokenReserves === 0n) {
|
|
55
|
+
return 0n;
|
|
56
|
+
}
|
|
57
|
+
// Price in wei per token (scaled by 1e18 for precision)
|
|
58
|
+
return (this.state.virtualEthReserves * 10n ** 18n) / this.state.virtualTokenReserves;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get the market cap in ETH
|
|
63
|
+
*
|
|
64
|
+
* Market cap = current price * total supply
|
|
65
|
+
*/
|
|
66
|
+
getMarketCapEth(): bigint {
|
|
67
|
+
const price = this.getCurrentPrice();
|
|
68
|
+
return (price * this.state.tokenTotalSupply) / 10n ** 18n;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get the progress towards migration threshold (0-100)
|
|
73
|
+
*/
|
|
74
|
+
getMigrationProgress(): number {
|
|
75
|
+
if (this.state.complete) {
|
|
76
|
+
return 100;
|
|
77
|
+
}
|
|
78
|
+
const progress = (this.state.realEthReserves * 100n) / MIGRATION_THRESHOLD;
|
|
79
|
+
return Number(progress);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Calculate ETH remaining until migration
|
|
84
|
+
*/
|
|
85
|
+
getEthUntilMigration(): bigint {
|
|
86
|
+
if (this.state.realEthReserves >= MIGRATION_THRESHOLD) {
|
|
87
|
+
return 0n;
|
|
88
|
+
}
|
|
89
|
+
return MIGRATION_THRESHOLD - this.state.realEthReserves;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get a quote for buying tokens with a specific ETH amount
|
|
94
|
+
*
|
|
95
|
+
* @param ethAmount - Amount of ETH to spend (in wei)
|
|
96
|
+
* @returns Quote with tokens out, fees, and price impact
|
|
97
|
+
*/
|
|
98
|
+
getBuyQuote(ethAmount: bigint): BuyQuote {
|
|
99
|
+
// Calculate fee
|
|
100
|
+
const fee = (ethAmount * this.feeBasisPoints) / BPS_DENOMINATOR;
|
|
101
|
+
const netEth = ethAmount - fee;
|
|
102
|
+
|
|
103
|
+
// Calculate tokens out using constant product formula
|
|
104
|
+
// tokensOut = (virtualTokenReserves * netEth) / (virtualEthReserves + netEth)
|
|
105
|
+
const tokensOut =
|
|
106
|
+
(this.state.virtualTokenReserves * netEth) /
|
|
107
|
+
(this.state.virtualEthReserves + netEth);
|
|
108
|
+
|
|
109
|
+
// Cap to available supply
|
|
110
|
+
const cappedTokensOut = tokensOut > this.state.realTokenReserves
|
|
111
|
+
? this.state.realTokenReserves
|
|
112
|
+
: tokensOut;
|
|
113
|
+
|
|
114
|
+
// Calculate effective price per token
|
|
115
|
+
const pricePerToken = cappedTokensOut > 0n
|
|
116
|
+
? (netEth * 10n ** 18n) / cappedTokensOut
|
|
117
|
+
: 0n;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
tokensOut: cappedTokensOut,
|
|
121
|
+
fee,
|
|
122
|
+
netEth,
|
|
123
|
+
pricePerToken,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get a quote for selling tokens
|
|
129
|
+
*
|
|
130
|
+
* @param tokenAmount - Amount of tokens to sell
|
|
131
|
+
* @returns Quote with ETH out, fees, and price impact
|
|
132
|
+
*/
|
|
133
|
+
getSellQuote(tokenAmount: bigint): SellQuote {
|
|
134
|
+
// Calculate ETH out using constant product formula
|
|
135
|
+
// ethOut = (tokenAmount * virtualEthReserves) / (virtualTokenReserves + tokenAmount)
|
|
136
|
+
const ethOutGross =
|
|
137
|
+
(tokenAmount * this.state.virtualEthReserves) /
|
|
138
|
+
(this.state.virtualTokenReserves + tokenAmount);
|
|
139
|
+
|
|
140
|
+
// Calculate fee
|
|
141
|
+
const fee = (ethOutGross * this.feeBasisPoints) / BPS_DENOMINATOR;
|
|
142
|
+
const ethOutNet = ethOutGross - fee;
|
|
143
|
+
|
|
144
|
+
// Calculate effective price per token
|
|
145
|
+
const pricePerToken = tokenAmount > 0n
|
|
146
|
+
? (ethOutNet * 10n ** 18n) / tokenAmount
|
|
147
|
+
: 0n;
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
ethOutGross,
|
|
151
|
+
fee,
|
|
152
|
+
ethOutNet,
|
|
153
|
+
pricePerToken,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Calculate minimum tokens out with slippage tolerance
|
|
159
|
+
*
|
|
160
|
+
* @param ethAmount - Amount of ETH to spend
|
|
161
|
+
* @param slippageBps - Slippage tolerance in basis points (e.g., 100 = 1%)
|
|
162
|
+
* @returns Minimum tokens to receive
|
|
163
|
+
*/
|
|
164
|
+
calculateMinTokensOut(ethAmount: bigint, slippageBps: number): bigint {
|
|
165
|
+
const quote = this.getBuyQuote(ethAmount);
|
|
166
|
+
const slippageMultiplier = BPS_DENOMINATOR - BigInt(slippageBps);
|
|
167
|
+
return (quote.tokensOut * slippageMultiplier) / BPS_DENOMINATOR;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Calculate minimum ETH out with slippage tolerance
|
|
172
|
+
*
|
|
173
|
+
* @param tokenAmount - Amount of tokens to sell
|
|
174
|
+
* @param slippageBps - Slippage tolerance in basis points (e.g., 100 = 1%)
|
|
175
|
+
* @returns Minimum ETH to receive
|
|
176
|
+
*/
|
|
177
|
+
calculateMinEthOut(tokenAmount: bigint, slippageBps: number): bigint {
|
|
178
|
+
const quote = this.getSellQuote(tokenAmount);
|
|
179
|
+
const slippageMultiplier = BPS_DENOMINATOR - BigInt(slippageBps);
|
|
180
|
+
return (quote.ethOutNet * slippageMultiplier) / BPS_DENOMINATOR;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Calculate price impact for a buy order
|
|
185
|
+
*
|
|
186
|
+
* @param ethAmount - Amount of ETH to spend
|
|
187
|
+
* @returns Price impact as a percentage (e.g., 2.5 = 2.5%)
|
|
188
|
+
*/
|
|
189
|
+
calculateBuyPriceImpact(ethAmount: bigint): number {
|
|
190
|
+
const currentPrice = this.getCurrentPrice();
|
|
191
|
+
if (currentPrice === 0n) return 0;
|
|
192
|
+
|
|
193
|
+
const quote = this.getBuyQuote(ethAmount);
|
|
194
|
+
if (quote.tokensOut === 0n) return 0;
|
|
195
|
+
|
|
196
|
+
// New price after trade
|
|
197
|
+
const newVirtualEth = this.state.virtualEthReserves + quote.netEth;
|
|
198
|
+
const newVirtualTokens = this.state.virtualTokenReserves - quote.tokensOut;
|
|
199
|
+
const newPrice = (newVirtualEth * 10n ** 18n) / newVirtualTokens;
|
|
200
|
+
|
|
201
|
+
// Price impact = (newPrice - currentPrice) / currentPrice * 100
|
|
202
|
+
const impact = Number((newPrice - currentPrice) * 10000n / currentPrice) / 100;
|
|
203
|
+
return impact;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Calculate price impact for a sell order
|
|
208
|
+
*
|
|
209
|
+
* @param tokenAmount - Amount of tokens to sell
|
|
210
|
+
* @returns Price impact as a percentage (negative for sells)
|
|
211
|
+
*/
|
|
212
|
+
calculateSellPriceImpact(tokenAmount: bigint): number {
|
|
213
|
+
const currentPrice = this.getCurrentPrice();
|
|
214
|
+
if (currentPrice === 0n) return 0;
|
|
215
|
+
|
|
216
|
+
const quote = this.getSellQuote(tokenAmount);
|
|
217
|
+
|
|
218
|
+
// New price after trade
|
|
219
|
+
const newVirtualEth = this.state.virtualEthReserves - quote.ethOutGross;
|
|
220
|
+
const newVirtualTokens = this.state.virtualTokenReserves + tokenAmount;
|
|
221
|
+
|
|
222
|
+
if (newVirtualTokens === 0n) return -100;
|
|
223
|
+
|
|
224
|
+
const newPrice = (newVirtualEth * 10n ** 18n) / newVirtualTokens;
|
|
225
|
+
|
|
226
|
+
// Price impact = (newPrice - currentPrice) / currentPrice * 100
|
|
227
|
+
const impact = Number((newPrice - currentPrice) * 10000n / currentPrice) / 100;
|
|
228
|
+
return impact;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if a buy would exceed the migration threshold
|
|
233
|
+
*
|
|
234
|
+
* @param ethAmount - Amount of ETH to spend
|
|
235
|
+
* @returns True if the buy would exceed threshold
|
|
236
|
+
*/
|
|
237
|
+
wouldExceedMigrationThreshold(ethAmount: bigint): boolean {
|
|
238
|
+
const fee = (ethAmount * this.feeBasisPoints) / BPS_DENOMINATOR;
|
|
239
|
+
const netEth = ethAmount - fee;
|
|
240
|
+
return this.state.realEthReserves + netEth > MIGRATION_THRESHOLD;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Calculate maximum ETH that can be spent before hitting migration threshold
|
|
245
|
+
*
|
|
246
|
+
* @returns Maximum ETH amount (in wei)
|
|
247
|
+
*/
|
|
248
|
+
getMaxBuyAmount(): bigint {
|
|
249
|
+
if (this.state.complete) {
|
|
250
|
+
return 0n;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const remainingEth = MIGRATION_THRESHOLD - this.state.realEthReserves;
|
|
254
|
+
|
|
255
|
+
// Account for fees: netEth = ethAmount - fee
|
|
256
|
+
// netEth = ethAmount - (ethAmount * feeBps / 10000)
|
|
257
|
+
// netEth = ethAmount * (10000 - feeBps) / 10000
|
|
258
|
+
// ethAmount = netEth * 10000 / (10000 - feeBps)
|
|
259
|
+
const maxEthWithFees =
|
|
260
|
+
(remainingEth * BPS_DENOMINATOR) /
|
|
261
|
+
(BPS_DENOMINATOR - this.feeBasisPoints);
|
|
262
|
+
|
|
263
|
+
return maxEthWithFees;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Update the internal state (used after trades)
|
|
268
|
+
*/
|
|
269
|
+
updateState(newState: BondingCurveState): void {
|
|
270
|
+
this.state = { ...newState };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Create a BondingCurve instance from raw contract data
|
|
275
|
+
*/
|
|
276
|
+
static fromContractData(
|
|
277
|
+
data: readonly [bigint, bigint, bigint, bigint, bigint, boolean, boolean],
|
|
278
|
+
feeBasisPoints: bigint
|
|
279
|
+
): BondingCurve {
|
|
280
|
+
const state: BondingCurveState = {
|
|
281
|
+
virtualTokenReserves: data[0],
|
|
282
|
+
virtualEthReserves: data[1],
|
|
283
|
+
realTokenReserves: data[2],
|
|
284
|
+
realEthReserves: data[3],
|
|
285
|
+
tokenTotalSupply: data[4],
|
|
286
|
+
complete: data[5],
|
|
287
|
+
trackVolume: data[6],
|
|
288
|
+
};
|
|
289
|
+
return new BondingCurve(state, feeBasisPoints);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Utility function to calculate tokens out without instantiating a class
|
|
295
|
+
*/
|
|
296
|
+
export function calculateBuyAmount(
|
|
297
|
+
virtualTokenReserves: bigint,
|
|
298
|
+
virtualEthReserves: bigint,
|
|
299
|
+
ethAmount: bigint,
|
|
300
|
+
feeBasisPoints: bigint
|
|
301
|
+
): bigint {
|
|
302
|
+
const fee = (ethAmount * feeBasisPoints) / BPS_DENOMINATOR;
|
|
303
|
+
const netEth = ethAmount - fee;
|
|
304
|
+
return (virtualTokenReserves * netEth) / (virtualEthReserves + netEth);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Utility function to calculate ETH out without instantiating a class
|
|
309
|
+
*/
|
|
310
|
+
export function calculateSellAmount(
|
|
311
|
+
virtualTokenReserves: bigint,
|
|
312
|
+
virtualEthReserves: bigint,
|
|
313
|
+
tokenAmount: bigint,
|
|
314
|
+
feeBasisPoints: bigint
|
|
315
|
+
): bigint {
|
|
316
|
+
const ethOutGross = (tokenAmount * virtualEthReserves) / (virtualTokenReserves + tokenAmount);
|
|
317
|
+
const fee = (ethOutGross * feeBasisPoints) / BPS_DENOMINATOR;
|
|
318
|
+
return ethOutGross - fee;
|
|
319
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Address } from "viem";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Protocol fee constants (matching smart contract)
|
|
5
|
+
*/
|
|
6
|
+
export const PROTOCOL_FEE_BASIS_POINTS = 70n; // 0.70%
|
|
7
|
+
export const CREATOR_FEE_BASIS_POINTS = 30n; // 0.30%
|
|
8
|
+
export const TOTAL_FEE_BASIS_POINTS =
|
|
9
|
+
PROTOCOL_FEE_BASIS_POINTS + CREATOR_FEE_BASIS_POINTS; // 1.00%
|
|
10
|
+
export const MAX_FEE_BASIS_POINTS = 1000n; // 10% maximum
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Migration constants
|
|
14
|
+
*/
|
|
15
|
+
export const MIGRATION_THRESHOLD = 4_000_000_000_000_000_000n; // 4 ETH in wei
|
|
16
|
+
export const TOKENS_FOR_LP = 206_900_000_000_000_000_000_000_000n; // 206.9M tokens
|
|
17
|
+
export const MAX_MIGRATE_FEES = 300_000_000_000_000n; // 0.0003 ETH
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Uniswap V3 constants
|
|
21
|
+
*/
|
|
22
|
+
export const UNISWAP_FEE_TIER = 3000; // 0.3%
|
|
23
|
+
export const TICK_LOWER = -887220; // Full range
|
|
24
|
+
export const TICK_UPPER = 887220; // Full range
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default slippage tolerance
|
|
28
|
+
*/
|
|
29
|
+
export const DEFAULT_SLIPPAGE_BPS = 100; // 1%
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Basis points denominator
|
|
33
|
+
*/
|
|
34
|
+
export const BPS_DENOMINATOR = 10000n;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Chain configurations
|
|
38
|
+
*/
|
|
39
|
+
export interface ChainConfig {
|
|
40
|
+
chainId: number;
|
|
41
|
+
name: string;
|
|
42
|
+
factoryAddress: Address;
|
|
43
|
+
wethAddress: Address;
|
|
44
|
+
nonfungiblePositionManager: Address;
|
|
45
|
+
uniswapV3Factory: Address;
|
|
46
|
+
blockExplorer: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Supported chain configurations
|
|
51
|
+
* Add your deployed contract addresses here
|
|
52
|
+
*/
|
|
53
|
+
export const CHAIN_CONFIGS: Record<number, ChainConfig> = {
|
|
54
|
+
// Mega Eth Testnet
|
|
55
|
+
6543: {
|
|
56
|
+
chainId: 6543,
|
|
57
|
+
name: "Mega Eth Testnet",
|
|
58
|
+
factoryAddress: "0x0d593cE47EBA2d15a77ddbAc41BdE6d03CC9241b" as Address,
|
|
59
|
+
wethAddress: "0x4200000000000000000000000000000000000006" as Address,
|
|
60
|
+
nonfungiblePositionManager:
|
|
61
|
+
"0xa204A97EF8Bd2E3198f19EB5a804680467BD85f5" as Address,
|
|
62
|
+
uniswapV3Factory: "0x619fb6C12c36b57a8bAb05e98F42C43745DCf69f" as Address,
|
|
63
|
+
blockExplorer: "https://megaeth-testnet-v2.blockscout.com",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get chain configuration by chain ID
|
|
69
|
+
*/
|
|
70
|
+
export function getChainConfig(chainId: number): ChainConfig | undefined {
|
|
71
|
+
return CHAIN_CONFIGS[chainId];
|
|
72
|
+
}
|