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
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 January 28, 2026
Duration: 9min
Duration: 1h 19min
Duration: 1h 25min
Duration: 16min
Duration: 11min
Duration: 12min
Duration: 7min
Duration: 2min
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 January 28, 2026