Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 133 additions & 13 deletions scripts/mainnet-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,17 @@ else:
GRADUATED_MINT=$(echo "$TRENDING" | python3 -c "
import json, sys
tokens = json.load(sys.stdin)
# Prefer graduated tokens with higher SOL reserves — pools with extreme
# base/quote ratios can trigger on-chain overflow at buy.rs:400.
best = None
for t in tokens:
if t.get('complete') and t.get('pump_swap_pool'):
print(t['mint'])
break
if best is None:
best = t
elif t.get('market_cap', 0) > best.get('market_cap', 0):
best = t
if best:
print(best['mint'])
else:
print('')
" 2>/dev/null)
Expand Down Expand Up @@ -259,22 +266,22 @@ if [[ "$SKIP_TRADING" == true ]]; then
else
echo "=== Group 5: Bonding Curve Trading ==="
if [[ -n "$ACTIVE_MINT" ]]; then
BUY_OUT=$(uv run pumpfun buy "$ACTIVE_MINT" 0.001 --confirm 2>&1)
BUY_OUT=$(uv run pumpfun buy "$ACTIVE_MINT" 0.001 --slippage 50 --confirm 2>&1)
BUY_EXIT=$?
if [[ $BUY_EXIT -eq 0 ]]; then
record "BC Trade" "buy 0.001 --confirm" "PASS"
echo " Buy OK. Waiting 5s for RPC state..."
sleep 5

SELL_OUT=$(uv run pumpfun sell "$ACTIVE_MINT" all --confirm 2>&1)
SELL_OUT=$(uv run pumpfun sell "$ACTIVE_MINT" all --slippage 50 --confirm 2>&1)
SELL_EXIT=$?
if [[ $SELL_EXIT -eq 0 ]]; then
record "BC Trade" "sell all --confirm" "PASS"
else
# Retry once after delay
echo " Sell failed, retrying in 5s..."
sleep 5
SELL_OUT=$(uv run pumpfun sell "$ACTIVE_MINT" all --confirm 2>&1)
SELL_OUT=$(uv run pumpfun sell "$ACTIVE_MINT" all --slippage 50 --confirm 2>&1)
SELL_EXIT=$?
if [[ $SELL_EXIT -eq 0 ]]; then
record "BC Trade" "sell all --confirm" "PASS" "Needed retry (RPC lag)"
Expand All @@ -297,20 +304,133 @@ else

echo "=== Group 6: PumpSwap AMM Trading ==="
if [[ -n "$GRADUATED_MINT" ]]; then
AMM_OUT=$(uv run pumpfun buy "$GRADUATED_MINT" 0.001 --force-amm --confirm 2>&1)
AMM_EXIT=$?
if [[ $AMM_EXIT -eq 0 ]]; then
record "PumpSwap" "buy --force-amm --confirm" "PASS" "BUG-1 may be fixed!"
elif echo "$AMM_OUT" | grep -q "6023"; then
record "PumpSwap" "buy --force-amm --confirm" "ISSUE" "Error 6023 — known BUG-1"
else
record "PumpSwap" "buy --force-amm --confirm" "FAIL" "$(echo "$AMM_OUT" | head -1 | cut -c1-60)"
# Build a list of graduated mints to try (some pools have on-chain overflow bugs)
ALL_GRADUATED=$(echo "$TRENDING" | python3 -c "
import json, sys
tokens = json.load(sys.stdin)
for t in tokens:
if t.get('complete') and t.get('pump_swap_pool'):
print(t['mint'])
" 2>/dev/null)

AMM_BUY_OK=false
AMM_MINT=""
AMM_OVERFLOW_ONLY=true
while IFS= read -r CANDIDATE; do
[[ -z "$CANDIDATE" ]] && continue
AMM_OUT=$(uv run pumpfun buy "$CANDIDATE" 0.001 --slippage 50 --force-amm --confirm 2>&1)
AMM_EXIT=$?
if [[ $AMM_EXIT -eq 0 ]]; then
AMM_BUY_OK=true
AMM_MINT="$CANDIDATE"
break
elif echo "$AMM_OUT" | grep -q "6023"; then
echo " Pool overflow (6023) on ${CANDIDATE:0:12}…, trying next"
continue
else
# Non-overflow failure — record and stop
AMM_OVERFLOW_ONLY=false
record "PumpSwap" "buy --force-amm --confirm" "FAIL" "$(echo "$AMM_OUT" | head -1 | cut -c1-60)"
break
fi
done <<< "$ALL_GRADUATED"

if [[ "$AMM_BUY_OK" == "true" ]]; then
record "PumpSwap" "buy --force-amm --confirm" "PASS"
echo " PumpSwap buy OK. Waiting 5s for RPC state..."
sleep 5

AMM_SELL_OUT=$(uv run pumpfun sell "$AMM_MINT" all --slippage 50 --confirm 2>&1)
AMM_SELL_EXIT=$?
if [[ $AMM_SELL_EXIT -eq 0 ]]; then
record "PumpSwap" "sell all --confirm" "PASS"
else
echo " PumpSwap sell failed, retrying in 5s..."
sleep 5
AMM_SELL_OUT=$(uv run pumpfun sell "$AMM_MINT" all --slippage 50 --confirm 2>&1)
AMM_SELL_EXIT=$?
if [[ $AMM_SELL_EXIT -eq 0 ]]; then
record "PumpSwap" "sell all --confirm" "PASS" "Needed retry (RPC lag)"
else
record "PumpSwap" "sell all --confirm" "FAIL" "Failed after retry"
fi
fi
elif [[ "$AMM_OVERFLOW_ONLY" == "true" ]]; then
record "PumpSwap" "buy --force-amm --confirm" "ISSUE" "All graduated pools hit error 6023"
fi
else
record "PumpSwap" "buy --force-amm" "ISSUE" "No graduated mint found"
fi
echo " Done."

# --- Group 6b: Token Launch ---

echo "=== Group 6b: Token Launch ==="
# Generate a minimal test image
python3 -c "from PIL import Image; Image.new('RGB',(100,100),'blue').save('/tmp/e2e_test_token.png')" 2>/dev/null
LAUNCH_IMG=""
[[ -f /tmp/e2e_test_token.png ]] && LAUNCH_IMG="--image /tmp/e2e_test_token.png"

LAUNCH_OUT=$(uv run pumpfun --json launch --name "E2E Test $(date +%s)" --ticker "E2ET" --desc "Automated e2e test token" $LAUNCH_IMG 2>&1)
LAUNCH_EXIT=$?
echo "$LAUNCH_OUT" > "$LAST_OUTPUT_FILE"
if [[ $LAUNCH_EXIT -eq 0 ]]; then
LAUNCHED_MINT=$(echo "$LAUNCH_OUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('mint',''))" 2>/dev/null)
if [[ -n "$LAUNCHED_MINT" ]]; then
record "Launch" "launch" "PASS"
echo " Launched: $LAUNCHED_MINT"
else
record "Launch" "launch" "FAIL" "Invalid --json payload (missing mint)"
LAUNCHED_MINT=""
fi
else
LAUNCH_ERR=$(echo "$LAUNCH_OUT" | grep -m1 "Error:" | cut -c1-60)
[[ -z "$LAUNCH_ERR" ]] && LAUNCH_ERR="exit=$LAUNCH_EXIT"
record "Launch" "launch" "FAIL" "$LAUNCH_ERR"
LAUNCHED_MINT=""
fi

# Launch + buy
LAUNCH_BUY_OUT=$(uv run pumpfun --json launch --name "E2E Buy Test $(date +%s)" --ticker "E2EB" --desc "Automated e2e launch+buy" $LAUNCH_IMG --buy 0.001 2>&1)
LAUNCH_BUY_EXIT=$?
echo "$LAUNCH_BUY_OUT" > "$LAST_OUTPUT_FILE"
if [[ $LAUNCH_BUY_EXIT -eq 0 ]]; then
LAUNCHED_BUY_MINT=$(echo "$LAUNCH_BUY_OUT" | python3 -c "import json,sys; print(json.load(sys.stdin).get('mint',''))" 2>/dev/null)
if [[ -n "$LAUNCHED_BUY_MINT" ]]; then
record "Launch" "launch --buy 0.001" "PASS"
echo " Launched+bought: $LAUNCHED_BUY_MINT"
else
record "Launch" "launch --buy 0.001" "FAIL" "Invalid --json payload (missing mint)"
LAUNCHED_BUY_MINT=""
fi

# Sell from the launched token to test full cycle
if [[ -n "$LAUNCHED_BUY_MINT" ]]; then
echo " Waiting 5s for RPC state..."
sleep 5
LSELL_OUT=$(uv run pumpfun sell "$LAUNCHED_BUY_MINT" all --slippage 50 --confirm 2>&1)
LSELL_EXIT=$?
if [[ $LSELL_EXIT -eq 0 ]]; then
record "Launch" "sell launched token" "PASS"
else
sleep 5
LSELL_OUT=$(uv run pumpfun sell "$LAUNCHED_BUY_MINT" all --slippage 50 --confirm 2>&1)
LSELL_EXIT=$?
if [[ $LSELL_EXIT -eq 0 ]]; then
record "Launch" "sell launched token" "PASS" "Needed retry"
else
record "Launch" "sell launched token" "FAIL" "Failed after retry"
fi
fi
fi
else
LAUNCH_BUY_ERR=$(echo "$LAUNCH_BUY_OUT" | grep -m1 "Error:" | cut -c1-60)
[[ -z "$LAUNCH_BUY_ERR" ]] && LAUNCH_BUY_ERR="exit=$LAUNCH_BUY_EXIT"
record "Launch" "launch --buy 0.001" "FAIL" "$LAUNCH_BUY_ERR"
fi
rm -f /tmp/e2e_test_token.png
echo " Done."

# --- Group 7: Extras ---

echo "=== Group 7: Extras ==="
Expand Down
25 changes: 13 additions & 12 deletions src/pumpfun_cli/protocol/instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@
)
from pumpfun_cli.protocol.idl_parser import IDLParser

# Trailing bytes appended after the two u64 args (track_volume flag).
_TRACK_VOLUME = bytes([1, 1])
# OptionBool(true) = 1 byte: the IDL defines OptionBool as struct { bool },
# so it serialises to a single 0x01 byte, NOT 2 bytes (Option<bool>).
_TRACK_VOLUME = bytes([1])


def build_buy_instructions(
Expand Down Expand Up @@ -103,10 +104,7 @@ def build_buy_instructions(

discriminators = idl.get_instruction_discriminators()
instruction_data = (
discriminators["buy"]
+ struct.pack("<Q", token_amount)
+ struct.pack("<Q", max_sol_cost)
+ _TRACK_VOLUME
discriminators["buy"] + struct.pack("<Q", token_amount) + struct.pack("<Q", max_sol_cost)
)

buy_ix = Instruction(
Expand Down Expand Up @@ -180,7 +178,6 @@ def build_buy_exact_sol_in_instructions(
BUY_EXACT_SOL_IN_DISCRIMINATOR
+ struct.pack("<Q", spendable_sol_in)
+ struct.pack("<Q", min_tokens_out)
+ _TRACK_VOLUME
)

buy_ix = Instruction(
Expand Down Expand Up @@ -254,7 +251,6 @@ def build_sell_instructions(
discriminators["sell"]
+ struct.pack("<Q", token_amount)
+ struct.pack("<Q", min_sol_output)
+ _TRACK_VOLUME
)

sell_ix = Instruction(
Expand Down Expand Up @@ -323,7 +319,7 @@ def build_create_instructions(
AccountMeta(pubkey=ASSOCIATED_TOKEN_PROGRAM, is_signer=False, is_writable=False),
# Mayhem accounts are always required by create_v2 per IDL,
# regardless of is_mayhem_mode flag value.
AccountMeta(pubkey=MAYHEM_PROGRAM_ID, is_signer=False, is_writable=False),
AccountMeta(pubkey=MAYHEM_PROGRAM_ID, is_signer=False, is_writable=True),
AccountMeta(pubkey=MAYHEM_GLOBAL_PARAMS, is_signer=False, is_writable=False),
AccountMeta(pubkey=MAYHEM_SOL_VAULT, is_signer=False, is_writable=True),
AccountMeta(pubkey=mayhem_state, is_signer=False, is_writable=True),
Expand Down Expand Up @@ -369,7 +365,7 @@ def build_extend_account_instruction(
"""
accounts = [
AccountMeta(pubkey=bonding_curve, is_signer=False, is_writable=True),
AccountMeta(pubkey=user, is_signer=True, is_writable=True),
AccountMeta(pubkey=user, is_signer=False, is_writable=False),
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_PROGRAM, is_signer=False, is_writable=False),
Expand Down Expand Up @@ -486,7 +482,10 @@ def build_pumpswap_buy_instructions(
]

instruction_data = (
PUMPSWAP_BUY_DISCRIMINATOR + struct.pack("<Q", amount_out) + struct.pack("<Q", max_sol_in)
PUMPSWAP_BUY_DISCRIMINATOR
+ struct.pack("<Q", amount_out)
+ struct.pack("<Q", max_sol_in)
+ _TRACK_VOLUME
)

buy_ix = Instruction(
Expand Down Expand Up @@ -591,6 +590,7 @@ def build_pumpswap_buy_exact_quote_in_instructions(
PUMPSWAP_BUY_EXACT_QUOTE_IN_DISCRIMINATOR
+ struct.pack("<Q", spendable_quote_in)
+ struct.pack("<Q", min_base_amount_out)
+ _TRACK_VOLUME
)

buy_ix = Instruction(
Expand Down Expand Up @@ -705,7 +705,7 @@ def build_claim_cashback_instruction(
from pumpfun_cli.protocol.address import find_user_volume_accumulator

accounts = [
AccountMeta(pubkey=user, is_signer=True, is_writable=True),
AccountMeta(pubkey=user, is_signer=False, is_writable=True),
AccountMeta(pubkey=find_user_volume_accumulator(user), is_signer=False, is_writable=True),
AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
Expand Down Expand Up @@ -880,6 +880,7 @@ def build_collect_coin_creator_fee_instruction(
AccountMeta(pubkey=creator_vault_ata, is_signer=False, is_writable=True),
AccountMeta(pubkey=creator_wsol_ata, is_signer=False, is_writable=True),
AccountMeta(pubkey=PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
AccountMeta(pubkey=PUMP_AMM_PROGRAM, is_signer=False, is_writable=False),
]

return Instruction(
Expand Down
20 changes: 17 additions & 3 deletions tests/test_protocol/test_extras_instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_claim_cashback_has_5_accounts():
assert len(ix.accounts) == 5
assert ix.program_id == PUMP_PROGRAM
assert ix.accounts[0].pubkey == _USER
assert ix.accounts[0].is_signer is True
assert ix.accounts[0].is_signer is False
assert ix.accounts[0].is_writable is True


Expand Down Expand Up @@ -74,8 +74,22 @@ def test_collect_creator_fee_has_5_accounts():
assert ix.data[:8] == COLLECT_CREATOR_FEE_DISCRIMINATOR


def test_collect_coin_creator_fee_has_7_accounts():
def test_claim_cashback_user_not_signer():
"""claim_cashback account[0] (user) must be is_signer=False per IDL."""
idl = IDLParser(str(IDL_PATH))
ix = build_claim_cashback_instruction(idl=idl, user=_USER)
assert ix.accounts[0].pubkey == _USER
assert ix.accounts[0].is_signer is False


def test_collect_coin_creator_fee_has_8_accounts():
ix = build_collect_coin_creator_fee_instruction(creator=_USER)
assert len(ix.accounts) == 7
assert len(ix.accounts) == 8
assert ix.program_id == PUMP_AMM_PROGRAM
assert ix.data[:8] == COLLECT_COIN_CREATOR_FEE_DISCRIMINATOR


def test_collect_coin_creator_fee_last_account_is_program():
"""collect_coin_creator_fee 8th account must be PUMP_AMM_PROGRAM."""
ix = build_collect_coin_creator_fee_instruction(creator=_USER)
assert ix.accounts[7].pubkey == PUMP_AMM_PROGRAM
63 changes: 63 additions & 0 deletions tests/test_protocol/test_instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
build_buy_exact_sol_in_instructions,
build_buy_instructions,
build_create_instructions,
build_extend_account_instruction,
build_sell_instructions,
)

Expand Down Expand Up @@ -165,3 +166,65 @@ def test_create_instructions_mayhem_and_cashback():
# Last byte = cashback true, second-to-last = mayhem true
assert create_ix.data[-1:] == b"\x01"
assert create_ix.data[-2:-1] == b"\x01"


def test_sell_instruction_data_no_track_volume():
"""sell instruction data should be 24 bytes: 8 disc + 8 amount + 8 min_sol. No _TRACK_VOLUME."""
idl = IDLParser(str(IDL_PATH))
ixs = build_sell_instructions(
idl=idl,
mint=_MINT,
user=_USER,
bonding_curve=_BC,
assoc_bc=_ABC,
creator=_USER,
is_mayhem=False,
token_amount=1000,
min_sol_output=0,
)
sell_ix = ixs[0]
assert len(bytes(sell_ix.data)) == 24 # 8 + 8 + 8, no trailing track_volume


def test_sell_instruction_data_no_track_volume_with_cashback():
"""sell with is_cashback=True also has 24-byte data (no _TRACK_VOLUME)."""
idl = IDLParser(str(IDL_PATH))
ixs = build_sell_instructions(
idl=idl,
mint=_MINT,
user=_USER,
bonding_curve=_BC,
assoc_bc=_ABC,
creator=_USER,
is_mayhem=False,
token_amount=1000,
min_sol_output=0,
is_cashback=True,
)
sell_ix = ixs[0]
assert len(bytes(sell_ix.data)) == 24


def test_create_v2_mayhem_program_is_writable():
"""create_v2 account[9] (MAYHEM_PROGRAM_ID) must be is_writable=True per IDL."""
idl = IDLParser(str(IDL_PATH))
ixs = build_create_instructions(
idl=idl,
mint=_MINT,
user=_USER,
name="Test",
symbol="TST",
uri="https://example.com",
)
create_ix = ixs[0]
assert create_ix.accounts[9].pubkey == MAYHEM_PROGRAM_ID
assert create_ix.accounts[9].is_writable is True


def test_extend_account_user_not_writable():
"""extend_account account[1] (user) must be is_writable=False, is_signer=False per IDL."""
idl = IDLParser(str(IDL_PATH))
ix = build_extend_account_instruction(idl=idl, bonding_curve=_BC, user=_USER)
assert ix.accounts[1].pubkey == _USER
assert ix.accounts[1].is_signer is False
assert ix.accounts[1].is_writable is False
Loading
Loading