Swagger

Operation rails

The prepare response decides what the account signs. Branch on it, not on your own network table.

The rails

One prepare, one submit — but the rail decides what the account signs. Three shapes, and the response tells you which. Branch on it, not on your own network table.

A prepare response without a rail field is the UserOperation rail. Every other rail names itself in rail, so treat its absence as the default and handle unknown values as an error rather than as the default.

Some prepared operations answer with rail "sponsored". Sign and submit one exactly as on the executor rail, and read ugtpFee as optional on it: when the field is absent, treat it as absent rather than as zero.

UserOperation rail

no rail field
Response
Carries userOperation and userOperationHash.
Networks
Ethereum, Arbitrum One, Polygon PoS, Base, BNB Smart Chain
Assets
Any supported fee token, and the network's native coin
The account signs
The returned userOperationHash
You submit
POST /transaction/submit with operationId and signature, plus authorization when authorizationRequired is true
Gas and fee
Gas is settled in the token being sent: the paymaster pays the network, then deducts gas plus the UGTP fee from the transferred asset.

Executor rail

rail: "forward_request"
Response
Carries requestHash and signingPayload. No UserOperation is returned.
Networks
Avalanche C-Chain
Assets
ERC-20 tokens only
The account signs
The EIP-712 signingPayload, with eth_signTypedData_v4
You submit
POST /transaction/submit with operationId and signature
Gas and fee
Gas is settled in the fee token: the executor pulls gas plus the UGTP fee in an ERC-20 fee token against an allowance the account grants once.

Self-funded rail

rail: "self_funded"
Response
Carries a whole transaction, the account's on-chain nonce, and a non-binding gasSuggestion.
Networks
Avalanche C-Chain
Assets
The network's native coin, which no allowance can move
The account signs
The whole transaction, exactly as handed over, including the returned nonce
You submit
POST /transaction/submit with signedTransaction; send the returned operationId with it for a swap, and omit it for a transfer
Gas and fee
The account pays its own gas in the native coin. UGTP charges nothing, so no ugtpFee is returned. We broadcast and track the transaction.

These examples are based on recorded operations and link to their on-chain transactions. Requests and responses are shortened and sensitive fields are withheld. Request formatting follows the current API. Replace placeholders and prepare a fresh operation before signing; these historical payloads are not reusable.

An ERC-20 transfer with no native coin

UserOperation rail (EIP-7702 account) · Arbitrum One

2.148465421470024202 ARB moved to 0xCa9d43184Cc3179609f77D38433D363833C73c60. The account held no ETH and paid the gas cost in ARB through the operation fee.

Operation id
ugtp_operation_E6SnXDoAl9AQ
Charged
0.564385946233880654 ARB, deducted from the same token: 0.112759966958839437 gas + 0.449477513853571193 static + 0.002148465421470024 transfer percentage. ARB was priced at $0.11124027, which is what turns the $0.05 static fee into 0.4494 ARB.

The UserOperation the EntryPoint executed

The same object as in the response, now carrying the account's signature. Its 228-byte callData is one execute(ARB, 0, transfer(recipient, amount)); the 214-byte paymasterAndData carries the paymaster, its gas limits, the fee token, the signed maximum, the transferred value, the validity window, the collection mode and the backend signature.

{
  "sender": "0x9b5fcd6f647a14ebf0f88b1c3e5a3d34b951fc6f",
  "nonce": "0x2",
  "initCode": "0x",
  "callData": "0xb61d27f6000000000000000000000000912ce59144191c…228 bytes",
  "accountGasLimits": "0x000000000000000000000000000084b800000000000000000000000000014393",
  "preVerificationGas": "0xc208",
  "gasFees": "0x00000000000000000000000000000000000000000000000000000000016eafe0",
  "paymasterAndData": "0x49f26881b5ea4d3f3d451c7faa4bca1b4aebcd97…214 bytes",
  "signature": "0xa3087507768f541a4eaa9d46b581c351d29c3137…65 bytes",
  "userOperationHash": "0x1a4fece09e742c04398006ebdae6d03227aa7e822ba825e0c64b44fc78d12cb3",
  "entryPoint": "0x433709009B8330FDa32311DF1C2AFA402eD8D009",
  "handleOpsTransaction": "0x5977159b21734798f90fad1e442216dccec579168c00354268085a6e1d608af0"
}

POST /transaction/transfer
{
  "ownerAddress": "0x9b5fcd6f647a14ebf0f88b1c3e5a3d34b951fc6f",
  "networkId": "42161",
  "transfers": [
    {
      "tokenAddress": "0x912ce59144191c1204e64559fe8253a0e49e6548",
      "amount": "2148465421470024202",
      "recipientAddress": "0xCa9d43184Cc3179609f77D38433D363833C73c60"
    }
  ]
}

{
  "operationId": "ugtp_operation_E6SnXDoAl9AQ",
  "userOperation": {
    "sender": "0x9b5fcd6f647a14ebf0f88b1c3e5a3d34b951fc6f",
    "nonce": "0x2",
    "initCode": "0x",
    "callData": "0xb61d27f6000000000000000000000000912ce59144191c…228 bytes",
    "accountGasLimits": "0x000000000000000000000000000084b800000000000000000000000000014393",
    "preVerificationGas": "0xc208",
    "gasFees": "0x00000000000000000000000000000000000000000000000000000000016eafe0",
    "paymasterAndData": "0x49f26881b5ea4d3f3d451c7faa4bca1b4aebcd97…214 bytes",
    "signature": "0x"
  },
  "userOperationHash": "0x1a4fece09e742c04398006ebdae6d03227aa7e822ba825e0c64b44fc78d12cb3",
  "ugtpFee": {
    "tokenAmount": "622595557466841530",
    "feeToken": "0x912ce59144191c1204e64559fe8253a0e49e6548"
  },
  "accountType": "eoa_7702",
  "authorizationRequired": false
}

POST /transaction/submit
{
  "operationId": "ugtp_operation_E6SnXDoAl9AQ",
  "signature": "0xa3087507768f541a4eaa9d46b581c351d29c3137…65 bytes"
}

{
  "operationId": "ugtp_operation_E6SnXDoAl9AQ",
  "status": "queued",
  "userOperationHash": "0x1a4fece09e742c04398006ebdae6d03227aa7e822ba825e0c64b44fc78d12cb3",
  "networkId": "42161"
}

GET /transaction/:id
{
  "operationId": "ugtp_operation_E6SnXDoAl9AQ",
  "status": "finalized",
  "transactionHash": "0x5977159b21734798f90fad1e442216dccec579168c00354268085a6e1d608af0",
  "userOperationHash": "0x1a4fece09e742c04398006ebdae6d03227aa7e822ba825e0c64b44fc78d12cb3",
  "networkId": "42161",
  "replacementCount": 0,
  "reason": null
}

Open the transaction on the block explorer ↗

The same transfer, where there is no EIP-7702

Executor rail (EIP-712 execution request) · Avalanche C-Chain

0.2 USDt moved on Avalanche C-Chain. Same API call as the Arbitrum transfer; the response asks for typed data instead of a UserOperation hash.

Operation id
ugtp_operation_ILmX7eF4Ngf5
Charged
0.050351 USDT: 0.00014 gas + 0.050011 static + 0.0002 transfer percentage. The signed ceiling was 0.050908 USDT, so the charge landed under the maximum the account had agreed to.

The ExecutionRequest the executor received

The account's own EIP-712 signature travels with it, and so does the backend signature that proves UGTP quoted these terms — the contract verifies both and refuses anything the account did not sign. The action stays ABI-encoded: kind 0 is a send.

{
  "account": "0x0a1758ad202a2059e8c2970b562597ca45d863c4",
  "nonce": "0x3",
  "actions": [
    {
      "kind": 0,
      "params": "0x0000000000000000000000009702230a8ea53601f5cd2dc00fdbc13d4d…96 bytes"
    }
  ],
  "terms": {
    "feeToken": "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7",
    "transferredValueInFeeToken": "200000",
    "platformTransferFeeBps": 10,
    "projectTransferFeeBps": 0,
    "platformStaticFeeUsd8dp": "5000000",
    "projectStaticFeeUsd8dp": "0",
    "projectPayoutAddress": "0x0000000000000000000000000000000000000000",
    "calldataGas": "7296",
    "maxFeeTokenAmount": "50908",
    "validUntil": 1788518426
  },
  "backendSignature": "0x0c9eb1440086c6f661c9c370fa25d623277990b3…65 bytes",
  "executor": "0xA7C8776C6ac6f21eC923321793F62A4887f9E767",
  "requestHash": "0x8f2f1873e757aea22e8d896fff6e6d03ef1835f922f49cb7fca6ff032fce813f",
  "transaction": "0x6369d823789469741cecf701e9000710b24a3180984dc07e47fdfc277fe1a7ed"
}

POST /transaction/transfer
{
  "ownerAddress": "0x0a1758ad202a2059e8c2970b562597ca45d863c4",
  "networkId": "43114",
  "transfers": [
    {
      "tokenAddress": "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7",
      "amount": "200000",
      "recipientAddress": "0x9b5fcd6f647a14ebf0f88b1c3e5a3d34b951fc6f"
    }
  ],
  "feeTokenAddress": "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7"
}

{
  "operationId": "ugtp_operation_ILmX7eF4Ngf5",
  "rail": "forward_request",
  "requestHash": "0x8f2f1873e757aea22e8d896fff6e6d03ef1835f922f49cb7fca6ff032fce813f",
  "signingPayload": {
    "domain": {
      "name": "UGTPAvalancheExecutor",
      "version": "1",
      "chainId": 43114,
      "verifyingContract": "0xA7C8776C6ac6f21eC923321793F62A4887f9E767"
    },
    "types": {
      "ExecutionRequest": [
        {
          "name": "account",
          "type": "address"
        },
        {
          "name": "nonce",
          "type": "uint256"
        },
        {
          "name": "sends",
          "type": "Send[]"
        },
        {
          "name": "swaps",
          "type": "Swap[]"
        },
        {
          "name": "bridges",
          "type": "Bridge[]"
        },
        {
          "name": "privates",
          "type": "Private[]"
        },
        {
          "name": "terms",
          "type": "FeeTerms"
        }
      ],
      "Send": [
        {
          "name": "token",
          "type": "address"
        },
        {
          "name": "recipient",
          "type": "address"
        },
        {
          "name": "amount",
          "type": "uint256"
        }
      ],
      "...": "Swap, Bridge, Private and FeeTerms members follow; the payload carries the whole tree"
    },
    "primaryType": "ExecutionRequest",
    "message": {
      "account": "0x0a1758ad202a2059e8c2970b562597ca45d863c4",
      "nonce": "3",
      "sends": [
        {
          "token": "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7",
          "recipient": "0x9b5fcd6f647a14ebf0f88b1c3e5a3d34b951fc6f",
          "amount": "200000"
        }
      ],
      "swaps": [],
      "bridges": [],
      "privates": [],
      "terms": {
        "feeToken": "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7",
        "transferredValueInFeeToken": "200000",
        "platformTransferFeeBps": 10,
        "projectTransferFeeBps": 0,
        "platformStaticFeeUsd8dp": "5000000",
        "projectStaticFeeUsd8dp": "0",
        "projectPayoutAddress": "0x0000000000000000000000000000000000000000",
        "calldataGas": "7296",
        "maxFeeTokenAmount": "50908",
        "validUntil": 1788518426
      }
    }
  },
  "ugtpFee": {
    "tokenAmount": "50908",
    "expectedTokenAmount": "50392",
    "feeToken": "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7"
  }
}

GET /transaction/:id
{
  "operationId": "ugtp_operation_ILmX7eF4Ngf5",
  "status": "finalized",
  "transactionHash": "0x6369d823789469741cecf701e9000710b24a3180984dc07e47fdfc277fe1a7ed",
  "userOperationHash": "0x8f2f1873e757aea22e8d896fff6e6d03ef1835f922f49cb7fca6ff032fce813f",
  "networkId": "43114",
  "replacementCount": 0,
  "reason": null
}

Open the transaction on the block explorer ↗

The native coin, which no allowance can move

Self-funded rail (a whole transaction) · Avalanche C-Chain

0.001 AVAX moved. On this rail the account signs the native transfer itself, so it signed the whole transaction and paid its own gas — and UGTP charged nothing.

Operation id
ugtp_operation_d99WHSYNXv4j
Charged
No UGTP fee at all. The operation has a fee row with every amount null, because there is nothing to collect on this rail.

The signed transaction, as broadcast

A plain EIP-1559 transaction — type 0x02, 117 bytes of RLP. There is no UserOperation and no executor in the picture: the operation IS this transaction, which is why its hash and the operation's own hash are the same value.

{
  "type": "0x2",
  "chainId": "0xa86a",
  "nonce": "0x4",
  "maxPriorityFeePerGas": "0x1",
  "maxFeePerGas": "0x5f9510d",
  "gas": "0x7b0c",
  "to": "0x0ea9337dc1055df7bbd16f3d4824b01c560af4c5",
  "value": "0x38d7ea4c68000",
  "data": "0x",
  "raw": "0x02f86f82a86a04018405f9510d827b0c940ea9337dc…117 bytes",
  "transactionHash": "0xd805c96742bc1aed12029fe2e1a78c487b635c0d64f94d8520a6715578f67e17"
}

POST /transaction/transfer
{
  "ownerAddress": "0x760ECC99d6eA52D4D209591E2eC10878cFF4e7E3",
  "networkId": "43114",
  "transfers": [
    {
      "tokenAddress": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
      "amount": "1000000000000000",
      "recipientAddress": "0x0ea9337dc1055df7bbd16f3d4824b01c560af4c5"
    }
  ]
}

{
  "rail": "self_funded",
  "senderAddress": "0x760ECC99d6eA52D4D209591E2eC10878cFF4e7E3",
  "networkId": "43114",
  "nonce": 4,
  "transaction": {
    "to": "0x0ea9337dc1055df7bbd16f3d4824b01c560af4c5",
    "data": "0x",
    "value": "1000000000000000"
  },
  "gasSuggestion": {
    "gasLimit": "31500",
    "maxFeePerGas": "100000013",
    "maxPriorityFeePerGas": "1"
  }
}

POST /transaction/submit
{
  "signedTransaction": "0x02f86f82a86a04018405f9510d827b0c940ea9337dc…117 bytes"
}

{
  "operationId": "ugtp_operation_d99WHSYNXv4j",
  "status": "submitted",
  "userOperationHash": "0xd805c96742bc1aed12029fe2e1a78c487b635c0d64f94d8520a6715578f67e17",
  "networkId": "43114"
}

Open the transaction on the block explorer ↗