pumpfun-python 0.1.0__py3-none-any.whl

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.
pumpfun/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ """
2
+ pumpfun-python — buy and sell on PumpFun bonding curves + PumpSwap AMM.
3
+ Directly from Python. No Jupiter needed.
4
+ """
5
+
6
+ from .bonding_curve import (
7
+ BondingCurveState,
8
+ build_buy_instruction,
9
+ build_sell_instruction,
10
+ calculate_buy_amount,
11
+ calculate_sell_amount,
12
+ fetch_bonding_curve_state,
13
+ )
14
+ from .constants import (
15
+ PUMP_AMM_PROGRAM,
16
+ PUMP_FUN_PROGRAM,
17
+ PUMP_SWAP_PROGRAM,
18
+ )
19
+ from .pda import (
20
+ get_associated_token_address,
21
+ get_bonding_curve_pda,
22
+ )
23
+ from .pumpswap import (
24
+ PoolState,
25
+ build_swap_instruction,
26
+ calculate_swap_output,
27
+ fetch_pool_state,
28
+ )
29
+
30
+ __version__ = "0.1.0"
31
+
32
+ __all__ = [
33
+ # Bonding curve (pre-graduation)
34
+ "BondingCurveState",
35
+ "build_buy_instruction",
36
+ "build_sell_instruction",
37
+ "calculate_buy_amount",
38
+ "calculate_sell_amount",
39
+ "fetch_bonding_curve_state",
40
+ # PumpSwap AMM (post-graduation)
41
+ "PoolState",
42
+ "build_swap_instruction",
43
+ "calculate_swap_output",
44
+ "fetch_pool_state",
45
+ # PDA helpers
46
+ "get_associated_token_address",
47
+ "get_bonding_curve_pda",
48
+ # Program IDs
49
+ "PUMP_FUN_PROGRAM",
50
+ "PUMP_SWAP_PROGRAM",
51
+ "PUMP_AMM_PROGRAM",
52
+ "__version__",
53
+ ]
@@ -0,0 +1,364 @@
1
+ """
2
+ PumpFun bonding curve v2 — buy/sell instructions and on-chain state reading.
3
+
4
+ Program: 6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P
5
+
6
+ BUY: 16 accounts
7
+ SELL: 14 accounts (creator_vault and token_program are SWAPPED vs buy)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import base64
13
+ import struct
14
+ from dataclasses import dataclass, field
15
+
16
+ import httpx
17
+ from solders.instruction import AccountMeta, Instruction # type: ignore[import-untyped]
18
+ from solders.pubkey import Pubkey # type: ignore[import-untyped]
19
+
20
+ from .constants import (
21
+ PUMP_BUY_DISCRIMINATOR,
22
+ PUMP_FEE_PROGRAM,
23
+ PUMP_FUN_EVENT_AUTHORITY,
24
+ PUMP_FUN_GLOBAL,
25
+ PUMP_FUN_PROGRAM,
26
+ PUMP_SELL_DISCRIMINATOR,
27
+ SYSTEM_PROGRAM,
28
+ TOKEN_PROGRAM,
29
+ )
30
+ from .pda import (
31
+ get_associated_token_address,
32
+ get_bonding_curve_pda,
33
+ get_bonding_curve_v2_pda,
34
+ get_creator_vault_pda,
35
+ get_fee_config_pda,
36
+ get_global_volume_accumulator_pda,
37
+ get_user_volume_accumulator_pda,
38
+ )
39
+
40
+
41
+ class PumpFunError(Exception):
42
+ """Raised when a PumpFun operation fails."""
43
+
44
+
45
+ @dataclass(slots=True)
46
+ class BondingCurveState:
47
+ """Parsed on-chain bonding curve account state."""
48
+
49
+ virtual_token_reserves: int
50
+ virtual_sol_reserves: int
51
+ real_token_reserves: int
52
+ real_sol_reserves: int
53
+ token_total_supply: int
54
+ complete: bool
55
+ creator: Pubkey
56
+ is_mayhem_mode: bool = False
57
+ is_cashback_coin: bool = False
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Price calculations (pure functions — no RPC needed)
62
+ # ---------------------------------------------------------------------------
63
+
64
+
65
+ def calculate_buy_amount(
66
+ sol_amount_lamports: int,
67
+ virtual_sol_reserves: int,
68
+ virtual_token_reserves: int,
69
+ ) -> int:
70
+ """
71
+ Calculate tokens received for a given SOL input.
72
+
73
+ Uses PumpFun's constant-product formula with 1% fee on SOL input.
74
+
75
+ Args:
76
+ sol_amount_lamports: SOL amount in lamports.
77
+ virtual_sol_reserves: Virtual SOL reserves from bonding curve state.
78
+ virtual_token_reserves: Virtual token reserves from bonding curve state.
79
+
80
+ Returns:
81
+ Number of tokens received.
82
+ """
83
+ fee = sol_amount_lamports // 100
84
+ sol_after_fee = sol_amount_lamports - fee
85
+ tokens_out = (virtual_token_reserves * sol_after_fee) // (virtual_sol_reserves + sol_after_fee)
86
+ return tokens_out
87
+
88
+
89
+ def calculate_sell_amount(
90
+ token_amount: int,
91
+ virtual_sol_reserves: int,
92
+ virtual_token_reserves: int,
93
+ ) -> int:
94
+ """
95
+ Calculate SOL received for selling tokens.
96
+
97
+ Uses PumpFun's constant-product formula with 1% fee on SOL output.
98
+
99
+ Args:
100
+ token_amount: Number of tokens to sell.
101
+ virtual_sol_reserves: Virtual SOL reserves from bonding curve state.
102
+ virtual_token_reserves: Virtual token reserves from bonding curve state.
103
+
104
+ Returns:
105
+ SOL output in lamports (after fee).
106
+ """
107
+ if virtual_token_reserves + token_amount == 0:
108
+ return 0
109
+ sol_out_raw = (virtual_sol_reserves * token_amount) // (virtual_token_reserves + token_amount)
110
+ fee = sol_out_raw // 100
111
+ return sol_out_raw - fee
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # On-chain state reading
116
+ # ---------------------------------------------------------------------------
117
+
118
+
119
+ async def fetch_bonding_curve_state(
120
+ rpc_url: str,
121
+ token_mint: str | Pubkey,
122
+ *,
123
+ http_client: httpx.AsyncClient | None = None,
124
+ ) -> BondingCurveState:
125
+ """
126
+ Fetch the PumpFun bonding curve state from on-chain.
127
+
128
+ Args:
129
+ rpc_url: Solana RPC endpoint URL.
130
+ token_mint: Token mint address (string or Pubkey).
131
+ http_client: Optional httpx client (creates one if not provided).
132
+
133
+ Returns:
134
+ BondingCurveState with reserves, creator, completion status.
135
+
136
+ Raises:
137
+ PumpFunError: If the account can't be read or parsed.
138
+ """
139
+ if isinstance(token_mint, str):
140
+ token_mint = Pubkey.from_string(token_mint)
141
+
142
+ bonding_curve = get_bonding_curve_pda(token_mint)
143
+
144
+ should_close = http_client is None
145
+ client = http_client or httpx.AsyncClient(timeout=httpx.Timeout(15.0))
146
+
147
+ try:
148
+ resp = await client.post(rpc_url, json={
149
+ "jsonrpc": "2.0",
150
+ "id": 1,
151
+ "method": "getAccountInfo",
152
+ "params": [str(bonding_curve), {"encoding": "base64"}],
153
+ })
154
+ resp.raise_for_status()
155
+ data = resp.json()
156
+ finally:
157
+ if should_close:
158
+ await client.aclose()
159
+
160
+ result = data.get("result", {})
161
+ value = result.get("value")
162
+ if not value:
163
+ raise PumpFunError(f"Bonding curve account {bonding_curve} not found")
164
+
165
+ raw_data = value.get("data", [])
166
+ if not isinstance(raw_data, list) or not raw_data:
167
+ raise PumpFunError("Cannot decode bonding curve account data")
168
+
169
+ account_bytes = base64.b64decode(raw_data[0])
170
+
171
+ if len(account_bytes) < 81:
172
+ raise PumpFunError(f"Bonding curve data too short: {len(account_bytes)} bytes")
173
+
174
+ off = 8 # skip Anchor discriminator
175
+ virtual_token_reserves = struct.unpack_from("<Q", account_bytes, off)[0]; off += 8
176
+ virtual_sol_reserves = struct.unpack_from("<Q", account_bytes, off)[0]; off += 8
177
+ real_token_reserves = struct.unpack_from("<Q", account_bytes, off)[0]; off += 8
178
+ real_sol_reserves = struct.unpack_from("<Q", account_bytes, off)[0]; off += 8
179
+ token_total_supply = struct.unpack_from("<Q", account_bytes, off)[0]; off += 8
180
+ complete = account_bytes[off] != 0; off += 1
181
+ creator = Pubkey.from_bytes(account_bytes[off: off + 32]); off += 32
182
+
183
+ is_mayhem_mode = len(account_bytes) >= 82 and account_bytes[81] != 0
184
+ is_cashback_coin = len(account_bytes) >= 83 and account_bytes[82] != 0
185
+
186
+ return BondingCurveState(
187
+ virtual_token_reserves=virtual_token_reserves,
188
+ virtual_sol_reserves=virtual_sol_reserves,
189
+ real_token_reserves=real_token_reserves,
190
+ real_sol_reserves=real_sol_reserves,
191
+ token_total_supply=token_total_supply,
192
+ complete=complete,
193
+ creator=creator,
194
+ is_mayhem_mode=is_mayhem_mode,
195
+ is_cashback_coin=is_cashback_coin,
196
+ )
197
+
198
+
199
+ async def fetch_fee_recipient(
200
+ rpc_url: str,
201
+ *,
202
+ http_client: httpx.AsyncClient | None = None,
203
+ ) -> Pubkey:
204
+ """
205
+ Read the fee_recipient from the PumpFun Global account.
206
+
207
+ Falls back to default on failure.
208
+ """
209
+ default = Pubkey.from_string("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV")
210
+
211
+ should_close = http_client is None
212
+ client = http_client or httpx.AsyncClient(timeout=httpx.Timeout(15.0))
213
+
214
+ try:
215
+ resp = await client.post(rpc_url, json={
216
+ "jsonrpc": "2.0",
217
+ "id": 1,
218
+ "method": "getAccountInfo",
219
+ "params": [str(PUMP_FUN_GLOBAL), {"encoding": "base64"}],
220
+ })
221
+ resp.raise_for_status()
222
+ data = resp.json()
223
+ value = data.get("result", {}).get("value")
224
+ if value:
225
+ raw = value.get("data", [])
226
+ if isinstance(raw, list) and raw:
227
+ buf = base64.b64decode(raw[0])
228
+ if len(buf) >= 73:
229
+ return Pubkey.from_bytes(buf[41:73])
230
+ except Exception:
231
+ pass
232
+ finally:
233
+ if should_close:
234
+ await client.aclose()
235
+
236
+ return default
237
+
238
+
239
+ # ---------------------------------------------------------------------------
240
+ # Instruction builders (pure functions)
241
+ # ---------------------------------------------------------------------------
242
+
243
+
244
+ def build_buy_instruction(
245
+ user: Pubkey,
246
+ token_mint: Pubkey,
247
+ bonding_curve: Pubkey,
248
+ sol_amount_lamports: int,
249
+ min_tokens_out: int,
250
+ creator: Pubkey,
251
+ fee_recipient: Pubkey,
252
+ token_program: Pubkey = TOKEN_PROGRAM,
253
+ ) -> Instruction:
254
+ """
255
+ Build the PumpFun v2 BUY instruction (16 accounts).
256
+
257
+ Args:
258
+ user: Buyer's wallet (signer).
259
+ token_mint: Token mint address.
260
+ bonding_curve: Bonding curve PDA (from get_bonding_curve_pda).
261
+ sol_amount_lamports: Max SOL to spend (lamports).
262
+ min_tokens_out: Minimum tokens expected (slippage protection).
263
+ creator: Token creator from bonding curve state.
264
+ fee_recipient: Fee recipient (from fetch_fee_recipient or default).
265
+ token_program: TOKEN_PROGRAM or TOKEN_2022_PROGRAM.
266
+
267
+ Returns:
268
+ Solders Instruction ready to add to a transaction.
269
+ """
270
+ associated_user = get_associated_token_address(user, token_mint, token_program)
271
+ associated_bonding_curve = get_associated_token_address(bonding_curve, token_mint, token_program)
272
+ creator_vault = get_creator_vault_pda(creator)
273
+ global_vol = get_global_volume_accumulator_pda()
274
+ user_vol = get_user_volume_accumulator_pda(user)
275
+ fee_config = get_fee_config_pda()
276
+
277
+ data = (
278
+ PUMP_BUY_DISCRIMINATOR
279
+ + struct.pack("<Q", min_tokens_out)
280
+ + struct.pack("<Q", sol_amount_lamports)
281
+ + b"\x01" # track_volume = Some(true)
282
+ )
283
+
284
+ accounts = [
285
+ AccountMeta(PUMP_FUN_GLOBAL, is_signer=False, is_writable=False),
286
+ AccountMeta(fee_recipient, is_signer=False, is_writable=True),
287
+ AccountMeta(token_mint, is_signer=False, is_writable=False),
288
+ AccountMeta(bonding_curve, is_signer=False, is_writable=True),
289
+ AccountMeta(associated_bonding_curve, is_signer=False, is_writable=True),
290
+ AccountMeta(associated_user, is_signer=False, is_writable=True),
291
+ AccountMeta(user, is_signer=True, is_writable=True),
292
+ AccountMeta(SYSTEM_PROGRAM, is_signer=False, is_writable=False),
293
+ AccountMeta(token_program, is_signer=False, is_writable=False),
294
+ AccountMeta(creator_vault, is_signer=False, is_writable=True),
295
+ AccountMeta(PUMP_FUN_EVENT_AUTHORITY, is_signer=False, is_writable=False),
296
+ AccountMeta(PUMP_FUN_PROGRAM, is_signer=False, is_writable=False),
297
+ AccountMeta(global_vol, is_signer=False, is_writable=True),
298
+ AccountMeta(user_vol, is_signer=False, is_writable=True),
299
+ AccountMeta(fee_config, is_signer=False, is_writable=False),
300
+ AccountMeta(PUMP_FEE_PROGRAM, is_signer=False, is_writable=False),
301
+ AccountMeta(get_bonding_curve_v2_pda(token_mint), is_signer=False, is_writable=False),
302
+ ]
303
+
304
+ return Instruction(PUMP_FUN_PROGRAM, data, accounts)
305
+
306
+
307
+ def build_sell_instruction(
308
+ user: Pubkey,
309
+ token_mint: Pubkey,
310
+ bonding_curve: Pubkey,
311
+ token_amount: int,
312
+ min_sol_output: int,
313
+ creator: Pubkey,
314
+ fee_recipient: Pubkey,
315
+ token_program: Pubkey = TOKEN_PROGRAM,
316
+ ) -> Instruction:
317
+ """
318
+ Build the PumpFun v2 SELL instruction (14 accounts).
319
+
320
+ NOTE: creator_vault [8] and token_program [9] are SWAPPED vs buy!
321
+
322
+ Args:
323
+ user: Seller's wallet (signer).
324
+ token_mint: Token mint address.
325
+ bonding_curve: Bonding curve PDA.
326
+ token_amount: Number of tokens to sell.
327
+ min_sol_output: Minimum SOL output (lamports, slippage protection).
328
+ creator: Token creator from bonding curve state.
329
+ fee_recipient: Fee recipient.
330
+ token_program: TOKEN_PROGRAM or TOKEN_2022_PROGRAM.
331
+
332
+ Returns:
333
+ Solders Instruction.
334
+ """
335
+ associated_user = get_associated_token_address(user, token_mint, token_program)
336
+ associated_bonding_curve = get_associated_token_address(bonding_curve, token_mint, token_program)
337
+ creator_vault = get_creator_vault_pda(creator)
338
+ fee_config = get_fee_config_pda()
339
+
340
+ data = (
341
+ PUMP_SELL_DISCRIMINATOR
342
+ + struct.pack("<Q", token_amount)
343
+ + struct.pack("<Q", min_sol_output)
344
+ )
345
+
346
+ accounts = [
347
+ AccountMeta(PUMP_FUN_GLOBAL, is_signer=False, is_writable=False),
348
+ AccountMeta(fee_recipient, is_signer=False, is_writable=True),
349
+ AccountMeta(token_mint, is_signer=False, is_writable=False),
350
+ AccountMeta(bonding_curve, is_signer=False, is_writable=True),
351
+ AccountMeta(associated_bonding_curve, is_signer=False, is_writable=True),
352
+ AccountMeta(associated_user, is_signer=False, is_writable=True),
353
+ AccountMeta(user, is_signer=True, is_writable=True),
354
+ AccountMeta(SYSTEM_PROGRAM, is_signer=False, is_writable=False),
355
+ AccountMeta(creator_vault, is_signer=False, is_writable=True), # [8] SWAPPED
356
+ AccountMeta(token_program, is_signer=False, is_writable=False), # [9] SWAPPED
357
+ AccountMeta(PUMP_FUN_EVENT_AUTHORITY, is_signer=False, is_writable=False),
358
+ AccountMeta(PUMP_FUN_PROGRAM, is_signer=False, is_writable=False),
359
+ AccountMeta(fee_config, is_signer=False, is_writable=False),
360
+ AccountMeta(PUMP_FEE_PROGRAM, is_signer=False, is_writable=False),
361
+ AccountMeta(get_bonding_curve_v2_pda(token_mint), is_signer=False, is_writable=False),
362
+ ]
363
+
364
+ return Instruction(PUMP_FUN_PROGRAM, data, accounts)
pumpfun/constants.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ Solana program IDs and instruction discriminators for PumpFun and PumpSwap.
3
+ """
4
+
5
+ import hashlib
6
+
7
+ from solders.pubkey import Pubkey # type: ignore[import-untyped]
8
+
9
+ # ── PumpFun bonding curve program ──────────────────────────────────────────
10
+ PUMP_FUN_PROGRAM = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
11
+ PUMP_FUN_GLOBAL = Pubkey.from_string("4wTV1YmiEkRvAtNtsSGPtUrqRYQMe5SKy2uB4Jjaxnjf")
12
+ PUMP_FUN_EVENT_AUTHORITY = Pubkey.from_string("Ce6TQqeHC9p8KetsN6JsjHK7UTZk7nasjjnr7XxXp9F1")
13
+
14
+ # ── PumpSwap AMM — legacy program ─────────────────────────────────────────
15
+ PUMP_SWAP_PROGRAM = Pubkey.from_string("PSwapMdSai8tjrEXcxFeQth87xC4rRsa4VA5mhGhXkP")
16
+
17
+ # ── Pump AMM — current production program (pAMMBay) ───────────────────────
18
+ PUMP_AMM_PROGRAM = Pubkey.from_string("pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA")
19
+ PUMP_AMM_GLOBAL_CONFIG = Pubkey.from_string("ADyA8hdefvWN2dbGGWFotbzWxrAvLW83WG6QCVXvJKqw")
20
+ PUMP_AMM_EVENT_AUTHORITY = Pubkey.from_string("GS4CU59F31iL7aR2Q8zVS8DRrcRnXX1yjQ66TqNVQnaR")
21
+
22
+ # ── Pump Fee program ──────────────────────────────────────────────────────
23
+ PUMP_FEE_PROGRAM = Pubkey.from_string("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ")
24
+
25
+ # ── PumpSwap fee accounts ─────────────────────────────────────────────────
26
+ PUMP_SWAP_GLOBAL_CONFIG = Pubkey.from_string("ADyA8hdefbpth3kCbkbEVuNyGPfbmSYdoMjMCuKLxMJo")
27
+ PUMP_SWAP_FEE_RECIPIENT = Pubkey.from_string("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV")
28
+ PUMP_SWAP_EVENT_AUTHORITY = Pubkey.from_string("GS4CU59F31iL7aR2Q8xmZRbmMfXdK4cqeLGSBDmkXSWm")
29
+
30
+ # ── System / Token Programs ───────────────────────────────────────────────
31
+ TOKEN_PROGRAM = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
32
+ TOKEN_2022_PROGRAM = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
33
+ ASSOCIATED_TOKEN_PROGRAM = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
34
+ SYSTEM_PROGRAM = Pubkey.from_string("11111111111111111111111111111111")
35
+ SOL_MINT = Pubkey.from_string("So11111111111111111111111111111111111111112")
36
+
37
+ LAMPORTS_PER_SOL = 1_000_000_000
38
+
39
+ # ── Instruction discriminators ─────────────────────────────────────────────
40
+ # Anchor convention: sha256("global:<instruction_name>")[:8]
41
+ PUMP_BUY_DISCRIMINATOR = bytes([102, 6, 61, 18, 1, 218, 235, 234])
42
+ PUMP_SELL_DISCRIMINATOR = bytes([51, 230, 133, 164, 1, 127, 131, 173])
43
+ PUMPSWAP_SWAP_DISCRIMINATOR = hashlib.sha256(b"global:swap").digest()[:8]
pumpfun/pda.py ADDED
@@ -0,0 +1,65 @@
1
+ """
2
+ PDA derivation helpers for PumpFun and PumpSwap.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from solders.pubkey import Pubkey # type: ignore[import-untyped]
8
+
9
+ from .constants import (
10
+ ASSOCIATED_TOKEN_PROGRAM,
11
+ PUMP_FEE_PROGRAM,
12
+ PUMP_FUN_PROGRAM,
13
+ TOKEN_PROGRAM,
14
+ )
15
+
16
+
17
+ def _find_pda(seeds: list[bytes], program_id: Pubkey) -> Pubkey:
18
+ """Derive a Program Derived Address."""
19
+ pubkey, _bump = Pubkey.find_program_address(seeds, program_id)
20
+ return pubkey
21
+
22
+
23
+ def get_bonding_curve_pda(token_mint: Pubkey) -> Pubkey:
24
+ """Derive the PumpFun bonding curve PDA for a token mint."""
25
+ return _find_pda([b"bonding-curve", bytes(token_mint)], PUMP_FUN_PROGRAM)
26
+
27
+
28
+ def get_associated_token_address(
29
+ owner: Pubkey,
30
+ mint: Pubkey,
31
+ token_program: Pubkey = TOKEN_PROGRAM,
32
+ ) -> Pubkey:
33
+ """Derive the Associated Token Account (ATA) address."""
34
+ return _find_pda(
35
+ [bytes(owner), bytes(token_program), bytes(mint)],
36
+ ASSOCIATED_TOKEN_PROGRAM,
37
+ )
38
+
39
+
40
+ def get_creator_vault_pda(creator: Pubkey) -> Pubkey:
41
+ """PDA["creator-vault", creator] on the PumpFun program."""
42
+ return _find_pda([b"creator-vault", bytes(creator)], PUMP_FUN_PROGRAM)
43
+
44
+
45
+ def get_bonding_curve_v2_pda(token_mint: Pubkey) -> Pubkey:
46
+ """PDA["bonding-curve-v2", mint] — required remaining account for v2."""
47
+ return _find_pda([b"bonding-curve-v2", bytes(token_mint)], PUMP_FUN_PROGRAM)
48
+
49
+
50
+ def get_global_volume_accumulator_pda() -> Pubkey:
51
+ """PDA["global_volume_accumulator"] on the PumpFun program."""
52
+ return _find_pda([b"global_volume_accumulator"], PUMP_FUN_PROGRAM)
53
+
54
+
55
+ def get_user_volume_accumulator_pda(user: Pubkey) -> Pubkey:
56
+ """PDA["user_volume_accumulator", user] on the PumpFun program."""
57
+ return _find_pda([b"user_volume_accumulator", bytes(user)], PUMP_FUN_PROGRAM)
58
+
59
+
60
+ def get_fee_config_pda() -> Pubkey:
61
+ """PDA["fee_config", pump_program] on the fee program."""
62
+ return _find_pda(
63
+ [b"fee_config", bytes(PUMP_FUN_PROGRAM)],
64
+ PUMP_FEE_PROGRAM,
65
+ )
pumpfun/pumpswap.py ADDED
@@ -0,0 +1,262 @@
1
+ """
2
+ PumpSwap AMM — constant-product pool swap instructions and on-chain state.
3
+
4
+ Supports both legacy PumpSwap (PSwapMdSai...) and Pump AMM (pAMMBay...) pools.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import struct
11
+ from dataclasses import dataclass
12
+
13
+ import httpx
14
+ from solders.instruction import AccountMeta, Instruction # type: ignore[import-untyped]
15
+ from solders.pubkey import Pubkey # type: ignore[import-untyped]
16
+
17
+ from .constants import (
18
+ PUMP_AMM_PROGRAM,
19
+ PUMP_SWAP_EVENT_AUTHORITY,
20
+ PUMP_SWAP_FEE_RECIPIENT,
21
+ PUMP_SWAP_GLOBAL_CONFIG,
22
+ PUMP_SWAP_PROGRAM,
23
+ PUMPSWAP_SWAP_DISCRIMINATOR,
24
+ SOL_MINT,
25
+ TOKEN_PROGRAM,
26
+ )
27
+ from .pda import get_associated_token_address
28
+
29
+
30
+ class PumpSwapError(Exception):
31
+ """Raised when a PumpSwap operation fails."""
32
+
33
+
34
+ @dataclass(slots=True)
35
+ class PoolState:
36
+ """Parsed on-chain PumpSwap/Pump AMM pool state."""
37
+
38
+ pool_bump: int
39
+ index: int
40
+ creator: Pubkey
41
+ coin_creator: Pubkey
42
+ base_mint: Pubkey
43
+ quote_mint: Pubkey
44
+ lp_mint: Pubkey
45
+ pool_base_token_account: Pubkey
46
+ pool_quote_token_account: Pubkey
47
+ lp_fee_basis_points: int
48
+ protocol_fee_basis_points: int
49
+ is_pump_amm: bool = False
50
+ base_is_sol: bool = False
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Swap math (pure functions — no RPC needed)
55
+ # ---------------------------------------------------------------------------
56
+
57
+
58
+ def calculate_swap_output(
59
+ amount_in: int,
60
+ reserve_in: int,
61
+ reserve_out: int,
62
+ lp_fee_bps: int,
63
+ protocol_fee_bps: int,
64
+ ) -> tuple[int, int]:
65
+ """
66
+ Calculate output for a constant-product AMM swap.
67
+
68
+ Fee is deducted from the input amount before computing swap.
69
+
70
+ Args:
71
+ amount_in: Input token amount.
72
+ reserve_in: Reserve of the input token in the pool.
73
+ reserve_out: Reserve of the output token in the pool.
74
+ lp_fee_bps: LP fee in basis points.
75
+ protocol_fee_bps: Protocol fee in basis points.
76
+
77
+ Returns:
78
+ Tuple of (amount_out, total_fee).
79
+ """
80
+ total_fee_bps = lp_fee_bps + protocol_fee_bps
81
+ fee = (amount_in * total_fee_bps) // 10_000
82
+ amount_in_after_fee = amount_in - fee
83
+
84
+ if reserve_in + amount_in_after_fee == 0:
85
+ return 0, fee
86
+
87
+ amount_out = (reserve_out * amount_in_after_fee) // (reserve_in + amount_in_after_fee)
88
+ return amount_out, fee
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # On-chain state reading
93
+ # ---------------------------------------------------------------------------
94
+
95
+
96
+ async def fetch_pool_state(
97
+ rpc_url: str,
98
+ pool_address: str,
99
+ *,
100
+ http_client: httpx.AsyncClient | None = None,
101
+ ) -> PoolState:
102
+ """
103
+ Read a PumpSwap/Pump AMM pool account from on-chain.
104
+
105
+ Layout (after 8-byte Anchor discriminator):
106
+ - offset 8: pool_bump (u8)
107
+ - offset 9: index (u16 LE)
108
+ - offset 11: creator (Pubkey, 32B)
109
+ - offset 43: base_mint (Pubkey, 32B)
110
+ - offset 75: quote_mint (Pubkey, 32B)
111
+ - offset 107: lp_mint (Pubkey, 32B)
112
+ - offset 139: pool_base_token_account (Pubkey, 32B)
113
+ - offset 171: pool_quote_token_account (Pubkey, 32B)
114
+ - offset 203: lp_fee_basis_points (u64 LE) — legacy only
115
+ - offset 211: protocol_fee_basis_points (u64 LE) — legacy only
116
+
117
+ Args:
118
+ rpc_url: Solana RPC endpoint URL.
119
+ pool_address: Pool account address.
120
+ http_client: Optional httpx client.
121
+
122
+ Returns:
123
+ PoolState with reserves, mints, and vault addresses.
124
+
125
+ Raises:
126
+ PumpSwapError: If the account can't be read or parsed.
127
+ """
128
+ should_close = http_client is None
129
+ client = http_client or httpx.AsyncClient(timeout=httpx.Timeout(15.0))
130
+
131
+ try:
132
+ resp = await client.post(rpc_url, json={
133
+ "jsonrpc": "2.0",
134
+ "id": 1,
135
+ "method": "getAccountInfo",
136
+ "params": [pool_address, {"encoding": "base64"}],
137
+ })
138
+ resp.raise_for_status()
139
+ data = resp.json()
140
+ finally:
141
+ if should_close:
142
+ await client.aclose()
143
+
144
+ result = data.get("result", {})
145
+ value = result.get("value")
146
+ if not value:
147
+ raise PumpSwapError(f"Pool account {pool_address} not found")
148
+
149
+ raw_data = value.get("data", [])
150
+ if not isinstance(raw_data, list) or not raw_data:
151
+ raise PumpSwapError("Cannot decode pool account data")
152
+
153
+ account_bytes = base64.b64decode(raw_data[0])
154
+ if len(account_bytes) < 219:
155
+ raise PumpSwapError(f"Pool data too short: {len(account_bytes)} bytes (need >= 219)")
156
+
157
+ owner = value.get("owner", "")
158
+ valid_owners = {str(PUMP_SWAP_PROGRAM), str(PUMP_AMM_PROGRAM)}
159
+ if owner and owner not in valid_owners:
160
+ raise PumpSwapError(f"Account owned by {owner}, not PumpSwap/PumpAMM program")
161
+
162
+ is_pump_amm = owner == str(PUMP_AMM_PROGRAM)
163
+
164
+ def _pubkey_at(offset: int) -> Pubkey:
165
+ return Pubkey.from_bytes(account_bytes[offset: offset + 32])
166
+
167
+ off = 8
168
+ pool_bump = account_bytes[off]; off += 1
169
+ index = struct.unpack_from("<H", account_bytes, off)[0]; off += 2
170
+ creator = _pubkey_at(off); off += 32
171
+ base_mint = _pubkey_at(off); off += 32
172
+ quote_mint = _pubkey_at(off); off += 32
173
+ lp_mint = _pubkey_at(off); off += 32
174
+ pool_base_token_account = _pubkey_at(off); off += 32
175
+ pool_quote_token_account = _pubkey_at(off); off += 32
176
+
177
+ if is_pump_amm:
178
+ lp_fee_basis_points = 200
179
+ protocol_fee_basis_points = 100
180
+ coin_creator = Pubkey.default()
181
+ if len(account_bytes) >= 243:
182
+ coin_creator = _pubkey_at(211)
183
+ else:
184
+ lp_fee_basis_points = struct.unpack_from("<Q", account_bytes, off)[0]; off += 8
185
+ protocol_fee_basis_points = struct.unpack_from("<Q", account_bytes, off)[0]; off += 8
186
+ coin_creator = creator
187
+
188
+ base_is_sol = is_pump_amm and str(base_mint) == str(SOL_MINT)
189
+
190
+ return PoolState(
191
+ pool_bump=pool_bump,
192
+ index=index,
193
+ creator=creator,
194
+ coin_creator=coin_creator,
195
+ base_mint=base_mint,
196
+ quote_mint=quote_mint,
197
+ lp_mint=lp_mint,
198
+ pool_base_token_account=pool_base_token_account,
199
+ pool_quote_token_account=pool_quote_token_account,
200
+ lp_fee_basis_points=lp_fee_basis_points,
201
+ protocol_fee_basis_points=protocol_fee_basis_points,
202
+ is_pump_amm=is_pump_amm,
203
+ base_is_sol=base_is_sol,
204
+ )
205
+
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # Instruction builder
209
+ # ---------------------------------------------------------------------------
210
+
211
+
212
+ def build_swap_instruction(
213
+ pool: Pubkey,
214
+ user: Pubkey,
215
+ user_base_token_account: Pubkey,
216
+ user_quote_token_account: Pubkey,
217
+ pool_base_token_account: Pubkey,
218
+ pool_quote_token_account: Pubkey,
219
+ protocol_fee_token_account: Pubkey,
220
+ base_in: bool,
221
+ amount_in: int,
222
+ min_amount_out: int,
223
+ base_token_program: Pubkey = TOKEN_PROGRAM,
224
+ ) -> Instruction:
225
+ """
226
+ Build the PumpSwap swap instruction.
227
+
228
+ Args:
229
+ pool: Pool account address.
230
+ user: Trader's wallet (signer).
231
+ user_base_token_account: User's ATA for the base (meme) token.
232
+ user_quote_token_account: User's ATA for the quote token (WSOL).
233
+ pool_base_token_account: Pool's base token vault.
234
+ pool_quote_token_account: Pool's quote token vault.
235
+ protocol_fee_token_account: Protocol fee recipient's quote ATA.
236
+ base_in: True = sell base for quote, False = buy base with quote.
237
+ amount_in: Input amount (base units / lamports).
238
+ min_amount_out: Minimum output (slippage protection).
239
+ base_token_program: Token program for base token (SPL or Token-2022).
240
+
241
+ Returns:
242
+ Solders Instruction ready to add to a transaction.
243
+ """
244
+ data = PUMPSWAP_SWAP_DISCRIMINATOR + struct.pack("<BQQ", int(base_in), amount_in, min_amount_out)
245
+
246
+ accounts = [
247
+ AccountMeta(PUMP_SWAP_GLOBAL_CONFIG, is_signer=False, is_writable=False),
248
+ AccountMeta(PUMP_SWAP_FEE_RECIPIENT, is_signer=False, is_writable=True),
249
+ AccountMeta(pool, is_signer=False, is_writable=True),
250
+ AccountMeta(user, is_signer=True, is_writable=True),
251
+ AccountMeta(user_base_token_account, is_signer=False, is_writable=True),
252
+ AccountMeta(user_quote_token_account, is_signer=False, is_writable=True),
253
+ AccountMeta(pool_base_token_account, is_signer=False, is_writable=True),
254
+ AccountMeta(pool_quote_token_account, is_signer=False, is_writable=True),
255
+ AccountMeta(protocol_fee_token_account, is_signer=False, is_writable=True),
256
+ AccountMeta(base_token_program, is_signer=False, is_writable=False),
257
+ AccountMeta(TOKEN_PROGRAM, is_signer=False, is_writable=False),
258
+ AccountMeta(PUMP_SWAP_EVENT_AUTHORITY, is_signer=False, is_writable=False),
259
+ AccountMeta(PUMP_SWAP_PROGRAM, is_signer=False, is_writable=False),
260
+ ]
261
+
262
+ return Instruction(PUMP_SWAP_PROGRAM, data, accounts)
pumpfun/py.typed ADDED
File without changes
@@ -0,0 +1,208 @@
1
+ Metadata-Version: 2.4
2
+ Name: pumpfun-python
3
+ Version: 0.1.0
4
+ Summary: Buy and sell on PumpFun bonding curves + PumpSwap AMM. Directly from Python.
5
+ Project-URL: Homepage, https://github.com/JinUltimate1995/pumpfun-python
6
+ Project-URL: Repository, https://github.com/JinUltimate1995/pumpfun-python
7
+ Project-URL: Issues, https://github.com/JinUltimate1995/pumpfun-python/issues
8
+ Author: JinUltimate1995
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: amm,async,bonding-curve,crypto,defi,dex,pumpfun,pumpswap,python,solana,trading
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: solders>=0.21
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.10; extra == 'dev'
28
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
29
+ Requires-Dist: pytest>=8.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.4; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # pumpfun-python
34
+
35
+ **Buy and sell on PumpFun bonding curves + PumpSwap AMM. Directly from Python. No Jupiter needed.**
36
+
37
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
39
+ [![Typed](https://img.shields.io/badge/typed-py.typed-blue.svg)](https://peps.python.org/pep-0561/)
40
+
41
+ ---
42
+
43
+ ## Why?
44
+
45
+ PumpFun tokens start on a **bonding curve** and graduate to a **PumpSwap AMM pool**. Jupiter doesn't always route through these — and when it does, you're at the mercy of their routing engine. This library builds the raw Solana instructions so you can swap directly.
46
+
47
+ - **Pre-graduation** — buy/sell against the PumpFun bonding curve (v2, 16/14 accounts)
48
+ - **Post-graduation** — swap on PumpSwap AMM pools (constant-product, 13 accounts)
49
+ - **Zero dependencies beyond solders + httpx** — no SDK bloat
50
+ - **Production-tested** — extracted from a live trading system
51
+
52
+ ---
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install pumpfun-python
58
+ ```
59
+
60
+ Or from source:
61
+
62
+ ```bash
63
+ pip install git+https://github.com/JinUltimate1995/pumpfun-python.git
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Quick Start
69
+
70
+ ### 💰 Calculate buy/sell amounts (no RPC needed)
71
+
72
+ ```python
73
+ from pumpfun import calculate_buy_amount, calculate_sell_amount
74
+
75
+ # How many tokens for 0.1 SOL?
76
+ tokens = calculate_buy_amount(
77
+ sol_amount_lamports=100_000_000, # 0.1 SOL
78
+ virtual_sol_reserves=30_000_000_000,
79
+ virtual_token_reserves=1_000_000_000_000,
80
+ )
81
+ print(f"Tokens received: {tokens:,}")
82
+
83
+ # How much SOL for selling 1M tokens?
84
+ sol_out = calculate_sell_amount(
85
+ token_amount=1_000_000,
86
+ virtual_sol_reserves=30_000_000_000,
87
+ virtual_token_reserves=1_000_000_000_000,
88
+ )
89
+ print(f"SOL received: {sol_out / 1e9:.6f}")
90
+ ```
91
+
92
+ ### 📈 Read bonding curve state
93
+
94
+ > **RPC calls require your own Solana RPC endpoint.** Get a free key from [Helius](https://helius.dev), [QuickNode](https://quicknode.com), or use the public endpoint (rate-limited).
95
+
96
+ ```python
97
+ import asyncio
98
+ from pumpfun import fetch_bonding_curve_state
99
+
100
+ async def main():
101
+ state = await fetch_bonding_curve_state(
102
+ rpc_url="https://api.mainnet-beta.solana.com",
103
+ token_mint="YourTokenMintAddress",
104
+ )
105
+ print(f"Reserves: {state.virtual_sol_reserves} SOL, {state.virtual_token_reserves} tokens")
106
+ print(f"Complete (graduated): {state.complete}")
107
+ print(f"Creator: {state.creator}")
108
+
109
+ asyncio.run(main())
110
+ ```
111
+
112
+ ### 🔨 Build a buy instruction
113
+
114
+ ```python
115
+ from solders.pubkey import Pubkey
116
+ from pumpfun import build_buy_instruction, get_bonding_curve_pda
117
+
118
+ user = Pubkey.from_string("YourWalletAddress")
119
+ token_mint = Pubkey.from_string("TokenMintAddress")
120
+ bonding_curve = get_bonding_curve_pda(token_mint)
121
+
122
+ ix = build_buy_instruction(
123
+ user=user,
124
+ token_mint=token_mint,
125
+ bonding_curve=bonding_curve,
126
+ sol_amount_lamports=100_000_000, # 0.1 SOL max
127
+ min_tokens_out=950_000, # slippage protection
128
+ creator=Pubkey.from_string("CreatorAddress"),
129
+ fee_recipient=Pubkey.from_string("62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV"),
130
+ )
131
+ # Add `ix` to your transaction
132
+ ```
133
+
134
+ ### 🏊 PumpSwap AMM swap
135
+
136
+ ```python
137
+ from pumpfun import calculate_swap_output, build_swap_instruction, fetch_pool_state
138
+
139
+ # Read pool state
140
+ pool = await fetch_pool_state(rpc_url, "PoolAddress")
141
+
142
+ # Calculate output
143
+ amount_out, fee = calculate_swap_output(
144
+ amount_in=100_000_000, # 0.1 SOL
145
+ reserve_in=5_000_000_000,
146
+ reserve_out=1_000_000_000_000,
147
+ lp_fee_bps=pool.lp_fee_basis_points,
148
+ protocol_fee_bps=pool.protocol_fee_basis_points,
149
+ )
150
+ ```
151
+
152
+ ---
153
+
154
+ ## API Reference
155
+
156
+ ### Bonding Curve (pre-graduation)
157
+
158
+ | Function | Description |
159
+ |---|---|
160
+ | `calculate_buy_amount()` | Tokens received for SOL input (1% fee) |
161
+ | `calculate_sell_amount()` | SOL received for token input (1% fee) |
162
+ | `build_buy_instruction()` | Build PumpFun v2 BUY ix (16 accounts) |
163
+ | `build_sell_instruction()` | Build PumpFun v2 SELL ix (14 accounts) |
164
+ | `fetch_bonding_curve_state()` | Read reserves, creator, completion from chain |
165
+ | `fetch_fee_recipient()` | Read fee recipient from Global account |
166
+
167
+ ### PumpSwap AMM (post-graduation)
168
+
169
+ | Function | Description |
170
+ |---|---|
171
+ | `calculate_swap_output()` | Output amount for constant-product swap |
172
+ | `build_swap_instruction()` | Build PumpSwap swap ix (13 accounts) |
173
+ | `fetch_pool_state()` | Read pool vaults, mints, fees from chain |
174
+
175
+ ### PDA Helpers
176
+
177
+ | Function | Description |
178
+ |---|---|
179
+ | `get_bonding_curve_pda()` | Derive bonding curve PDA for a mint |
180
+ | `get_associated_token_address()` | Derive ATA (SPL Token or Token-2022) |
181
+
182
+ ### Constants
183
+
184
+ All program IDs are exported: `PUMP_FUN_PROGRAM`, `PUMP_SWAP_PROGRAM`, `PUMP_AMM_PROGRAM`, plus system programs, discriminators, and fee accounts.
185
+
186
+ ---
187
+
188
+ ## Account Layout Notes
189
+
190
+ **BUY instruction** uses 16 accounts + 1 remaining account (bonding-curve-v2 PDA).
191
+
192
+ **SELL instruction** uses 14 accounts + 1 remaining — but **creator_vault [8] and token_program [9] are SWAPPED** relative to buy. This is an undocumented PumpFun quirk that causes failed transactions if you copy the buy layout.
193
+
194
+ **Pump AMM pools** (pAMMBay program) can have **reversed base/quote** — base_mint=SOL, quote_mint=TOKEN. The `PoolState.base_is_sol` flag indicates this.
195
+
196
+ ---
197
+
198
+ ## Also by JinUltimate1995
199
+
200
+ - **[jupiter-swap-python](https://github.com/JinUltimate1995/jupiter-swap-python)** — Jupiter swap client for Python. Async. Typed.
201
+ - **[solana-rpc-resilient](https://github.com/JinUltimate1995/solana-rpc-resilient)** — Fault-tolerant Solana RPC with automatic failover.
202
+ - **[dexscreener-python](https://github.com/JinUltimate1995/dexscreener-python)** — DexScreener API client for Python.
203
+
204
+ ---
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,10 @@
1
+ pumpfun/__init__.py,sha256=Sbnl2OdOjn4PfX_OMrxgdBO5gFutHkBse1k4GDi5FhQ,1190
2
+ pumpfun/bonding_curve.py,sha256=1_bVU5KfHkmoux4GHXf772hmGjJaIK2fOAxlkJj6gkQ,12797
3
+ pumpfun/constants.py,sha256=XP0PHH9EuQW5kYsPY1RrEz37_4jotvb4nybsHSbnjOs,3055
4
+ pumpfun/pda.py,sha256=7FfwymxsPQjxpKoQdMNG6Osn8cvzuc2dwl0wWJN1iVA,2018
5
+ pumpfun/pumpswap.py,sha256=cpvFPOLbBpjZlVSt1c3OGihDNc5-lToA75gP4iRRsMc,8990
6
+ pumpfun/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ pumpfun_python-0.1.0.dist-info/METADATA,sha256=fwssk43-kbP0qiO4QlXrW9hj0B8wSafqCVxfK5ll1-0,7072
8
+ pumpfun_python-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
9
+ pumpfun_python-0.1.0.dist-info/licenses/LICENSE,sha256=D7y6S1R0LcQMuzvEM3af4NM2zASwS0H82DKH-3g_FDE,1072
10
+ pumpfun_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 JinUltimate1995
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.