From f811632c3c2e9002ac7c593c9b14bf2285234acf Mon Sep 17 00:00:00 2001 From: smypmsa Date: Tue, 13 May 2025 07:38:46 +0000 Subject: [PATCH 1/5] feat: manual buy and sell with creator fee --- learning-examples/manual_buy.py | 33 ++++++++++++--- learning-examples/manual_sell.py | 72 ++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 15 deletions(-) diff --git a/learning-examples/manual_buy.py b/learning-examples/manual_buy.py index a4850b8..793e68c 100644 --- a/learning-examples/manual_buy.py +++ b/learning-examples/manual_buy.py @@ -8,7 +8,7 @@ import struct import base58 import spl.token.instructions as spl_token import websockets -from construct import Flag, Int64ul, Struct +from construct import Bytes, Flag, Int64ul, Struct from solana.rpc.async_api import AsyncClient from solana.rpc.commitment import Confirmed from solana.rpc.types import TxOpts @@ -36,7 +36,6 @@ SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss62 SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string( "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" ) -SYSTEM_RENT = Pubkey.from_string("SysvarRent111111111111111111111111111111111") SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") LAMPORTS_PER_SOL = 1_000_000_000 @@ -53,11 +52,20 @@ class BondingCurveState: "real_sol_reserves" / Int64ul, "token_total_supply" / Int64ul, "complete" / Flag, + "creator" / Bytes(32), # Added new creator field - 32 bytes for Pubkey ) def __init__(self, data: bytes) -> None: + """Parse bonding curve data.""" + if data[:8] != EXPECTED_DISCRIMINATOR: + raise ValueError("Invalid curve state discriminator") + parsed = self._STRUCT.parse(data[8:]) self.__dict__.update(parsed) + + # Convert raw bytes to Pubkey for creator field + if hasattr(self, 'creator') and isinstance(self.creator, bytes): + self.creator = Pubkey.from_bytes(self.creator) async def get_pump_curve_state( @@ -83,10 +91,22 @@ def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: ) +def _find_creator_vault(creator: Pubkey) -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [ + b"creator-vault", + bytes(creator) + ], + PUMP_PROGRAM, + ) + return derived_address + + async def buy_token( mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curve: Pubkey, + creator_vault: Pubkey, amount: float, slippage: float = 0.25, max_retries=5, @@ -188,7 +208,7 @@ async def buy_token( AccountMeta( pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False ), - AccountMeta(pubkey=SYSTEM_RENT, is_signer=False, is_writable=False), + AccountMeta(pubkey=creator_vault, is_signer=False, is_writable=True), AccountMeta( pubkey=PUMP_EVENT_AUTHORITY, is_signer=False, is_writable=False ), @@ -254,8 +274,8 @@ def decode_create_instruction(ix_data, ix_def, accounts): offset += 4 value = ix_data[offset : offset + length].decode("utf-8") offset += length - elif arg["type"] == "publicKey": - value = base64.b64encode(ix_data[offset : offset + 32]).decode("utf-8") + elif arg["type"] == "pubkey": + value = base58.b58encode(ix_data[offset : offset + 32]).decode("utf-8") offset += 32 else: raise ValueError(f"Unsupported type: {arg['type']}") @@ -362,6 +382,7 @@ async def main(): mint = Pubkey.from_string(token_data["mint"]) bonding_curve = Pubkey.from_string(token_data["bondingCurve"]) associated_bonding_curve = Pubkey.from_string(token_data["associatedBondingCurve"]) + creator_vault = _find_creator_vault(Pubkey.from_string(token_data["creator"])) # Fetch the token price async with AsyncClient(RPC_ENDPOINT) as client: @@ -377,7 +398,7 @@ async def main(): print( f"Buying {amount:.6f} SOL worth of the new token with {slippage * 100:.1f}% slippage tolerance..." ) - await buy_token(mint, bonding_curve, associated_bonding_curve, amount, slippage) + await buy_token(mint, bonding_curve, associated_bonding_curve, creator_vault, amount, slippage) if __name__ == "__main__": diff --git a/learning-examples/manual_sell.py b/learning-examples/manual_sell.py index c943801..6922b01 100644 --- a/learning-examples/manual_sell.py +++ b/learning-examples/manual_sell.py @@ -3,7 +3,7 @@ import os import struct import base58 -from construct import Flag, Int64ul, Struct +from construct import Bytes, Flag, Int64ul, Struct from solana.rpc.async_api import AsyncClient from solana.rpc.commitment import Confirmed from solana.rpc.types import TxOpts @@ -49,11 +49,61 @@ class BondingCurveState: "real_sol_reserves" / Int64ul, "token_total_supply" / Int64ul, "complete" / Flag, + "creator" / Bytes(32), # Added new creator field - 32 bytes for Pubkey ) def __init__(self, data: bytes) -> None: + """Parse bonding curve data.""" + if data[:8] != EXPECTED_DISCRIMINATOR: + raise ValueError("Invalid curve state discriminator") + parsed = self._STRUCT.parse(data[8:]) self.__dict__.update(parsed) + + # Convert raw bytes to Pubkey for creator field + if hasattr(self, 'creator') and isinstance(self.creator, bytes): + self.creator = Pubkey.from_bytes(self.creator) + + +async def get_pump_curve_state( + conn: AsyncClient, curve_address: Pubkey +) -> BondingCurveState: + response = await conn.get_account_info(curve_address, encoding="base64") + if not response.value or not response.value.data: + raise ValueError("Invalid curve state: No data") + + data = response.value.data + if data[:8] != EXPECTED_DISCRIMINATOR: + raise ValueError("Invalid curve state discriminator") + + return BondingCurveState(data) + + +def get_bonding_curve_address(mint: Pubkey) -> tuple[Pubkey, int]: + return Pubkey.find_program_address([b"bonding-curve", bytes(mint)], PUMP_PROGRAM) + + +def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [ + bytes(bonding_curve), + bytes(SYSTEM_TOKEN_PROGRAM), + bytes(mint), + ], + SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, + ) + return derived_address + + +def find_creator_vault(creator: Pubkey) -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [ + b"creator-vault", + bytes(creator) + ], + PUMP_PROGRAM, + ) + return derived_address async def get_pump_curve_state( @@ -90,6 +140,7 @@ async def sell_token( mint: Pubkey, bonding_curve: Pubkey, associated_bonding_curve: Pubkey, + creator_vault: Pubkey, slippage: float = 0.25, max_retries=5, ): @@ -148,9 +199,9 @@ async def sell_token( pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False ), AccountMeta( - pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, + pubkey=creator_vault, is_signer=False, - is_writable=False, + is_writable=True, ), AccountMeta( pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False @@ -199,17 +250,20 @@ async def sell_token( async def main(): # Replace these with the actual values for the token you want to sell - mint = Pubkey.from_string("7aXYndxpkHRU9xg1hyAE6z3X3KYPc2LR3dJMzYTSpump") - bonding_curve = Pubkey.from_string("5UYdCZigGDyAh1doCW8FdnA7VwJTJePayTLCyZkAWMxg") - associated_bonding_curve = Pubkey.from_string( - "BS2RUaPXjQpfzncizvmEfnARZG6SNp1imXnv5USA7PMA" - ) + mint = Pubkey.from_string("5GkGpfvRLusWGqxSkwk7uEs6mHzNxH8QaZD6uPvYpump") + bonding_curve, _ = get_bonding_curve_address(mint) + associated_bonding_curve = find_associated_bonding_curve(mint, bonding_curve) + + async with AsyncClient(RPC_ENDPOINT) as client: + curve_state = await get_pump_curve_state(client, bonding_curve) + + creator_vault = find_creator_vault(curve_state.creator) slippage = 0.25 # 25% slippage tolerance print(f"Bonding curve address: {bonding_curve}") print(f"Selling tokens with {slippage * 100:.1f}% slippage tolerance...") - await sell_token(mint, bonding_curve, associated_bonding_curve, slippage) + await sell_token(mint, bonding_curve, associated_bonding_curve, creator_vault, slippage) if __name__ == "__main__": From e4675c5284b3c250349692634d1e263d4d6ed0f4 Mon Sep 17 00:00:00 2001 From: smypmsa Date: Wed, 14 May 2025 07:22:35 +0000 Subject: [PATCH 2/5] feat: update rest of scripts with creator fee --- .../raw_create_tx_from_blockSubscribe.json | 406 ++++++------------ .../blockSubscribe_extract_transactions.py | 11 +- .../get_bonding_curve_status.py | 35 +- .../compute_associated_bonding_curve.py | 21 +- .../decode_from_blockSubscribe.py | 5 +- .../decode_from_getAccountInfo.py | 35 +- .../decode_from_getTransaction.py | 2 +- .../listen_programsubscribe.py | 1 + learning-examples/manual_sell.py | 1 - .../pumpswap/get_pumpswap_pools.py | 1 + .../pumpswap/manual_buy_pumpswap.py | 323 ++++++++++++++ .../pumpswap/manual_sell_pumpswap.py | 20 +- 12 files changed, 555 insertions(+), 306 deletions(-) create mode 100644 learning-examples/pumpswap/manual_buy_pumpswap.py diff --git a/learning-examples/blockSubscribe-transactions/raw_create_tx_from_blockSubscribe.json b/learning-examples/blockSubscribe-transactions/raw_create_tx_from_blockSubscribe.json index 80727b1..233abc2 100644 --- a/learning-examples/blockSubscribe-transactions/raw_create_tx_from_blockSubscribe.json +++ b/learning-examples/blockSubscribe-transactions/raw_create_tx_from_blockSubscribe.json @@ -1,6 +1,6 @@ { "transaction": [ - "AgJ5wlQKptQK5lR1rKuLex98aANgrofIxDKh4bYcGBMqRrxspLReufQPA/MY9iyOBRs9J9ZAJvcIpuUzlP8lRQYKgV5owXDiYc0bAtKwtbffRx+4AujjJwkjulgWBz/08YbOU0cmqeRb3AUlhjOE5TkvHJGGT6D7X77Jpbxh9soDgAIAChL2MdL3xlA70bgIpTlRVYJ5yU7c4gCGkVyzuRpXUJYvmr5Oab9+zoJi5E/7feiMCGSkHpgBjBAk2HT/Omepbi/vvCtXBl7x3WZUML5ga6ZZbAKVMBut74ta/EEBQVD0EnQ7v7zcho+rNAKa57FiZ8EqFb32kF/Udj9NCpqzDWcquz307oLnDRm67n4w0qFL1j/3+kqE89q4xc3YQSOLLomVa8GPpVJkRHOYFAgQ5WYKuVxAmb0NVDhLEkMsLPLU/zvJ9WOIIMD9HQRJCq0s/CX00AY27y+Miq5i8Ng5rck8jK0R5qT8KUSk+oJRvvgVQm4b+yjGtmRmd2B8atn1ZqZGAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFW4PaTZlrPRNsVaL8XW6pRicuX9dL/O2VdK7b9bRiwBsXBzmONJWfSZGiwXrlR0aKNzG4SNIK1xnUUl3DmK/I6hl5p7g9UgMq89mNX5NwvGNWNRcHqdIn7NyPZeTxypgtwZbHj0XxFOJ1Sf2sEw81YuGxzGqD9tUm20bwD+ClGBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKmMlyWPTiSJ8bs9ECkUjg2DC1oTmdr/EIQEjnvY2+n4WQan1RcZLFxRIYzJTD1K8X9Y2u4Im6H9ROPb2YoAAAAArPE26wH8HE6IPSPItYRKtZo39mrdV8XprDtT4FnTXGTKXRcVnMiLdm8v4gNxeTkclw+K5btmaK2siraOY9HdQAYIAAUCkNADAAkCAAIMAgAAAAAJPQAAAAAACAAJA6BoBgAAAAAACg4BCwMEDA0FAAkODxARCmEYHsgoBRwHdwMAAABkb2QDAAAARE9ERwAAAGh0dHBzOi8vY2YtaXBmcy5jb20vaXBmcy9RbVkxbTJvd2hqQlp6eTFNdGhZbVdpbjNYd0dpbUdLV0M1WndOWFBDYmJhQjFrDwYABgABCQ4ACgwMBwEDBAYACQ4QEQoYZgY9EgHa6+op661VhCIAAMCDOEIAAAAAAA==", + "AYqt4iSMLUKIiCn+roUn59P7c0BroL8VCv5X+0515tjH9d5OCez5MIQdftuWcDwEymBNZMWiqy51LDU76WpahAMBAAoTcFi8Lm3PgW7X4XZm7q8+G4Jf4IaSLzptQdkNfSFrd2kW7510gs4rvSfIQ6BD7SbUQJaGC396SVFLuDF387+XgV1pfIfVUZFTFHxe1tb+5NIGBoToM9rJFbh9akpqDSa8XwszrDDJuVPznYGftPUIqlfBd55SSEGIUQWL6ZogaoujB/IYgIAD1tGqXa268xcm661aIHELJcoHVKW8TJPIRaOJkcMpfWvPuYxyTGf253mdFudiJ5NiHvVmi0CTGjvCsU4N5V6fuoY5br/VSM/4ySAR6se3W6qbLZxqhvWhcUHXqo+wYNgpG0xNR12v92LJa9wNrOs2wBLq0S7TqUhBYfprphss/CT2/qy59h94SA3zpgqeFhalg6tebfEaQX2yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVuD2k2Zaz0TbFWi/F1uqUYnLl/XS/ztlXSu2/W0YsAMGRm/lIRcy/+ytunLDm+e8jOW7xfcSayxDmzpAAAAABt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKkG3fbh7nWP3hhCXbzkbM3athr8TYO5DSf+vfko2KGL/Ai2qPUzOEL8dkpbTkkIfplULeP8horDFNap3dmbsYVGOoZeae4PVIDKvPZjV+TcLxjVjUXB6nSJ+zcj2Xk8cqZE+dQlVubfmlZx8g4Yr7VZS6dIqY5CCXXxzYTAYMWwb4yXJY9OJInxuz0QKRSODYMLWhOZ2v8QhASOe9jb6fhZrPE26wH8HE6IPSPItYRKtZo39mrdV8XprDtT4FnTXGT58ypqlPJGff+K9zL4EgTVmKxLEmG7xOccSwzdPrM4LwULAAUCiIoBAAsACQM83OEAAAAAAAkCAAYMAgAAAAAbtwAAAAAAEQYABQAQCQwBAA4PCQwKAgABBQMNDwcQCAQSXgAA4DIpAAAAAAAAAAAAAAAAAMGhI2gAAAAADAkBCgALAQAIDAAABgAEAAIBAQ0ADgEAAxgAZgY9EgHa6+quClnNAQIAAGjN5REAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], "meta": { @@ -8,187 +8,85 @@ "status": { "Ok": null }, - "fee": 115000, + "fee": 1500000, "preBalances": [ - 1299959000, + 2005440908, 0, - 20558783, + 4696550893895, + 13463471281, + 377180764, 0, - 0, - 0, - 0, - 297249326667618, - 1, + 57019670, + 4441807831495, + 2039280, 1, 1141440, - 323296939, - 2500000, - 1141440, + 1, 934087680, + 1141440, + 1141440, + 290898417, + 1461600, 731913600, - 1009200, - 0 + 137104014 ], "postBalances": [ - 162455200, - 1461600, - 24558783, - 1101231920, + 1686928628, + 0, + 4696553593895, + 13760771281, + 377329414, 2039280, - 15616720, + 69019670, + 4441810655845, 2039280, - 297249337667618, - 1, 1, 1141440, - 323296939, - 2500000, - 1141440, + 1, 934087680, + 1141440, + 1141440, + 290898417, + 1461600, 731913600, - 1009200, - 0 + 137104014 ], "innerInstructions": [ { "index": 3, "instructions": [ { - "programIdIndex": 9, + "programIdIndex": 12, "accounts": [ - 0, - 1 - ], - "data": "11114XtYk9gGfZoo968fyjNUYQJKf9gdmkGoaoBpzFv4vyaSMBn3VKxZdv7mZLzoyX5YNC", - "stackHeight": 2 - }, - { - "programIdIndex": 14, - "accounts": [ - 1 - ], - "data": "2zt6UCCHp66bJGRS4G7bTsjdxFh6FQ9sBEyRfGyPQKxYisAw", - "stackHeight": 2 - }, - { - "programIdIndex": 9, - "accounts": [ - 0, - 3 - ], - "data": "11112npZeiggj74jpdjnyaoXKQznZSuR59vRrSV7vxdg9g2eRJ4seDwRwaQLDUqBNSEnrB", - "stackHeight": 2 - }, - { - "programIdIndex": 15, - "accounts": [ - 0, - 4, - 3, - 1, - 9, - 14 - ], - "data": "1", - "stackHeight": 2 - }, - { - "programIdIndex": 14, - "accounts": [ - 1 + 16 ], "data": "84eT", - "stackHeight": 3 + "stackHeight": 2 }, { "programIdIndex": 9, "accounts": [ 0, - 4 + 5 ], "data": "11119os1e9qSs2u7TsThXqkBSRVFxhmYaFKFZ1waB2X7armDmvK3p5GmLdUxYdg3h7QSrL", - "stackHeight": 3 + "stackHeight": 2 }, { - "programIdIndex": 14, + "programIdIndex": 12, "accounts": [ - 4 + 5 ], "data": "P", - "stackHeight": 3 + "stackHeight": 2 }, { - "programIdIndex": 14, - "accounts": [ - 4, - 1 - ], - "data": "6R9wJMnJhbG9ZQYKJuVP2PB6vT2C61iaQx6VwGbPagDmk", - "stackHeight": 3 - }, - { - "programIdIndex": 13, + "programIdIndex": 12, "accounts": [ 5, - 1, - 11, - 0, - 11, - 9 + 16 ], - "data": "sCA7VhscVFkCjwGP776CKweeQ17GA3y6hoefZyQZ7igmWRvSXA49qEc3vT7wBUxVA13HoHqvXDFZ4UuibYrj1fSiqN7Hw2T1PxZfuF1ebruak4VwaCoseJMTaoCSRGbUZZSf", - "stackHeight": 2 - }, - { - "programIdIndex": 9, - "accounts": [ - 0, - 5 - ], - "data": "3Bxs4bhMrSrmPkVD", - "stackHeight": 3 - }, - { - "programIdIndex": 9, - "accounts": [ - 5 - ], - "data": "9krTDUMpjBo4wxLP", - "stackHeight": 3 - }, - { - "programIdIndex": 9, - "accounts": [ - 5 - ], - "data": "SYXsBkG6yKW2wWDcW8EDHR6D3P82bKxJGPpM65DD8nHqBfMP", - "stackHeight": 3 - }, - { - "programIdIndex": 14, - "accounts": [ - 1, - 4, - 11 - ], - "data": "6ApXSNCamGdm", - "stackHeight": 2 - }, - { - "programIdIndex": 14, - "accounts": [ - 1, - 11 - ], - "data": "31tb", - "stackHeight": 2 - }, - { - "programIdIndex": 10, - "accounts": [ - 17 - ], - "data": "7iazyA59wwtAStxsqtCeosLjmdhF7YPNeRh62abjnRpjo8BFJQyM79SCTR8EdrQNP4pFWzB9LRwFM7eYmn6zhAZ8UjEWrgARNaSmjuLGwaXuB6CQiRHZ2XpENVyDtB5adZ2hqDvmbGzTNsMd9CvgKxusSLTGhhW5Y7b4jvGji585fx1Bq3XKBg6Zr1mCNjcVFzQ45dRSbDad9ZQaVjDLSHMRVuwNvhvLM3RQCJLhyo4e6S156JeFmweeAp7HqHvGw3fVitNxwbrmnU8MsJ9", + "data": "6UhFp3r3cKbQWwHQYPuDqtb5CAdjXP45vy8Z8DQwgGRSQ", "stackHeight": 2 } ] @@ -196,54 +94,52 @@ { "index": 4, "instructions": [ - { - "programIdIndex": 14, - "accounts": [ - 1 - ], - "data": "84eT", - "stackHeight": 2 - }, { "programIdIndex": 9, "accounts": [ 0, - 6 + 2 ], - "data": "11119os1e9qSs2u7TsThXqkBSRVFxhmYaFKFZ1waB2X7armDmvK3p5GmLdUxYdg3h7QSrL", + "data": "3Bxs4eMeB358Cb5h", "stackHeight": 2 }, { - "programIdIndex": 14, - "accounts": [ - 6 - ], - "data": "P", - "stackHeight": 2 - }, - { - "programIdIndex": 14, - "accounts": [ - 6, - 1 - ], - "data": "6dhk2iczksvz6s1rFHcSMywmmwmB1z7XmGVUbnAVDoyj7", - "stackHeight": 2 - } - ] - }, - { - "index": 5, - "instructions": [ - { - "programIdIndex": 14, + "programIdIndex": 10, "accounts": [ + 15, + 7, + 16, + 3, + 8, + 5, + 0, + 9, + 12, 4, - 6, + 18, + 10 + ], + "data": "AJTQ2h9DXrBo1MiibpgfkQHmpSKmNnQgP", + "stackHeight": 2 + }, + { + "programIdIndex": 12, + "accounts": [ + 8, + 5, 3 ], - "data": "3LUFBVrqeiX9", - "stackHeight": 2 + "data": "3SpdELLWnXkK", + "stackHeight": 3 + }, + { + "programIdIndex": 9, + "accounts": [ + 0, + 4 + ], + "data": "3Bxs4VLT5WjmR4oh", + "stackHeight": 3 }, { "programIdIndex": 9, @@ -251,8 +147,8 @@ 0, 3 ], - "data": "3Bxs3zyALUmzCtNB", - "stackHeight": 2 + "data": "3Bxs46HNXQ8jq79R", + "stackHeight": 3 }, { "programIdIndex": 9, @@ -260,16 +156,16 @@ 0, 7 ], - "data": "3Bxs4Z7Wb6tTKEWB", - "stackHeight": 2 + "data": "3Bxs4TJNecvxKcVV", + "stackHeight": 3 }, { "programIdIndex": 10, "accounts": [ - 17 + 18 ], - "data": "2K7nL28PxCW8ejnyCeuMpbXaZKMjMgWS9mpWYzdhxxxazMRueEhr8dhzoSv8cJB4QmssgbcPVf7uHyA7xpBWFRc9qBGRzEzUtR1t2P8uGyxRepqEGGoak55qnosFUyXaDoRPdY3r2GR952e4KnxiYyGBD4epcJmcs5akyXSEueVSCuYxtXDaCp7HgAoy", - "stackHeight": 2 + "data": "2zjR1PvPvgqdhPdZLxuWCL7GiE5h1bKn4ziAcRd2FBFaC3kf2EYsG31a2aigsk9mgAfieqmo561JLMNk2uSKF65QKzv64GtLAULg9zNrDFfLe7DrBDcgQBwTwRqqLVkWBrksYNBFeyBeSPwcQe5DNEEqwfSowV6pk2t9QuN6JDSpxRegP8ekTV5zwc3ytD2hSo7wu9s4ynRb5ccHwRaoy9Lzm7M66sikRLa74mXuFzdjzGuq454PDDP8eqUSWxt1ddTchsUvqsAnv92vMN2BLu7XZ3UcTv96fz8KrV93qqHSsNwfKdCsY7QMwxbCE7R", + "stackHeight": 3 } ] } @@ -277,72 +173,15 @@ "logMessages": [ "Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", - "Program 11111111111111111111111111111111 invoke [1]", - "Program 11111111111111111111111111111111 success", "Program ComputeBudget111111111111111111111111111111 invoke [1]", "Program ComputeBudget111111111111111111111111111111 success", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [1]", - "Program log: Instruction: Create", - "Program 11111111111111111111111111111111 invoke [2]", + "Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", - "Program log: Instruction: InitializeMint2", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2780 of 238190 compute units", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program 11111111111111111111111111111111 invoke [2]", - "Program 11111111111111111111111111111111 success", - "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]", - "Program log: Create", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", - "Program log: Instruction: GetAccountDataSize", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1595 of 212794 compute units", - "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program 11111111111111111111111111111111 invoke [3]", - "Program 11111111111111111111111111111111 success", - "Program log: Initialize the associated token account", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", - "Program log: Instruction: InitializeImmutableOwner", - "Program log: Please upgrade to SPL Token 2022 for immutable owner support", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1405 of 206181 compute units", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", - "Program log: Instruction: InitializeAccount3", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4214 of 202297 compute units", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 21990 of 219769 compute units", - "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success", - "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s invoke [2]", - "Program log: IX: Create Metadata Accounts v3", - "Program 11111111111111111111111111111111 invoke [3]", - "Program 11111111111111111111111111111111 success", - "Program log: Allocate space for the account", - "Program 11111111111111111111111111111111 invoke [3]", - "Program 11111111111111111111111111111111 success", - "Program log: Assign the account to the owning program", - "Program 11111111111111111111111111111111 invoke [3]", - "Program 11111111111111111111111111111111 success", - "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s consumed 37631 of 181110 compute units", - "Program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s success", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", - "Program log: Instruction: MintTo", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4492 of 140964 compute units", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", - "Program log: Instruction: SetAuthority", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2911 of 134325 compute units", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 2003 of 127301 compute units", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", - "Program data: G3KpTd7rY3YDAAAAZG9kAwAAAERPREcAAABodHRwczovL2NmLWlwZnMuY29tL2lwZnMvUW1ZMW0yb3doakJaenkxTXRoWW1XaW4zWHdHaW1HS1dDNVp3TlhQQ2JiYUIxa75Oab9+zoJi5E/7feiMCGSkHpgBjBAk2HT/Omepbi/vO7+83IaPqzQCmuexYmfBKhW99pBf1HY/TQqasw1nKrv2MdL3xlA70bgIpTlRVYJ5yU7c4gCGkVyzuRpXUJYvmg==", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 126103 of 249550 compute units", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]", "Program log: Create", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: GetAccountDataSize", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 118084 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 93653 compute units", "Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program 11111111111111111111111111111111 invoke [2]", @@ -351,55 +190,76 @@ "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: InitializeImmutableOwner", "Program log: Please upgrade to SPL Token 2022 for immutable owner support", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1405 of 111497 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1405 of 87066 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", "Program log: Instruction: InitializeAccount3", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4188 of 107617 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4188 of 83184 compute units", "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 20301 of 123447 compute units", + "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 21837 of 100550 compute units", "Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [1]", - "Program log: Instruction: Buy", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]", - "Program log: Instruction: Transfer", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 81505 compute units", - "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", - "Program 11111111111111111111111111111111 invoke [2]", - "Program 11111111111111111111111111111111 success", + "Program b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1 invoke [1]", + "Program log: Powered by https://telegram.bloombot.app", "Program 11111111111111111111111111111111 invoke [2]", "Program 11111111111111111111111111111111 success", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [2]", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 2003 of 69417 compute units", + "Program log: Instruction: Buy", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]", + "Program log: Instruction: Transfer", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 49415 compute units", + "Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program 11111111111111111111111111111111 invoke [3]", + "Program 11111111111111111111111111111111 success", + "Program data: vdt/007mYe5E+dQlVubfmlZx8g4Yr7VZS6dIqY5CCXXxzYTAYMWwbyBwuBEAAAAAT+SenZMEAAABcFi8Lm3PgW7X4XZm7q8+G4Jf4IaSLzptQdkNfSFrd2m6oSNoAAAAALHaOjAKAAAAgP3BigudAgCxLhc0AwAAAIBlrz56ngEA16qPsGDYKRtMTUddr/diyWvcDazrNsAS6tEu06lIQWFfAAAAAAAAAJ4YKwAAAAAAkJajVbvLxqgCZ/z53+NIXoh5GMXQLRXjoGjmO6GR/6UFAAAAAAAAAKpEAgAAAAAA", + "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P invoke [3]", + "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 2006 of 33261 compute units", "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", - "Program data: vdt/007mYe6+Tmm/fs6CYuRP+33ojAhkpB6YAYwQJNh0/zpnqW4v7wCrkEEAAAAAKeutVYQiAAAB9jHS98ZQO9G4CKU5UVWCeclO3OIAhpFcs7kaV1CWL5oKQsNmAAAAAABXtD0HAAAA1yQq8l6tAwAAq5BBAAAAANeMF6bNrgIA", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 37457 of 103146 compute units", - "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success" + "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P consumed 40072 of 70491 compute units", + "Program 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P success", + "Program b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1 consumed 48895 of 78713 compute units", + "Program b1oomGGqPKGD6errbyfbVMBuzSC8WtAAYo8MwNafWW1 success" + ], + "preTokenBalances": [ + { + "accountIndex": 8, + "mint": "5eFg3giaX2c3RydHvx1S3yEKpYjBuwtLUsmbAAG9pump", + "uiTokenAmount": { + "uiAmount": 667654902.731215, + "decimals": 6, + "amount": "667654902731215", + "uiAmountString": "667654902.731215" + }, + "owner": "7Q1e26aB8kpyamjKPCt5oD73b2Hx7nAEjAMUwfHmZy7G", + "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + } ], - "preTokenBalances": [], "postTokenBalances": [ { - "accountIndex": 4, - "mint": "Dosp4t6w5kDzAgBQBMEsZSUNzdwTC5zHYiVZQqMKpump", + "accountIndex": 5, + "mint": "5eFg3giaX2c3RydHvx1S3yEKpYjBuwtLUsmbAAG9pump", "uiTokenAmount": { - "uiAmount": 962048231.511255, + "uiAmount": 5032051.139663, "decimals": 6, - "amount": "962048231511255", - "uiAmountString": "962048231.511255" + "amount": "5032051139663", + "uiAmountString": "5032051.139663" }, - "owner": "52EdRamuJQsj94DnpLBguCfXx8cGbyJiFBL5n1hmP5Ln", + "owner": "8ZZ97eWp2k8gfoK2Jk2WQcdofk9hyJpEGDPGiqFryH1S", "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { - "accountIndex": 6, - "mint": "Dosp4t6w5kDzAgBQBMEsZSUNzdwTC5zHYiVZQqMKpump", + "accountIndex": 8, + "mint": "5eFg3giaX2c3RydHvx1S3yEKpYjBuwtLUsmbAAG9pump", "uiTokenAmount": { - "uiAmount": 37951768.488745, + "uiAmount": 662622851.591552, "decimals": 6, - "amount": "37951768488745", - "uiAmountString": "37951768.488745" + "amount": "662622851591552", + "uiAmountString": "662622851.591552" }, - "owner": "Ha3MnRTxb5iGbXkjCTF2VyLPSsbCaNG4ZaJkHaoQWqJ9", + "owner": "7Q1e26aB8kpyamjKPCt5oD73b2Hx7nAEjAMUwfHmZy7G", "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" } ], @@ -408,7 +268,7 @@ "writable": [], "readonly": [] }, - "computeUnitsConsumed": 184311 + "computeUnitsConsumed": 71182 }, - "version": 0 + "version": "legacy" } \ No newline at end of file diff --git a/learning-examples/blockSubscribe_extract_transactions.py b/learning-examples/blockSubscribe_extract_transactions.py index 671fb49..e30a51f 100644 --- a/learning-examples/blockSubscribe_extract_transactions.py +++ b/learning-examples/blockSubscribe_extract_transactions.py @@ -2,14 +2,11 @@ import asyncio import hashlib import json import os -import sys import websockets +from solders.pubkey import Pubkey -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -from core.pubkeys import PumpAddresses - +PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") WSS_ENDPOINT = os.environ.get("SOLANA_NODE_WSS_ENDPOINT") @@ -30,7 +27,7 @@ async def listen_for_transactions(): "id": 1, "method": "blockSubscribe", "params": [ - {"mentionsAccountOrProgram": str(PumpAddresses.PROGRAM)}, + {"mentionsAccountOrProgram": str(PUMP_PROGRAM)}, { "commitment": "confirmed", "encoding": "base64", @@ -42,7 +39,7 @@ async def listen_for_transactions(): }, ) await websocket.send(subscription_message) - print(f"Subscribed to blocks mentioning program: {PumpAddresses.PROGRAM}") + print(f"Subscribed to blocks mentioning program: {PUMP_PROGRAM}") while True: try: diff --git a/learning-examples/bonding-curve-progress/get_bonding_curve_status.py b/learning-examples/bonding-curve-progress/get_bonding_curve_status.py index 29974f3..587c3c1 100644 --- a/learning-examples/bonding-curve-progress/get_bonding_curve_status.py +++ b/learning-examples/bonding-curve-progress/get_bonding_curve_status.py @@ -1,6 +1,9 @@ """ Module for checking the status of a token's bonding curve on the Solana network using the Pump.fun program. It allows querying the bonding curve state and completion status. + +Note: creator fee upgrade introduced updates in bonding curve structure. +https://github.com/pump-fun/pump-public-docs/blob/main/docs/PUMP_CREATOR_FEE_README.md """ import asyncio @@ -8,7 +11,7 @@ import os import struct from typing import Final -from construct import Flag, Int64ul, Struct +from construct import Bytes, Flag, Int64ul, Struct from dotenv import load_dotenv from solana.rpc.async_api import AsyncClient from solders.pubkey import Pubkey @@ -37,7 +40,7 @@ class BondingCurveState: token_total_supply: Total token supply in the curve complete: Whether the curve has completed and liquidity migrated """ - _STRUCT = Struct( + _STRUCT_1 = Struct( "virtual_token_reserves" / Int64ul, "virtual_sol_reserves" / Int64ul, "real_token_reserves" / Int64ul, @@ -46,9 +49,33 @@ class BondingCurveState: "complete" / Flag, ) + # Struct after creator fee update has been introduced + # https://github.com/pump-fun/pump-public-docs/blob/main/docs/PUMP_CREATOR_FEE_README.md + _STRUCT_2 = Struct( + "virtual_token_reserves" / Int64ul, + "virtual_sol_reserves" / Int64ul, + "real_token_reserves" / Int64ul, + "real_sol_reserves" / Int64ul, + "token_total_supply" / Int64ul, + "complete" / Flag, + "creator" / Bytes(32), # Added new creator field - 32 bytes for Pubkey + ) + def __init__(self, data: bytes) -> None: - parsed = self._STRUCT.parse(data[8:]) - self.__dict__.update(parsed) + """Parse bonding curve data.""" + if data[:8] != EXPECTED_DISCRIMINATOR: + raise ValueError("Invalid curve state discriminator") + + if len(data) < 150: + parsed = self._STRUCT_1.parse(data[8:]) + self.__dict__.update(parsed) + + else: + parsed = self._STRUCT_1.parse(data[8:]) + self.__dict__.update(parsed) + # Convert raw bytes to Pubkey for creator field + if hasattr(self, 'creator') and isinstance(self.creator, bytes): + self.creator = Pubkey.from_bytes(self.creator) def get_associated_bonding_curve_address( diff --git a/learning-examples/compute_associated_bonding_curve.py b/learning-examples/compute_associated_bonding_curve.py index 12343ec..7a2aa1b 100644 --- a/learning-examples/compute_associated_bonding_curve.py +++ b/learning-examples/compute_associated_bonding_curve.py @@ -1,12 +1,11 @@ -import os -import sys - from solders.pubkey import Pubkey -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -from core.pubkeys import PumpAddresses, SystemAddresses - +# Global constants +PUMP_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P") +SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") +SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string( + "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL" +) def get_bonding_curve_address(mint: Pubkey, program_id: Pubkey) -> tuple[Pubkey, int]: """ @@ -24,10 +23,10 @@ def find_associated_bonding_curve(mint: Pubkey, bonding_curve: Pubkey) -> Pubkey derived_address, _ = Pubkey.find_program_address( [ bytes(bonding_curve), - bytes(SystemAddresses.TOKEN_PROGRAM), + bytes(SYSTEM_TOKEN_PROGRAM), bytes(mint), ], - SystemAddresses.ASSOCIATED_TOKEN_PROGRAM, + SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, ) return derived_address @@ -39,7 +38,7 @@ def main(): mint = Pubkey.from_string(mint_address) bonding_curve_address, bump = get_bonding_curve_address( - mint, PumpAddresses.PROGRAM + mint, PUMP_PROGRAM ) # Calculate the associated bonding curve @@ -56,7 +55,7 @@ def main(): print("-" * 50) except ValueError as e: - print(f"Error: Invalid address format - {str(e)}") + print(f"Error: Invalid address format - {e!s}") if __name__ == "__main__": diff --git a/learning-examples/decode_from_blockSubscribe.py b/learning-examples/decode_from_blockSubscribe.py index 1bebc96..5b53ff6 100644 --- a/learning-examples/decode_from_blockSubscribe.py +++ b/learning-examples/decode_from_blockSubscribe.py @@ -26,7 +26,7 @@ def decode_instruction(ix_data, ix_def): if arg["type"] == "u64": value = struct.unpack_from(" None: - parsed = self._STRUCT.parse(data[8:]) - self.__dict__.update(parsed) + """Parse bonding curve data.""" + if data[:8] != EXPECTED_DISCRIMINATOR: + raise ValueError("Invalid curve state discriminator") + + if len(data) < 150: + parsed = self._STRUCT_1.parse(data[8:]) + self.__dict__.update(parsed) + + else: + parsed = self._STRUCT_1.parse(data[8:]) + self.__dict__.update(parsed) + # Convert raw bytes to Pubkey for creator field + if hasattr(self, 'creator') and isinstance(self.creator, bytes): + self.creator = Pubkey.from_bytes(self.creator) def calculate_bonding_curve_price(curve_state: BondingCurveState) -> float: @@ -41,7 +66,7 @@ def decode_bonding_curve_data(raw_data: str) -> BondingCurveState: # Load the JSON data -with open("learning-examples/raw_bondingCurve_from_getAccountInfo.json", "r") as file: +with open("learning-examples/raw_bondingCurve_from_getAccountInfo.json") as file: json_data = json.load(file) # Extract the base64 encoded data diff --git a/learning-examples/decode_from_getTransaction.py b/learning-examples/decode_from_getTransaction.py index 630d8f3..16a11a5 100644 --- a/learning-examples/decode_from_getTransaction.py +++ b/learning-examples/decode_from_getTransaction.py @@ -76,7 +76,7 @@ for ix in instructions: if "parsed" in ix: print(f"Parsed instruction: {ix['program']} - {ix['parsed']['type']}") print(f"Info: {json.dumps(ix['parsed']['info'], indent=2)}") - elif program_id == idl["metadata"]["address"]: + elif program_id == "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P": # Pump Fun Program matching_instruction = find_matching_instruction(accounts, data) if matching_instruction: decoded_data = decode_instruction_data( diff --git a/learning-examples/listen-migrations/listen_programsubscribe.py b/learning-examples/listen-migrations/listen_programsubscribe.py index 4045703..21f1e2c 100644 --- a/learning-examples/listen-migrations/listen_programsubscribe.py +++ b/learning-examples/listen-migrations/listen_programsubscribe.py @@ -69,6 +69,7 @@ def parse_market_account_data(data): ("pool_base_token_account", "pubkey"), ("pool_quote_token_account", "pubkey"), ("lp_supply", "u64"), + ("coin_creator", "pubkey") ] try: diff --git a/learning-examples/manual_sell.py b/learning-examples/manual_sell.py index 6922b01..95d99ef 100644 --- a/learning-examples/manual_sell.py +++ b/learning-examples/manual_sell.py @@ -37,7 +37,6 @@ LAMPORTS_PER_SOL = 1_000_000_000 UNIT_PRICE = 10_000_000 UNIT_BUDGET = 100_000 -# RPC endpoint RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT") diff --git a/learning-examples/pumpswap/get_pumpswap_pools.py b/learning-examples/pumpswap/get_pumpswap_pools.py index 6578367..1067429 100644 --- a/learning-examples/pumpswap/get_pumpswap_pools.py +++ b/learning-examples/pumpswap/get_pumpswap_pools.py @@ -60,6 +60,7 @@ async def get_market_data(market_address: Pubkey): ("pool_base_token_account", "pubkey"), ("pool_quote_token_account", "pubkey"), ("lp_supply", "u64"), + ("coin_creator", "pubkey"), ] for field_name, field_type in fields: diff --git a/learning-examples/pumpswap/manual_buy_pumpswap.py b/learning-examples/pumpswap/manual_buy_pumpswap.py new file mode 100644 index 0000000..ed59248 --- /dev/null +++ b/learning-examples/pumpswap/manual_buy_pumpswap.py @@ -0,0 +1,323 @@ +""" +WORK IN PROGRESS + +This module provides functionality to: +- Find market addresses by token mint. +- Fetch and parse market data from PUMP AMM pools. +- Calculate token prices in AMM pools. +- Create associated token accounts (ATAs) idempotently. +- Sell tokens on the PUMP AMM with slippage protection. +""" + +import asyncio +import os +import struct + +import base58 +from dotenv import load_dotenv +from solana.rpc.async_api import AsyncClient +from solana.rpc.commitment import Confirmed +from solana.rpc.types import MemcmpOpts, TxOpts +from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price +from solders.instruction import AccountMeta, Instruction +from solders.keypair import Keypair +from solders.message import Message +from solders.pubkey import Pubkey +from solders.transaction import VersionedTransaction +from spl.token.instructions import get_associated_token_address + +load_dotenv() + +# Configuration constants +RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT") +TOKEN_MINT = Pubkey.from_string("8XNrSusWrzYpizYXJb5yeB66AWX1cfQ86SBJFqVTpump") +PRIVATE_KEY = base58.b58decode(os.environ.get("SOLANA_PRIVATE_KEY")) +PAYER = Keypair.from_bytes(PRIVATE_KEY) +SLIPPAGE = 0.25 # Slippage tolerance (25%) - the maximum price movement you'll accept + +TOKEN_DECIMALS = 6 +BUY_DISCRIMINATOR = bytes.fromhex("66063d1201daebea28") # Program instruction identifier for the sell function + +# Solana system addresses and program IDs +SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") +PUMP_AMM_PROGRAM_ID = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA") +PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw") +PUMP_PROTOCOL_FEE_RECIPIENT = Pubkey.from_string("7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ") +PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT = Pubkey.from_string("7GFUN3bWzJMKMRZ34JLsvcqdssDbXnp589SiE33KVwcC") +SYSTEM_TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA") +SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111") +SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL") +PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR") +LAMPORTS_PER_SOL = 1_000_000_000 +COMPUTE_UNIT_PRICE = 10_000 # Price in micro-lamports per compute unit +COMPUTE_UNIT_BUDGET = 100_000 # Maximum compute units to use + + +async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey) -> Pubkey: + """Find the market address for a given token mint. + + Searches for the AMM pool that contains the specified token as its base token. + + Args: + client: Solana RPC client instance + base_mint_address: Address of the token mint you want to find the market for + amm_program_id: Address of the AMM program + + Returns: + The Pubkey of the market (AMM pool) for the token + """ + base_mint_bytes = bytes(base_mint_address) + offset = 43 # Offset where the base_mint field is stored in the account data structure + filters = [ + MemcmpOpts(offset=offset, bytes=base_mint_bytes) + ] + + response = await client.get_program_accounts( + amm_program_id, + encoding="base64", + filters=filters + ) + + market_address = [account.pubkey for account in response.value][0] + return market_address + +async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict: + """Fetch and parse market data from the blockchain. + + Retrieves and deserializes the data stored in the market account. + + Args: + client: Solana RPC client instance + market_address: Address of the market (AMM pool) to fetch data for + + Returns: + Dictionary containing the parsed market data + """ + response = await client.get_account_info(market_address, encoding="base64") + data = response.value.data + parsed_data: dict = {} + + # Start after the 8-byte discriminator + offset = 8 + # Define the structure of the market account data + fields = [ + ("pool_bump", "u8"), + ("index", "u16"), + ("creator", "pubkey"), + ("base_mint", "pubkey"), + ("quote_mint", "pubkey"), + ("lp_mint", "pubkey"), + ("pool_base_token_account", "pubkey"), + ("pool_quote_token_account", "pubkey"), + ("lp_supply", "u64"), + ("coin_creator", "pubkey") + ] + + for field_name, field_type in fields: + if field_type == "pubkey": + value = data[offset:offset + 32] + parsed_data[field_name] = base58.b58encode(value).decode("utf-8") + offset += 32 + elif field_type in {"u64", "i64"}: + value = struct.unpack(" Pubkey: + derived_address, _ = Pubkey.find_program_address( + [ + b"creator_vault", + bytes(coin_creator) + ], + PUMP_AMM_PROGRAM_ID, + ) + return derived_address + +async def calculate_token_pool_price(client: AsyncClient, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey) -> float: + """Calculate the price of tokens in the pool. + + Fetches the balance of tokens in the pool and calculates the price ratio. + + Args: + client: Solana RPC client instance + pool_base_token_account: Address of the pool's base token account (your token) + pool_quote_token_account: Address of the pool's quote token account (SOL) + + Returns: + The price of the base token in terms of the quote token (usually SOL) + """ + base_balance_resp = await client.get_token_account_balance(pool_base_token_account) + quote_balance_resp = await client.get_token_account_balance(pool_quote_token_account) + + # Extract the UI amounts (human-readable with decimals) + base_amount = float(base_balance_resp.value.ui_amount) + quote_amount = float(quote_balance_resp.value.ui_amount) + + token_price = quote_amount / base_amount + + return token_price + +def create_ata_idempotent_ix(payer_pubkey: Pubkey) -> Instruction: + """Create an instruction to create an Associated Token Account (ATA) if it doesn't exist. + + This creates an instruction that will create an Associated Token Account for SOL + if it doesn't already exist. + + Args: + payer_pubkey: The public key of the account that will pay for the creation + + Returns: + An instruction to create the ATA + """ + associated_token_address = get_associated_token_address(payer_pubkey, SOL) + + instruction_accounts = [ + AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True), + AccountMeta(pubkey=associated_token_address, is_signer=False, is_writable=True), + AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True), + AccountMeta(pubkey=SOL, is_signer=False, is_writable=False), + AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), + AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), + ] + + # The data for creating an ATA idempotently is just a single byte with value 1 + # Check the details here: + # https://github.com/solana-program/associated-token-account/blob/main/program/src/instruction.rs + data = bytes([1]) + return Instruction(SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, data, instruction_accounts) + +async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair, + base_mint: Pubkey, user_base_token_account: Pubkey, + user_quote_token_account: Pubkey, pool_base_token_account: Pubkey, + pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey, + coin_creator_vault_ata: Pubkey,slippage: float = 0.25) -> str | None: + """Sell tokens on the PUMP AMM. + + This function sells all tokens in the user's token account with the specified slippage tolerance. + + Args: + client: Solana RPC client instance + pump_fun_amm_market: Address of the AMM market + payer: Keypair of the transaction signer and token seller + base_mint: Address of the token mint being sold + user_base_token_account: Address of the user's token account for the token being sold + user_quote_token_account: Address of the user's SOL token account + pool_base_token_account: Address of the pool's token account for the token being sold + pool_quote_token_account: Address of the pool's SOL token account + slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%) + + Returns: + Transaction signature if successful, None otherwise + """ + # Calculate token price + token_price_sol = await calculate_token_pool_price(client, pool_base_token_account, pool_quote_token_account) + print(f"Price per Token: {token_price_sol:.20f} SOL") + + # Calculate minimum SOL output with slippage protection + amount = token_balance + min_sol_output = float(token_balance_decimal) * float(token_price_sol) + slippage_factor = 1 - slippage + min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL) + + print(f"Selling {token_balance_decimal} tokens") + print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL") + + # Define all accounts needed for the sell instruction + accounts = [ + AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False), + AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True), + AccountMeta(pubkey=PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=True), + AccountMeta(pubkey=base_mint, is_signer=False, is_writable=False), + AccountMeta(pubkey=SOL, is_signer=False, is_writable=False), + AccountMeta(pubkey=user_base_token_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=user_quote_token_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=pool_base_token_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=pool_quote_token_account, is_signer=False, is_writable=True), + AccountMeta(pubkey=PUMP_PROTOCOL_FEE_RECIPIENT, is_signer=False, is_writable=False), + AccountMeta(pubkey=PUMP_PROTOCOL_FEE_RECIPIENT_TOKEN_ACCOUNT, is_signer=False, is_writable=True), + AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), + AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), + AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), + AccountMeta(pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, is_signer=False, is_writable=False), + AccountMeta(pubkey=PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False), + AccountMeta(pubkey=PUMP_AMM_PROGRAM_ID, is_signer=False, is_writable=False), + AccountMeta(pubkey=coin_creator_vault_ata, is_signer=False, is_writable=True), + AccountMeta(pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False), + ] + + data = BUY_DISCRIMINATOR + struct.pack(" dict: ("pool_base_token_account", "pubkey"), ("pool_quote_token_account", "pubkey"), ("lp_supply", "u64"), + ("coin_creator", "pubkey") ] for field_name, field_type in fields: @@ -130,6 +131,16 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict: return parsed_data +def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey: + derived_address, _ = Pubkey.find_program_address( + [ + b"creator_vault", + bytes(coin_creator) + ], + PUMP_AMM_PROGRAM_ID, + ) + return derived_address + async def calculate_token_pool_price(client: AsyncClient, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey) -> float: """Calculate the price of tokens in the pool. @@ -186,7 +197,8 @@ def create_ata_idempotent_ix(payer_pubkey: Pubkey) -> Instruction: async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair, base_mint: Pubkey, user_base_token_account: Pubkey, user_quote_token_account: Pubkey, pool_base_token_account: Pubkey, - pool_quote_token_account: Pubkey, slippage: float = 0.25) -> str | None: + pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey, + coin_creator_vault_ata: Pubkey,slippage: float = 0.25) -> str | None: """Sell tokens on the PUMP AMM. This function sells all tokens in the user's token account with the specified slippage tolerance. @@ -245,6 +257,8 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer AccountMeta(pubkey=SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, is_signer=False, is_writable=False), AccountMeta(pubkey=PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False), AccountMeta(pubkey=PUMP_AMM_PROGRAM_ID, is_signer=False, is_writable=False), + AccountMeta(pubkey=coin_creator_vault_ata, is_signer=False, is_writable=True), + AccountMeta(pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False), ] data = SELL_DISCRIMINATOR + struct.pack(" Date: Wed, 14 May 2025 15:25:04 +0000 Subject: [PATCH 3/5] fix: repair pumpswap buy tx --- .../pumpswap/manual_buy_pumpswap.py | 144 +++++++++--------- .../pumpswap/manual_sell_pumpswap.py | 20 ++- 2 files changed, 90 insertions(+), 74 deletions(-) diff --git a/learning-examples/pumpswap/manual_buy_pumpswap.py b/learning-examples/pumpswap/manual_buy_pumpswap.py index ed59248..a281659 100644 --- a/learning-examples/pumpswap/manual_buy_pumpswap.py +++ b/learning-examples/pumpswap/manual_buy_pumpswap.py @@ -1,12 +1,12 @@ """ -WORK IN PROGRESS +Solana PUMP AMM Interface -This module provides functionality to: -- Find market addresses by token mint. -- Fetch and parse market data from PUMP AMM pools. -- Calculate token prices in AMM pools. -- Create associated token accounts (ATAs) idempotently. -- Sell tokens on the PUMP AMM with slippage protection. +This module provides functionality to interact with the PUMP AMM program on Solana, enabling: +- Finding market addresses by token mint +- Fetching and parsing market data from PUMP AMM pools +- Calculating token prices in AMM pools +- Creating associated token accounts (ATAs) idempotently +- Buying tokens on the PUMP AMM with slippage protection """ import asyncio @@ -24,19 +24,22 @@ from solders.keypair import Keypair from solders.message import Message from solders.pubkey import Pubkey from solders.transaction import VersionedTransaction -from spl.token.instructions import get_associated_token_address +from spl.token.instructions import ( + create_idempotent_associated_token_account, + get_associated_token_address, +) load_dotenv() # Configuration constants RPC_ENDPOINT = os.environ.get("SOLANA_NODE_RPC_ENDPOINT") -TOKEN_MINT = Pubkey.from_string("8XNrSusWrzYpizYXJb5yeB66AWX1cfQ86SBJFqVTpump") +TOKEN_MINT = Pubkey.from_string("...") PRIVATE_KEY = base58.b58decode(os.environ.get("SOLANA_PRIVATE_KEY")) PAYER = Keypair.from_bytes(PRIVATE_KEY) -SLIPPAGE = 0.25 # Slippage tolerance (25%) - the maximum price movement you'll accept +SLIPPAGE = 0.3 # Slippage tolerance (30%) - the maximum price movement you'll accept TOKEN_DECIMALS = 6 -BUY_DISCRIMINATOR = bytes.fromhex("66063d1201daebea28") # Program instruction identifier for the sell function +BUY_DISCRIMINATOR = bytes.fromhex("66063d1201daebea") # Program instruction identifier for the buy function # Solana system addresses and program IDs SOL = Pubkey.from_string("So11111111111111111111111111111111111111112") @@ -56,7 +59,8 @@ COMPUTE_UNIT_BUDGET = 100_000 # Maximum compute units to use async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address: Pubkey, amm_program_id: Pubkey) -> Pubkey: """Find the market address for a given token mint. - Searches for the AMM pool that contains the specified token as its base token. + Searches for the AMM pool that contains the specified token mint as its base token + by querying program accounts with a filter for the base_mint field. Args: client: Solana RPC client instance @@ -84,7 +88,8 @@ async def get_market_address_by_base_mint(client: AsyncClient, base_mint_address async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict: """Fetch and parse market data from the blockchain. - Retrieves and deserializes the data stored in the market account. + Retrieves and deserializes the binary data stored in the market account + into a structured dictionary containing key market information. Args: client: Solana RPC client instance @@ -134,6 +139,20 @@ async def get_market_data(client: AsyncClient, market_address: Pubkey) -> dict: return parsed_data def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey: + """Derive the Program Derived Address (PDA) for a coin creator's vault. + + Calculates the deterministic PDA that serves as the vault authority + for a specific coin creator in the PUMP AMM protocol. + + Args: + coin_creator: Pubkey of the coin creator account + + Returns: + Pubkey of the derived coin creator vault authority + + Note: + This vault is used to collect creator fees from token transactions + """ derived_address, _ = Pubkey.find_program_address( [ b"creator_vault", @@ -144,9 +163,10 @@ def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey: return derived_address async def calculate_token_pool_price(client: AsyncClient, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey) -> float: - """Calculate the price of tokens in the pool. + """Calculate the current price of tokens in an AMM pool. - Fetches the balance of tokens in the pool and calculates the price ratio. + Fetches the balance of tokens in the pool and calculates the price ratio + between the base token and quote token (typically SOL). Args: client: Solana RPC client instance @@ -154,7 +174,7 @@ async def calculate_token_pool_price(client: AsyncClient, pool_base_token_accoun pool_quote_token_account: Address of the pool's quote token account (SOL) Returns: - The price of the base token in terms of the quote token (usually SOL) + The price of the base token in terms of the quote token (SOL per token) """ base_balance_resp = await client.get_token_account_balance(pool_base_token_account) quote_balance_resp = await client.get_token_account_balance(pool_quote_token_account) @@ -167,53 +187,28 @@ async def calculate_token_pool_price(client: AsyncClient, pool_base_token_accoun return token_price -def create_ata_idempotent_ix(payer_pubkey: Pubkey) -> Instruction: - """Create an instruction to create an Associated Token Account (ATA) if it doesn't exist. - - This creates an instruction that will create an Associated Token Account for SOL - if it doesn't already exist. - - Args: - payer_pubkey: The public key of the account that will pay for the creation - - Returns: - An instruction to create the ATA - """ - associated_token_address = get_associated_token_address(payer_pubkey, SOL) - - instruction_accounts = [ - AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True), - AccountMeta(pubkey=associated_token_address, is_signer=False, is_writable=True), - AccountMeta(pubkey=payer_pubkey, is_signer=True, is_writable=True), - AccountMeta(pubkey=SOL, is_signer=False, is_writable=False), - AccountMeta(pubkey=SYSTEM_PROGRAM, is_signer=False, is_writable=False), - AccountMeta(pubkey=SYSTEM_TOKEN_PROGRAM, is_signer=False, is_writable=False), - ] - - # The data for creating an ATA idempotently is just a single byte with value 1 - # Check the details here: - # https://github.com/solana-program/associated-token-account/blob/main/program/src/instruction.rs - data = bytes([1]) - return Instruction(SYSTEM_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM, data, instruction_accounts) - -async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair, +async def buy_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer: Keypair, base_mint: Pubkey, user_base_token_account: Pubkey, user_quote_token_account: Pubkey, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey, - coin_creator_vault_ata: Pubkey,slippage: float = 0.25) -> str | None: - """Sell tokens on the PUMP AMM. + coin_creator_vault_ata: Pubkey, sol_amount_to_spend: int, slippage: float = 0.25) -> str | None: + """Buy tokens on the PUMP AMM with slippage protection. - This function sells all tokens in the user's token account with the specified slippage tolerance. + Executes a token purchase on the PUMP AMM protocol, calculating the expected + token amount based on the current pool price and applying slippage protection. Args: client: Solana RPC client instance pump_fun_amm_market: Address of the AMM market - payer: Keypair of the transaction signer and token seller - base_mint: Address of the token mint being sold - user_base_token_account: Address of the user's token account for the token being sold + payer: Keypair of the transaction signer and token buyer + base_mint: Address of the token mint being purchased + user_base_token_account: Address of the user's token account for receiving purchased tokens user_quote_token_account: Address of the user's SOL token account - pool_base_token_account: Address of the pool's token account for the token being sold + pool_base_token_account: Address of the pool's token account for the token being purchased pool_quote_token_account: Address of the pool's SOL token account + coin_creator_vault_authority: Address of the coin creator's vault authority + coin_creator_vault_ata: Address of the coin creator's associated token account for fees + sol_amount_to_spend: Amount of SOL to spend on the purchase (in SOL, not lamports) slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%) Returns: @@ -221,18 +216,17 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer """ # Calculate token price token_price_sol = await calculate_token_pool_price(client, pool_base_token_account, pool_quote_token_account) - print(f"Price per Token: {token_price_sol:.20f} SOL") + print(f"Token price in SOL: {token_price_sol:.10f} SOL") - # Calculate minimum SOL output with slippage protection - amount = token_balance - min_sol_output = float(token_balance_decimal) * float(token_price_sol) - slippage_factor = 1 - slippage - min_sol_output = int((min_sol_output * slippage_factor) * LAMPORTS_PER_SOL) + # Calculate maximum SOL input with slippage protection + base_amount_out = int((sol_amount_to_spend / token_price_sol) * 10**TOKEN_DECIMALS) + slippage_factor = 1 + slippage + max_sol_input = int((sol_amount_to_spend * slippage_factor) * LAMPORTS_PER_SOL) - print(f"Selling {token_balance_decimal} tokens") - print(f"Minimum SOL output: {min_sol_output / LAMPORTS_PER_SOL:.10f} SOL") + print(f"Buying {base_amount_out / (10**TOKEN_DECIMALS):.10f} tokens") + print(f"Maximum SOL input: {max_sol_input / LAMPORTS_PER_SOL:.10f} SOL") - # Define all accounts needed for the sell instruction + # Define all accounts needed for the buy instruction accounts = [ AccountMeta(pubkey=pump_fun_amm_market, is_signer=False, is_writable=False), AccountMeta(pubkey=payer.pubkey(), is_signer=True, is_writable=True), @@ -255,34 +249,37 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer AccountMeta(pubkey=coin_creator_vault_authority, is_signer=False, is_writable=False), ] - data = BUY_DISCRIMINATOR + struct.pack(" dict: return parsed_data def find_coin_creator_vault(coin_creator: Pubkey) -> Pubkey: + """Derive the Program Derived Address (PDA) for a coin creator's vault. + + Calculates the deterministic PDA that serves as the vault authority + for a specific coin creator in the PUMP AMM protocol. + + Args: + coin_creator: Pubkey of the coin creator account + + Returns: + Pubkey of the derived coin creator vault authority + + Note: + This vault is used to collect creator fees from token transactions + """ derived_address, _ = Pubkey.find_program_address( [ b"creator_vault", @@ -198,7 +212,7 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer base_mint: Pubkey, user_base_token_account: Pubkey, user_quote_token_account: Pubkey, pool_base_token_account: Pubkey, pool_quote_token_account: Pubkey, coin_creator_vault_authority: Pubkey, - coin_creator_vault_ata: Pubkey,slippage: float = 0.25) -> str | None: + coin_creator_vault_ata: Pubkey, slippage: float = 0.25) -> str | None: """Sell tokens on the PUMP AMM. This function sells all tokens in the user's token account with the specified slippage tolerance. @@ -212,6 +226,8 @@ async def sell_pump_swap(client: AsyncClient, pump_fun_amm_market: Pubkey, payer user_quote_token_account: Address of the user's SOL token account pool_base_token_account: Address of the pool's token account for the token being sold pool_quote_token_account: Address of the pool's SOL token account + coin_creator_vault_authority: Address of the coin creator's vault authority + coin_creator_vault_ata: Address of the coin creator's associated token account for fees slippage: Maximum acceptable price slippage, as a decimal (0.25 = 25%) Returns: From 957722ba7320c3db3034874b54acc17f967d5925 Mon Sep 17 00:00:00 2001 From: Anton <43762166+smypmsa@users.noreply.github.com> Date: Wed, 14 May 2025 17:33:17 +0200 Subject: [PATCH 4/5] Update learning-examples/decode_from_getAccountInfo.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- learning-examples/decode_from_getAccountInfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learning-examples/decode_from_getAccountInfo.py b/learning-examples/decode_from_getAccountInfo.py index c3dc5d0..6eba8c0 100644 --- a/learning-examples/decode_from_getAccountInfo.py +++ b/learning-examples/decode_from_getAccountInfo.py @@ -42,7 +42,7 @@ class BondingCurveState: self.__dict__.update(parsed) else: - parsed = self._STRUCT_1.parse(data[8:]) + parsed = self._STRUCT_2.parse(data[8:]) self.__dict__.update(parsed) # Convert raw bytes to Pubkey for creator field if hasattr(self, 'creator') and isinstance(self.creator, bytes): From 44e2c831204f325e0f6ca2b8ad5070a2117fa563 Mon Sep 17 00:00:00 2001 From: Anton <43762166+smypmsa@users.noreply.github.com> Date: Wed, 14 May 2025 17:36:23 +0200 Subject: [PATCH 5/5] fix: remove duplicated function --- learning-examples/manual_sell.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/learning-examples/manual_sell.py b/learning-examples/manual_sell.py index 95d99ef..b50d074 100644 --- a/learning-examples/manual_sell.py +++ b/learning-examples/manual_sell.py @@ -105,20 +105,6 @@ def find_creator_vault(creator: Pubkey) -> Pubkey: return derived_address -async def get_pump_curve_state( - conn: AsyncClient, curve_address: Pubkey -) -> BondingCurveState: - response = await conn.get_account_info(curve_address, encoding="base64") - if not response.value or not response.value.data: - raise ValueError("Invalid curve state: No data") - - data = response.value.data - if data[:8] != EXPECTED_DISCRIMINATOR: - raise ValueError("Invalid curve state discriminator") - - return BondingCurveState(data) - - def calculate_pump_curve_price(curve_state: BondingCurveState) -> float: if curve_state.virtual_token_reserves <= 0 or curve_state.virtual_sol_reserves <= 0: raise ValueError("Invalid reserve state")