Skip to main content
Delegated trading lets a trader approve another EOA to submit trading transactions on their behalf. This is the on-chain primitive used by One-Click Trading, but it can also be used by backend services, bots, and partner integrations. The trader keeps custody of their collateral and positions. The delegated wallet signs and pays gas for transactions, while the protocol records the trade under the trader address.

Flow

  1. The trader approves an agent wallet with setTradingDelegate.
  2. The agent wallet is funded with native gas token.
  3. The backend builds the calldata for a normal trading action, such as openTrade, closeTradeMarket, updateSl, or updateTp.
  4. The backend wraps that calldata in delegatedTradingAction, passing the trader address and the action calldata.
  5. The agent wallet submits the wrapped transaction to GNSMultiCollatDiamond.
During the delegated call, the trading facet resolves the effective sender as the trader address. Collateral transfers, trade ownership, and PnL accounting therefore remain tied to the trader.

Approve a delegate

The trader must approve the delegate from their own wallet:
import { ethers } from "ethers";

const diamond = new ethers.Contract(
  GNS_MULTI_COLLAT_DIAMOND,
  [
    "function setTradingDelegate(address delegate)",
    "function getTradingDelegate(address trader) view returns (address)",
    "function removeTradingDelegate()",
  ],
  traderSigner
);

await diamond.setTradingDelegate(agentWalletAddress);

const currentDelegate = await diamond.getTradingDelegate(await traderSigner.getAddress());
Each trader has one active trading delegate. Calling setTradingDelegate replaces the previous delegate. Calling removeTradingDelegate clears it.

Submit from a backend signer

The agent wallet signs the wrapped transaction. The trader address is still passed into the wrapped call:
import { Wallet } from "ethers";
import { TradingSDK, SupportedChainId } from "@gainsnetwork/trading-sdk";

const agentWallet = new Wallet(process.env.AGENT_PRIVATE_KEY!, provider);
const trader = "0x..."; // user who approved agentWallet.address

const tradingSdk = new TradingSDK({
  chainId: SupportedChainId.Arbitrum,
  signer: agentWallet,
});

await tradingSdk.initialize();

const openTradeTx = await tradingSdk.build.openTrade({
  user: trader,
  pairIndex: 0,
  collateralAmount: 100_000_000n,
  openPrice: 66108.8,
  long: true,
  leverage: 2,
  tp: 0,
  sl: 0,
  collateralIndex: 3,
  tradeType: 0,
  maxSlippage: 1,
});

const response = await tradingSdk.write.delegatedTradingAction({
  trader,
  data: openTradeTx.data,
});

await response?.wait();
The trader must still satisfy the normal requirements for the underlying action. For example, openTrade with ERC-20 collateral requires the trader to have enough collateral and to have approved the diamond to pull that collateral.

Direct contract call

You can also build the wrapped calldata manually:
import { ethers } from "ethers";

const diamond = new ethers.Contract(
  GNS_MULTI_COLLAT_DIAMOND,
  [
    "function openTrade((address user,uint32 index,uint16 pairIndex,uint24 leverage,bool long,bool isOpen,uint8 collateralIndex,uint8 tradeType,uint120 collateralAmount,uint64 openPrice,uint64 tp,uint64 sl,uint192 __placeholder) trade,uint16 maxSlippageP,address referrer)",
    "function delegatedTradingAction(address trader, bytes callData) returns (bytes)",
  ],
  agentWallet
);

const callData = diamond.interface.encodeFunctionData("openTrade", [
  trade,
  maxSlippageP,
  ethers.ZeroAddress,
]);

await diamond.delegatedTradingAction(trader, callData);

Supported actions

Delegation is designed for trading and trade management. Common supported actions include:
  • openTrade
  • closeTradeMarket
  • updateSl
  • updateTp
  • updateOpenOrder
  • cancelOpenOrder
  • updateLeverage
  • increasePositionSize
  • decreasePositionSize
  • updateMaxClosingSlippageP
  • cancelOrderAfterTimeout
The current contracts also allow a delegate to initiate withdrawPositivePnl. Any withdrawn PnL is transferred to the trader, not to the delegate.

Unsupported actions

Functions guarded by notDelegatedAction cannot be executed through delegatedTradingAction. This includes native-token wrapper flows and trigger-order flows:
  • openTradeNative
  • increasePositionSizeNative
  • updateLeverageNative
  • triggerOrder
  • triggerOrderWithSignatures
  • nested delegatedTradingAction
Use ERC-20 collateral flows for delegated opens and position increases. On ApeChain, delegated trading requires wrapped collateral because native $APE does not follow ERC-20 approve and transferFrom semantics.

Security notes

Delegated trading is broad trading authority, not a granular permission system. A delegate cannot withdraw user funds to itself through the trading delegation flow, but it can submit trading actions that affect the trader’s positions and collateral. Recommended practices:
  • Use a dedicated agent wallet per integration or user group.
  • Keep the agent wallet funded only with enough gas for expected operations.
  • Let users revoke access with removeTradingDelegate.
  • Enforce any additional policy, such as max trade size, allowed pairs, or disabled PnL withdrawals, in your backend before signing transactions.
  • Treat backend policy as off-chain protection. The current on-chain delegation model does not enforce per-function, per-market, per-size, or expiry-scoped permissions.

References