pumpswap-python-sdk 1.0.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.
- pump_swap_sdk/__init__.py +37 -0
- pump_swap_sdk/constants.py +30 -0
- pump_swap_sdk/exceptions.py +83 -0
- pump_swap_sdk/sdk/fees.py +171 -0
- pump_swap_sdk/sdk/liquidity_operations.py +328 -0
- pump_swap_sdk/sdk/online_pump_amm_sdk.py +431 -0
- pump_swap_sdk/sdk/pda.py +231 -0
- pump_swap_sdk/sdk/pump_amm_admin_sdk.py +303 -0
- pump_swap_sdk/sdk/pump_amm_sdk.py +503 -0
- pump_swap_sdk/sdk/swap_operations.py +302 -0
- pump_swap_sdk/sdk/token_incentives.py +254 -0
- pump_swap_sdk/sdk/utils.py +273 -0
- pump_swap_sdk/types/amm_types.py +153 -0
- pump_swap_sdk/types/sdk_types.py +193 -0
- pump_swap_sdk/validators.py +315 -0
- pumpswap_python_sdk-1.0.0.dist-info/METADATA +399 -0
- pumpswap_python_sdk-1.0.0.dist-info/RECORD +21 -0
- pumpswap_python_sdk-1.0.0.dist-info/WHEEL +5 -0
- pumpswap_python_sdk-1.0.0.dist-info/entry_points.txt +2 -0
- pumpswap_python_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
- pumpswap_python_sdk-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PumpSwap SDK - Python implementation for interacting with Pump Swap AMM protocol on Solana
|
|
3
|
+
|
|
4
|
+
This SDK provides high-level and low-level interfaces for:
|
|
5
|
+
- Creating AMM pools
|
|
6
|
+
- Performing token swaps (buy/sell)
|
|
7
|
+
- Managing liquidity (deposit/withdraw)
|
|
8
|
+
- Collecting fees
|
|
9
|
+
- Token incentives
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from .sdk.pump_amm_sdk import PumpAmmSdk
|
|
13
|
+
from .sdk.online_pump_amm_sdk import OnlinePumpAmmSdk
|
|
14
|
+
from .sdk.pump_amm_admin_sdk import PumpAmmAdminSdk
|
|
15
|
+
from .sdk.swap_operations import buy_base_input, buy_quote_input, sell_base_input, sell_quote_input
|
|
16
|
+
from .sdk.liquidity_operations import deposit_lp_token, withdraw
|
|
17
|
+
from .sdk.token_incentives import total_unclaimed_tokens, current_day_tokens
|
|
18
|
+
from .types.sdk_types import *
|
|
19
|
+
from .types.amm_types import *
|
|
20
|
+
from .constants import *
|
|
21
|
+
|
|
22
|
+
__version__ = "1.0.0"
|
|
23
|
+
__author__ = "PumpSwap Python SDK"
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"PumpAmmSdk",
|
|
27
|
+
"OnlinePumpAmmSdk",
|
|
28
|
+
"PumpAmmAdminSdk",
|
|
29
|
+
"buy_base_input",
|
|
30
|
+
"buy_quote_input",
|
|
31
|
+
"sell_base_input",
|
|
32
|
+
"sell_quote_input",
|
|
33
|
+
"deposit_lp_token",
|
|
34
|
+
"withdraw",
|
|
35
|
+
"total_unclaimed_tokens",
|
|
36
|
+
"current_day_tokens",
|
|
37
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants used throughout the PumpSwap SDK
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from solders.pubkey import Pubkey
|
|
6
|
+
from typing import Final
|
|
7
|
+
|
|
8
|
+
# Program IDs
|
|
9
|
+
PUMP_AMM_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P")
|
|
10
|
+
TOKEN_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
|
11
|
+
TOKEN_2022_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
|
|
12
|
+
ASSOCIATED_TOKEN_PROGRAM_ID: Final[Pubkey] = Pubkey.from_string("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL")
|
|
13
|
+
NATIVE_MINT: Final[Pubkey] = Pubkey.from_string("So11111111111111111111111111111111111111112")
|
|
14
|
+
|
|
15
|
+
# Pool Constants
|
|
16
|
+
POOL_ACCOUNT_NEW_SIZE: Final[int] = 288
|
|
17
|
+
|
|
18
|
+
# Global PDAs
|
|
19
|
+
GLOBAL_CONFIG_SEED: Final[str] = "global_config"
|
|
20
|
+
GLOBAL_VOLUME_ACCUMULATOR_SEED: Final[str] = "global_volume_accumulator"
|
|
21
|
+
PUMP_AMM_EVENT_AUTHORITY_SEED: Final[str] = "event_authority"
|
|
22
|
+
|
|
23
|
+
# Fee Configuration
|
|
24
|
+
DEFAULT_SLIPPAGE_BPS: Final[int] = 100 # 1%
|
|
25
|
+
MAX_SLIPPAGE_BPS: Final[int] = 1000 # 10%
|
|
26
|
+
BASIS_POINTS: Final[int] = 10000 # 100%
|
|
27
|
+
|
|
28
|
+
# Precision
|
|
29
|
+
LAMPORTS_PER_SOL: Final[int] = 1_000_000_000
|
|
30
|
+
TOKEN_DECIMALS: Final[int] = 6
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for the PumpSwap SDK
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PumpSwapSDKError(Exception):
|
|
7
|
+
"""Base exception class for PumpSwap SDK errors"""
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ValidationError(PumpSwapSDKError):
|
|
12
|
+
"""Raised when input validation fails"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class InsufficientLiquidityError(PumpSwapSDKError):
|
|
17
|
+
"""Raised when there's insufficient liquidity for an operation"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SlippageExceededError(PumpSwapSDKError):
|
|
22
|
+
"""Raised when slippage tolerance is exceeded"""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InvalidPoolError(PumpSwapSDKError):
|
|
27
|
+
"""Raised when pool data is invalid or corrupted"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AccountNotFoundError(PumpSwapSDKError):
|
|
32
|
+
"""Raised when a required account is not found on-chain"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class UnauthorizedError(PumpSwapSDKError):
|
|
37
|
+
"""Raised when user lacks required permissions"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ConfigurationError(PumpSwapSDKError):
|
|
42
|
+
"""Raised when SDK configuration is invalid"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FeeCalculationError(PumpSwapSDKError):
|
|
47
|
+
"""Raised when fee calculation fails"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NetworkError(PumpSwapSDKError):
|
|
52
|
+
"""Raised when network operations fail"""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class SerializationError(PumpSwapSDKError):
|
|
57
|
+
"""Raised when data serialization/deserialization fails"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class InstructionBuildError(PumpSwapSDKError):
|
|
62
|
+
"""Raised when instruction building fails"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TokenAccountError(PumpSwapSDKError):
|
|
67
|
+
"""Raised when token account operations fail"""
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class PoolCreationError(PumpSwapSDKError):
|
|
72
|
+
"""Raised when pool creation fails"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SwapError(PumpSwapSDKError):
|
|
77
|
+
"""Raised when swap operations fail"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class LiquidityError(PumpSwapSDKError):
|
|
82
|
+
"""Raised when liquidity operations fail"""
|
|
83
|
+
pass
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Fee calculation utilities for the PumpSwap SDK
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from solders.pubkey import Pubkey
|
|
7
|
+
from ..types.amm_types import FeeConfig, GlobalConfig, ComputeFeesResult
|
|
8
|
+
from ..constants import BASIS_POINTS
|
|
9
|
+
from .utils import is_pump_pool
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def calculate_fee_tier(fee_tiers: list, market_cap: int) -> Optional[FeeConfig.FeeTier]:
|
|
13
|
+
"""
|
|
14
|
+
Find the appropriate fee tier based on market cap
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
fee_tiers: List of fee tiers ordered by market cap threshold
|
|
18
|
+
market_cap: Current market cap
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Matching fee tier or None
|
|
22
|
+
"""
|
|
23
|
+
# Tiers should be ordered by threshold (ascending)
|
|
24
|
+
for tier in reversed(fee_tiers):
|
|
25
|
+
if market_cap >= tier.market_cap_threshold:
|
|
26
|
+
return tier
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def compute_fees_bps(
|
|
31
|
+
global_config: GlobalConfig,
|
|
32
|
+
fee_config: FeeConfig,
|
|
33
|
+
creator: Optional[Pubkey] = None,
|
|
34
|
+
market_cap: int = 0,
|
|
35
|
+
mint: Optional[Pubkey] = None
|
|
36
|
+
) -> ComputeFeesResult:
|
|
37
|
+
"""
|
|
38
|
+
Compute fee rates in basis points for a swap
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
global_config: Global configuration
|
|
42
|
+
fee_config: Fee configuration with tiers
|
|
43
|
+
creator: Pool creator (for pump pool detection)
|
|
44
|
+
market_cap: Current market cap of the token
|
|
45
|
+
mint: Token mint (for pump pool detection)
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
ComputeFeesResult with all fee rates
|
|
49
|
+
"""
|
|
50
|
+
# Start with default fees
|
|
51
|
+
lp_fee_bps = fee_config.default_lp_fee_rate_bps
|
|
52
|
+
protocol_fee_bps = fee_config.default_protocol_fee_rate_bps
|
|
53
|
+
creator_fee_bps = fee_config.default_creator_fee_rate_bps
|
|
54
|
+
|
|
55
|
+
# Check if mayhem mode is active (higher fees)
|
|
56
|
+
if global_config.is_mayhem_mode:
|
|
57
|
+
# Apply mayhem mode multiplier (example: 2x fees)
|
|
58
|
+
lp_fee_bps *= 2
|
|
59
|
+
protocol_fee_bps *= 2
|
|
60
|
+
creator_fee_bps *= 2
|
|
61
|
+
|
|
62
|
+
# Find appropriate fee tier based on market cap
|
|
63
|
+
fee_tier = calculate_fee_tier(fee_config.fee_tiers, market_cap)
|
|
64
|
+
if fee_tier:
|
|
65
|
+
lp_fee_bps = fee_tier.lp_fee_rate_bps
|
|
66
|
+
protocol_fee_bps = fee_tier.protocol_fee_rate_bps
|
|
67
|
+
creator_fee_bps = fee_tier.creator_fee_rate_bps
|
|
68
|
+
|
|
69
|
+
# Special handling for pump pools
|
|
70
|
+
if mint and creator and is_pump_pool(mint, creator):
|
|
71
|
+
# Pump pools might have different fee structures
|
|
72
|
+
# This would be implemented based on specific pump.fun requirements
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
# Calculate total fee rate
|
|
76
|
+
total_fee_bps = lp_fee_bps + protocol_fee_bps + creator_fee_bps
|
|
77
|
+
|
|
78
|
+
# Ensure total fees don't exceed 100%
|
|
79
|
+
if total_fee_bps > BASIS_POINTS:
|
|
80
|
+
raise ValueError(f"Total fees ({total_fee_bps} bps) exceed 100%")
|
|
81
|
+
|
|
82
|
+
return ComputeFeesResult(
|
|
83
|
+
lp_fee_bps=lp_fee_bps,
|
|
84
|
+
protocol_fee_bps=protocol_fee_bps,
|
|
85
|
+
creator_fee_bps=creator_fee_bps,
|
|
86
|
+
total_fee_bps=total_fee_bps
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_fee_recipient(
|
|
91
|
+
global_config: GlobalConfig,
|
|
92
|
+
pool_creator: Optional[Pubkey] = None
|
|
93
|
+
) -> Pubkey:
|
|
94
|
+
"""
|
|
95
|
+
Get the fee recipient address
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
global_config: Global configuration
|
|
99
|
+
pool_creator: Pool creator (unused for now, but could be used for dynamic routing)
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Fee recipient address
|
|
103
|
+
"""
|
|
104
|
+
return global_config.fee_recipient
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def calculate_swap_fees(
|
|
108
|
+
amount_in: int,
|
|
109
|
+
fees_result: ComputeFeesResult
|
|
110
|
+
) -> dict:
|
|
111
|
+
"""
|
|
112
|
+
Calculate actual fee amounts for a swap
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
amount_in: Input amount
|
|
116
|
+
fees_result: Fee rates from compute_fees_bps
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dictionary with fee amounts
|
|
120
|
+
"""
|
|
121
|
+
from .utils import fee
|
|
122
|
+
|
|
123
|
+
lp_fee_amount = fee(amount_in, fees_result.lp_fee_bps)
|
|
124
|
+
protocol_fee_amount = fee(amount_in, fees_result.protocol_fee_bps)
|
|
125
|
+
creator_fee_amount = fee(amount_in, fees_result.creator_fee_bps)
|
|
126
|
+
total_fee_amount = lp_fee_amount + protocol_fee_amount + creator_fee_amount
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"lp_fee_amount": lp_fee_amount,
|
|
130
|
+
"protocol_fee_amount": protocol_fee_amount,
|
|
131
|
+
"creator_fee_amount": creator_fee_amount,
|
|
132
|
+
"total_fee_amount": total_fee_amount,
|
|
133
|
+
"amount_after_fees": amount_in - total_fee_amount
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def validate_fee_rates(
|
|
138
|
+
lp_fee_bps: int,
|
|
139
|
+
protocol_fee_bps: int,
|
|
140
|
+
creator_fee_bps: int
|
|
141
|
+
) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Validate that fee rates are reasonable
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
lp_fee_bps: LP fee rate in basis points
|
|
147
|
+
protocol_fee_bps: Protocol fee rate in basis points
|
|
148
|
+
creator_fee_bps: Creator fee rate in basis points
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
ValueError: If fees are invalid
|
|
152
|
+
"""
|
|
153
|
+
# Check individual fee rates
|
|
154
|
+
if lp_fee_bps < 0 or lp_fee_bps > BASIS_POINTS:
|
|
155
|
+
raise ValueError(f"Invalid LP fee rate: {lp_fee_bps} bps")
|
|
156
|
+
|
|
157
|
+
if protocol_fee_bps < 0 or protocol_fee_bps > BASIS_POINTS:
|
|
158
|
+
raise ValueError(f"Invalid protocol fee rate: {protocol_fee_bps} bps")
|
|
159
|
+
|
|
160
|
+
if creator_fee_bps < 0 or creator_fee_bps > BASIS_POINTS:
|
|
161
|
+
raise ValueError(f"Invalid creator fee rate: {creator_fee_bps} bps")
|
|
162
|
+
|
|
163
|
+
# Check total fees
|
|
164
|
+
total_fee_bps = lp_fee_bps + protocol_fee_bps + creator_fee_bps
|
|
165
|
+
if total_fee_bps > BASIS_POINTS:
|
|
166
|
+
raise ValueError(f"Total fees exceed 100%: {total_fee_bps} bps")
|
|
167
|
+
|
|
168
|
+
# Reasonable maximum (e.g., 10% total fees)
|
|
169
|
+
max_reasonable_fee = BASIS_POINTS // 10 # 10%
|
|
170
|
+
if total_fee_bps > max_reasonable_fee:
|
|
171
|
+
raise ValueError(f"Total fees too high: {total_fee_bps} bps (max reasonable: {max_reasonable_fee} bps)")
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Liquidity operation implementations (deposit/withdraw) for the PumpSwap SDK
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
from ..types.sdk_types import (
|
|
8
|
+
DepositBaseResult, DepositQuoteResult, DepositLpTokenResult,
|
|
9
|
+
DepositBaseAndLpTokenFromQuoteResult, DepositQuoteAndLpTokenFromBaseResult,
|
|
10
|
+
WithdrawResult, WithdrawAutocompleteResult
|
|
11
|
+
)
|
|
12
|
+
from .utils import (
|
|
13
|
+
ceil_div, floor_div, apply_slippage,
|
|
14
|
+
calculate_lp_tokens_for_deposit, calculate_tokens_for_lp_withdrawal
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def deposit_token0(
|
|
19
|
+
token0: int,
|
|
20
|
+
slippage: int,
|
|
21
|
+
token0_reserve: int,
|
|
22
|
+
token1_reserve: int,
|
|
23
|
+
total_lp_supply: int
|
|
24
|
+
) -> DepositBaseResult:
|
|
25
|
+
"""
|
|
26
|
+
Calculate deposit amounts when user provides token0 amount
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
token0: Amount of token0 to deposit
|
|
30
|
+
slippage: Slippage tolerance in basis points
|
|
31
|
+
token0_reserve: Current token0 reserve
|
|
32
|
+
token1_reserve: Current token1 reserve
|
|
33
|
+
total_lp_supply: Current total LP supply
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
DepositBaseResult with calculated amounts
|
|
37
|
+
"""
|
|
38
|
+
if token0 <= 0:
|
|
39
|
+
raise ValueError("Token0 amount must be positive")
|
|
40
|
+
|
|
41
|
+
if total_lp_supply == 0:
|
|
42
|
+
# Initial liquidity - special case
|
|
43
|
+
# For initial deposit, we can accept any ratio
|
|
44
|
+
# LP tokens = sqrt(token0 * token1) where token1 is provided
|
|
45
|
+
raise ValueError("Use deposit_lp_token for initial liquidity provision")
|
|
46
|
+
|
|
47
|
+
if token0_reserve == 0:
|
|
48
|
+
raise ValueError("Invalid token0 reserve")
|
|
49
|
+
|
|
50
|
+
# Calculate proportional token1 amount needed
|
|
51
|
+
# token1 = token0 * token1_reserve / token0_reserve
|
|
52
|
+
quote = ceil_div(token0 * token1_reserve, token0_reserve)
|
|
53
|
+
|
|
54
|
+
# Calculate LP tokens to be minted
|
|
55
|
+
# lp_tokens = token0 * total_lp_supply / token0_reserve
|
|
56
|
+
lp_token = floor_div(token0 * total_lp_supply, token0_reserve)
|
|
57
|
+
|
|
58
|
+
if lp_token == 0:
|
|
59
|
+
raise ValueError("Deposit amount too small")
|
|
60
|
+
|
|
61
|
+
# Apply slippage protection
|
|
62
|
+
max_base = apply_slippage(token0, slippage, is_maximum=True)
|
|
63
|
+
max_quote = apply_slippage(quote, slippage, is_maximum=True)
|
|
64
|
+
|
|
65
|
+
return DepositBaseResult(
|
|
66
|
+
quote=quote,
|
|
67
|
+
lp_token=lp_token,
|
|
68
|
+
max_base=max_base,
|
|
69
|
+
max_quote=max_quote
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def deposit_quote_and_lp_token_from_base(
|
|
74
|
+
base: int,
|
|
75
|
+
slippage: int,
|
|
76
|
+
base_reserve: int,
|
|
77
|
+
quote_reserve: int,
|
|
78
|
+
total_lp_supply: int
|
|
79
|
+
) -> DepositQuoteAndLpTokenFromBaseResult:
|
|
80
|
+
"""
|
|
81
|
+
Calculate quote amount and LP tokens when user provides base amount
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
base: Base token amount to deposit
|
|
85
|
+
slippage: Slippage tolerance in basis points
|
|
86
|
+
base_reserve: Current base token reserve
|
|
87
|
+
quote_reserve: Current quote token reserve
|
|
88
|
+
total_lp_supply: Current total LP supply
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
DepositQuoteAndLpTokenFromBaseResult
|
|
92
|
+
"""
|
|
93
|
+
if base <= 0:
|
|
94
|
+
raise ValueError("Base amount must be positive")
|
|
95
|
+
|
|
96
|
+
if total_lp_supply == 0 or base_reserve == 0:
|
|
97
|
+
raise ValueError("Pool not initialized")
|
|
98
|
+
|
|
99
|
+
# Calculate proportional quote amount needed
|
|
100
|
+
quote = ceil_div(base * quote_reserve, base_reserve)
|
|
101
|
+
|
|
102
|
+
# Calculate LP tokens to be minted
|
|
103
|
+
lp_token = floor_div(base * total_lp_supply, base_reserve)
|
|
104
|
+
|
|
105
|
+
if lp_token == 0:
|
|
106
|
+
raise ValueError("Deposit amount too small")
|
|
107
|
+
|
|
108
|
+
return DepositQuoteAndLpTokenFromBaseResult(
|
|
109
|
+
quote=quote,
|
|
110
|
+
lp_token=lp_token
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def deposit_base_and_lp_token_from_quote(
|
|
115
|
+
quote: int,
|
|
116
|
+
slippage: int,
|
|
117
|
+
base_reserve: int,
|
|
118
|
+
quote_reserve: int,
|
|
119
|
+
total_lp_supply: int
|
|
120
|
+
) -> DepositBaseAndLpTokenFromQuoteResult:
|
|
121
|
+
"""
|
|
122
|
+
Calculate base amount and LP tokens when user provides quote amount
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
quote: Quote token amount to deposit
|
|
126
|
+
slippage: Slippage tolerance in basis points
|
|
127
|
+
base_reserve: Current base token reserve
|
|
128
|
+
quote_reserve: Current quote token reserve
|
|
129
|
+
total_lp_supply: Current total LP supply
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
DepositBaseAndLpTokenFromQuoteResult
|
|
133
|
+
"""
|
|
134
|
+
if quote <= 0:
|
|
135
|
+
raise ValueError("Quote amount must be positive")
|
|
136
|
+
|
|
137
|
+
if total_lp_supply == 0 or quote_reserve == 0:
|
|
138
|
+
raise ValueError("Pool not initialized")
|
|
139
|
+
|
|
140
|
+
# Calculate proportional base amount needed
|
|
141
|
+
base = ceil_div(quote * base_reserve, quote_reserve)
|
|
142
|
+
|
|
143
|
+
# Calculate LP tokens to be minted
|
|
144
|
+
lp_token = floor_div(quote * total_lp_supply, quote_reserve)
|
|
145
|
+
|
|
146
|
+
if lp_token == 0:
|
|
147
|
+
raise ValueError("Deposit amount too small")
|
|
148
|
+
|
|
149
|
+
return DepositBaseAndLpTokenFromQuoteResult(
|
|
150
|
+
base=base,
|
|
151
|
+
lp_token=lp_token
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def deposit_lp_token(
|
|
156
|
+
lp_token: int,
|
|
157
|
+
slippage: int,
|
|
158
|
+
base_reserve: int,
|
|
159
|
+
quote_reserve: int,
|
|
160
|
+
total_lp_supply: int
|
|
161
|
+
) -> DepositLpTokenResult:
|
|
162
|
+
"""
|
|
163
|
+
Calculate token amounts needed for a specific LP token amount
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
lp_token: Desired LP token amount
|
|
167
|
+
slippage: Slippage tolerance in basis points
|
|
168
|
+
base_reserve: Current base token reserve
|
|
169
|
+
quote_reserve: Current quote token reserve
|
|
170
|
+
total_lp_supply: Current total LP supply
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
DepositLpTokenResult with maximum token amounts needed
|
|
174
|
+
"""
|
|
175
|
+
if lp_token <= 0:
|
|
176
|
+
raise ValueError("LP token amount must be positive")
|
|
177
|
+
|
|
178
|
+
if total_lp_supply == 0:
|
|
179
|
+
# Initial liquidity case
|
|
180
|
+
# For initial deposit, we use the geometric mean
|
|
181
|
+
# lp_token = sqrt(base_amount * quote_amount)
|
|
182
|
+
# This means base_amount * quote_amount = lp_token^2
|
|
183
|
+
# We need to determine the ratio, but for now return equal amounts
|
|
184
|
+
estimated_base = int(math.sqrt(lp_token))
|
|
185
|
+
estimated_quote = int(math.sqrt(lp_token))
|
|
186
|
+
|
|
187
|
+
max_base = apply_slippage(estimated_base, slippage, is_maximum=True)
|
|
188
|
+
max_quote = apply_slippage(estimated_quote, slippage, is_maximum=True)
|
|
189
|
+
|
|
190
|
+
return DepositLpTokenResult(
|
|
191
|
+
max_base=max_base,
|
|
192
|
+
max_quote=max_quote
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Calculate required token amounts proportionally
|
|
196
|
+
# base_amount = lp_token * base_reserve / total_lp_supply
|
|
197
|
+
max_base_needed = ceil_div(lp_token * base_reserve, total_lp_supply)
|
|
198
|
+
max_quote_needed = ceil_div(lp_token * quote_reserve, total_lp_supply)
|
|
199
|
+
|
|
200
|
+
# Apply slippage protection
|
|
201
|
+
max_base = apply_slippage(max_base_needed, slippage, is_maximum=True)
|
|
202
|
+
max_quote = apply_slippage(max_quote_needed, slippage, is_maximum=True)
|
|
203
|
+
|
|
204
|
+
return DepositLpTokenResult(
|
|
205
|
+
max_base=max_base,
|
|
206
|
+
max_quote=max_quote
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def withdraw(
|
|
211
|
+
lp_amount: int,
|
|
212
|
+
slippage: int,
|
|
213
|
+
base_reserve: int,
|
|
214
|
+
quote_reserve: int,
|
|
215
|
+
total_lp_supply: int
|
|
216
|
+
) -> WithdrawResult:
|
|
217
|
+
"""
|
|
218
|
+
Calculate token amounts received when withdrawing LP tokens
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
lp_amount: LP token amount to withdraw
|
|
222
|
+
slippage: Slippage tolerance in basis points
|
|
223
|
+
base_reserve: Current base token reserve
|
|
224
|
+
quote_reserve: Current quote token reserve
|
|
225
|
+
total_lp_supply: Current total LP supply
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
WithdrawResult with token amounts and slippage protection
|
|
229
|
+
"""
|
|
230
|
+
if lp_amount <= 0:
|
|
231
|
+
raise ValueError("LP amount must be positive")
|
|
232
|
+
|
|
233
|
+
if lp_amount > total_lp_supply:
|
|
234
|
+
raise ValueError("LP amount exceeds total supply")
|
|
235
|
+
|
|
236
|
+
if total_lp_supply == 0:
|
|
237
|
+
raise ValueError("No liquidity to withdraw")
|
|
238
|
+
|
|
239
|
+
# Calculate proportional token amounts
|
|
240
|
+
base, quote = calculate_tokens_for_lp_withdrawal(
|
|
241
|
+
lp_amount, base_reserve, quote_reserve, total_lp_supply
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
if base == 0 and quote == 0:
|
|
245
|
+
raise ValueError("Withdrawal amount too small")
|
|
246
|
+
|
|
247
|
+
# Apply slippage protection for minimum amounts
|
|
248
|
+
min_base = apply_slippage(base, slippage, is_maximum=False)
|
|
249
|
+
min_quote = apply_slippage(quote, slippage, is_maximum=False)
|
|
250
|
+
|
|
251
|
+
return WithdrawResult(
|
|
252
|
+
base=base,
|
|
253
|
+
quote=quote,
|
|
254
|
+
min_base=min_base,
|
|
255
|
+
min_quote=min_quote
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def withdraw_autocomplete_base_and_quote_from_lp_token(
|
|
260
|
+
lp_amount: int,
|
|
261
|
+
slippage: int,
|
|
262
|
+
base_reserve: int,
|
|
263
|
+
quote_reserve: int,
|
|
264
|
+
total_lp_supply: int
|
|
265
|
+
) -> WithdrawAutocompleteResult:
|
|
266
|
+
"""
|
|
267
|
+
Simple autocomplete calculation for withdrawal amounts
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
lp_amount: LP token amount to withdraw
|
|
271
|
+
slippage: Slippage tolerance in basis points
|
|
272
|
+
base_reserve: Current base token reserve
|
|
273
|
+
quote_reserve: Current quote token reserve
|
|
274
|
+
total_lp_supply: Current total LP supply
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
WithdrawAutocompleteResult with base and quote amounts
|
|
278
|
+
"""
|
|
279
|
+
if lp_amount <= 0:
|
|
280
|
+
raise ValueError("LP amount must be positive")
|
|
281
|
+
|
|
282
|
+
if total_lp_supply == 0:
|
|
283
|
+
return WithdrawAutocompleteResult(base=0, quote=0)
|
|
284
|
+
|
|
285
|
+
# Calculate proportional amounts without slippage
|
|
286
|
+
base, quote = calculate_tokens_for_lp_withdrawal(
|
|
287
|
+
lp_amount, base_reserve, quote_reserve, total_lp_supply
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return WithdrawAutocompleteResult(
|
|
291
|
+
base=base,
|
|
292
|
+
quote=quote
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def validate_deposit_amounts(
|
|
297
|
+
base_amount: int,
|
|
298
|
+
quote_amount: int,
|
|
299
|
+
base_reserve: int,
|
|
300
|
+
quote_reserve: int,
|
|
301
|
+
tolerance_bps: int = 100 # 1% tolerance
|
|
302
|
+
) -> bool:
|
|
303
|
+
"""
|
|
304
|
+
Validate that deposit amounts maintain proper pool ratio
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
base_amount: Base token amount to deposit
|
|
308
|
+
quote_amount: Quote token amount to deposit
|
|
309
|
+
base_reserve: Current base token reserve
|
|
310
|
+
quote_reserve: Current quote token reserve
|
|
311
|
+
tolerance_bps: Allowed deviation in basis points
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
True if amounts are within tolerance
|
|
315
|
+
"""
|
|
316
|
+
if base_reserve == 0 or quote_reserve == 0:
|
|
317
|
+
# Initial deposit - any ratio is acceptable
|
|
318
|
+
return True
|
|
319
|
+
|
|
320
|
+
# Calculate expected ratio
|
|
321
|
+
expected_ratio = quote_reserve * 10000 // base_reserve # Scale up for precision
|
|
322
|
+
actual_ratio = quote_amount * 10000 // base_amount
|
|
323
|
+
|
|
324
|
+
# Check if within tolerance
|
|
325
|
+
deviation = abs(actual_ratio - expected_ratio)
|
|
326
|
+
max_deviation = expected_ratio * tolerance_bps // 10000
|
|
327
|
+
|
|
328
|
+
return deviation <= max_deviation
|