@metadaoproject/programs 0.1.1-alpha.0 → 0.1.1-relaunch-demo.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.
Files changed (34) hide show
  1. package/dist/constants.d.ts +6 -0
  2. package/dist/constants.js +14 -0
  3. package/dist/constants.js.map +1 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/relaunch/index.d.ts +1 -0
  8. package/dist/relaunch/index.js +2 -0
  9. package/dist/relaunch/index.js.map +1 -0
  10. package/dist/relaunch/v0.1/RelaunchClient.d.ts +1157 -0
  11. package/dist/relaunch/v0.1/RelaunchClient.js +733 -0
  12. package/dist/relaunch/v0.1/RelaunchClient.js.map +1 -0
  13. package/dist/relaunch/v0.1/index.d.ts +6 -0
  14. package/dist/relaunch/v0.1/index.js +7 -0
  15. package/dist/relaunch/v0.1/index.js.map +1 -0
  16. package/dist/relaunch/v0.1/pda.d.ts +14 -0
  17. package/dist/relaunch/v0.1/pda.js +12 -0
  18. package/dist/relaunch/v0.1/pda.js.map +1 -0
  19. package/dist/relaunch/v0.1/pumpAmm.d.ts +39 -0
  20. package/dist/relaunch/v0.1/pumpAmm.js +105 -0
  21. package/dist/relaunch/v0.1/pumpAmm.js.map +1 -0
  22. package/dist/relaunch/v0.1/raydiumAmm.d.ts +18 -0
  23. package/dist/relaunch/v0.1/raydiumAmm.js +37 -0
  24. package/dist/relaunch/v0.1/raydiumAmm.js.map +1 -0
  25. package/dist/relaunch/v0.1/types/index.d.ts +5 -0
  26. package/dist/relaunch/v0.1/types/index.js +3 -0
  27. package/dist/relaunch/v0.1/types/index.js.map +1 -0
  28. package/dist/relaunch/v0.1/types/relaunch.d.ts +2274 -0
  29. package/dist/relaunch/v0.1/types/relaunch.js +2274 -0
  30. package/dist/relaunch/v0.1/types/relaunch.js.map +1 -0
  31. package/dist/relaunch/v0.1/whirlpool.d.ts +18 -0
  32. package/dist/relaunch/v0.1/whirlpool.js +66 -0
  33. package/dist/relaunch/v0.1/whirlpool.js.map +1 -0
  34. package/package.json +3 -1
@@ -0,0 +1,733 @@
1
+ import { Program } from "@coral-xyz/anchor";
2
+ import { ComputeBudgetProgram, Keypair, PublicKey, SystemProgram, Transaction, } from "@solana/web3.js";
3
+ import { AccountLayout, ASSOCIATED_TOKEN_PROGRAM_ID, createAssociatedTokenAccountIdempotentInstruction, createInitializeMint2Instruction, createSyncNativeInstruction, getAssociatedTokenAddressSync, MINT_SIZE, NATIVE_MINT, TOKEN_PROGRAM_ID, } from "@solana/spl-token";
4
+ import BN from "bn.js";
5
+ import * as multisig from "@sqds/multisig";
6
+ import { FUTARCHY_V0_6_PROGRAM_ID, MAINNET_USDC, MPL_TOKEN_METADATA_PROGRAM_ID, PUMP_AMM_PROGRAM_ID, PUMP_FEES_PROGRAM_ID, RELAUNCH_V0_1_PROGRAM_ID, SQUADS_PROGRAM_CONFIG, SQUADS_PROGRAM_CONFIG_TREASURY, SQUADS_PROGRAM_ID, WHIRLPOOL_PROGRAM_ID, } from "../../constants.js";
7
+ import { getDaoAddr } from "../../futarchy/v0.6/index.js";
8
+ import { RelaunchIDL, } from "./types/index.js";
9
+ import { getRelaunchAddr, getRelaunchSignerAddr, getDepositRecordAddr, } from "./pda.js";
10
+ import { fetchPumpPool, getPumpCreatorVaultAuthorityAddr, getPumpFeeRecipients, getPumpPoolV2Addr, getPumpUserVolumeAccumulatorAddr, PUMP_AMM_EVENT_AUTHORITY, PUMP_AMM_FEE_CONFIG, PUMP_AMM_GLOBAL_CONFIG, PUMP_AMM_GLOBAL_VOLUME_ACCUMULATOR, } from "./pumpAmm.js";
11
+ import { fetchRaydiumPool, parseRaydiumPool, RAYDIUM_AMM_AUTHORITY, RAYDIUM_AMM_PROGRAM_ID, } from "./raydiumAmm.js";
12
+ import { fetchWhirlpool, getWhirlpoolOracleAddr, getWhirlpoolSwapTickArrayAddrs, MEMO_PROGRAM_ID, USDC_SWAP_POOL, } from "./whirlpool.js";
13
+ import { getEventAuthorityAddr, getMetadataAddr } from "../../pda.js";
14
+ function ceilDiv(a, b) {
15
+ return (a + b - 1n) / b;
16
+ }
17
+ export class RelaunchClient {
18
+ provider;
19
+ relaunchProgram;
20
+ programId;
21
+ constructor(provider, relaunchProgramId) {
22
+ this.provider = provider;
23
+ this.programId = relaunchProgramId;
24
+ this.relaunchProgram = new Program(RelaunchIDL, relaunchProgramId, provider);
25
+ }
26
+ static createClient(createRelaunchClientParams) {
27
+ let { provider, relaunchProgramId } = createRelaunchClientParams;
28
+ return new RelaunchClient(provider, relaunchProgramId || RELAUNCH_V0_1_PROGRAM_ID);
29
+ }
30
+ getProgramId() {
31
+ return this.programId;
32
+ }
33
+ initializeRelaunchIx({ newMint, oldMint, oldTokenProgram, sourcePool,
34
+ // Required for Raydium sources (the pool's LP mint), null for PumpSwap.
35
+ sourcePoolLpMint = null, sourceQuoteMint, tokenName, tokenSymbol, tokenUri, secondsForDeposits, gracePeriodSeconds, thresholdBps,
36
+ // Zero amount with no members initializes without a spending limit.
37
+ monthlySpendingLimitAmount = new BN(0), monthlySpendingLimitMembers = [], teamAddress, mintAuthority = this.provider.publicKey, admin = this.provider.publicKey, payer = this.provider.publicKey, }) {
38
+ const relaunch = this.getRelaunchAddress({ newMint });
39
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
40
+ const oldTokenVault = getAssociatedTokenAddressSync(oldMint, relaunchSigner, true, oldTokenProgram);
41
+ const newTokenVault = getAssociatedTokenAddressSync(newMint, relaunchSigner, true);
42
+ const sourceQuoteVault = getAssociatedTokenAddressSync(sourceQuoteMint, relaunchSigner, true);
43
+ const usdcVault = getAssociatedTokenAddressSync(MAINNET_USDC, relaunchSigner, true);
44
+ const [tokenMetadata] = getMetadataAddr(newMint);
45
+ return this.relaunchProgram.methods
46
+ .initializeRelaunch({
47
+ tokenName,
48
+ tokenSymbol,
49
+ tokenUri,
50
+ secondsForDeposits,
51
+ gracePeriodSeconds,
52
+ thresholdBps,
53
+ monthlySpendingLimitAmount,
54
+ monthlySpendingLimitMembers,
55
+ teamAddress,
56
+ })
57
+ .accounts({
58
+ relaunch,
59
+ newMint,
60
+ mintAuthority,
61
+ relaunchSigner,
62
+ oldMint,
63
+ sourcePool,
64
+ sourcePoolLpMint,
65
+ sourceQuoteMint,
66
+ usdcMint: MAINNET_USDC,
67
+ oldTokenVault,
68
+ newTokenVault,
69
+ sourceQuoteVault,
70
+ usdcVault,
71
+ tokenMetadata,
72
+ admin,
73
+ payer,
74
+ oldTokenProgram,
75
+ tokenProgram: TOKEN_PROGRAM_ID,
76
+ tokenMetadataProgram: MPL_TOKEN_METADATA_PROGRAM_ID,
77
+ })
78
+ .preInstructions([
79
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }),
80
+ ]);
81
+ }
82
+ startDepositsIx({ relaunch, admin = this.provider.publicKey, }) {
83
+ return this.relaunchProgram.methods.startDeposits().accounts({
84
+ relaunch,
85
+ admin,
86
+ });
87
+ }
88
+ depositIx({ relaunch, oldMint, oldTokenProgram, amount, depositor = this.provider.publicKey, payer = this.provider.publicKey, }) {
89
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
90
+ const depositRecord = this.getDepositRecordAddress({
91
+ relaunch,
92
+ depositor,
93
+ });
94
+ const oldTokenVault = getAssociatedTokenAddressSync(oldMint, relaunchSigner, true, oldTokenProgram);
95
+ const depositorTokenAccount = getAssociatedTokenAddressSync(oldMint, depositor, false, oldTokenProgram);
96
+ return this.relaunchProgram.methods.deposit({ amount }).accounts({
97
+ relaunch,
98
+ depositRecord,
99
+ oldMint,
100
+ oldTokenVault,
101
+ depositor,
102
+ depositorTokenAccount,
103
+ payer,
104
+ oldTokenProgram,
105
+ });
106
+ }
107
+ closeDepositsIx({ relaunch }) {
108
+ return this.relaunchProgram.methods.closeDeposits().accounts({
109
+ relaunch,
110
+ });
111
+ }
112
+ depositViaBuyIx({ relaunch, oldMint, oldTokenProgram, sourceQuoteMint, sourcePool, poolBaseTokenAccount, poolQuoteTokenAccount, coinCreator, protocolFeeRecipient, buybackFeeRecipient, baseOut, maxQuoteIn, depositor = this.provider.publicKey, payer = this.provider.publicKey, depositorQuoteAccount = getAssociatedTokenAddressSync(sourceQuoteMint, depositor), }) {
113
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
114
+ const depositRecord = this.getDepositRecordAddress({
115
+ relaunch,
116
+ depositor,
117
+ });
118
+ const oldTokenVault = getAssociatedTokenAddressSync(oldMint, relaunchSigner, true, oldTokenProgram);
119
+ const sourceQuoteVault = getAssociatedTokenAddressSync(sourceQuoteMint, relaunchSigner, true);
120
+ const coinCreatorVaultAuthority = getPumpCreatorVaultAuthorityAddr(coinCreator);
121
+ return this.relaunchProgram.methods
122
+ .depositViaBuy({ baseOut, maxQuoteIn })
123
+ .accounts({
124
+ relaunch,
125
+ depositRecord,
126
+ depositor,
127
+ payer,
128
+ relaunchSigner,
129
+ oldMint,
130
+ sourceQuoteMint,
131
+ oldTokenVault,
132
+ sourceQuoteVault,
133
+ depositorQuoteAccount,
134
+ sourcePool,
135
+ pumpGlobalConfig: PUMP_AMM_GLOBAL_CONFIG,
136
+ protocolFeeRecipient,
137
+ protocolFeeRecipientTokenAccount: getAssociatedTokenAddressSync(sourceQuoteMint, protocolFeeRecipient, true),
138
+ poolBaseTokenAccount,
139
+ poolQuoteTokenAccount,
140
+ coinCreatorVaultAta: getAssociatedTokenAddressSync(sourceQuoteMint, coinCreatorVaultAuthority, true),
141
+ coinCreatorVaultAuthority,
142
+ globalVolumeAccumulator: PUMP_AMM_GLOBAL_VOLUME_ACCUMULATOR,
143
+ userVolumeAccumulator: getPumpUserVolumeAccumulatorAddr(relaunchSigner),
144
+ pumpFeeConfig: PUMP_AMM_FEE_CONFIG,
145
+ pumpFeeProgram: PUMP_FEES_PROGRAM_ID,
146
+ poolV2: getPumpPoolV2Addr(oldMint),
147
+ buybackFeeRecipient,
148
+ buybackFeeRecipientTokenAccount: getAssociatedTokenAddressSync(sourceQuoteMint, buybackFeeRecipient, true),
149
+ pumpEventAuthority: PUMP_AMM_EVENT_AUTHORITY,
150
+ pumpAmmProgram: PUMP_AMM_PROGRAM_ID,
151
+ baseTokenProgram: oldTokenProgram,
152
+ quoteTokenProgram: TOKEN_PROGRAM_ID,
153
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
154
+ systemProgram: SystemProgram.programId,
155
+ })
156
+ .preInstructions([
157
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 300_000 }),
158
+ ]);
159
+ }
160
+ depositViaBuyRaydiumIx({ relaunch, oldMint, sourceQuoteMint, sourcePool, ammCoinVault, ammPcVault, baseOut, maxQuoteIn, depositor = this.provider.publicKey, payer = this.provider.publicKey, depositorQuoteAccount = getAssociatedTokenAddressSync(sourceQuoteMint, depositor), }) {
161
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
162
+ const depositRecord = this.getDepositRecordAddress({
163
+ relaunch,
164
+ depositor,
165
+ });
166
+ const oldTokenVault = getAssociatedTokenAddressSync(oldMint, relaunchSigner, true);
167
+ const sourceQuoteVault = getAssociatedTokenAddressSync(sourceQuoteMint, relaunchSigner, true);
168
+ return this.relaunchProgram.methods
169
+ .depositViaBuyRaydium({ baseOut, maxQuoteIn })
170
+ .accounts({
171
+ relaunch,
172
+ depositRecord,
173
+ depositor,
174
+ payer,
175
+ relaunchSigner,
176
+ sourceQuoteMint,
177
+ oldTokenVault,
178
+ sourceQuoteVault,
179
+ depositorQuoteAccount,
180
+ sourcePool,
181
+ ammAuthority: RAYDIUM_AMM_AUTHORITY,
182
+ ammCoinVault,
183
+ ammPcVault,
184
+ raydiumAmmProgram: RAYDIUM_AMM_PROGRAM_ID,
185
+ tokenProgram: TOKEN_PROGRAM_ID,
186
+ systemProgram: SystemProgram.programId,
187
+ });
188
+ }
189
+ executeSellIx({ relaunch, oldMint, oldTokenProgram, sourceQuoteMint, sourcePool, poolBaseTokenAccount, poolQuoteTokenAccount, coinCreator, protocolFeeRecipient, buybackFeeRecipient, minQuoteOut, admin = this.provider.publicKey, }) {
190
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
191
+ const oldTokenVault = getAssociatedTokenAddressSync(oldMint, relaunchSigner, true, oldTokenProgram);
192
+ const sourceQuoteVault = getAssociatedTokenAddressSync(sourceQuoteMint, relaunchSigner, true);
193
+ const coinCreatorVaultAuthority = getPumpCreatorVaultAuthorityAddr(coinCreator);
194
+ return this.relaunchProgram.methods
195
+ .executeSell({ minQuoteOut })
196
+ .accounts({
197
+ relaunch,
198
+ admin,
199
+ relaunchSigner,
200
+ oldMint,
201
+ sourceQuoteMint,
202
+ oldTokenVault,
203
+ sourceQuoteVault,
204
+ sourcePool,
205
+ pumpGlobalConfig: PUMP_AMM_GLOBAL_CONFIG,
206
+ protocolFeeRecipient,
207
+ protocolFeeRecipientTokenAccount: getAssociatedTokenAddressSync(sourceQuoteMint, protocolFeeRecipient, true),
208
+ poolBaseTokenAccount,
209
+ poolQuoteTokenAccount,
210
+ coinCreatorVaultAta: getAssociatedTokenAddressSync(sourceQuoteMint, coinCreatorVaultAuthority, true),
211
+ coinCreatorVaultAuthority,
212
+ pumpFeeConfig: PUMP_AMM_FEE_CONFIG,
213
+ pumpFeeProgram: PUMP_FEES_PROGRAM_ID,
214
+ poolV2: getPumpPoolV2Addr(oldMint),
215
+ buybackFeeRecipient,
216
+ buybackFeeRecipientTokenAccount: getAssociatedTokenAddressSync(sourceQuoteMint, buybackFeeRecipient, true),
217
+ pumpEventAuthority: PUMP_AMM_EVENT_AUTHORITY,
218
+ pumpAmmProgram: PUMP_AMM_PROGRAM_ID,
219
+ baseTokenProgram: oldTokenProgram,
220
+ quoteTokenProgram: TOKEN_PROGRAM_ID,
221
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
222
+ systemProgram: SystemProgram.programId,
223
+ })
224
+ .preInstructions([
225
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 250_000 }),
226
+ ]);
227
+ }
228
+ executeSellRaydiumIx({ relaunch, oldMint, sourceQuoteMint, sourcePool, ammCoinVault, ammPcVault, minQuoteOut, admin = this.provider.publicKey, }) {
229
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
230
+ const oldTokenVault = getAssociatedTokenAddressSync(oldMint, relaunchSigner, true);
231
+ const sourceQuoteVault = getAssociatedTokenAddressSync(sourceQuoteMint, relaunchSigner, true);
232
+ return this.relaunchProgram.methods
233
+ .executeSellRaydium({ minQuoteOut })
234
+ .accounts({
235
+ relaunch,
236
+ admin,
237
+ relaunchSigner,
238
+ oldTokenVault,
239
+ sourceQuoteVault,
240
+ sourcePool,
241
+ ammAuthority: RAYDIUM_AMM_AUTHORITY,
242
+ ammCoinVault,
243
+ ammPcVault,
244
+ raydiumAmmProgram: RAYDIUM_AMM_PROGRAM_ID,
245
+ tokenProgram: TOKEN_PROGRAM_ID,
246
+ });
247
+ }
248
+ executeUsdcSwapIx({ relaunch, whirlpoolWsolVault, whirlpoolUsdcVault, tickArrays, minUsdcOut, whirlpool = USDC_SWAP_POOL, admin = this.provider.publicKey, }) {
249
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
250
+ const sourceQuoteVault = getAssociatedTokenAddressSync(NATIVE_MINT, relaunchSigner, true);
251
+ const usdcVault = getAssociatedTokenAddressSync(MAINNET_USDC, relaunchSigner, true);
252
+ return this.relaunchProgram.methods
253
+ .executeUsdcSwap({ minUsdcOut })
254
+ .accounts({
255
+ relaunch,
256
+ admin,
257
+ relaunchSigner,
258
+ sourceQuoteVault,
259
+ usdcVault,
260
+ whirlpool,
261
+ wsolMint: NATIVE_MINT,
262
+ usdcMint: MAINNET_USDC,
263
+ whirlpoolWsolVault,
264
+ whirlpoolUsdcVault,
265
+ tickArray0: tickArrays[0],
266
+ tickArray1: tickArrays[1],
267
+ tickArray2: tickArrays[2],
268
+ oracle: getWhirlpoolOracleAddr(whirlpool),
269
+ memoProgram: MEMO_PROGRAM_ID,
270
+ whirlpoolProgram: WHIRLPOOL_PROGRAM_ID,
271
+ tokenProgram: TOKEN_PROGRAM_ID,
272
+ });
273
+ }
274
+ // Swaps the whole WSOL vault to USDC as the admin (the provider wallet),
275
+ // deriving the whirlpool account set from the pinned pool's live state.
276
+ // When minUsdcOut is not given, it is computed from the pool's spot price
277
+ // minus slippageBps (which must also cover the swap fee + price impact).
278
+ async executeUsdcSwap({ relaunch, minUsdcOut, slippageBps = 100, }) {
279
+ const whirlpool = await fetchWhirlpool(this.provider.connection);
280
+ if (minUsdcOut === undefined) {
281
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
282
+ const wsolIn = await this.fetchTokenBalance(getAssociatedTokenAddressSync(NATIVE_MINT, relaunchSigner, true));
283
+ // Spot price in USDC-raw per WSOL-raw is (sqrtPrice / 2^64)^2.
284
+ const spotOut = (wsolIn * whirlpool.sqrtPrice * whirlpool.sqrtPrice) >> 128n;
285
+ const floor = (spotOut * (10000n - BigInt(slippageBps))) / 10000n;
286
+ minUsdcOut = new BN(floor.toString());
287
+ }
288
+ return this.executeUsdcSwapIx({
289
+ relaunch,
290
+ whirlpoolWsolVault: whirlpool.tokenVaultA,
291
+ whirlpoolUsdcVault: whirlpool.tokenVaultB,
292
+ tickArrays: getWhirlpoolSwapTickArrayAddrs(USDC_SWAP_POOL, whirlpool.tickCurrentIndex, whirlpool.tickSpacing, true),
293
+ minUsdcOut,
294
+ }).rpc();
295
+ }
296
+ completeRelaunchIx({ relaunch, newMint, payer = this.provider.publicKey, }) {
297
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
298
+ const newTokenVault = getAssociatedTokenAddressSync(newMint, relaunchSigner, true);
299
+ const usdcVault = getAssociatedTokenAddressSync(MAINNET_USDC, relaunchSigner, true);
300
+ const [tokenMetadata] = getMetadataAddr(newMint);
301
+ const [dao] = getDaoAddr({ nonce: new BN(0), daoCreator: relaunchSigner });
302
+ const [futarchyEventAuthority] = getEventAuthorityAddr(FUTARCHY_V0_6_PROGRAM_ID);
303
+ const [multisigPda] = multisig.getMultisigPda({ createKey: dao });
304
+ const [multisigVault] = multisig.getVaultPda({ multisigPda, index: 0 });
305
+ const [spendingLimit] = multisig.getSpendingLimitPda({
306
+ multisigPda,
307
+ createKey: dao,
308
+ });
309
+ const [ammPosition] = PublicKey.findProgramAddressSync([Buffer.from("amm_position"), dao.toBuffer(), multisigVault.toBuffer()], FUTARCHY_V0_6_PROGRAM_ID);
310
+ return this.relaunchProgram.methods
311
+ .completeRelaunch()
312
+ .accounts({
313
+ relaunch,
314
+ payer,
315
+ relaunchSigner,
316
+ newMint,
317
+ usdcMint: MAINNET_USDC,
318
+ newTokenVault,
319
+ usdcVault,
320
+ tokenMetadata,
321
+ dao,
322
+ futarchyAmmBaseVault: getAssociatedTokenAddressSync(newMint, dao, true),
323
+ futarchyAmmQuoteVault: getAssociatedTokenAddressSync(MAINNET_USDC, dao, true),
324
+ ammPosition,
325
+ squadsMultisig: multisigPda,
326
+ squadsMultisigVault: multisigVault,
327
+ spendingLimit,
328
+ squadsProgramConfig: SQUADS_PROGRAM_CONFIG,
329
+ squadsProgramConfigTreasury: SQUADS_PROGRAM_CONFIG_TREASURY,
330
+ futarchyProgram: FUTARCHY_V0_6_PROGRAM_ID,
331
+ futarchyEventAuthority,
332
+ squadsProgram: SQUADS_PROGRAM_ID,
333
+ tokenMetadataProgram: MPL_TOKEN_METADATA_PROGRAM_ID,
334
+ tokenProgram: TOKEN_PROGRAM_ID,
335
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
336
+ systemProgram: SystemProgram.programId,
337
+ })
338
+ .preInstructions([
339
+ ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }),
340
+ ]);
341
+ }
342
+ // Completes the relaunch as any cranker (the provider wallet), reading the
343
+ // new mint from the stored relaunch.
344
+ async completeRelaunch({ relaunch, }) {
345
+ const storedRelaunch = await this.fetchRelaunch(relaunch);
346
+ if (storedRelaunch === null) {
347
+ throw new Error(`relaunch ${relaunch.toBase58()} does not exist`);
348
+ }
349
+ return this.completeRelaunchIx({
350
+ relaunch,
351
+ newMint: storedRelaunch.newMint,
352
+ }).rpc();
353
+ }
354
+ markFailedIx({ relaunch }) {
355
+ return this.relaunchProgram.methods.markFailed().accounts({
356
+ relaunch,
357
+ });
358
+ }
359
+ claimRefundIx({ relaunch, oldMint, oldTokenProgram, depositor = this.provider.publicKey, }) {
360
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
361
+ const depositRecord = this.getDepositRecordAddress({
362
+ relaunch,
363
+ depositor,
364
+ });
365
+ const oldTokenVault = getAssociatedTokenAddressSync(oldMint, relaunchSigner, true, oldTokenProgram);
366
+ const depositorTokenAccount = getAssociatedTokenAddressSync(oldMint, depositor, false, oldTokenProgram);
367
+ return this.relaunchProgram.methods.claimRefund().accounts({
368
+ relaunch,
369
+ depositRecord,
370
+ oldMint,
371
+ oldTokenVault,
372
+ relaunchSigner,
373
+ depositor,
374
+ depositorTokenAccount,
375
+ oldTokenProgram,
376
+ });
377
+ }
378
+ claimIx({ relaunch, newMint, depositor = this.provider.publicKey, payer = this.provider.publicKey, }) {
379
+ const relaunchSigner = this.getRelaunchSignerAddress({ relaunch });
380
+ const depositRecord = this.getDepositRecordAddress({
381
+ relaunch,
382
+ depositor,
383
+ });
384
+ const newTokenVault = getAssociatedTokenAddressSync(newMint, relaunchSigner, true);
385
+ const depositorTokenAccount = getAssociatedTokenAddressSync(newMint, depositor, false);
386
+ return this.relaunchProgram.methods
387
+ .claim()
388
+ .accounts({
389
+ relaunch,
390
+ depositRecord,
391
+ newMint,
392
+ newTokenVault,
393
+ relaunchSigner,
394
+ depositor,
395
+ depositorTokenAccount,
396
+ tokenProgram: TOKEN_PROGRAM_ID,
397
+ })
398
+ .preInstructions([
399
+ createAssociatedTokenAccountIdempotentInstruction(payer, depositorTokenAccount, depositor, newMint),
400
+ ]);
401
+ }
402
+ // Deposits from the provider wallet, reading the old mint and its owner
403
+ // program from the stored relaunch.
404
+ async deposit({ relaunch, amount, }) {
405
+ const storedRelaunch = await this.fetchRelaunch(relaunch);
406
+ if (storedRelaunch === null) {
407
+ throw new Error(`relaunch ${relaunch.toBase58()} does not exist`);
408
+ }
409
+ const oldMintAccount = await this.provider.connection.getAccountInfo(storedRelaunch.oldMint);
410
+ if (oldMintAccount === null) {
411
+ throw new Error(`old mint ${storedRelaunch.oldMint.toBase58()} does not exist`);
412
+ }
413
+ return this.depositIx({
414
+ relaunch,
415
+ oldMint: storedRelaunch.oldMint,
416
+ oldTokenProgram: oldMintAccount.owner,
417
+ amount,
418
+ }).rpc();
419
+ }
420
+ // Tops the provider wallet's WSOL ATA up to maxQuoteIn, wrapping any
421
+ // shortfall from SOL in a separate preparatory transaction
422
+ async wrapWsolShortfall(maxQuoteIn) {
423
+ const wsolAta = getAssociatedTokenAddressSync(NATIVE_MINT, this.provider.publicKey);
424
+ let wsolAtaAccount = null;
425
+ try {
426
+ wsolAtaAccount = await this.provider.connection.getAccountInfo(wsolAta);
427
+ }
428
+ catch {
429
+ // anchor-bankrun's connection proxy throws for missing accounts
430
+ // instead of returning null.
431
+ }
432
+ const wsolBalance = wsolAtaAccount === null
433
+ ? 0n
434
+ : AccountLayout.decode(wsolAtaAccount.data).amount;
435
+ const shortfall = BigInt(maxQuoteIn.toString()) - wsolBalance;
436
+ if (shortfall > 0n) {
437
+ await this.provider.sendAndConfirm(new Transaction().add(createAssociatedTokenAccountIdempotentInstruction(this.provider.publicKey, wsolAta, this.provider.publicKey, NATIVE_MINT), SystemProgram.transfer({
438
+ fromPubkey: this.provider.publicKey,
439
+ toPubkey: wsolAta,
440
+ lamports: Number(shortfall),
441
+ }), createSyncNativeInstruction(wsolAta)));
442
+ }
443
+ }
444
+ // Buys baseOut old tokens off the source pool as the provider wallet and
445
+ // credits them as a deposit, dispatching on the relaunch's stored source
446
+ // venue and deriving the venue's account set like executeSell. When
447
+ // maxQuoteIn is not given, it is computed live from the pool reserves: the
448
+ // constant-product input for the exact output plus slippageBps. Pump's
449
+ // swap fees are not modeled, so slippageBps must also cover them;
450
+ // Raydium's flat 25 bps fee is modeled exactly, so slippageBps only covers
451
+ // price movement. For WSOL-quoted pools, any shortfall in the depositor's
452
+ // WSOL ATA is wrapped from SOL in a separate preparatory transaction.
453
+ async depositViaBuy({ relaunch, baseOut, maxQuoteIn, slippageBps = 100, }) {
454
+ const storedRelaunch = await this.fetchRelaunch(relaunch);
455
+ if (storedRelaunch === null) {
456
+ throw new Error(`relaunch ${relaunch.toBase58()} does not exist`);
457
+ }
458
+ if (storedRelaunch.sourceVenue.raydiumAmmV4 !== undefined) {
459
+ const pool = await fetchRaydiumPool(this.provider.connection, storedRelaunch.sourcePool);
460
+ if (maxQuoteIn === undefined) {
461
+ const tokenIsCoin = pool.coinMint.equals(storedRelaunch.oldMint);
462
+ const [tokenReserve, quoteReserve] = await Promise.all([
463
+ tokenIsCoin ? pool.coinVault : pool.pcVault,
464
+ tokenIsCoin ? pool.pcVault : pool.coinVault,
465
+ ].map((address) => this.fetchTokenBalance(address)));
466
+ const baseOutBig = BigInt(baseOut.toString());
467
+ if (baseOutBig >= tokenReserve) {
468
+ throw new Error(`baseOut ${baseOutBig} exceeds the pool's token reserve ${tokenReserve}`);
469
+ }
470
+ // The exact-out input is ceil-rounded, with the 25 bps fee
471
+ // ceil-rounded on top of it (the fee stays in the pool).
472
+ const inBeforeFee = ceilDiv(quoteReserve * baseOutBig, tokenReserve - baseOutBig);
473
+ const grossIn = ceilDiv(inBeforeFee * 10000n, 9975n);
474
+ const cap = (grossIn * (10000n + BigInt(slippageBps))) / 10000n;
475
+ maxQuoteIn = new BN(cap.toString());
476
+ }
477
+ // Raydium sources are WSOL-quoted by construction.
478
+ await this.wrapWsolShortfall(maxQuoteIn);
479
+ return this.depositViaBuyRaydiumIx({
480
+ relaunch,
481
+ oldMint: storedRelaunch.oldMint,
482
+ sourceQuoteMint: storedRelaunch.sourceQuoteMint,
483
+ sourcePool: storedRelaunch.sourcePool,
484
+ ammCoinVault: pool.coinVault,
485
+ ammPcVault: pool.pcVault,
486
+ baseOut,
487
+ maxQuoteIn,
488
+ }).rpc();
489
+ }
490
+ const oldMintAccount = await this.provider.connection.getAccountInfo(storedRelaunch.oldMint);
491
+ if (oldMintAccount === null) {
492
+ throw new Error(`old mint ${storedRelaunch.oldMint.toBase58()} does not exist`);
493
+ }
494
+ const pool = await fetchPumpPool(this.provider.connection, storedRelaunch.sourcePool);
495
+ const { protocolFeeRecipient, buybackFeeRecipient } = await getPumpFeeRecipients(this.provider.connection);
496
+ if (maxQuoteIn === undefined) {
497
+ const [baseReserve, quoteReserve] = await Promise.all([pool.poolBaseTokenAccount, pool.poolQuoteTokenAccount].map((address) => this.fetchTokenBalance(address)));
498
+ const baseOutBig = BigInt(baseOut.toString());
499
+ if (baseOutBig >= baseReserve) {
500
+ throw new Error(`baseOut ${baseOutBig} exceeds the pool's base reserve ${baseReserve}`);
501
+ }
502
+ const grossIn = (quoteReserve * baseOutBig) / (baseReserve - baseOutBig);
503
+ const cap = (grossIn * (10000n + BigInt(slippageBps))) / 10000n;
504
+ maxQuoteIn = new BN(cap.toString());
505
+ }
506
+ if (storedRelaunch.sourceQuoteMint.equals(NATIVE_MINT)) {
507
+ await this.wrapWsolShortfall(maxQuoteIn);
508
+ }
509
+ return this.depositViaBuyIx({
510
+ relaunch,
511
+ oldMint: storedRelaunch.oldMint,
512
+ oldTokenProgram: oldMintAccount.owner,
513
+ sourceQuoteMint: storedRelaunch.sourceQuoteMint,
514
+ sourcePool: storedRelaunch.sourcePool,
515
+ poolBaseTokenAccount: pool.poolBaseTokenAccount,
516
+ poolQuoteTokenAccount: pool.poolQuoteTokenAccount,
517
+ coinCreator: pool.coinCreator,
518
+ protocolFeeRecipient,
519
+ buybackFeeRecipient,
520
+ baseOut,
521
+ maxQuoteIn,
522
+ }).rpc();
523
+ }
524
+ // Sells the whole old-token vault as the admin (the provider wallet),
525
+ // dispatching on the relaunch's stored source venue and deriving the
526
+ // venue's account set from the stored relaunch and its pool. When
527
+ // minQuoteOut is not given, it is computed live from the pool reserves:
528
+ // the constant-product output of the sell minus slippageBps. Pump's swap
529
+ // fees are not modeled, so slippageBps must also cover them; Raydium's
530
+ // flat 25 bps fee is exact, so slippageBps only covers price movement.
531
+ async executeSell({ relaunch, minQuoteOut, slippageBps = 100, }) {
532
+ const storedRelaunch = await this.fetchRelaunch(relaunch);
533
+ if (storedRelaunch === null) {
534
+ throw new Error(`relaunch ${relaunch.toBase58()} does not exist`);
535
+ }
536
+ if (storedRelaunch.sourceVenue.raydiumAmmV4 !== undefined) {
537
+ const pool = await fetchRaydiumPool(this.provider.connection, storedRelaunch.sourcePool);
538
+ if (minQuoteOut === undefined) {
539
+ const tokenIsCoin = pool.coinMint.equals(storedRelaunch.oldMint);
540
+ const [baseIn, tokenReserve, quoteReserve] = await Promise.all([
541
+ storedRelaunch.oldTokenVault,
542
+ tokenIsCoin ? pool.coinVault : pool.pcVault,
543
+ tokenIsCoin ? pool.pcVault : pool.coinVault,
544
+ ].map((address) => this.fetchTokenBalance(address)));
545
+ // The 25 bps fee is ceil-rounded off the input and stays in the pool.
546
+ const netIn = baseIn - (baseIn * 25n + 9999n) / 10000n;
547
+ const grossOut = (quoteReserve * netIn) / (tokenReserve + netIn);
548
+ const floor = (grossOut * (10000n - BigInt(slippageBps))) / 10000n;
549
+ minQuoteOut = new BN(floor.toString());
550
+ }
551
+ return this.executeSellRaydiumIx({
552
+ relaunch,
553
+ oldMint: storedRelaunch.oldMint,
554
+ sourceQuoteMint: storedRelaunch.sourceQuoteMint,
555
+ sourcePool: storedRelaunch.sourcePool,
556
+ ammCoinVault: pool.coinVault,
557
+ ammPcVault: pool.pcVault,
558
+ minQuoteOut,
559
+ }).rpc();
560
+ }
561
+ const oldMintAccount = await this.provider.connection.getAccountInfo(storedRelaunch.oldMint);
562
+ if (oldMintAccount === null) {
563
+ throw new Error(`old mint ${storedRelaunch.oldMint.toBase58()} does not exist`);
564
+ }
565
+ const pool = await fetchPumpPool(this.provider.connection, storedRelaunch.sourcePool);
566
+ const { protocolFeeRecipient, buybackFeeRecipient } = await getPumpFeeRecipients(this.provider.connection);
567
+ if (minQuoteOut === undefined) {
568
+ const [baseIn, baseReserve, quoteReserve] = await Promise.all([
569
+ storedRelaunch.oldTokenVault,
570
+ pool.poolBaseTokenAccount,
571
+ pool.poolQuoteTokenAccount,
572
+ ].map((address) => this.fetchTokenBalance(address)));
573
+ const grossOut = (quoteReserve * baseIn) / (baseReserve + baseIn);
574
+ const floor = (grossOut * (10000n - BigInt(slippageBps))) / 10000n;
575
+ minQuoteOut = new BN(floor.toString());
576
+ }
577
+ return this.executeSellIx({
578
+ relaunch,
579
+ oldMint: storedRelaunch.oldMint,
580
+ oldTokenProgram: oldMintAccount.owner,
581
+ sourceQuoteMint: storedRelaunch.sourceQuoteMint,
582
+ sourcePool: storedRelaunch.sourcePool,
583
+ poolBaseTokenAccount: pool.poolBaseTokenAccount,
584
+ poolQuoteTokenAccount: pool.poolQuoteTokenAccount,
585
+ coinCreator: pool.coinCreator,
586
+ protocolFeeRecipient,
587
+ buybackFeeRecipient,
588
+ minQuoteOut,
589
+ }).rpc();
590
+ }
591
+ async fetchTokenBalance(address) {
592
+ const accountInfo = await this.provider.connection.getAccountInfo(address);
593
+ if (accountInfo === null) {
594
+ throw new Error(`token account ${address.toBase58()} does not exist`);
595
+ }
596
+ return AccountLayout.decode(accountInfo.data).amount;
597
+ }
598
+ // Claims a refund for the given depositor (the provider wallet by default),
599
+ // reading the old mint and its owner program from the stored relaunch.
600
+ async claimRefund({ relaunch, depositor = this.provider.publicKey, }) {
601
+ const storedRelaunch = await this.fetchRelaunch(relaunch);
602
+ if (storedRelaunch === null) {
603
+ throw new Error(`relaunch ${relaunch.toBase58()} does not exist`);
604
+ }
605
+ const oldMintAccount = await this.provider.connection.getAccountInfo(storedRelaunch.oldMint);
606
+ if (oldMintAccount === null) {
607
+ throw new Error(`old mint ${storedRelaunch.oldMint.toBase58()} does not exist`);
608
+ }
609
+ return this.claimRefundIx({
610
+ relaunch,
611
+ oldMint: storedRelaunch.oldMint,
612
+ oldTokenProgram: oldMintAccount.owner,
613
+ depositor,
614
+ }).rpc();
615
+ }
616
+ // Claims the depositor's pro-rata share of the new token (the provider
617
+ // wallet by default), reading the new mint from the stored relaunch.
618
+ async claim({ relaunch, depositor = this.provider.publicKey, }) {
619
+ const storedRelaunch = await this.fetchRelaunch(relaunch);
620
+ if (storedRelaunch === null) {
621
+ throw new Error(`relaunch ${relaunch.toBase58()} does not exist`);
622
+ }
623
+ return this.claimIx({
624
+ relaunch,
625
+ newMint: storedRelaunch.newMint,
626
+ depositor,
627
+ }).rpc();
628
+ }
629
+ // Builds the create-mint-to-self pre-instructions: a `createAccountWithSeed`
630
+ // + `initializeMint2` pair with the payer as mint authority, so
631
+ // `initialize_relaunch` can take the authority from a mint the payer
632
+ // provably controls.
633
+ async createNewMintIxs({ payer = this.provider.publicKey, seed = Keypair.generate().publicKey.toBase58().slice(0, 32), } = {}) {
634
+ const newMint = await PublicKey.createWithSeed(payer, seed, TOKEN_PROGRAM_ID);
635
+ const lamports = await this.provider.connection.getMinimumBalanceForRentExemption(MINT_SIZE);
636
+ const instructions = [
637
+ SystemProgram.createAccountWithSeed({
638
+ fromPubkey: payer,
639
+ basePubkey: payer,
640
+ seed,
641
+ newAccountPubkey: newMint,
642
+ lamports,
643
+ space: MINT_SIZE,
644
+ programId: TOKEN_PROGRAM_ID,
645
+ }),
646
+ createInitializeMint2Instruction(newMint, 6, payer, null),
647
+ ];
648
+ return { newMint, instructions };
649
+ }
650
+ // Creates the new mint and initializes the relaunch in a single
651
+ // transaction, signed entirely by the provider wallet.
652
+ async initializeRelaunch({ oldMint, sourcePool, sourceQuoteMint, tokenName, tokenSymbol, tokenUri, secondsForDeposits, gracePeriodSeconds, thresholdBps, monthlySpendingLimitAmount, monthlySpendingLimitMembers, teamAddress, admin, }) {
653
+ const { newMint, instructions } = await this.createNewMintIxs();
654
+ const oldMintAccount = await this.provider.connection.getAccountInfo(oldMint);
655
+ if (oldMintAccount === null) {
656
+ throw new Error(`old mint ${oldMint.toBase58()} does not exist`);
657
+ }
658
+ const sourcePoolAccount = await this.provider.connection.getAccountInfo(sourcePool);
659
+ if (sourcePoolAccount === null) {
660
+ throw new Error(`source pool ${sourcePool.toBase58()} does not exist`);
661
+ }
662
+ // Raydium sources need the pool's LP mint alongside for the burned-LP
663
+ // check; PumpSwap sources are validated purely from the pool account.
664
+ const sourcePoolLpMint = sourcePoolAccount.owner.equals(RAYDIUM_AMM_PROGRAM_ID)
665
+ ? parseRaydiumPool(sourcePoolAccount.data).lpMint
666
+ : null;
667
+ const txSignature = await this.initializeRelaunchIx({
668
+ newMint,
669
+ oldMint,
670
+ oldTokenProgram: oldMintAccount.owner,
671
+ sourcePool,
672
+ sourcePoolLpMint,
673
+ sourceQuoteMint,
674
+ tokenName,
675
+ tokenSymbol,
676
+ tokenUri,
677
+ secondsForDeposits,
678
+ gracePeriodSeconds,
679
+ thresholdBps,
680
+ monthlySpendingLimitAmount,
681
+ monthlySpendingLimitMembers,
682
+ teamAddress,
683
+ admin,
684
+ })
685
+ .preInstructions(instructions)
686
+ .rpc();
687
+ return {
688
+ newMint,
689
+ relaunch: this.getRelaunchAddress({ newMint }),
690
+ txSignature,
691
+ };
692
+ }
693
+ async fetchRelaunch(relaunch) {
694
+ return this.relaunchProgram.account.relaunch.fetchNullable(relaunch);
695
+ }
696
+ async deserializeRelaunch(accountInfo) {
697
+ return this.relaunchProgram.coder.accounts.decode("relaunch", accountInfo.data);
698
+ }
699
+ async fetchDepositRecord(depositRecord) {
700
+ return this.relaunchProgram.account.depositRecord.fetchNullable(depositRecord);
701
+ }
702
+ async deserializeDepositRecord(accountInfo) {
703
+ return this.relaunchProgram.coder.accounts.decode("depositRecord", accountInfo.data);
704
+ }
705
+ async getRelaunch({ newMint, }) {
706
+ const relaunch = this.getRelaunchAddress({ newMint });
707
+ return this.fetchRelaunch(relaunch);
708
+ }
709
+ async getDepositRecord({ relaunch, depositor, }) {
710
+ const depositRecord = this.getDepositRecordAddress({
711
+ relaunch,
712
+ depositor,
713
+ });
714
+ return this.fetchDepositRecord(depositRecord);
715
+ }
716
+ getRelaunchAddress({ newMint }) {
717
+ return getRelaunchAddr({ programId: this.programId, newMint })[0];
718
+ }
719
+ getRelaunchSignerAddress({ relaunch, }) {
720
+ return getRelaunchSignerAddr({ programId: this.programId, relaunch })[0];
721
+ }
722
+ getDepositRecordAddress({ relaunch, depositor, }) {
723
+ return getDepositRecordAddr({
724
+ programId: this.programId,
725
+ relaunch,
726
+ depositor,
727
+ })[0];
728
+ }
729
+ getEventAuthorityAddress() {
730
+ return getEventAuthorityAddr(this.programId)[0];
731
+ }
732
+ }
733
+ //# sourceMappingURL=RelaunchClient.js.map