Skip to main content

Gasless Transactions

Execute sponsored meta-transactions without requiring users to hold native tokens for gas.
This guide extends the standard Swaps.xyz swap flow with optional gasless execution.

Overview

Gasless transactions allow users to execute swaps and bridges without paying gas fees directly. The API handles meta-transaction encoding and submission to a relay service. Supported Chains: Tron (EVM chains coming soon)

Requesting Gasless Execution

Add the optional gasless parameter to your ActionRequest:
const actionRequest = {
  actionType: "swap-action",
  sender: "TRiskt1RqdWMfvWEU8CMxSBh3MPSPP9UoL",
  srcToken: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", // USDT on Tron
  dstToken: "0x0000000000000000000000000000000000000000", // TRX on Tron
  srcChainId: 728126428, // Tron
  dstChainId: 728126428,
  slippage: 100,
  swapDirection: "exact-amount-in",
  amount: 1000000n,
  recipient: "TRiskt1RqdWMfvWEU8CMxSBh3MPSPP9UoL",
  gasless: true, // ← Request gasless execution
};
Note: Omit gasless or set to false for standard transactions.

Understanding the Response

The API response includes an execute field that describes how to execute the transaction:
interface ActionResponse {
  tx: Transaction;
  execute: {
    type: "STANDARD" | "GASLESS";
    approval: ExecutionStep | null;
    tx: ExecutionStep;
  };
  // ... other fields
}

Standard Execution

{
  type: "STANDARD",
  approval: null,
  tx: {
    type: "BROADCAST",
    input: { to, data, value, chainId } // Standard transaction
  }
}
Action: Broadcast tx directly on-chain (see Swaps.xyz broadcast guide).

Gasless Execution

{
  type: "GASLESS",
  approval: null,
  tx: {
    type: "SIGN_TYPED_DATA",
    input: {
      domain: { name, version, chainId, verifyingContract },
      types: { MetaTransaction: [...] },
      message: { nonce, from, functionSignature }
    }
  }
}
Action: Sign the EIP-712 data and submit to /api/submit.

Executing Gasless Transactions

Step 1: Handle Approval (if required)

If execute.approval exists, sign it first before the main transaction:
const { execute } = actionResponse;

if (execute.approval) {
  if (execute.approval.type === "SIGN_TYPED_DATA") {
    const { domain, types, message } = execute.approval.input;
    execute.approval.output = await wallet.signTypedData(
      domain,
      types,
      message
    );
  } else if (execute.approval.type === "SIGN_TRANSACTION") {
    const signedTx = await wallet.signTransaction(execute.approval.input);
    execute.approval.output = signedTx;
  }
}

Step 2: Sign Main Transaction

if (execute.tx.type === "SIGN_TYPED_DATA") {
  const { domain, types, message } = execute.tx.input;
  execute.tx.output = await wallet.signTypedData(domain, types, message);
} else if (execute.tx.type === "SIGN_TRANSACTION") {
  const signedTx = await wallet.signTransaction(execute.tx.input);
  execute.tx.output = signedTx;
}

Step 3: Submit to Backend

const response = await fetch("https://api-v2.swaps.xyz/api/submit", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": YOUR_API_KEY,
  },
  body: JSON.stringify({
    chainId: actionRequest.srcChainId,
    approval: execute.approval, // null or SignedExecutionStep
    tx: execute.tx, // SignedExecutionStep
  }),
});

const result = await response.json();
// { success: true, id: "relay-task-id" }

Notes

  • Approval Handling: The approval step (if present) must be signed before the main transaction. It can be either SIGN_TRANSACTION (for airdrop approvals) or SIGN_TYPED_DATA (for permit approvals)
  • Standard Fallback: Always check execute.type - if "STANDARD", broadcast the tx directly (see Swaps.xyz broadcast guide)
  • Error Handling: The /api/submit endpoint validates signatures and simulates transactions before broadcasting

Complete Example

// 1. Request gasless action
const actionRequest = { ...params, gasless: true };
const actionResponse = await getAction(actionRequest);

// 2. Check execution type
if (actionResponse.execute.type === "GASLESS") {
  const { execute } = actionResponse;

  // 3. Sign approval if required
  if (execute.approval) {
    const { domain, types, message } = execute.approval.input;
    execute.approval.output = await wallet.signTypedData(
      domain,
      types,
      message
    );
  }

  // 4. Sign main transaction
  const { domain, types, message } = execute.tx.input;
  execute.tx.output = await wallet.signTypedData(domain, types, message);

  // 5. Submit for broadcast
  const submitResponse = await fetch("/api/submit", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-api-key": YOUR_API_KEY,
    },
    body: JSON.stringify({
      chainId: actionRequest.srcChainId,
      approval: execute.approval,
      tx: execute.tx,
    }),
  });

  const { success, id } = await submitResponse.json();
  console.log(`Transaction submitted: ${id}`);
}

Type Definitions

type ExecutionStepType = "BROADCAST" | "SIGN_TRANSACTION" | "SIGN_TYPED_DATA";

interface ExecutionStep {
  type: ExecutionStepType;
  input: Transaction | EIP712TypedData;
}

interface SignedExecutionStep extends ExecutionStep {
  output: string; // Signature or signed transaction
}

interface PostSubmitRequest {
  chainId: number;
  approval: SignedExecutionStep | null;
  tx: SignedExecutionStep;
}

interface PostSubmitResponse {
  success: boolean;
  id?: string; // Relay task ID
}