diff --git a/src/decibel/_utils.py b/src/decibel/_utils.py index cadf922..4c813d7 100644 --- a/src/decibel/_utils.py +++ b/src/decibel/_utils.py @@ -35,6 +35,7 @@ "round_to_valid_order_size", "amount_to_chain_units", "chain_units_to_amount", + "bps_to_chain_units", "extract_vault_address_from_create_tx", "generate_random_replay_protection_nonce", ] @@ -385,6 +386,21 @@ def amount_to_chain_units(amount: float, decimals: int = 6) -> int: return round(amount * (10**decimals)) +def bps_to_chain_units(bps: int | float) -> int: + """Convert basis points (bps) to chain units for builder fees. + + The contract uses FEE_PRECISION = 10000 to represent 1%. + Since a basis point is 1/100th of a percent, multiply by 100. + + Args: + bps: Basis point value (e.g. 10 for 0.1%) + + Returns: + Chain units as integer (e.g. 10 bps -> 1000) + """ + return round(bps * 100) + + def chain_units_to_amount(chain_units: int, decimals: int = 6) -> float: """Convert chain units to a decimal amount (e.g., 5670000 -> 5.67).""" return chain_units / (10**decimals) diff --git a/src/decibel/write/__init__.py b/src/decibel/write/__init__.py index 894dc00..0a6bd67 100644 --- a/src/decibel/write/__init__.py +++ b/src/decibel/write/__init__.py @@ -16,6 +16,7 @@ from decibel._subaccount_types import RenameSubaccount, RenameSubaccountArgs from decibel._transaction_builder import InputEntryFunctionData from decibel._utils import ( + bps_to_chain_units, get_market_addr, get_primary_subaccount_addr, post_request, @@ -270,6 +271,7 @@ async def place_order( if sl_limit_price is not None and tick_size else sl_limit_price ) + final_builder_fee = bps_to_chain_units(builder_fee) if builder_fee is not None else None pkg = self._config.deployment.package @@ -293,7 +295,7 @@ async def _send(addr: str) -> dict[str, Any]: final_sl_trigger, final_sl_limit, builder_addr, - builder_fee, + final_builder_fee, ], ), account_override, @@ -359,6 +361,7 @@ async def place_twap_order( ) -> PlaceOrderResult: market_addr = get_market_addr(market_name, self._config.deployment.perp_engine_global) pkg = self._config.deployment.package + final_builder_fees = bps_to_chain_units(builder_fees) if builder_fees is not None else None async def _send(addr: str) -> dict[str, Any]: return await self._send_tx( @@ -375,7 +378,7 @@ async def _send(addr: str) -> dict[str, Any]: twap_frequency_seconds, twap_duration_seconds, builder_address, - builder_fees, + final_builder_fees, ], ), account_override, @@ -1049,17 +1052,18 @@ async def approve_max_builder_fee( self, *, builder_addr: str, - max_fee: int, + max_fee: int | float, subaccount_addr: str | None = None, ) -> dict[str, Any]: pkg = self._config.deployment.package + final_max_fee = bps_to_chain_units(max_fee) async def _send(addr: str) -> dict[str, Any]: return await self._send_tx( InputEntryFunctionData( function=f"{pkg}::dex_accounts_entry::approve_max_builder_fee_for_subaccount", type_arguments=[], - function_arguments=[addr, builder_addr, max_fee], + function_arguments=[addr, builder_addr, final_max_fee], ) ) @@ -1302,6 +1306,7 @@ def place_order( if sl_limit_price is not None and tick_size else sl_limit_price ) + final_builder_fee = bps_to_chain_units(builder_fee) if builder_fee is not None else None pkg = self._config.deployment.package @@ -1325,7 +1330,7 @@ def _send(addr: str) -> dict[str, Any]: final_sl_trigger, final_sl_limit, builder_addr, - builder_fee, + final_builder_fee, ], ), account_override, @@ -1391,6 +1396,7 @@ def place_twap_order( ) -> PlaceOrderResult: market_addr = get_market_addr(market_name, self._config.deployment.perp_engine_global) pkg = self._config.deployment.package + final_builder_fees = bps_to_chain_units(builder_fees) if builder_fees is not None else None def _send(addr: str) -> dict[str, Any]: return self._send_tx( @@ -1407,7 +1413,7 @@ def _send(addr: str) -> dict[str, Any]: twap_frequency_seconds, twap_duration_seconds, builder_address, - builder_fees, + final_builder_fees, ], ), account_override, @@ -2077,17 +2083,18 @@ def approve_max_builder_fee( self, *, builder_addr: str, - max_fee: int, + max_fee: int | float, subaccount_addr: str | None = None, ) -> dict[str, Any]: pkg = self._config.deployment.package + final_max_fee = bps_to_chain_units(max_fee) def _send(addr: str) -> dict[str, Any]: return self._send_tx( InputEntryFunctionData( function=f"{pkg}::dex_accounts_entry::approve_max_builder_fee_for_subaccount", type_arguments=[], - function_arguments=[addr, builder_addr, max_fee], + function_arguments=[addr, builder_addr, final_max_fee], ) ) diff --git a/src/decibel/write/_types.py b/src/decibel/write/_types.py index 381de50..11046b8 100644 --- a/src/decibel/write/_types.py +++ b/src/decibel/write/_types.py @@ -151,7 +151,7 @@ class CancelTpSlOrderArgs(TypedDict): class ApproveBuilderFeeArgs(TypedDict): builder_addr: str - max_fee: int + max_fee: int | float subaccount_addr: NotRequired[str | None]