-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy path44-amm-withdraw.ts
More file actions
179 lines (164 loc) · 5.66 KB
/
Copy path44-amm-withdraw.ts
File metadata and controls
179 lines (164 loc) · 5.66 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
170
171
172
173
174
175
176
177
178
179
/**
* Example 44: AMM Withdraw
*
* Category: AMM & Advanced
*
* Reads an LP position with getLpTokenBalance, quotes burning it back into
* base and SOL with quoteAmmWithdraw and ammWithdrawAutocomplete, and builds
* the withdraw instructions without sending them. Checks the quote against the
* pro-rata share the reserves say the LP tokens are worth.
*
* Run: npm run example 44
*/
import { OnlinePumpSdk } from "@nirholas/pump-sdk";
import BN from "bn.js";
import { getConnection } from "./_lib/connection";
import { formatSol, formatTokens, heading, row } from "./_lib/format";
import { findGraduatedMint } from "./_lib/discovery";
import { loadWallet } from "./_lib/wallet";
/** What a burn of `lpToken` is worth against the pool's current reserves. */
export interface WithdrawShare {
/** Base tokens the position is entitled to. */
base: BN;
/** Lamports of SOL the position is entitled to. */
quote: BN;
/** The position's share of the pool, in basis points. */
shareOfPoolBps: BN;
}
/**
* Value an LP position pro rata.
*
* A PumpAMM withdraw pays out reserves in proportion to the LP tokens burned
* against total LP supply, so the entitlement is exact integer arithmetic and
* needs no pool simulation. Comparing this against quoteAmmWithdraw is a cheap
* sanity check on any withdraw path.
*/
export function withdrawShare({
lpToken,
lpSupply,
poolBase,
poolQuote,
}: {
lpToken: BN;
lpSupply: BN;
poolBase: BN;
poolQuote: BN;
}): WithdrawShare {
if (lpSupply.isZero()) {
throw new Error("Pool has no LP supply; nothing to withdraw against");
}
if (lpToken.gt(lpSupply)) {
throw new Error("Cannot burn more LP tokens than the pool has issued");
}
return {
base: poolBase.mul(lpToken).div(lpSupply),
quote: poolQuote.mul(lpToken).div(lpSupply),
shareOfPoolBps: lpToken.muln(10_000).div(lpSupply),
};
}
/**
* How far below the quoted amount a slippage-adjusted minimum sits, in basis
* points. This is the worst fill the transaction will accept before it aborts.
*/
export function slippageFloorBps(amount: BN, minAmount: BN): BN {
if (amount.isZero()) return new BN(0);
return BN.max(new BN(0), amount.sub(minAmount)).muln(10_000).div(amount);
}
export async function main(): Promise<void> {
const connection = getConnection();
const wallet = loadWallet();
const sdk = new OnlinePumpSdk(connection);
heading("Finding a graduated token");
const { mint } = await findGraduatedMint(connection);
const pool = await sdk.fetchPool(mint);
row("Mint", mint.toBase58());
row("Withdrawer", wallet.publicKey.toBase58());
row("LP mint", pool.lpMint.toBase58());
row("LP supply", pool.lpSupply.toString());
heading("The wallet's LP position");
const lpBalance = await sdk.getLpTokenBalance(mint, wallet.publicKey);
row("LP balance", lpBalance.toString());
// Every quote below needs a position to price. A wallet that holds LP
// tokens prices its own; anything else prices one percent of the pool,
// which is the same call with a different number.
const lpToken = lpBalance.isZero()
? BN.max(new BN(1), pool.lpSupply.divn(100))
: lpBalance;
row(
"Sizing the quote from",
lpBalance.isZero() ? "1% of the pool" : "the wallet balance",
);
row("LP tokens to burn", lpToken.toString());
const slippage = 0.01; // 1%
heading("Quote: quoteAmmWithdraw");
const quote = await sdk.quoteAmmWithdraw({
mint,
user: wallet.publicKey,
lpToken,
slippage,
});
row("Base out", formatTokens(quote.base));
row("SOL out", formatSol(quote.quote));
row("Min base (slippage)", formatTokens(quote.minBase));
row("Min SOL (slippage)", formatSol(quote.minQuote));
row("Base floor", `${slippageFloorBps(quote.base, quote.minBase).toString()} bps`);
row("SOL floor", `${slippageFloorBps(quote.quote, quote.minQuote).toString()} bps`);
heading("Form autocomplete: ammWithdrawAutocomplete");
const auto = await sdk.ammWithdrawAutocomplete({
mint,
user: wallet.publicKey,
lpToken,
slippage,
});
row("lpToken -> base", formatTokens(auto.base));
row("lpToken -> quote", formatSol(auto.quote));
console.log(
"\nThe autocomplete pair is what a withdraw slider displays; the quote",
);
console.log("above adds the minimums the transaction will enforce.");
heading("Cross-check against the reserves");
const reserves = await sdk.ammQuoteSell({
mint,
user: wallet.publicKey,
baseAmountIn: new BN(1_000_000),
});
const share = withdrawShare({
lpToken,
lpSupply: pool.lpSupply,
poolBase: reserves.poolBaseAmount,
poolQuote: reserves.poolQuoteAmount,
});
row("Pool base reserve", formatTokens(reserves.poolBaseAmount));
row("Pool quote reserve", formatSol(reserves.poolQuoteAmount));
row("Pro-rata base", formatTokens(share.base));
row("Pro-rata SOL", formatSol(share.quote));
row("Share of pool", `${share.shareOfPoolBps.toString()} bps`);
heading("Instructions: ammWithdrawInstructions (not sent)");
const ixs = await sdk.ammWithdrawInstructions({
mint,
user: wallet.publicKey,
lpTokenIn: lpToken,
slippage,
});
row("Instruction count", ixs.length);
for (const [i, ix] of ixs.entries()) {
row(
` ix[${i}]`,
`${ix.programId.toBase58()} keys=${ix.keys.length} data=${ix.data.length}B`,
);
}
heading("Next step (not performed here)");
console.log(
"Signing and sending burns the LP tokens and returns both sides of the",
);
console.log(
"position. Withdrawing never trades, so it carries no price impact: the",
);
console.log("only cost is the fees already earned by staying in.");
}
if (require.main === module) {
main().catch((err) => {
console.error(err);
process.exit(1);
});
}