pump-trader 1.1.3 → 1.1.5
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/dist/index.d.ts +7 -1
- package/dist/index.js +134 -46
- package/docs/plans/2026-04-29-pump-fee-recipient-design.md +34 -0
- package/docs/plans/2026-04-29-pump-fee-recipient.md +143 -0
- package/index.js +173 -52
- package/index.ts +206 -47
- package/package.json +2 -1
- package/tests/instruction-accounts.test.ts +301 -0
package/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
PublicKey,
|
|
4
4
|
Transaction,
|
|
5
5
|
TransactionInstruction,
|
|
6
|
+
AccountMeta,
|
|
6
7
|
SystemProgram,
|
|
7
8
|
ComputeBudgetProgram,
|
|
8
9
|
Keypair
|
|
@@ -63,6 +64,7 @@ interface BondingCurveState {
|
|
|
63
64
|
realSolReserves: bigint;
|
|
64
65
|
tokenTotalSupply: bigint;
|
|
65
66
|
complete: boolean;
|
|
67
|
+
quoteMint?: PublicKey;
|
|
66
68
|
isMayhemMode?: boolean;
|
|
67
69
|
isCashbackCoin?: boolean;
|
|
68
70
|
}
|
|
@@ -153,6 +155,16 @@ const DISCRIMINATORS = {
|
|
|
153
155
|
|
|
154
156
|
const AMM_FEE_BPS = 100n;
|
|
155
157
|
const BPS_DENOMINATOR = 10000n;
|
|
158
|
+
const PUMP_NEW_FEE_RECIPIENTS = [
|
|
159
|
+
"5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
|
|
160
|
+
"9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
|
|
161
|
+
"GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
|
|
162
|
+
"3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
|
|
163
|
+
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
164
|
+
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
165
|
+
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
166
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
167
|
+
].map((value) => new PublicKey(value));
|
|
156
168
|
|
|
157
169
|
/* ================= 工具函数 ================= */
|
|
158
170
|
|
|
@@ -394,6 +406,102 @@ export class PumpTrader {
|
|
|
394
406
|
)[0];
|
|
395
407
|
}
|
|
396
408
|
|
|
409
|
+
private pickFeeRecipient(index = 0): PublicKey {
|
|
410
|
+
return PUMP_NEW_FEE_RECIPIENTS[index % PUMP_NEW_FEE_RECIPIENTS.length];
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
private buildBondingBuyKeys(args: {
|
|
414
|
+
global: PublicKey;
|
|
415
|
+
globalFeeRecipient: PublicKey;
|
|
416
|
+
mint: PublicKey;
|
|
417
|
+
bonding: PublicKey;
|
|
418
|
+
associatedBondingCurve: PublicKey;
|
|
419
|
+
userAta: PublicKey;
|
|
420
|
+
wallet: PublicKey;
|
|
421
|
+
creatorVault: PublicKey;
|
|
422
|
+
eventAuthority: PublicKey;
|
|
423
|
+
pumpProgram: PublicKey;
|
|
424
|
+
globalVolumeAccumulator: PublicKey;
|
|
425
|
+
userVolumeAccumulator: PublicKey;
|
|
426
|
+
feeConfig: PublicKey;
|
|
427
|
+
feeProgram: PublicKey;
|
|
428
|
+
bondingCurveV2: PublicKey;
|
|
429
|
+
feeRecipient: PublicKey;
|
|
430
|
+
tokenProgramId?: PublicKey;
|
|
431
|
+
}): AccountMeta[] {
|
|
432
|
+
const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
|
|
433
|
+
|
|
434
|
+
return [
|
|
435
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
436
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
437
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
438
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
439
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
440
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
441
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
442
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
443
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
444
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
445
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
446
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
447
|
+
{ pubkey: args.globalVolumeAccumulator, isSigner: false, isWritable: false },
|
|
448
|
+
{ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true },
|
|
449
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
450
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false },
|
|
451
|
+
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
452
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
453
|
+
];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private buildBondingSellKeys(args: {
|
|
457
|
+
global: PublicKey;
|
|
458
|
+
globalFeeRecipient: PublicKey;
|
|
459
|
+
mint: PublicKey;
|
|
460
|
+
bonding: PublicKey;
|
|
461
|
+
associatedBondingCurve: PublicKey;
|
|
462
|
+
userAta: PublicKey;
|
|
463
|
+
wallet: PublicKey;
|
|
464
|
+
creatorVault: PublicKey;
|
|
465
|
+
eventAuthority: PublicKey;
|
|
466
|
+
pumpProgram: PublicKey;
|
|
467
|
+
feeConfig: PublicKey;
|
|
468
|
+
feeProgram: PublicKey;
|
|
469
|
+
bondingCurveV2: PublicKey;
|
|
470
|
+
feeRecipient: PublicKey;
|
|
471
|
+
isCashbackCoin: boolean;
|
|
472
|
+
userVolumeAccumulator: PublicKey;
|
|
473
|
+
tokenProgramId?: PublicKey;
|
|
474
|
+
}): AccountMeta[] {
|
|
475
|
+
const tokenProgramId = args.tokenProgramId ?? TOKEN_PROGRAM_ID;
|
|
476
|
+
const keys: AccountMeta[] = [
|
|
477
|
+
{ pubkey: args.global, isSigner: false, isWritable: false },
|
|
478
|
+
{ pubkey: args.globalFeeRecipient, isSigner: false, isWritable: true },
|
|
479
|
+
{ pubkey: args.mint, isSigner: false, isWritable: false },
|
|
480
|
+
{ pubkey: args.bonding, isSigner: false, isWritable: true },
|
|
481
|
+
{ pubkey: args.associatedBondingCurve, isSigner: false, isWritable: true },
|
|
482
|
+
{ pubkey: args.userAta, isSigner: false, isWritable: true },
|
|
483
|
+
{ pubkey: args.wallet, isSigner: true, isWritable: true },
|
|
484
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
485
|
+
{ pubkey: args.creatorVault, isSigner: false, isWritable: true },
|
|
486
|
+
{ pubkey: tokenProgramId, isSigner: false, isWritable: false },
|
|
487
|
+
{ pubkey: args.eventAuthority, isSigner: false, isWritable: false },
|
|
488
|
+
{ pubkey: args.pumpProgram, isSigner: false, isWritable: false },
|
|
489
|
+
{ pubkey: args.feeConfig, isSigner: false, isWritable: false },
|
|
490
|
+
{ pubkey: args.feeProgram, isSigner: false, isWritable: false }
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
if (args.isCashbackCoin) {
|
|
494
|
+
keys.push({ pubkey: args.userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
keys.push(
|
|
498
|
+
{ pubkey: args.bondingCurveV2, isSigner: false, isWritable: false },
|
|
499
|
+
{ pubkey: args.feeRecipient, isSigner: false, isWritable: true }
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
return keys;
|
|
503
|
+
}
|
|
504
|
+
|
|
397
505
|
async loadBonding(mint: PublicKey): Promise<BondingInfo> {
|
|
398
506
|
const bonding = this.getBondingPda(mint);
|
|
399
507
|
const acc = await this.connection.getAccountInfo(bonding);
|
|
@@ -414,6 +522,11 @@ export class PumpTrader {
|
|
|
414
522
|
const creator = new PublicKey(data.slice(offset, offset + 32));
|
|
415
523
|
offset += 32;
|
|
416
524
|
|
|
525
|
+
if (offset + 32 <= data.length - 2) {
|
|
526
|
+
state.quoteMint = new PublicKey(data.slice(offset, offset + 32));
|
|
527
|
+
offset += 32;
|
|
528
|
+
}
|
|
529
|
+
|
|
417
530
|
state.isMayhemMode = offset < data.length ? data[offset] === 1 : false;
|
|
418
531
|
offset += 1;
|
|
419
532
|
state.isCashbackCoin = offset < data.length ? data[offset] === 1 : false;
|
|
@@ -454,19 +567,21 @@ export class PumpTrader {
|
|
|
454
567
|
async getPriceAndStatus(tokenAddr: string): Promise<{ price: number; completed: boolean }> {
|
|
455
568
|
const mint = new PublicKey(tokenAddr);
|
|
456
569
|
const { state } = await this.loadBonding(mint);
|
|
570
|
+
const quoteMint = this.getEffectiveQuoteMint(state.quoteMint);
|
|
457
571
|
|
|
458
572
|
if (state.complete) {
|
|
459
|
-
const price = await this.getAmmPrice(mint);
|
|
573
|
+
const price = await this.getAmmPrice(mint, quoteMint);
|
|
460
574
|
return { price, completed: true };
|
|
461
575
|
}
|
|
462
576
|
|
|
463
577
|
const oneToken = BigInt(1_000_000);
|
|
464
|
-
const
|
|
465
|
-
const
|
|
578
|
+
const quoteOut = this.calcSell(oneToken, state);
|
|
579
|
+
const quoteDecimals = await this.getMintDecimals(quoteMint);
|
|
580
|
+
const price = Number(quoteOut) / 10 ** quoteDecimals;
|
|
466
581
|
return { price, completed: false };
|
|
467
582
|
}
|
|
468
583
|
|
|
469
|
-
async getAmmPrice(mint: PublicKey): Promise<number> {
|
|
584
|
+
async getAmmPrice(mint: PublicKey, quoteMint: PublicKey = SOL_MINT): Promise<number> {
|
|
470
585
|
const [poolCreator] = PublicKey.findProgramAddressSync(
|
|
471
586
|
[Buffer.from("pool-authority"), mint.toBuffer()],
|
|
472
587
|
PROGRAM_IDS.PUMP
|
|
@@ -474,7 +589,7 @@ export class PumpTrader {
|
|
|
474
589
|
|
|
475
590
|
const indexBuffer = new BN(0).toArrayLike(Buffer, "le", 2);
|
|
476
591
|
const [pool] = PublicKey.findProgramAddressSync(
|
|
477
|
-
[Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(),
|
|
592
|
+
[Buffer.from("pool"), indexBuffer, poolCreator.toBuffer(), mint.toBuffer(), quoteMint.toBuffer()],
|
|
478
593
|
PROGRAM_IDS.PUMP_AMM
|
|
479
594
|
);
|
|
480
595
|
|
|
@@ -490,6 +605,28 @@ export class PumpTrader {
|
|
|
490
605
|
return quoteInfo.value.uiAmount! / baseInfo.value.uiAmount!;
|
|
491
606
|
}
|
|
492
607
|
|
|
608
|
+
private getEffectiveQuoteMint(quoteMint?: PublicKey): PublicKey {
|
|
609
|
+
if (!quoteMint || quoteMint.equals(PublicKey.default)) {
|
|
610
|
+
return SOL_MINT;
|
|
611
|
+
}
|
|
612
|
+
return quoteMint;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
private async getMintDecimals(mint: PublicKey): Promise<number> {
|
|
616
|
+
if (mint.equals(SOL_MINT)) {
|
|
617
|
+
return 9;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const mintInfo = await this.connection.getParsedAccountInfo(mint);
|
|
621
|
+
const parsedData = mintInfo.value?.data;
|
|
622
|
+
|
|
623
|
+
if (parsedData && "parsed" in parsedData) {
|
|
624
|
+
return parsedData.parsed.info.decimals;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
throw new Error(`Unable to determine mint decimals for ${mint.toBase58()}`);
|
|
628
|
+
}
|
|
629
|
+
|
|
493
630
|
/* ---------- 余额查询 ---------- */
|
|
494
631
|
|
|
495
632
|
/**
|
|
@@ -777,6 +914,7 @@ export class PumpTrader {
|
|
|
777
914
|
[Buffer.from("fee_config"), SEEDS.FEE_CONFIG],
|
|
778
915
|
PROGRAM_IDS.FEE
|
|
779
916
|
);
|
|
917
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
780
918
|
|
|
781
919
|
for (let i = 0; i < solChunks.length; i++) {
|
|
782
920
|
try {
|
|
@@ -800,25 +938,25 @@ export class PumpTrader {
|
|
|
800
938
|
tx.add(
|
|
801
939
|
new TransactionInstruction({
|
|
802
940
|
programId: PROGRAM_IDS.PUMP,
|
|
803
|
-
keys:
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
941
|
+
keys: this.buildBondingBuyKeys({
|
|
942
|
+
global: this.global,
|
|
943
|
+
globalFeeRecipient: this.globalState!.feeRecipient,
|
|
944
|
+
mint,
|
|
945
|
+
bonding,
|
|
946
|
+
associatedBondingCurve,
|
|
947
|
+
userAta,
|
|
948
|
+
wallet: this.wallet.publicKey,
|
|
949
|
+
creatorVault,
|
|
950
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
951
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
952
|
+
globalVolumeAccumulator,
|
|
953
|
+
userVolumeAccumulator,
|
|
954
|
+
feeConfig,
|
|
955
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
956
|
+
bondingCurveV2,
|
|
957
|
+
feeRecipient,
|
|
958
|
+
tokenProgramId: tokenProgram.programId
|
|
959
|
+
}),
|
|
822
960
|
data: Buffer.concat([DISCRIMINATORS.BUY, u64(tokenOut), u64(maxSol)])
|
|
823
961
|
})
|
|
824
962
|
);
|
|
@@ -901,12 +1039,7 @@ export class PumpTrader {
|
|
|
901
1039
|
[Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
|
|
902
1040
|
PROGRAM_IDS.PUMP
|
|
903
1041
|
);
|
|
904
|
-
|
|
905
|
-
const sellRemainingKeys = [];
|
|
906
|
-
if (state.isCashbackCoin) {
|
|
907
|
-
sellRemainingKeys.push({ pubkey: userVolumeAccumulator, isSigner: false, isWritable: true });
|
|
908
|
-
}
|
|
909
|
-
sellRemainingKeys.push({ pubkey: bondingCurveV2, isSigner: false, isWritable: false });
|
|
1042
|
+
const feeRecipient = this.pickFeeRecipient();
|
|
910
1043
|
|
|
911
1044
|
for (let i = 0; i < tokenChunks.length; i++) {
|
|
912
1045
|
try {
|
|
@@ -928,23 +1061,25 @@ export class PumpTrader {
|
|
|
928
1061
|
tx.add(
|
|
929
1062
|
new TransactionInstruction({
|
|
930
1063
|
programId: PROGRAM_IDS.PUMP,
|
|
931
|
-
keys:
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1064
|
+
keys: this.buildBondingSellKeys({
|
|
1065
|
+
global: this.global,
|
|
1066
|
+
globalFeeRecipient: this.globalState!.feeRecipient,
|
|
1067
|
+
mint,
|
|
1068
|
+
bonding,
|
|
1069
|
+
associatedBondingCurve,
|
|
1070
|
+
userAta,
|
|
1071
|
+
wallet: this.wallet.publicKey,
|
|
1072
|
+
creatorVault,
|
|
1073
|
+
eventAuthority: PROGRAM_IDS.EVENT_AUTHORITY,
|
|
1074
|
+
pumpProgram: PROGRAM_IDS.PUMP,
|
|
1075
|
+
feeConfig,
|
|
1076
|
+
feeProgram: PROGRAM_IDS.FEE,
|
|
1077
|
+
bondingCurveV2,
|
|
1078
|
+
feeRecipient,
|
|
1079
|
+
isCashbackCoin: !!state.isCashbackCoin,
|
|
1080
|
+
userVolumeAccumulator,
|
|
1081
|
+
tokenProgramId: tokenProgram.programId
|
|
1082
|
+
}),
|
|
948
1083
|
data: Buffer.concat([
|
|
949
1084
|
DISCRIMINATORS.SELL,
|
|
950
1085
|
u64(tokenIn),
|
|
@@ -1269,6 +1404,14 @@ export class PumpTrader {
|
|
|
1269
1404
|
TOKEN_PROGRAM_ID,
|
|
1270
1405
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1271
1406
|
);
|
|
1407
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
1408
|
+
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
1409
|
+
poolKeys.quoteMint,
|
|
1410
|
+
newFeeRecipient,
|
|
1411
|
+
true,
|
|
1412
|
+
TOKEN_PROGRAM_ID,
|
|
1413
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1414
|
+
);
|
|
1272
1415
|
|
|
1273
1416
|
const remainingKeys = [];
|
|
1274
1417
|
if (poolKeys.isCashbackCoin) {
|
|
@@ -1282,6 +1425,10 @@ export class PumpTrader {
|
|
|
1282
1425
|
remainingKeys.push({ pubkey: userVolumeAccumulatorWsolAta, isSigner: false, isWritable: true });
|
|
1283
1426
|
}
|
|
1284
1427
|
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1428
|
+
remainingKeys.push(
|
|
1429
|
+
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1430
|
+
{ pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
|
|
1431
|
+
);
|
|
1285
1432
|
|
|
1286
1433
|
return new TransactionInstruction({
|
|
1287
1434
|
programId: PROGRAM_IDS.PUMP_AMM,
|
|
@@ -1362,6 +1509,14 @@ export class PumpTrader {
|
|
|
1362
1509
|
TOKEN_PROGRAM_ID,
|
|
1363
1510
|
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1364
1511
|
);
|
|
1512
|
+
const newFeeRecipient = this.pickFeeRecipient();
|
|
1513
|
+
const newFeeRecipientTokenAccount = getAssociatedTokenAddressSync(
|
|
1514
|
+
poolKeys.quoteMint,
|
|
1515
|
+
newFeeRecipient,
|
|
1516
|
+
true,
|
|
1517
|
+
TOKEN_PROGRAM_ID,
|
|
1518
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
1519
|
+
);
|
|
1365
1520
|
|
|
1366
1521
|
const [userVolumeAccumulator] = PublicKey.findProgramAddressSync(
|
|
1367
1522
|
[Buffer.from("user_volume_accumulator"), this.wallet.publicKey.toBuffer()],
|
|
@@ -1384,6 +1539,10 @@ export class PumpTrader {
|
|
|
1384
1539
|
);
|
|
1385
1540
|
}
|
|
1386
1541
|
remainingKeys.push({ pubkey: poolV2, isSigner: false, isWritable: false });
|
|
1542
|
+
remainingKeys.push(
|
|
1543
|
+
{ pubkey: newFeeRecipient, isSigner: false, isWritable: false },
|
|
1544
|
+
{ pubkey: newFeeRecipientTokenAccount, isSigner: false, isWritable: true }
|
|
1545
|
+
);
|
|
1387
1546
|
|
|
1388
1547
|
return new TransactionInstruction({
|
|
1389
1548
|
programId: PROGRAM_IDS.PUMP_AMM,
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pump-trader",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
4
4
|
"description": "PumpFun 交易库 - 自动判断 Token Program 和内盘/外盘",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
|
+
"test": "node --test --import tsx tests/**/*.test.ts",
|
|
9
10
|
"format": "prettier --write ."
|
|
10
11
|
},
|
|
11
12
|
"keywords": [
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import bs58 from "bs58";
|
|
5
|
+
import { Keypair, PublicKey } from "@solana/web3.js";
|
|
6
|
+
import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
7
|
+
|
|
8
|
+
import { PumpTrader } from "../index";
|
|
9
|
+
|
|
10
|
+
const SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
|
|
11
|
+
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
12
|
+
const FEE_RECIPIENTS = [
|
|
13
|
+
"5YxQFdt3Tr9zJLvkFccqXVUwhdTWJQc1fFg2YPbxvxeD",
|
|
14
|
+
"9M4giFFMxmFGXtc3feFzRai56WbBqehoSeRE5GK7gf7",
|
|
15
|
+
"GXPFM2caqTtQYC2cJ5yJRi9VDkpsYZXzYdwYpGnLmtDL",
|
|
16
|
+
"3BpXnfJaUTiwXnJNe7Ej1rcbzqTTQUvLShZaWazebsVR",
|
|
17
|
+
"5cjcW9wExnJJiqgLjq7DEG75Pm6JBgE1hNv4B2vHXUW6",
|
|
18
|
+
"EHAAiTxcdDwQ3U4bU6YcMsQGaekdzLS3B5SmYo46kJtL",
|
|
19
|
+
"5eHhjP8JaYkz83CWwvGU2uMUXefd3AazWGx4gpcuEEYD",
|
|
20
|
+
"A7hAgCzFw14fejgCp387JUJRMNyz4j89JKnhtKU8piqW"
|
|
21
|
+
].map((value) => new PublicKey(value));
|
|
22
|
+
|
|
23
|
+
function createTrader() {
|
|
24
|
+
return new PumpTrader("http://127.0.0.1:8899", bs58.encode(Keypair.generate().secretKey));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createPoolInfo(isCashbackCoin: boolean) {
|
|
28
|
+
return {
|
|
29
|
+
pool: Keypair.generate().publicKey,
|
|
30
|
+
poolAuthority: Keypair.generate().publicKey,
|
|
31
|
+
poolKeys: {
|
|
32
|
+
baseMint: Keypair.generate().publicKey,
|
|
33
|
+
quoteMint: SOL_MINT,
|
|
34
|
+
poolBaseTokenAccount: Keypair.generate().publicKey,
|
|
35
|
+
poolQuoteTokenAccount: Keypair.generate().publicKey,
|
|
36
|
+
coinCreator: Keypair.generate().publicKey,
|
|
37
|
+
isCashbackCoin
|
|
38
|
+
},
|
|
39
|
+
globalConfig: {
|
|
40
|
+
address: Keypair.generate().publicKey,
|
|
41
|
+
protocolFeeRecipients: FEE_RECIPIENTS
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
test("bonding buy appends mutable fee recipient after bondingCurveV2", () => {
|
|
47
|
+
const trader = createTrader() as any;
|
|
48
|
+
const bondingCurveV2 = Keypair.generate().publicKey;
|
|
49
|
+
const feeRecipient = FEE_RECIPIENTS[0];
|
|
50
|
+
|
|
51
|
+
const keys = trader.buildBondingBuyKeys({
|
|
52
|
+
global: Keypair.generate().publicKey,
|
|
53
|
+
globalFeeRecipient: Keypair.generate().publicKey,
|
|
54
|
+
mint: Keypair.generate().publicKey,
|
|
55
|
+
bonding: Keypair.generate().publicKey,
|
|
56
|
+
associatedBondingCurve: Keypair.generate().publicKey,
|
|
57
|
+
userAta: Keypair.generate().publicKey,
|
|
58
|
+
wallet: Keypair.generate().publicKey,
|
|
59
|
+
creatorVault: Keypair.generate().publicKey,
|
|
60
|
+
eventAuthority: Keypair.generate().publicKey,
|
|
61
|
+
pumpProgram: Keypair.generate().publicKey,
|
|
62
|
+
globalVolumeAccumulator: Keypair.generate().publicKey,
|
|
63
|
+
userVolumeAccumulator: Keypair.generate().publicKey,
|
|
64
|
+
feeConfig: Keypair.generate().publicKey,
|
|
65
|
+
feeProgram: Keypair.generate().publicKey,
|
|
66
|
+
bondingCurveV2,
|
|
67
|
+
feeRecipient
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
assert.equal(keys.length, 18);
|
|
71
|
+
assert.equal(keys.at(-2).pubkey.toBase58(), bondingCurveV2.toBase58());
|
|
72
|
+
assert.equal(keys.at(-2).isWritable, false);
|
|
73
|
+
assert.equal(keys.at(-1).pubkey.toBase58(), feeRecipient.toBase58());
|
|
74
|
+
assert.equal(keys.at(-1).isWritable, true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("bonding sell appends mutable fee recipient after optional cashback and bondingCurveV2", () => {
|
|
78
|
+
const trader = createTrader() as any;
|
|
79
|
+
const baseArgs = {
|
|
80
|
+
global: Keypair.generate().publicKey,
|
|
81
|
+
globalFeeRecipient: Keypair.generate().publicKey,
|
|
82
|
+
mint: Keypair.generate().publicKey,
|
|
83
|
+
bonding: Keypair.generate().publicKey,
|
|
84
|
+
associatedBondingCurve: Keypair.generate().publicKey,
|
|
85
|
+
userAta: Keypair.generate().publicKey,
|
|
86
|
+
wallet: Keypair.generate().publicKey,
|
|
87
|
+
creatorVault: Keypair.generate().publicKey,
|
|
88
|
+
eventAuthority: Keypair.generate().publicKey,
|
|
89
|
+
pumpProgram: Keypair.generate().publicKey,
|
|
90
|
+
feeConfig: Keypair.generate().publicKey,
|
|
91
|
+
feeProgram: Keypair.generate().publicKey,
|
|
92
|
+
bondingCurveV2: Keypair.generate().publicKey,
|
|
93
|
+
feeRecipient: FEE_RECIPIENTS[0],
|
|
94
|
+
userVolumeAccumulator: Keypair.generate().publicKey
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const nonCashback = trader.buildBondingSellKeys({
|
|
98
|
+
...baseArgs,
|
|
99
|
+
isCashbackCoin: false
|
|
100
|
+
});
|
|
101
|
+
assert.equal(nonCashback.length, 16);
|
|
102
|
+
assert.equal(nonCashback.at(-2).pubkey.toBase58(), baseArgs.bondingCurveV2.toBase58());
|
|
103
|
+
assert.equal(nonCashback.at(-1).pubkey.toBase58(), baseArgs.feeRecipient.toBase58());
|
|
104
|
+
assert.equal(nonCashback.at(-1).isWritable, true);
|
|
105
|
+
|
|
106
|
+
const cashback = trader.buildBondingSellKeys({
|
|
107
|
+
...baseArgs,
|
|
108
|
+
isCashbackCoin: true
|
|
109
|
+
});
|
|
110
|
+
assert.equal(cashback.length, 17);
|
|
111
|
+
assert.equal(cashback.at(-3).pubkey.toBase58(), baseArgs.userVolumeAccumulator.toBase58());
|
|
112
|
+
assert.equal(cashback.at(-2).pubkey.toBase58(), baseArgs.bondingCurveV2.toBase58());
|
|
113
|
+
assert.equal(cashback.at(-1).pubkey.toBase58(), baseArgs.feeRecipient.toBase58());
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("amm buy places poolV2 before fee recipient pair for non-cashback coins", () => {
|
|
117
|
+
const trader = createTrader();
|
|
118
|
+
const poolInfo = createPoolInfo(false);
|
|
119
|
+
const userBaseAta = Keypair.generate().publicKey;
|
|
120
|
+
const userQuoteAta = Keypair.generate().publicKey;
|
|
121
|
+
|
|
122
|
+
const instruction = trader.createAmmBuyInstruction(
|
|
123
|
+
poolInfo as any,
|
|
124
|
+
userBaseAta,
|
|
125
|
+
userQuoteAta,
|
|
126
|
+
1n,
|
|
127
|
+
2n,
|
|
128
|
+
TOKEN_PROGRAM_ID
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const feeRecipient = poolInfo.globalConfig.protocolFeeRecipients[0];
|
|
132
|
+
const feeRecipientAta = getAssociatedTokenAddressSync(
|
|
133
|
+
SOL_MINT,
|
|
134
|
+
feeRecipient,
|
|
135
|
+
true,
|
|
136
|
+
TOKEN_PROGRAM_ID,
|
|
137
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
assert.equal(instruction.keys.length, 26);
|
|
141
|
+
assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
|
|
142
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), feeRecipient.toBase58());
|
|
143
|
+
assert.equal(instruction.keys.at(-2)?.isWritable, false);
|
|
144
|
+
assert.equal(instruction.keys.at(-1)?.pubkey.toBase58(), feeRecipientAta.toBase58());
|
|
145
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("amm buy places cashback account before poolV2 and fee recipient tail", () => {
|
|
149
|
+
const trader = createTrader();
|
|
150
|
+
const poolInfo = createPoolInfo(true);
|
|
151
|
+
|
|
152
|
+
const instruction = trader.createAmmBuyInstruction(
|
|
153
|
+
poolInfo as any,
|
|
154
|
+
Keypair.generate().publicKey,
|
|
155
|
+
Keypair.generate().publicKey,
|
|
156
|
+
1n,
|
|
157
|
+
2n,
|
|
158
|
+
TOKEN_PROGRAM_ID
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
assert.equal(instruction.keys.length, 27);
|
|
162
|
+
assert.equal(instruction.keys.at(-3)?.isWritable, false);
|
|
163
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
|
|
164
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("amm sell places poolV2 before fee recipient pair for non-cashback coins", () => {
|
|
168
|
+
const trader = createTrader();
|
|
169
|
+
const poolInfo = createPoolInfo(false);
|
|
170
|
+
|
|
171
|
+
const instruction = trader.createAmmSellInstruction(
|
|
172
|
+
poolInfo as any,
|
|
173
|
+
Keypair.generate().publicKey,
|
|
174
|
+
Keypair.generate().publicKey,
|
|
175
|
+
1n,
|
|
176
|
+
2n,
|
|
177
|
+
TOKEN_PROGRAM_ID
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
assert.equal(instruction.keys.length, 24);
|
|
181
|
+
assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
|
|
182
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
|
|
183
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("amm sell places cashback accounts before poolV2 and fee recipient tail", () => {
|
|
187
|
+
const trader = createTrader();
|
|
188
|
+
const poolInfo = createPoolInfo(true);
|
|
189
|
+
|
|
190
|
+
const instruction = trader.createAmmSellInstruction(
|
|
191
|
+
poolInfo as any,
|
|
192
|
+
Keypair.generate().publicKey,
|
|
193
|
+
Keypair.generate().publicKey,
|
|
194
|
+
1n,
|
|
195
|
+
2n,
|
|
196
|
+
TOKEN_PROGRAM_ID
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
assert.equal(instruction.keys.length, 26);
|
|
200
|
+
assert.equal(instruction.keys.at(-3)?.pubkey.toBase58(), trader.deriveAmmPoolV2(poolInfo.poolKeys.baseMint).toBase58());
|
|
201
|
+
assert.equal(instruction.keys.at(-2)?.pubkey.toBase58(), poolInfo.globalConfig.protocolFeeRecipients[0].toBase58());
|
|
202
|
+
assert.equal(instruction.keys.at(-1)?.pubkey.toBase58(), getAssociatedTokenAddressSync(
|
|
203
|
+
SOL_MINT,
|
|
204
|
+
poolInfo.globalConfig.protocolFeeRecipients[0],
|
|
205
|
+
true,
|
|
206
|
+
TOKEN_PROGRAM_ID,
|
|
207
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
208
|
+
).toBase58());
|
|
209
|
+
assert.equal(instruction.keys.at(-1)?.isWritable, true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("loadBonding parses quote mint from v2 bonding curve accounts", async () => {
|
|
213
|
+
const trader = createTrader() as any;
|
|
214
|
+
const mint = Keypair.generate().publicKey;
|
|
215
|
+
const creator = Keypair.generate().publicKey;
|
|
216
|
+
const data = Buffer.alloc(115);
|
|
217
|
+
|
|
218
|
+
let offset = 8;
|
|
219
|
+
data.writeBigUInt64LE(11n, offset);
|
|
220
|
+
offset += 8;
|
|
221
|
+
data.writeBigUInt64LE(22n, offset);
|
|
222
|
+
offset += 8;
|
|
223
|
+
data.writeBigUInt64LE(33n, offset);
|
|
224
|
+
offset += 8;
|
|
225
|
+
data.writeBigUInt64LE(44n, offset);
|
|
226
|
+
offset += 8;
|
|
227
|
+
data.writeBigUInt64LE(55n, offset);
|
|
228
|
+
offset += 8;
|
|
229
|
+
data[offset] = 1;
|
|
230
|
+
offset += 1;
|
|
231
|
+
creator.toBuffer().copy(data, offset);
|
|
232
|
+
offset += 32;
|
|
233
|
+
USDC_MINT.toBuffer().copy(data, offset);
|
|
234
|
+
offset += 32;
|
|
235
|
+
data[offset] = 1;
|
|
236
|
+
offset += 1;
|
|
237
|
+
data[offset] = 0;
|
|
238
|
+
|
|
239
|
+
trader.connection.getAccountInfo = async () => ({ data });
|
|
240
|
+
|
|
241
|
+
const result = await trader.loadBonding(mint);
|
|
242
|
+
|
|
243
|
+
assert.equal(result.creator.toBase58(), creator.toBase58());
|
|
244
|
+
assert.equal(result.state.quoteMint?.toBase58(), USDC_MINT.toBase58());
|
|
245
|
+
assert.equal(result.state.isMayhemMode, true);
|
|
246
|
+
assert.equal(result.state.isCashbackCoin, false);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("getPriceAndStatus uses quote mint decimals for incomplete bonding curves", async () => {
|
|
250
|
+
const trader = createTrader() as any;
|
|
251
|
+
trader.loadBonding = async () => ({
|
|
252
|
+
bonding: Keypair.generate().publicKey,
|
|
253
|
+
creator: Keypair.generate().publicKey,
|
|
254
|
+
state: {
|
|
255
|
+
complete: false,
|
|
256
|
+
quoteMint: USDC_MINT
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
trader.calcSell = () => 1_234_567n;
|
|
260
|
+
trader.connection.getParsedAccountInfo = async () => ({
|
|
261
|
+
value: {
|
|
262
|
+
data: {
|
|
263
|
+
parsed: {
|
|
264
|
+
info: {
|
|
265
|
+
decimals: 6
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const result = await trader.getPriceAndStatus(Keypair.generate().publicKey.toBase58());
|
|
273
|
+
|
|
274
|
+
assert.equal(result.completed, false);
|
|
275
|
+
assert.equal(result.price, 1.234567);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("getPriceAndStatus forwards quote mint to AMM pricing for completed curves", async () => {
|
|
279
|
+
const trader = createTrader() as any;
|
|
280
|
+
const calls: PublicKey[][] = [];
|
|
281
|
+
|
|
282
|
+
trader.loadBonding = async () => ({
|
|
283
|
+
bonding: Keypair.generate().publicKey,
|
|
284
|
+
creator: Keypair.generate().publicKey,
|
|
285
|
+
state: {
|
|
286
|
+
complete: true,
|
|
287
|
+
quoteMint: USDC_MINT
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
trader.getAmmPrice = async (...args: PublicKey[]) => {
|
|
291
|
+
calls.push(args);
|
|
292
|
+
return 0.42;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const result = await trader.getPriceAndStatus(Keypair.generate().publicKey.toBase58());
|
|
296
|
+
|
|
297
|
+
assert.equal(result.completed, true);
|
|
298
|
+
assert.equal(result.price, 0.42);
|
|
299
|
+
assert.equal(calls.length, 1);
|
|
300
|
+
assert.equal(calls[0][1]?.toBase58(), USDC_MINT.toBase58());
|
|
301
|
+
});
|