5/5
## Analyzing a GMX V2 Deposit Order Transaction with Tenderly This lesson demonstrates how to use the Tenderly transaction debugger to analyze the process of creating an order to deposit liquidity into a GMX V2 GM (Global Market) pool on the Arbitrum network. We will trace the execution flow of an example transaction targeting the ETH/USDC pool, examining the contract interactions and key parameters involved. ## Transaction Overview: Depositing Liquidity into a GMX GM Pool The objective of the transaction under analysis is to create a pending order to deposit Wrapped Ether (WETH) and USDC into the GMX V2 ETH/USDC GM pool. We utilize the Tenderly platform, focusing on its transaction debugging capabilities. Initially, the Tenderly interface displays the summary of a successful transaction. The "Tokens transferred" section provides a high-level view, showing WETH being minted (if applicable, from ETH), transferred from the user to the `ExchangeRouter` contract, and subsequently from the `ExchangeRouter` to the `DepositVault` contract. This initial transfer hints at fee mechanics, which we'll explore further in the trace. ## Deconstructing the Execution Trace: The `multicall` Entry Point Observing the transaction's execution trace in Tenderly reveals the primary interaction starts with the user's address calling the `multicall` function on the GMX `ExchangeRouter` contract. * **Code Context:** `[Sender] 0xd24cba...f49e => [Receiver] ExchangeRouter).multicall(data = [0x7d39aaf1...])` The `multicall` pattern is frequently used in DeFi protocols to bundle several distinct actions into a single, atomic transaction. This improves user experience and gas efficiency compared to sending multiple separate transactions. In this case, `multicall` orchestrates both the fee payment and the order creation logic. ## Pre-paying the Execution Fee: The `sendWnt` Call Within the `multicall` execution, the first significant internal function call is to `ExchangeRouter.sendWnt`. * **Code Context:** `[Receiver] ExchangeRouter.sendWnt(receiver = 0xf89e..., amount = 1040...)` * **Purpose:** This function is responsible for transferring the *execution fee* required by the GMX protocol to process asynchronous orders. * **Parameters:** * `receiver`: `0xf89e77e8dc11691c9e8757e84aafbcd8a67d7a55` - Identified via Arbiscan as the GMX `DepositVault` contract address. * `amount`: `1040420991000000` wei (equivalent to 0.001040420991 WETH on Arbitrum). * **Concept:** GMX utilizes keepers to execute pending orders (like deposits, withdrawals, swaps) when specific conditions are met. To incentivize keepers and cover their gas costs, users must pre-pay an execution fee in the network's wrapped native token (WNT – WETH on Arbitrum). This fee is sent to and held by the `DepositVault` until an order is executed. ## Routing the Main Action: The `fallback` Function The second internal call executed within the `multicall` bundle targets the `ExchangeRouter.fallback` function. * **Code Context:** `[Receiver] ExchangeRouter.fallback(0xb4e9561...)` * **Purpose:** In this architecture, the `fallback` function acts as a router. It receives encoded data (`0xb4e9561...`) which contains the instructions for the primary action the user intends to perform – in this instance, creating a deposit order. The `ExchangeRouter` decodes this data and directs the execution flow accordingly. ## Initiating the Deposit: The `createDeposit` Call Chain Expanding the `fallback` call in the Tenderly trace reveals that it internally triggers a call to the `ExchangeRouter.createDeposit` function. * **Code Context (within fallback):** `[Receiver] ExchangeRouter.createDeposit(params = null) => (0x0ff0...)` Notably, the high-level trace might initially display `params = null`. This often indicates that the parameters are passed in a format (like `calldata`) that the top-level trace view doesn't fully decode. Further expanding this step shows that `ExchangeRouter.createDeposit` doesn't contain the core deposit logic itself. Instead, it acts as a proxy or entry point, delegating the actual work to the `DepositHandler` contract. * **Code Context (deeper call):** `([Receiver] ExchangeRouter -> DepositHandler).createDeposit(account = 0xd24cba..., ...)` To understand the specifics, we select the call to `DepositHandler.createDeposit` and utilize Tenderly's "Debug" feature to step into the code-level execution. ## Debugging `createDeposit`: Understanding `calldata` Parameters The Tenderly Debugger loads the source code for the `ExchangeRouter.createDeposit` function. Examining its signature provides a key insight: ```solidity // Simplified representation from video context function createDeposit( DepositUtils.CreateDepositParams calldata params ) external override payable nonReentrant returns (bytes32) { address account = msg.sender; // User initiating the call // ... internal logic ... return depositHandler.createDeposit( // Delegate call account, params // Pass calldata parameters directly ); } ``` * **Concept:** The `params` argument is declared with the `calldata` storage location. In Solidity, `calldata` is a special data location for function arguments that is non-modifiable and non-persistent. It avoids copying the argument data into memory, making the function call more gas-efficient, especially for complex data structures. The `ExchangeRouter` reads these parameters directly from the transaction's input data and passes them straight through to the `DepositHandler.createDeposit` function. This explains why the higher-level trace struggled to display them, but the debugger, examining the actual execution context, can reveal the values. ## Examining the `createDeposit` Input Parameters Within the debugger view focused on the `DepositHandler.createDeposit` call, we can inspect the actual values passed in the `params` struct: * `account`: `0xd24cba75f7af6081bff9e6122f4054f32140f49e` (The EOA initiating the transaction). * `params`: A struct containing the specific details of the deposit order: * `receiver`: `0xd24cba75f7af6081bff9e6122f4054f32140f49e` (The address designated to receive the GM market tokens once the deposit order is executed. Often the user's own address). * `callbackContract`: `0x0000...0000` (Address for an optional callback after execution; zero address indicates no callback). * `uiFeeReceiver`: `0xff00...0001` (An address potentially used for tracking UI referrals or distributing platform fees. May often be a default or zero address per GMX documentation). * `market`: `0x70d95587d40a2caf56bd97485ab3eec10bee6336` (The unique address identifying the target GMX V2 market – the ETH/USDC GM pool in this case). * `initialLongToken`: `0x82af49447d8a07e3bd95bd0d56f35241523fbab1` (Address of WETH on Arbitrum, the asset being deposited on the "long" side of the pool's index). * `initialShortToken`: `0xaf88d065e77c8cc2239327c5edb3a432e628e583` (Address of USDC on Arbitrum, the asset being deposited on the "short" side). * `longTokenSwapPath`: `[]` (An empty array, signifying that the user is providing WETH directly and no swap is required within the deposit transaction to obtain it). * `shortTokenSwapPath`: `[]` (An empty array, signifying that USDC is provided directly, requiring no swap). * `minMarketTokens`: `1857687810084938024` (A slippage protection parameter; the minimum amount of GM pool tokens the user is willing to accept for their deposited assets). * `shouldUnwrapNativeToken`: `true` (A boolean flag, likely related to internal handling of ETH vs. WETH, indicating if WETH should be unwrapped to ETH at some stage, though specifics aren't detailed here). * `executionFee`: `40420991000000` wei (0.000040420991 WETH. This fee amount is part of the *order parameters* themselves. It might represent the fee allocated *from the deposited assets* to the keeper upon execution, or a minimum acceptable fee for the keeper, distinct from the fee pre-paid via `sendWnt`). * `callbackGasLimit`: `0` (Gas limit allocated for the optional callback function, if one were specified). Understanding these parameters is crucial for developers interacting programmatically with the GMX V2 protocol to create deposit orders. ## Understanding the Output: The Deposit Order Key The `DepositHandler.createDeposit` function (and therefore the `ExchangeRouter.createDeposit` function that called it) returns a value. The debugger shows this output: * **Output Value:** ```json "output": { "0": "0x0ff0643c9a595b5e719c22c067c5f83510ec3bb804e833cc6ea0be2870a96fd3" } ``` * **Concept:** Because this transaction *creates an order* rather than executing an immediate deposit, the function doesn't return direct confirmation of liquidity provision. Instead, it returns a unique identifier for the pending order. * **Meaning:** The `bytes32` value `0x0ff0...fd3` is the **order key**. This key uniquely identifies the deposit request within the GMX system. * **Use Case:** This order key is essential for interacting with the pending order later. It can be used to query the order's status (pending, executed, cancelled), update its parameters (if allowed), or cancel the order entirely using other functions within the GMX V2 contract suite. ## Key Concepts Recap This analysis highlights several important web3 and GMX V2 concepts: * **Asynchronous Order Execution:** Many GMX V2 actions (deposits, swaps) are created as orders executed later by keepers, rather than happening instantaneously within the initial transaction. * **Multicall Pattern:** Bundling multiple related actions (`sendWnt` fee payment + `createDeposit` order creation) into one transaction. * **Keeper Execution Fees:** The necessity of pre-paying fees (`sendWnt` to `DepositVault`) to cover the gas costs of off-chain keepers who execute orders. The order parameters also contain an `executionFee` field related to this. * **Contract Interaction & Delegation:** Understanding how user interactions with a primary contract (`ExchangeRouter`) can be delegated to specialized handler contracts (`DepositHandler`). * **GM Pool Parameters:** The importance of identifying specific market addresses and the addresses of the long/short tokens involved. * **`calldata` Optimization:** Recognizing the use of `calldata` for passing complex parameters efficiently, saving gas. * **Order Key (`bytes32`):** The unique identifier returned when creating an order, used for tracking and managing its lifecycle. * **Transaction Debugging:** The value of tools like Tenderly for in-depth analysis of complex smart contract interactions, revealing details obscured by standard block explorers. ## Summary and Takeaways Analyzing a GMX V2 deposit transaction using Tenderly provides deep insights into the protocol's mechanics. Creating a liquidity deposit typically involves placing an order via the `ExchangeRouter`, which utilizes `multicall` to handle fee pre-payment (`sendWnt`) and delegate the core logic to the `DepositHandler`'s `createDeposit` function. Understanding the specific parameters passed via `calldata` to `createDeposit` is vital for correct interaction. The transaction concludes by returning a `bytes32` order key, crucial for managing the pending deposit request. Tools like Tenderly are invaluable for dissecting these multi-step, multi-contract interactions common in sophisticated DeFi protocols.
This lesson demonstrates how to use the Tenderly transaction debugger to analyze the process of creating an order to deposit liquidity into a GMX V2 GM (Global Market) pool on the Arbitrum network. We will trace the execution flow of an example transaction targeting the ETH/USDC pool, examining the contract interactions and key parameters involved.
The objective of the transaction under analysis is to create a pending order to deposit Wrapped Ether (WETH) and USDC into the GMX V2 ETH/USDC GM pool. We utilize the Tenderly platform, focusing on its transaction debugging capabilities.
Initially, the Tenderly interface displays the summary of a successful transaction. The "Tokens transferred" section provides a high-level view, showing WETH being minted (if applicable, from ETH), transferred from the user to the ExchangeRouter
contract, and subsequently from the ExchangeRouter
to the DepositVault
contract. This initial transfer hints at fee mechanics, which we'll explore further in the trace.
multicall
Entry PointObserving the transaction's execution trace in Tenderly reveals the primary interaction starts with the user's address calling the multicall
function on the GMX ExchangeRouter
contract.
Code Context: [Sender] 0xd24cba...f49e => [Receiver] ExchangeRouter).multicall(data = [0x7d39aaf1...])
The multicall
pattern is frequently used in DeFi protocols to bundle several distinct actions into a single, atomic transaction. This improves user experience and gas efficiency compared to sending multiple separate transactions. In this case, multicall
orchestrates both the fee payment and the order creation logic.
sendWnt
CallWithin the multicall
execution, the first significant internal function call is to ExchangeRouter.sendWnt
.
Code Context: [Receiver] ExchangeRouter.sendWnt(receiver = 0xf89e..., amount = 1040...)
Purpose: This function is responsible for transferring the execution fee required by the GMX protocol to process asynchronous orders.
Parameters:
receiver
: 0xf89e77e8dc11691c9e8757e84aafbcd8a67d7a55
- Identified via Arbiscan as the GMX DepositVault
contract address.
amount
: 1040420991000000
wei (equivalent to 0.001040420991 WETH on Arbitrum).
Concept: GMX utilizes keepers to execute pending orders (like deposits, withdrawals, swaps) when specific conditions are met. To incentivize keepers and cover their gas costs, users must pre-pay an execution fee in the network's wrapped native token (WNT – WETH on Arbitrum). This fee is sent to and held by the DepositVault
until an order is executed.
fallback
FunctionThe second internal call executed within the multicall
bundle targets the ExchangeRouter.fallback
function.
Code Context: [Receiver] ExchangeRouter.fallback(0xb4e9561...)
Purpose: In this architecture, the fallback
function acts as a router. It receives encoded data (0xb4e9561...
) which contains the instructions for the primary action the user intends to perform – in this instance, creating a deposit order. The ExchangeRouter
decodes this data and directs the execution flow accordingly.
createDeposit
Call ChainExpanding the fallback
call in the Tenderly trace reveals that it internally triggers a call to the ExchangeRouter.createDeposit
function.
Code Context (within fallback): [Receiver] ExchangeRouter.createDeposit(params = null) => (0x0ff0...)
Notably, the high-level trace might initially display params = null
. This often indicates that the parameters are passed in a format (like calldata
) that the top-level trace view doesn't fully decode.
Further expanding this step shows that ExchangeRouter.createDeposit
doesn't contain the core deposit logic itself. Instead, it acts as a proxy or entry point, delegating the actual work to the DepositHandler
contract.
Code Context (deeper call): ([Receiver] ExchangeRouter -> DepositHandler).createDeposit(account = 0xd24cba..., ...)
To understand the specifics, we select the call to DepositHandler.createDeposit
and utilize Tenderly's "Debug" feature to step into the code-level execution.
createDeposit
: Understanding calldata
ParametersThe Tenderly Debugger loads the source code for the ExchangeRouter.createDeposit
function. Examining its signature provides a key insight:
Concept: The params
argument is declared with the calldata
storage location. In Solidity, calldata
is a special data location for function arguments that is non-modifiable and non-persistent. It avoids copying the argument data into memory, making the function call more gas-efficient, especially for complex data structures. The ExchangeRouter
reads these parameters directly from the transaction's input data and passes them straight through to the DepositHandler.createDeposit
function. This explains why the higher-level trace struggled to display them, but the debugger, examining the actual execution context, can reveal the values.
createDeposit
Input ParametersWithin the debugger view focused on the DepositHandler.createDeposit
call, we can inspect the actual values passed in the params
struct:
account
: 0xd24cba75f7af6081bff9e6122f4054f32140f49e
(The EOA initiating the transaction).
params
: A struct containing the specific details of the deposit order:
receiver
: 0xd24cba75f7af6081bff9e6122f4054f32140f49e
(The address designated to receive the GM market tokens once the deposit order is executed. Often the user's own address).
callbackContract
: 0x0000...0000
(Address for an optional callback after execution; zero address indicates no callback).
uiFeeReceiver
: 0xff00...0001
(An address potentially used for tracking UI referrals or distributing platform fees. May often be a default or zero address per GMX documentation).
market
: 0x70d95587d40a2caf56bd97485ab3eec10bee6336
(The unique address identifying the target GMX V2 market – the ETH/USDC GM pool in this case).
initialLongToken
: 0x82af49447d8a07e3bd95bd0d56f35241523fbab1
(Address of WETH on Arbitrum, the asset being deposited on the "long" side of the pool's index).
initialShortToken
: 0xaf88d065e77c8cc2239327c5edb3a432e628e583
(Address of USDC on Arbitrum, the asset being deposited on the "short" side).
longTokenSwapPath
: []
(An empty array, signifying that the user is providing WETH directly and no swap is required within the deposit transaction to obtain it).
shortTokenSwapPath
: []
(An empty array, signifying that USDC is provided directly, requiring no swap).
minMarketTokens
: 1857687810084938024
(A slippage protection parameter; the minimum amount of GM pool tokens the user is willing to accept for their deposited assets).
shouldUnwrapNativeToken
: true
(A boolean flag, likely related to internal handling of ETH vs. WETH, indicating if WETH should be unwrapped to ETH at some stage, though specifics aren't detailed here).
executionFee
: 40420991000000
wei (0.000040420991 WETH. This fee amount is part of the order parameters themselves. It might represent the fee allocated from the deposited assets to the keeper upon execution, or a minimum acceptable fee for the keeper, distinct from the fee pre-paid via sendWnt
).
callbackGasLimit
: 0
(Gas limit allocated for the optional callback function, if one were specified).
Understanding these parameters is crucial for developers interacting programmatically with the GMX V2 protocol to create deposit orders.
The DepositHandler.createDeposit
function (and therefore the ExchangeRouter.createDeposit
function that called it) returns a value. The debugger shows this output:
Output Value:
Concept: Because this transaction creates an order rather than executing an immediate deposit, the function doesn't return direct confirmation of liquidity provision. Instead, it returns a unique identifier for the pending order.
Meaning: The bytes32
value 0x0ff0...fd3
is the order key. This key uniquely identifies the deposit request within the GMX system.
Use Case: This order key is essential for interacting with the pending order later. It can be used to query the order's status (pending, executed, cancelled), update its parameters (if allowed), or cancel the order entirely using other functions within the GMX V2 contract suite.
This analysis highlights several important web3 and GMX V2 concepts:
Asynchronous Order Execution: Many GMX V2 actions (deposits, swaps) are created as orders executed later by keepers, rather than happening instantaneously within the initial transaction.
Multicall Pattern: Bundling multiple related actions (sendWnt
fee payment + createDeposit
order creation) into one transaction.
Keeper Execution Fees: The necessity of pre-paying fees (sendWnt
to DepositVault
) to cover the gas costs of off-chain keepers who execute orders. The order parameters also contain an executionFee
field related to this.
Contract Interaction & Delegation: Understanding how user interactions with a primary contract (ExchangeRouter
) can be delegated to specialized handler contracts (DepositHandler
).
GM Pool Parameters: The importance of identifying specific market addresses and the addresses of the long/short tokens involved.
calldata
Optimization: Recognizing the use of calldata
for passing complex parameters efficiently, saving gas.
Order Key (bytes32
): The unique identifier returned when creating an order, used for tracking and managing its lifecycle.
Transaction Debugging: The value of tools like Tenderly for in-depth analysis of complex smart contract interactions, revealing details obscured by standard block explorers.
Analyzing a GMX V2 deposit transaction using Tenderly provides deep insights into the protocol's mechanics. Creating a liquidity deposit typically involves placing an order via the ExchangeRouter
, which utilizes multicall
to handle fee pre-payment (sendWnt
) and delegate the core logic to the DepositHandler
's createDeposit
function. Understanding the specific parameters passed via calldata
to createDeposit
is vital for correct interaction. The transaction concludes by returning a bytes32
order key, crucial for managing the pending deposit request. Tools like Tenderly are invaluable for dissecting these multi-step, multi-contract interactions common in sophisticated DeFi protocols.
A step-by-step analysis to Understanding GMX V2 Order Creation via Tenderly - Follow a GMX V2 deposit order transaction using the Tenderly debugger tool. Uncover the sequence of contract calls (`multicall`, `sendWnt`, `createDeposit`), decode parameters, and see how the final order ID is generated.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Cyfrin Updraft has partnered with GMX to provide SSCD+ certifications vouchers to the first 25 students who demonstrate the completion of this course and are building on top of GMX. Join the GMX Discord server to redeem your coupon, or navigate to the docs to learn more!
Course Overview
About the course
Mechanics and contract architecture of the GMX protocol
Token pricing and fees
Liquidity: GM pools and GLV vaults
Math, funding rates, liquidation pricing, P&L calculations
Limit orders, take profit orders, stop loss, and stop market orders
Auto-cancel and auto-deleveraging
GLP, esGMX, GMX staking and delegation
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Last updated on June 26, 2025
Duration: 8min
Duration: 1h 19min
Duration: 1h 24min
Duration: 16min
Duration: 11min
Duration: 11min
Duration: 6min
Course Overview
About the course
Mechanics and contract architecture of the GMX protocol
Token pricing and fees
Liquidity: GM pools and GLV vaults
Math, funding rates, liquidation pricing, P&L calculations
Limit orders, take profit orders, stop loss, and stop market orders
Auto-cancel and auto-deleveraging
GLP, esGMX, GMX staking and delegation
DeFi Developer
$75,000 - $200,000 (avg. salary)
Smart Contract Engineer
$100,000 - $150,000 (avg. salary)
Web3 developer
$60,000 - $150,000 (avg. salary)
Web3 Developer Relations
$85,000 - $125,000 (avg. salary)
Smart Contract Auditor
$100,000 - $200,000 (avg. salary)
Security researcher
$49,999 - $120,000 (avg. salary)
Last updated on June 26, 2025