-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy path16-launch-price-ladder.ts
More file actions
169 lines (153 loc) · 5.29 KB
/
Copy path16-launch-price-ladder.ts
File metadata and controls
169 lines (153 loc) · 5.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/**
* Example 16: Launch Price Ladder
*
* Category: Curve Math & Fees
*
* Starts from newBondingCurve(global), then simulates a sequence of buys
* by applying each quote to the virtual and real reserves with BN math,
* printing spot price and market cap after every step. This is constant
* product curve mechanics end to end: why early buyers get more tokens
* per SOL and why the price can only ratchet up while people buy.
*
* Run: npm run example 16
*/
import {
bondingCurveMarketCap,
computeFeesBps,
getBuyTokenAmountFromSolAmount,
newBondingCurve,
} from "@nirholas/pump-sdk";
import { PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import type { BondingCurve, Global } from "@nirholas/pump-sdk";
import { EXAMPLE_CREATOR, mainnetGlobal } from "./_lib/curveState";
import { formatSol, formatTokens, heading, row } from "./_lib/format";
/** Lamports per 1,000,000 whole tokens (1e12 base units), pure BN. */
export function spotPriceLamportsPerMillionTokens(bondingCurve: BondingCurve): BN {
return bondingCurve.virtualSolReserves
.mul(new BN("1000000000000"))
.div(bondingCurve.virtualTokenReserves);
}
export interface BuyApplication {
/** Tokens the buyer receives for this step's SOL spend. */
tokensOut: BN;
/** The fee-stripped SOL that actually enters the reserves. */
solIntoReserves: BN;
/** The curve state after the buy. */
curve: BondingCurve;
}
/**
* Apply one buy to a bonding curve, exactly as the program does:
*
* 1. Strip fees from the spend: input = (sol - 1) * 10000 / (feeBps + 10000).
* 2. Quote tokens out with the constant product: input * vTok / (vSol + input).
* 3. Move reserves: vSol and realSol up by input, vTok and realTok down by
* the tokens sold.
*
* Returns a new curve; the input curve is not mutated.
*/
export function applyBuy(
global: Global,
bondingCurve: BondingCurve,
solIn: BN,
): BuyApplication {
const { protocolFeeBps, creatorFeeBps } = computeFeesBps({
global,
feeConfig: null,
mintSupply: global.tokenTotalSupply,
virtualSolReserves: bondingCurve.virtualSolReserves,
virtualTokenReserves: bondingCurve.virtualTokenReserves,
});
const totalFeeBps = protocolFeeBps.add(
PublicKey.default.equals(bondingCurve.creator) ? new BN(0) : creatorFeeBps,
);
const solIntoReserves = solIn
.subn(1)
.muln(10_000)
.div(totalFeeBps.addn(10_000));
const tokensOut = getBuyTokenAmountFromSolAmount({
global,
feeConfig: null,
mintSupply: global.tokenTotalSupply,
bondingCurve,
amount: solIn,
});
return {
tokensOut,
solIntoReserves,
curve: {
...bondingCurve,
virtualSolReserves: bondingCurve.virtualSolReserves.add(solIntoReserves),
virtualTokenReserves: bondingCurve.virtualTokenReserves.sub(tokensOut),
realSolReserves: bondingCurve.realSolReserves.add(solIntoReserves),
realTokenReserves: bondingCurve.realTokenReserves.sub(tokensOut),
},
};
}
export interface LadderStep {
buyIndex: number;
solIn: BN;
tokensOut: BN;
spotPrice: BN;
marketCap: BN;
curve: BondingCurve;
}
/**
* Simulate a sequence of buys from a fresh launch and record the price
* ladder they climb.
*/
export function simulateBuySequence(global: Global, buys: BN[]): LadderStep[] {
// A live curve always has its creator set, so creator fees apply from
// the first post-launch trade.
let curve: BondingCurve = { ...newBondingCurve(global), creator: EXAMPLE_CREATOR };
const steps: LadderStep[] = [];
buys.forEach((solIn, index) => {
const applied = applyBuy(global, curve, solIn);
curve = applied.curve;
steps.push({
buyIndex: index + 1,
solIn,
tokensOut: applied.tokensOut,
spotPrice: spotPriceLamportsPerMillionTokens(curve),
marketCap: bondingCurveMarketCap({
mintSupply: global.tokenTotalSupply,
virtualSolReserves: curve.virtualSolReserves,
virtualTokenReserves: curve.virtualTokenReserves,
}),
curve,
});
});
return steps;
}
export async function main(): Promise<void> {
const global = mainnetGlobal();
const start = newBondingCurve(global);
heading("Launch state (newBondingCurve)");
row("Virtual SOL", formatSol(start.virtualSolReserves));
row("Virtual tokens", formatTokens(start.virtualTokenReserves, 0));
row("Spot price", `${formatSol(spotPriceLamportsPerMillionTokens(start), 6)} per 1M tokens`);
heading("Ten buys of 1 SOL each");
console.log(
`${"buy".padEnd(6)}${"tokens out".padEnd(24)}${"spot / 1M tokens".padEnd(22)}market cap`,
);
const buys = Array.from({ length: 10 }, () => new BN("1000000000"));
for (const step of simulateBuySequence(global, buys)) {
console.log(
`${String(step.buyIndex).padEnd(6)}${formatTokens(step.tokensOut, 0).padEnd(24)}${formatSol(
step.spotPrice,
6,
).padEnd(22)}${formatSol(step.marketCap, 2)}`,
);
}
console.log("\nEach identical 1 SOL buy receives fewer tokens than the one");
console.log("before it: the fee-stripped SOL raises virtualSolReserves while");
console.log("tokens leave virtualTokenReserves, so the ratio (the price) only");
console.log("moves up. Nothing about this needs an oracle; the reserves ARE");
console.log("the price.");
}
if (require.main === module) {
main().catch((err) => {
console.error(err);
process.exit(1);
});
}