跳转至

GMX V2

Field Value
Module almanak.connectors.gmx_v2
Protocol kind Perp
Aliases N/A

Supported Chains And Intents

Chain Family Supported Intents
Arbitrum EVM PERP_CANCEL_ORDER, PERP_CLOSE, PERP_OPEN
Avalanche EVM PERP_CANCEL_ORDER, PERP_CLOSE, PERP_OPEN

API Reference

almanak.connectors.gmx_v2

GMX v2 Connector.

This module provides an adapter for interacting with GMX v2 perpetuals protocol, supporting position management, order execution, and event parsing.

GMX v2 is a decentralized perpetual exchange supporting: - Long and short positions with leverage - Multiple collateral types - Limit and market orders - Position sizing and management

Supported chains: - Arbitrum - Avalanche

Example

from almanak.connectors.gmx_v2 import GMXv2Adapter, GMXv2Config

config = GMXv2Config( chain="arbitrum", wallet_address="0x...", ) adapter = GMXv2Adapter(config)

Open a position

result = adapter.open_position( market="ETH/USD", collateral_token="USDC", collateral_amount=Decimal("1000"), size_delta_usd=Decimal("5000"), is_long=True, )

GMXv2Adapter

GMXv2Adapter(
    config: GMXv2Config,
    token_resolver: TokenResolver | None = None,
)

Adapter for GMX v2 perpetuals protocol.

This adapter provides methods for: - Opening and closing positions - Increasing and decreasing position size - Managing limit orders and stop losses - Querying position and market data - Parsing transaction receipts

Example

config = GMXv2Config( chain="arbitrum", wallet_address="0x...", ) adapter = GMXv2Adapter(config)

Open a long position

result = adapter.open_position( market="ETH/USD", collateral_token="USDC", collateral_amount=Decimal("1000"), size_delta_usd=Decimal("5000"), is_long=True, )

Check position

position = adapter.get_position( market="ETH/USD", collateral_token="USDC", is_long=True, )

Close position

result = adapter.close_position( market="ETH/USD", collateral_token="USDC", is_long=True, size_delta_usd=position.size_in_usd, )

Initialize the adapter.

Parameters:

Name Type Description Default
config GMXv2Config

GMX v2 adapter configuration

required
token_resolver TokenResolver | None

Optional TokenResolver instance. If None, uses singleton.

None

open_position

open_position(
    market: str,
    collateral_token: str,
    collateral_amount: Decimal,
    size_delta_usd: Decimal,
    is_long: bool,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult

Open a new position or increase existing position.

Parameters:

Name Type Description Default
market str

Market identifier (e.g., "ETH/USD") or market address

required
collateral_token str

Token symbol or address for collateral

required
collateral_amount Decimal

Amount of collateral in token decimals

required
size_delta_usd Decimal

Position size in USD (will be scaled to 30 decimals)

required
is_long bool

True for long, False for short

required
acceptable_price Decimal | None

Maximum (long) or minimum (short) execution price

None
trigger_price Decimal | None

Trigger price for limit orders

None

Returns:

Type Description
OrderResult

OrderResult with order details

close_position

close_position(
    market: str,
    collateral_token: str,
    is_long: bool,
    size_delta_usd: Decimal | None = None,
    receive_token: str | None = None,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult

Close a position or decrease position size.

Parameters:

Name Type Description Default
market str

Market identifier or address

required
collateral_token str

Token symbol or address for collateral

required
is_long bool

Position direction

required
size_delta_usd Decimal | None

Amount to close in USD (None = close entire position)

None
receive_token str | None

Token to receive (defaults to collateral_token)

None
acceptable_price Decimal | None

Minimum (long) or maximum (short) execution price

None
trigger_price Decimal | None

Trigger price for limit orders

None

Returns:

Type Description
OrderResult

OrderResult with order details

increase_position

increase_position(
    market: str,
    collateral_token: str,
    is_long: bool,
    collateral_delta: Decimal,
    size_delta_usd: Decimal,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult

Increase an existing position.

This is an alias for open_position with an existing position.

Parameters:

Name Type Description Default
market str

Market identifier or address

required
collateral_token str

Token symbol or address

required
is_long bool

Position direction

required
collateral_delta Decimal

Additional collateral to add

required
size_delta_usd Decimal

Additional size in USD

required
acceptable_price Decimal | None

Maximum (long) or minimum (short) execution price

None
trigger_price Decimal | None

Trigger price for limit orders

None

Returns:

Type Description
OrderResult

OrderResult with order details

decrease_position

decrease_position(
    market: str,
    collateral_token: str,
    is_long: bool,
    size_delta_usd: Decimal,
    collateral_delta: Decimal = Decimal("0"),
    receive_token: str | None = None,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult

Decrease an existing position.

This is similar to close_position but for partial closes.

Parameters:

Name Type Description Default
market str

Market identifier or address

required
collateral_token str

Token symbol or address

required
is_long bool

Position direction

required
size_delta_usd Decimal

Size to reduce in USD

required
collateral_delta Decimal

Collateral to withdraw

Decimal('0')
receive_token str | None

Token to receive

None
acceptable_price Decimal | None

Minimum (long) or maximum (short) execution price

None
trigger_price Decimal | None

Trigger price for limit orders

None

Returns:

Type Description
OrderResult

OrderResult with order details

get_position

get_position(
    market: str, collateral_token: str, is_long: bool
) -> GMXv2Position | None

Get position details.

Parameters:

Name Type Description Default
market str

Market identifier or address

required
collateral_token str

Token symbol or address

required
is_long bool

Position direction

required

Returns:

Type Description
GMXv2Position | None

Position details or None if not found

get_all_positions

get_all_positions() -> list[GMXv2Position]

Get all open positions from in-memory state.

Returns:

Type Description
list[GMXv2Position]

List of all open positions (in-memory only, not on-chain)

get_positions_onchain

get_positions_onchain(
    rpc_url: str | None = None,
    gateway_client: GatewayClient | None = None,
) -> list[GMXv2Position]

Read all open positions for this wallet directly from on-chain state.

Uses the GMX V2 SyntheticsReader contract to query the DataStore for all positions belonging to the configured wallet address. Includes fallback mechanisms for when Reader methods revert (e.g. after GMX contract upgrades).

Parameters:

Name Type Description Default
rpc_url str | None

RPC endpoint URL for on-chain queries

None

Returns:

Type Description
list[GMXv2Position]

List of GMXv2Position objects read from chain

Raises:

Type Description
ValueError

If the chain is not supported for on-chain reads

get_positions_as_teardown_summary

get_positions_as_teardown_summary(
    rpc_url: str | None = None,
    deployment_id: str = "",
    gateway_client: GatewayClient | None = None,
) -> TeardownPositionSummary

Read on-chain positions and return as TeardownPositionSummary.

This is the primary method for integrating with the teardown system. It reads positions directly from chain and converts them to the PositionInfo format used by get_open_positions().

Parameters:

Name Type Description Default
rpc_url str | None

DEPRECATED — RPC endpoint URL. Prefer gateway_client.

None
deployment_id str

Deployment identifier for the summary

''
gateway_client GatewayClient | None

Gateway client for routing eth_call through the gateway. Preferred over rpc_url.

None

Returns:

Type Description
TeardownPositionSummary

TeardownPositionSummary with on-chain position data

cancel_order

cancel_order(order_key: str) -> OrderResult

Cancel a pending order.

Parameters:

Name Type Description Default
order_key str

Order key to cancel

required

Returns:

Type Description
OrderResult

OrderResult indicating success/failure

get_order

get_order(order_key: str) -> GMXv2Order | None

Get order details.

Parameters:

Name Type Description Default
order_key str

Order key to look up

required

Returns:

Type Description
GMXv2Order | None

Order details or None if not found

get_all_orders

get_all_orders() -> list[GMXv2Order]

Get all pending orders.

Returns:

Type Description
list[GMXv2Order]

List of all pending orders

build_cancel_order_tx

build_cancel_order_tx(order_key: str) -> TransactionData

Build ExchangeRouter.cancelOrder(bytes32) transaction data for an order key.

Pure and stateless: keyed only by the on-chain order_key (bytes32), with no dependency on the adapter's in-memory _orders tracking. This is what the teardown recovery lane (VIB-5568) uses to cancel a discovered stranded order (fresh process, nothing tracked) — unlike :meth:cancel_order, which requires the order to be locally tracked and is blind to teardown-discovered residuals.

value=0 — a cancel carries no keeper execution fee; the OrderVault refund (committed collateral + unspent exec fee) lands in the wallet (cancellationReceiver defaults to the caller).

set_position

set_position(position: GMXv2Position) -> None

Set a position for testing.

Parameters:

Name Type Description Default
position GMXv2Position

Position to set

required

clear_positions

clear_positions() -> None

Clear all positions.

clear_orders

clear_orders() -> None

Clear all orders.

clear_all

clear_all() -> None

Clear all state.

GMXv2Config dataclass

GMXv2Config(
    chain: str,
    wallet_address: str,
    execution_fee: int | None = None,
    referral_code: bytes = b"\x00" * 32,
)

Configuration for GMXv2Adapter.

Attributes:

Name Type Description
chain str

Target blockchain (arbitrum or avalanche)

wallet_address str

Address executing transactions

execution_fee int | None

Execution fee in native token wei (auto-set per chain)

referral_code bytes

Optional referral code for fee discounts

__post_init__

__post_init__() -> None

Validate configuration and set defaults.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

GMXv2Order dataclass

GMXv2Order(
    order_key: str,
    market: str,
    initial_collateral_token: str,
    order_type: GMXv2OrderType,
    is_long: bool,
    size_delta_usd: Decimal,
    initial_collateral_delta_amount: Decimal,
    trigger_price: Decimal | None = None,
    acceptable_price: Decimal | None = None,
    execution_fee: int = 0,
    callback_gas_limit: int = 0,
    is_frozen: bool = False,
    created_at: datetime = (lambda: datetime.now(UTC))(),
    updated_at: datetime = (lambda: datetime.now(UTC))(),
)

Represents a GMX v2 order.

Attributes:

Name Type Description
order_key str

Unique identifier for the order

market str

Market address

initial_collateral_token str

Collateral token for the order

order_type GMXv2OrderType

Type of order

is_long bool

Position direction

size_delta_usd Decimal

Size change in USD (30 decimals)

initial_collateral_delta_amount Decimal

Collateral amount change

trigger_price Decimal | None

Trigger price for limit/stop orders

acceptable_price Decimal | None

Maximum/minimum acceptable execution price

execution_fee int

Fee paid to keeper

callback_gas_limit int

Gas limit for callback execution

is_frozen bool

Whether order is frozen

created_at datetime

Order creation timestamp

updated_at datetime

Last update timestamp

is_increase property

is_increase: bool

Check if order increases position size.

is_decrease property

is_decrease: bool

Check if order decreases position size.

is_market_order property

is_market_order: bool

Check if order is a market order.

is_limit_order property

is_limit_order: bool

Check if order is a limit order.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

from_dict classmethod

from_dict(data: dict[str, Any]) -> GMXv2Order

Create from dictionary.

GMXv2OrderType

Bases: Enum

GMX v2 order types.

to_int

to_int() -> int

Convert to GMX v2 order type integer.

GMXv2Position dataclass

GMXv2Position(
    position_key: str,
    market: str,
    collateral_token: str,
    size_in_usd: Decimal,
    size_in_tokens: Decimal,
    collateral_amount: Decimal,
    entry_price: Decimal,
    is_long: bool,
    realized_pnl: Decimal = Decimal("0"),
    unrealized_pnl: Decimal = Decimal("0"),
    leverage: Decimal = Decimal("1"),
    liquidation_price: Decimal | None = None,
    funding_fee_amount: Decimal = Decimal("0"),
    borrowing_fee_amount: Decimal = Decimal("0"),
    last_updated: datetime = (lambda: datetime.now(UTC))(),
)

Represents an open GMX v2 position.

Attributes:

Name Type Description
position_key str

Unique identifier for the position

market str

Market address

collateral_token str

Token used as collateral

size_in_usd Decimal

Position size in USD (30 decimals)

size_in_tokens Decimal

Position size in index tokens (token decimals)

collateral_amount Decimal

Collateral amount in token decimals

entry_price Decimal

Average entry price (30 decimals)

is_long bool

True for long, False for short

realized_pnl Decimal

Realized PnL (30 decimals)

unrealized_pnl Decimal

Unrealized PnL (30 decimals)

leverage Decimal

Current leverage (size / collateral)

liquidation_price Decimal | None

Price at which position gets liquidated

funding_fee_amount Decimal

Accumulated funding fees

borrowing_fee_amount Decimal

Accumulated borrowing fees

last_updated datetime

Timestamp of last update

side property

side: GMXv2PositionSide

Get position side.

total_fees property

total_fees: Decimal

Get total accumulated fees.

net_pnl property

net_pnl: Decimal

Get net PnL after fees.

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

from_dict classmethod

from_dict(data: dict[str, Any]) -> GMXv2Position

Create from dictionary.

GMXv2PositionSide

Bases: Enum

Position side (long/short).

GMXv2Event dataclass

GMXv2Event(
    event_type: GMXv2EventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)

Parsed GMX v2 event.

Attributes:

Name Type Description
event_type GMXv2EventType

Type of event

event_name str

Name of event (e.g., "PositionIncrease")

log_index int

Index of log in transaction

transaction_hash str

Transaction hash

block_number int

Block number

contract_address str

Contract that emitted event

data dict[str, Any]

Parsed event data

raw_topics list[str]

Raw event topics

raw_data str

Raw event data

timestamp datetime

Event timestamp

to_dict

to_dict() -> dict[str, Any]

Convert to dictionary.

from_dict classmethod

from_dict(data: dict[str, Any]) -> GMXv2Event

Create from dictionary.

GMXv2EventType

Bases: Enum

GMX v2 event types.

GMXv2ReceiptParser

GMXv2ReceiptParser(**kwargs: Any)

Parser for GMX v2 transaction receipts.

This parser extracts and decodes GMX v2 events from transaction receipts, providing structured data for position updates, order fills, and other protocol events.

SUPPORTED_EXTRACTIONS declares the extraction fields this parser can provide. Used by ResultEnricher to warn when expected fields are unsupported.

Example

parser = GMXv2ReceiptParser()

Parse a receipt dict (from web3.py)

result = parser.parse_receipt(receipt)

if result.success: for event in result.events: print(f"Event: {event.event_name}")

for increase in result.position_increases:
    print(f"Position increased: size=${increase.size_in_usd}")

Initialize the parser.

Parameters:

Name Type Description Default
chain

EVM chain slug (e.g. "arbitrum"). Used ONLY to resolve a market's index-token decimals when scaling executionPrice to USD-per-token (VIB-6110). When absent, entry_price/exit_price are left UNMEASURED (None) rather than shipped as the raw GMX-native ratio — the receipt decoder stays chain-agnostic for every other field.

required
**kwargs Any

Additional arguments (ignored for compatibility).

{}

build_extract_kwargs staticmethod

build_extract_kwargs(
    *, field: str, bundle_metadata: dict[str, Any]
) -> dict[str, Any]

Thread compiler-verified index decimals into perp fill scaling.

parse_receipt

parse_receipt(receipt: dict[str, Any]) -> ParseResult

Parse a transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict containing 'logs', 'transactionHash', 'blockNumber', etc.

required

Returns:

Type Description
ParseResult

ParseResult with extracted events and data

parse_logs

parse_logs(logs: list[dict[str, Any]]) -> list[GMXv2Event]

Parse a list of logs.

Parameters:

Name Type Description Default
logs list[dict[str, Any]]

List of log dicts

required

Returns:

Type Description
list[GMXv2Event]

List of parsed events

is_gmx_event

is_gmx_event(topic: str | bytes) -> bool

Check if a topic is a known GMX v2 event.

Parameters:

Name Type Description Default
topic str | bytes

Event topic (supports bytes, hex string with/without 0x, any case)

required

Returns:

Type Description
bool

True if topic is a known GMX v2 event

get_event_type

get_event_type(topic: str | bytes) -> GMXv2EventType

Get the event type for a topic.

Parameters:

Name Type Description Default
topic str | bytes

Event topic (supports bytes, hex string with/without 0x, any case)

required

Returns:

Type Description
GMXv2EventType

Event type or UNKNOWN

extract_swap_amounts_result

extract_swap_amounts_result(
    receipt: dict[str, Any],
) -> ExtractResult[Any]

Fail-closed variant of :meth:extract_swap_amounts — see VIB-3159.

extract_async_orders_result

extract_async_orders_result(
    receipt: dict[str, Any],
    *,
    intent_type: str | None = None,
) -> ExtractResult[list[AsyncOrderData]]

Extract authoritative GMX OrderCreated identifiers.

extract_async_orders

extract_async_orders(
    receipt: dict[str, Any],
    *,
    intent_type: str | None = None,
) -> list[AsyncOrderData] | None

Legacy-compatible raw accessor for authoritative created orders.

extract_position_id_result

extract_position_id_result(
    receipt: dict[str, Any],
) -> ExtractResult[str]

Fail-closed variant of :meth:extract_position_id — see VIB-3159.

extract_size_delta_result

extract_size_delta_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_size_delta — see VIB-3159.

extract_collateral_result

extract_collateral_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_collateral — see VIB-3159.

extract_entry_price_result

extract_entry_price_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_entry_price — see VIB-3159.

extract_leverage_result

extract_leverage_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_leverage — see VIB-3159.

extract_realized_pnl_result

extract_realized_pnl_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_realized_pnl — see VIB-3159.

extract_exit_price_result

extract_exit_price_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_exit_price — see VIB-3159.

extract_fees_paid_result

extract_fees_paid_result(
    receipt: dict[str, Any],
) -> ExtractResult[int]

Fail-closed variant of :meth:extract_fees_paid — see VIB-3159.

extract_collateral_returned_result

extract_collateral_returned_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_collateral_returned — see VIB-3159.

extract_swap_amounts

extract_swap_amounts(
    receipt: dict[str, Any],
    *,
    expected_out: Decimal | None = None,
) -> Any

Extract swap amounts from transaction receipt.

GMX V2 "swaps" are executed through perpetual orders, not spot swaps. For GMX orders: - amount_in = initial_collateral_delta_amount (collateral deposited) - amount_out = size_delta_usd (position size in USD, scaled by 1e30) - effective_price represents the leverage ratio

The VIB-3203 expected_out kwarg is accepted for interface parity with spot-swap parsers, but NOT used to compute slippage_bps — comparing "realized collateral" to a "quoted collateral" is not the same semantic as realized vs quoted swap output, and would produce misleading slippage values. Slippage reporting for GMX V2 perps (acceptable price vs execution price) is a separate semantic and is out of scope for VIB-3203.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required
expected_out Decimal | None

Accepted but ignored — see docstring.

None

Returns:

Type Description
Any

SwapAmounts dataclass if swap order found, None otherwise

extract_position_id

extract_position_id(receipt: dict[str, Any]) -> str | None

Extract position ID (key) from transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
str | None

Position key if found, None otherwise

extract_size_delta

extract_size_delta(
    receipt: dict[str, Any],
) -> Decimal | None

Extract size delta (in USD) from transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
Decimal | None

Size delta in USD if found, None otherwise

extract_collateral

extract_collateral(
    receipt: dict[str, Any],
) -> Decimal | None

Extract collateral amount from transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
Decimal | None

Collateral amount if found, None otherwise

extract_entry_price

extract_entry_price(
    receipt: dict[str, Any],
) -> Decimal | None

Extract entry price from transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
Decimal | None

Entry price in USD if found, None otherwise

extract_leverage

extract_leverage(receipt: dict[str, Any]) -> Decimal | None

Extract leverage from transaction receipt.

Leverage is calculated as size_in_usd / (collateral_amount * collateral_token_price).

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
Decimal | None

Leverage multiplier (e.g., Decimal("10") for 10x) if found, None otherwise.

extract_realized_pnl

extract_realized_pnl(
    receipt: dict[str, Any],
) -> Decimal | None

Extract realized PnL from transaction receipt.

Only available for position decreases (closing/reducing positions).

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
Decimal | None

Realized PnL in USD if found, None otherwise

extract_exit_price

extract_exit_price(
    receipt: dict[str, Any],
) -> Decimal | None

Extract exit price from transaction receipt.

Only available for position decreases (closing/reducing positions).

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
Decimal | None

Exit price in USD if found, None otherwise

extract_collateral_returned

extract_collateral_returned(
    receipt: dict[str, Any],
) -> Decimal | None

Extract collateral returned at close from a PERP_CLOSE receipt.

Sums raw collateralDeltaAmount values (the collateral withdrawn from the position) across every PositionDecrease event in the receipt. For a full close GMX sets the delta to the position's entire remaining collateral, so this is the raw collateral-token leg of the close payout. Raw smallest-unit semantics match the other perpetual receipt parsers and allow exact wallet-delta reconciliation without a guessed decimal scale.

Semantics — what this is NOT: the net wallet credit (GMX's outputAmount = collateral delta ± realized PnL − fees, optionally swapped to another token) lives in the EventUtils.EventLogData payload and is distinct from this collateral delta. PnL and fees are extracted separately (realized_pnl, fees_paid).

Empty != Zero: only values the decoder actually produced are summed. Events whose decode fell back to raw_data carry no collateral_delta_amount key and are skipped; if no event carries a decoded value this returns None (unmeasured), never a fabricated zero. A decoded 0 (size-only decrease) is a measured zero and is returned as Decimal("0").

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
Decimal | None

Total collateral withdrawn across PositionDecrease events, or None

Decimal | None

when no event carries a decoded collateral_delta_amount.

extract_fees_paid

extract_fees_paid(receipt: dict[str, Any]) -> int | None

Extract fees paid from transaction receipt.

Parameters:

Name Type Description Default
receipt dict[str, Any]

Transaction receipt dict with 'logs' field

required

Returns:

Type Description
int | None

Execution fee in wei if found, None otherwise.

extract_funding_fee_usd_result

extract_funding_fee_usd_result(
    receipt: dict[str, Any],
) -> ExtractResult[Decimal]

Fail-closed variant of :meth:extract_funding_fee_usd — see VIB-3159.

extract_funding_fee_usd

extract_funding_fee_usd(
    receipt: dict[str, Any],
) -> Decimal | None

Extract accumulated funding fee in USD from a CLOSE receipt (VIB-3497).

GMX V2 emits a PositionFeesCollected event alongside every PositionDecrease. The fundingFeeAmount field (collateral-token units) lives inside that event's keyed EventUtils.EventLogData payload. This decodes it BY NAME (VIB-3873) and converts it to USD with the SAME event's collateralTokenPrice bounds — a decimals-free conversion (amount * price / 1e30) that needs no live oracle read, so the parser stays a pure function of the receipt.

Empty != Zero: returns None when the receipt carries no PositionFeesCollected event (or the funding amount / price could not be decoded) — "funding cost unknown" — never a fabricated 0. A measured zero funding fee (short hold) returns Decimal("0").

Returns:

Type Description
Decimal | None

Funding fee in USD, or None when unmeasured.

extract_keeper_execution_fee

extract_keeper_execution_fee(
    receipt: dict[str, Any], *, account: str | None = None
) -> tuple[int | None, int | None]

Native-wei (keeper_fee, refund) from a keeper receipt (VIB-6061).

Returns (None, None) when the receipt carries no unambiguous, authentic, account-owned pair — see :meth:_select_execution_fee_events. Never raises: a malformed receipt is unmeasured, not an error, because this rides alongside the fill economics and must never take a settlement write down with it.

extract_perp_fill_result

extract_perp_fill_result(
    receipt: dict[str, Any],
    order_key: str | None = None,
    account: str | None = None,
    index_token_decimals_override: int | None = None,
) -> ExtractResult[PerpFillData]

Fail-closed variant of :meth:extract_perp_fill — see VIB-3159.

account is threaded through (VIB-6061) so this wrapper measures the same fields as the method it wraps. Dropping it here would leave the keeper execution fee unmeasured BY CONSTRUCTION for every caller of the fail-closed variant — a silent divergence between two functions whose whole contract is that they differ only in error handling.

extract_perp_fill

extract_perp_fill(
    receipt: dict[str, Any],
    order_key: str | None = None,
    account: str | None = None,
    index_token_decimals_override: int | None = None,
) -> PerpFillData | None

Build typed fill economics from a GMX keeper receipt (VIB-3872 WI-1).

Merges the receipt's PositionIncrease or PositionDecrease event (position identity, execution price, size / collateral deltas, price impact, realized PnL) with its PositionFeesCollected event (funding / position / borrowing fees in USD). Every field is decoded BY NAME from the keyed EventUtils payload and follows Empty != Zero.

order_key is the settlement being measured (VIB-6110). A GMX keeper transaction may execute SEVERAL orders — on different markets, in different directions — so the position and fee events must be correlated to the watched order rather than taken first-wins. Uncorrelated, closing order B inside a batch that also opened order A returns A's fill: is_open=True with A's market and A's entry price, and B's real exit price is lost. Because the impostor price is itself plausible, nothing downstream can detect the substitution.

Returns None when the receipt carries no PositionIncrease / PositionDecrease event attributable to order_key (nothing to settle) — callers map that to UNMEASURED rather than to a fill. A keeper receipt that only yields the position event (no matching fees event) still returns a PerpFillData with the fee fields left None (unmeasured). Passing order_key=None keeps the legacy first-wins behaviour for callers that have no watched order.

Source authentication. When order_key is supplied, only logs emitted by the chain's canonical GMX EventEmitter are considered. Correlating on an orderKey decoded from an unauthenticated log would not be correlation at all: _parse_log recognises GMX events by topic hash alone and never checks log["address"], and a GMX order carries an owner-chosen callbackContract that runs inside the very keeper transaction this method parses. A co-batched adversary can read the order nonce from DataStore, compute our key, and emit a forged PositionDecrease carrying it with an arbitrary market and price — moving the attack from "be first in the log list" to "write the right 32 bytes". Restricting to the EventEmitter is what makes the decoded orderKey trustworthy. If the emitter cannot be resolved (unknown or absent chain) a correlated call fails closed to None: an unmeasured settlement is recoverable, a forged one is not.

The emitter check runs against each PARSED event's contract_address (see :meth:_select_fill_events), never by pre-filtering the receipt's logs — a filtered receipt keeps its transactionHash, which is the key ResultEnricher's parse cache uses, so pre-filtering is defeatable by a warmed cache.

account (VIB-6061) is the strategy wallet. Supplying it additionally measures the keeper execution fee — the native-token cost of the fill that is NOT transaction gas and that the Cost Stack previously showed nowhere. Omitting it leaves keeper_execution_fee_wei unmeasured; it never affects the fill economics above.

extract_protocol_fees

extract_protocol_fees(_receipt: dict[str, Any]) -> None

Placeholder for GMX V2 perp-fee extraction (VIB-3204).

GMX V2 encodes open / close fees in PositionFeesInfo events emitted by the position handler. Decoding those events (including borrowing, funding, and execution-fee components) is non-trivial and is deferred to a follow-up; extract_fees_paid already surfaces the execution fee in wei for operator-level accounting.

Follow-up ticket: "Perps fee extraction for GMX V2 / Drift — follow-up to VIB-3204".

PerpFillData dataclass

PerpFillData(
    is_open: bool | None = None,
    is_long: bool | None = None,
    market: str | None = None,
    collateral_token: str | None = None,
    position_key: str | None = None,
    order_key: str | None = None,
    entry_price: Decimal | None = None,
    exit_price: Decimal | None = None,
    size_delta_usd: Decimal | None = None,
    size_delta_in_tokens: Decimal | None = None,
    collateral_delta_amount: Decimal | None = None,
    price_impact_usd: Decimal | None = None,
    realized_pnl_usd: Decimal | None = None,
    position_fee_usd: Decimal | None = None,
    funding_fee_usd: Decimal | None = None,
    borrowing_fee_usd: Decimal | None = None,
    keeper_tx_hash: str | None = None,
    block_number: int | None = None,
    keeper_execution_fee_wei: int | None = None,
    execution_fee_refund_wei: int | None = None,
)

Receipt-measured GMX V2 fill economics (VIB-3873 / VIB-3872 WI-1).

Built from a keeper transaction's PositionIncrease or PositionDecrease event, merged with its PositionFeesCollected event, each decoded BY KEY from the dynamic EventUtils.EventLogData payload (not fixed byte offsets — that is the VIB-3873 misread class). This is the typed verdict payload the WI-2 settlement capability carries into accounting; it is intentionally self-contained (no framework imports) so WI-2 can consume it without new plumbing.

Empty != Zero — every field is Optional. None means the keeper receipt did not carry that field (unmeasured); Decimal("0") means a measured zero (e.g. zero funding over a short hold). Never substitute one for the other.

Scaling:

  • size_delta_usd / price_impact_usd / realized_pnl_usd are plain USD Decimals (GMX's 30-decimal USD convention divided out).
  • position_fee_usd / funding_fee_usd / borrowing_fee_usd are plain USD, converted from the fee's collateral-token amount using the SAME PositionFeesCollected event's collateralTokenPrice (decimals-free: amount * price / 1e30).
  • entry_price / exit_price are USD-per-token: GMX executionPrice scaled by 10**(index_token_decimals) / 1e30 (VIB-6110). The index-token decimals are resolved from the parser's chain + the fill's market via the venue-verified catalog. Empty≠Zero: None when the price is absent OR the decimals cannot be resolved (parser constructed without a chain, or an unlisted market) — NEVER the wrongly-scaled raw GMX-native ratio (the 1.9e-15 bug this field previously shipped).
  • collateral_delta_amount is the raw collateral-token smallest-unit integer (matches extract_collateral_returned semantics — no guessed decimal scale).
  • keeper_execution_fee_wei / execution_fee_refund_wei are NATIVE-token wei integers, not USD (VIB-6061). They are the two halves of the escrow we posted as msg.value at createOrder: the keeper's cut and our refund. Kept in wei rather than converted here because the parser is a pure function of the receipt and holds no price oracle — the USD conversion happens at the settlement-commit seam against the SUBMISSION row's own price_inputs_json, so the booked USD is priced at the same moment as that row's gas_usd.

to_dict

to_dict() -> dict[str, Any]

Convert to a stable machine-readable dictionary (Empty != Zero preserved).

GMXV2SDK

GMXV2SDK(
    rpc_url: str | None = None,
    chain: str = "arbitrum",
    gateway_client: GatewayClient | None = None,
)

SDK for interacting with GMX V2 perpetuals on Arbitrum.

This SDK builds transactions for creating orders using the ExchangeRouter's multicall function which atomically: 1. Sends collateral to OrderVault (via sendWnt or sendTokens) 2. Creates the order

Initialize GMX V2 SDK.

Parameters:

Name Type Description Default
rpc_url str | None

DEPRECATED — direct RPC URL. Prefer gateway_client for any code path running in a strategy container.

None
chain str

Target chain — any key in GMX_V2_SDK_ADDRESSES (currently arbitrum and avalanche; see almanak/core/contracts.py:GMX_V2).

'arbitrum'
gateway_client GatewayClient | None

Gateway client for routing eth_call through the gateway. Preferred over rpc_url.

None

get_account_position_count

get_account_position_count(account: str) -> int

Get the number of open positions for an account.

Tries SyntheticsReader.getAccountPositionCount first. If it reverts (e.g. after a GMX contract upgrade removed the function), falls back to a DataStore getBytes32Count query using the account position list key.

Parameters:

Name Type Description Default
account str

Wallet address to query

required

Returns:

Type Description
int

Number of open positions (0 if both methods fail)

get_account_positions

get_account_positions(account: str) -> list[dict]

Read all open positions for an account.

Tries on-chain Reader contract first, then falls back to GMX REST API when Reader calls revert (common after GMX contract upgrades).

Parameters:

Name Type Description Default
account str

Wallet address to query

required

Returns:

Type Description
list[dict]

List of position dicts with keys: account, market, collateral_token,

list[dict]

size_in_usd, size_in_tokens, collateral_amount, borrowing_factor,

list[dict]

funding_fee_amount_per_size, is_long, increased_at_time, decreased_at_time

get_market_address

get_market_address(index_token_symbol: str) -> str

Get GMX V2 market address for an index token.

Parameters:

Name Type Description Default
index_token_symbol str

"ETH" or "BTC"

required

Returns:

Type Description
str

Market address

get_execution_fee

get_execution_fee(
    order_type: str = "increase", multiplier: float = 1.5
) -> int

Calculate execution fee for GMX order dynamically.

GMX V2 validates: executionFee >= adjustedGasLimit * tx.gasprice where adjustedGasLimit = baseGasLimit + orderGasLimit * multiplierFactor (callbackGasLimit = 0 for our orders).

Parameters:

Name Type Description Default
order_type str

"increase" or "decrease" to select appropriate gas limit

'increase'
multiplier float

Safety multiplier on top of the adjusted gas limit (default 1.5x for testing, use 2.0x for production)

1.5

Returns:

Type Description
int

Execution fee in wei

build_increase_order_multicall

build_increase_order_multicall(
    params: GMXV2OrderParams,
) -> GMXV2TransactionData

Build a multicall transaction to create an increase order.

This combines: 1. sendWnt or sendTokens (collateral to OrderVault) 2. createOrder

Parameters:

Name Type Description Default
params GMXV2OrderParams

Order parameters

required

Returns:

Type Description
GMXV2TransactionData

Transaction data ready for execution

build_decrease_order_multicall

build_decrease_order_multicall(
    params: GMXV2OrderParams,
) -> GMXV2TransactionData

Build a multicall transaction to create a decrease order.

For decrease orders, no collateral needs to be sent to OrderVault. Only the execution fee is needed, sent via sendWnt.

Parameters:

Name Type Description Default
params GMXV2OrderParams

Order parameters

required

Returns:

Type Description
GMXV2TransactionData

Transaction data ready for execution

DecreasePositionSwapType

Bases: IntEnum

GMX V2 Decrease Position Swap Types

GMXV2OrderParams dataclass

GMXV2OrderParams(
    from_address: str,
    market: str,
    initial_collateral_token: str,
    initial_collateral_delta_amount: int,
    size_delta_usd: int,
    is_long: bool,
    acceptable_price: int,
    execution_fee: int,
    trigger_price: int = 0,
    referral_code: bytes = b"\x00" * 32,
)

Parameters for creating a GMX V2 order.

GMXV2TransactionData dataclass

GMXV2TransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
)

Transaction data returned by the SDK.

OrderType

Bases: IntEnum

GMX V2 Order Types

get_allowed_collaterals

get_allowed_collaterals(
    chain: str, market: str
) -> tuple[str, ...]

Return the tuple of allowed collateral token symbols for a market.

Parameters:

Name Type Description Default
chain str

Chain name ("arbitrum" or "avalanche"). Case-insensitive.

required
market str

Canonical venue market label (e.g. "ETH/USD"). Case-insensitive.

required

Returns:

Type Description
str

Tuple of allowed collateral symbols. The tuple is non-empty for any

...

registered (chain, market) pair.

Raises:

Type Description
KeyError

If the market is not registered for the chain. Callers that want the "unknown market" path should call :func:is_market_registered first.

is_market_registered

is_market_registered(chain: str, market: str) -> bool

Return True if collateral rules are known for (chain, market).

Used by the compiler to distinguish between "market is unknown, cannot validate locally" (permissive path) and "market is known and the collateral is wrong" (strict reject path).

Chain and market inputs are case-insensitive.

registered_markets

registered_markets(chain: str) -> Iterable[str]

Return the iterable of market identifiers registered for a chain.

Useful for diagnostics / operator card messages. Chain input is case-insensitive.

validate_collateral

validate_collateral(
    chain: str, market: str, collateral_token: str
) -> None

Validate that collateral_token is a legal collateral for market.

Compile-path validation. This is the main entry point used by the _compile_perp_open path in the intent compiler. It must be called BEFORE any transaction actions are emitted.

Behaviour
  • If market is registered for chain and collateral_token is not one of the allowed symbols, raise :class:InvalidCollateralForMarketError.
  • If market is registered and collateral_token is recognised but passed as a raw 0x-address, the validation is skipped with a debug log and the compiler falls through to address-based resolution.
  • If market is NOT registered (unknown / new market), log a warning and return (permissive). The strict on-chain check still fires at order-time via the GMX keeper, but this module cannot do better without an RPC round trip.

Parameters:

Name Type Description Default
chain str

Chain name ("arbitrum" or "avalanche"). Case-insensitive.

required
market str

Market identifier (e.g. "ETH/USD"). Case-insensitive.

required
collateral_token str

Collateral token symbol (e.g. "USDC") or 0x-address. Comparison is case-insensitive for symbols; raw addresses are detected case-insensitively (0x... or 0X...).

required

Raises:

Type Description
InvalidCollateralForMarketError

When both the market is registered AND the collateral is a symbol that is not in the allowed set.

get_gmx_v2_sdk

get_gmx_v2_sdk(
    rpc_url: str | None = None,
    chain: str = "arbitrum",
    gateway_client: GatewayClient | None = None,
) -> GMXV2SDK

Factory function to create a GMX V2 SDK instance.

__getattr__

__getattr__(name: str) -> Any

PEP 562 lazy attribute access.