# Transaction encoding formats

When you use the [raw transaction API](/products/wallet/transactions/raw-signing), the `encodedTransaction` field must be encoded in a blockchain-specific format. This page details the exact encoding requirements for each supported blockchain.

The API decodes `encodedTransaction` when you submit it. A payload it can't decode returns HTTP 400 and creates no transaction: on an EVM wallet, the message is `invalid encoded transaction` with the reason `PAL010.011`, and on an XRP Ledger wallet, `xrp ledger orchestrator rejected the transaction` with the reason `PAL010.022`. A request without `encodedTransaction` returns HTTP 400 with the reason `PAL000.020`.

## EVM chains (Ethereum, Base, Polygon, Avalanche, Arbitrum, BNB Chain)

The `encodedTransaction` field must match the output of go-ethereum's `rlp.EncodeToBytes(types.Transaction)`.

**Format:**

```
RLP(type_byte || RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gas, to, value, data, accessList, v, r, s]))
```

**Key requirements:**

1. **Include v, r, s signature fields** — Set to `0` for unsigned transactions
2. **Apply outer RLP wrapper** — The typed transaction must be wrapped as an RLP byte string


Common error
If you receive `"typed transaction too short"`, you're likely using the standard unsigned format (`0x02 || RLP([9 fields])`) which is missing the v/r/s placeholders and outer RLP wrapper.

**Python example:**

```python
from eth_account.typed_transactions import DynamicFeeTransaction
import rlp

# Use the signed transaction serializer (includes v, r, s fields)
serializer = DynamicFeeTransaction._signed_transaction_serializer

tx = serializer(
    chainId=84532,  # Base Sepolia
    nonce=nonce,
    maxPriorityFeePerGas=max_priority,
    maxFeePerGas=max_fee,
    gas=gas_limit,
    to=bytes.fromhex(to_address[2:]),
    value=0,
    data=calldata_bytes,
    accessList=(),
    v=0,  # Placeholder for unsigned
    r=0,  # Placeholder for unsigned
    s=0,  # Placeholder for unsigned
)

# RLP encode the fields
tx_rlp = rlp.encode(tx)

# Add EIP-1559 type prefix (0x02)
typed_tx = bytes([2]) + tx_rlp

# Wrap in RLP string (matches go-ethereum's rlp.EncodeToBytes)
encoded_transaction = rlp.encode(typed_tx).hex()
```

**Go example:**

```go
import (
    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/rlp"
)

tx := types.NewTx(&types.DynamicFeeTx{
    ChainID:   big.NewInt(84532),
    Nonce:     nonce,
    GasTipCap: maxPriorityFeePerGas,
    GasFeeCap: maxFeePerGas,
    Gas:       gasLimit,
    To:        &toAddress,
    Value:     big.NewInt(0),
    Data:      calldata,
})

encodedBytes, _ := rlp.EncodeToBytes(tx)
encodedTransaction := hex.EncodeToString(encodedBytes)
```

## XRP Ledger

The `encodedTransaction` field must be an **XRP Binary Codec signing payload**: the output of `encodeForSigning` for single-signing, or `encodeForMultisigning` for multi-signing. Only these payloads produce valid signatures. The platform checks the signing prefix after it signs, and fails a transaction whose payload has neither prefix.

**Format:** Hex-encoded binary codec signing payload of the transaction JSON

**Key requirements:**

1. For single-signed transactions, set `SigningPubKey` to the wallet's public key
2. Use `encodeForSigning` (not `encode`) for unsigned transactions
3. For multi-signed transactions, use `encodeForMultisigning` with the signer's address, leave `SigningPubKey` empty, and set `signOnly: true`


encode() output fails
Ripple Custody (Wallet) signs exactly the bytes you submit. XRPL verifies each signature against a signing payload that starts with a 4-byte prefix: `encodeForSigning` prepends `53545800` (`STX\0`) for single-signing, and `encodeForMultisigning` prepends `534D5400` (`SMT\0`) for multi-signing. Plain `encode` adds neither. If you submit `encode` output, the request returns HTTP 200, but the transaction ends in `FAILED` with the reason `SignProcessor: unsupported signing payload prefix`. A multi-signing payload (`534D5400`) submitted with `signOnly: false` also ends in `FAILED`, because the platform can't submit a multi-signed transaction.

Quick check: confirm that your `encodedTransaction` hex starts with `53545800` (single-sign) or `534D5400` (multi-sign).

**Multi-signing:** Build the transaction's `Signers` array from the returned `canonicalSignature`. The platform assembles single-signed blobs only, so for a `534D5400` payload the response carries `canonicalSignature` but no `signedTransaction` or `hash`.

**JavaScript example:**

```javascript
const { encodeForSigning } = require('ripple-binary-codec');

const tx = {
    Account: "rSourceAddress...",
    Destination: "rDestAddress...",
    Amount: "1000000",  // In drops
    Fee: "10",
    Sequence: 12345,
    SigningPubKey: "02ECE63017B0FEFC...",  // Required
    TransactionType: "Payment"
};

const encodedTransaction = encodeForSigning(tx);

// For multi-signed transactions, set SigningPubKey to "" and pass the signer's address:
// const encodedTransaction = encodeForMultisigning({ ...tx, SigningPubKey: "" }, signerAddress);
```

**Python example:**

```python
from xrpl.core.binarycodec import encode_for_signing

tx = {
    "Account": "rSourceAddress...",
    "Destination": "rDestAddress...",
    "Amount": "1000000",
    "Fee": "10",
    "Sequence": 12345,
    "SigningPubKey": "02ECE63017B0FEFC...",
    "TransactionType": "Payment"
}

encoded_transaction = encode_for_signing(tx)

# For multi-signed transactions, set SigningPubKey to "" and pass the signer's address:
# encoded_transaction = encode_for_multisigning({**tx, "SigningPubKey": ""}, signer_address)
```

**Java example (xrpl4j):**

```java
import org.xrpl.xrpl4j.codec.binary.XrplBinaryCodec;

XrplBinaryCodec codec = XrplBinaryCodec.getInstance();

String txJson = """
    {
      "Account": "rSourceAddress...",
      "Destination": "rDestAddress...",
      "Amount": "1000000",
      "Fee": "10",
      "Sequence": 12345,
      "SigningPubKey": "02ECE63017B0FEFC...",
      "TransactionType": "Payment"
    }
    """;

// Single-signing: prepends 53545800
String encodedTransaction = codec.encodeForSigning(txJson);

// For multi-signed transactions, pass the signer's address
// (and leave SigningPubKey empty in the transaction JSON):
// String encodedTransaction = codec.encodeForMultiSigning(txJson, signerAddress);
```

## Solana

The `encodedTransaction` field must be a **base64-encoded** serialized Solana transaction.

**Format:** `base64(transaction.MarshalBinary())`

**Key requirements:**

1. Transaction must include a valid recent blockhash
2. Account keys must be properly ordered (fee payer first)
3. Use standard base64 encoding (not base58)


**Python example:**

```python
from solders.transaction import Transaction
from solders.message import Message
import base64

# Build your transaction message
message = Message.new_with_blockhash(
    instructions,
    payer,
    blockhash
)

# Create unsigned transaction
tx = Transaction.new_unsigned(message)

# Serialize and base64 encode
tx_bytes = bytes(tx)
encoded_transaction = base64.b64encode(tx_bytes).decode('utf-8')
```

**JavaScript example:**

```javascript
const { Transaction } = require('@solana/web3.js');

const tx = new Transaction();
tx.recentBlockhash = blockhash;
tx.feePayer = payerPublicKey;
tx.add(instruction);

// Serialize (without signing)
const serialized = tx.serialize({ requireAllSignatures: false });
const encodedTransaction = serialized.toString('base64');
```

## TRON

The `encodedTransaction` field must be a **hex-encoded** TRON Transaction protobuf.

**Format:** `hex(proto.Marshal(Transaction))`

**Key requirements:**

1. Transaction must include valid `ref_block_bytes` and `ref_block_hash` from a recent block
2. Expiration timestamp must be in the future (typically current time + 60 seconds, in milliseconds)
3. Fee limit must be set for TRC-20 transfers (recommended: 15,000,000 SUN = 15 TRX)
4. The signing hash is `SHA256(raw_data)` — Ripple Custody (Wallet) computes this automatically


**Supported contract types:**

| Contract Type | Description |
|  --- | --- |
| `TransferContract` | Native TRX transfers |
| `TriggerSmartContract` | TRC-20 token transfers and smart contract calls |


**Python example (using the TRON HTTP API):**

```python
import requests

# Build a TRX transfer transaction
tx = requests.post(
    "https://api.shasta.trongrid.io/wallet/createtransaction",  # or mainnet
    json={
        "owner_address": "TSourceAddress...",
        "to_address": "TDestAddress...",
        "amount": 1_000_000,  # 1 TRX in SUN
        "visible": True,
    },
).json()

# raw_data_hex is only the raw_data message. Wrap it as field 1 of a
# Transaction protobuf: tag 0x0a, varint length, then the raw_data bytes.
raw = bytes.fromhex(tx["raw_data_hex"])
length, n = b"", len(raw)
while n >= 0x80:
    length += bytes([(n & 0x7F) | 0x80])
    n >>= 7
length += bytes([n])

encoded_transaction = (b"\x0a" + length + raw).hex()

# For TRC-20 transfers, use wallet/triggersmartcontract instead
```

**JavaScript example (using TronWeb):**

```javascript
const TronWeb = require('tronweb');

const tronWeb = new TronWeb({
    fullHost: 'https://api.shasta.trongrid.io',  // or mainnet
});

// Build a TRX transfer transaction
const tx = await tronWeb.transactionBuilder.sendTrx(
    'TDestAddress...',      // to
    1000000,                // amount in SUN (1 TRX)
    'TSourceAddress...'     // from
);

// raw_data_hex is only the raw_data message. Wrap it as field 1 of a
// Transaction protobuf: tag 0x0a, varint length, then the raw_data bytes.
const raw = Buffer.from(tx.raw_data_hex, 'hex');
const length = [];
for (let n = raw.length; ; n >>>= 7) {
    if (n < 0x80) { length.push(n); break; }
    length.push((n & 0x7f) | 0x80);
}

const encodedTransaction = Buffer.concat([Buffer.from([0x0a, ...length]), raw]).toString('hex');
```

**Go example:**

```go
import (
    "encoding/hex"
    "time"
    
    "github.com/fbsobreira/gotron-sdk/pkg/proto/core"
    "google.golang.org/protobuf/proto"
    "google.golang.org/protobuf/types/known/anypb"
)

// Build a TRX transfer
transfer := &core.TransferContract{
    OwnerAddress: ownerAddrBytes,  // 21-byte TRON address
    ToAddress:    toAddrBytes,
    Amount:       1000000,         // 1 TRX in SUN
}

anyValue, _ := anypb.New(transfer)

tx := &core.Transaction{
    RawData: &core.TransactionRaw{
        Contract: []*core.Transaction_Contract{{
            Type:      core.Transaction_Contract_TransferContract,
            Parameter: anyValue,
        }},
        RefBlockBytes: refBlockBytes,  // From recent block
        RefBlockHash:  refBlockHash,
        Expiration:    time.Now().Add(60*time.Second).UnixMilli(),
        Timestamp:     time.Now().UnixMilli(),
    },
}

txBytes, _ := proto.Marshal(tx)
encodedTransaction := hex.EncodeToString(txBytes)
```

TRON address format
TRON addresses can be in base58 format (starts with `T`) or hex format (starts with `41`). The API accepts base58 addresses, but internally they are converted to 21-byte hex addresses in the protobuf.

Reference block requirements
TRON transactions require `ref_block_bytes` and `ref_block_hash` from a recent block (within ~18 hours). If you're building transactions manually, fetch the latest block and extract bytes 6-8 of the block number and bytes 8-16 of the block hash.

## 1Money

The `encodedTransaction` field must be a **hex-encoded JSON object** with a `transactionType` and a `payload`.

**Format:** `hex(JSON({ transactionType, payload }))`

```json
{
  "transactionType": "TokenMint",
  "payload": {
    "chain_id": 1212101,
    "nonce": 0,
    "recipient": "0x...",
    "value": 1000000000000000000,
    "token": "0x..."
  }
}
```

**Key requirements:**

1. Set `transactionType` to one of the supported transaction types. (See
[1Money transaction types](/products/wallet/transactions/raw-signing/1money-transaction-types).)
2. Include the 1Money `chain_id` in every payload
3. Set `nonce` to the wallet's current sequence, from `GET /v2/vaults/{vaultId}/wallets/{walletId}/sequence`. (See
[Example: mint tokens](/products/wallet/transactions/raw-signing/1money-transaction-types#example-mint-tokens).)
4. Use snake_case payload field names, as shown in the examples.


Payload shapes vary by transaction type
The envelope is the same for every 1Money transaction, but the `payload` fields differ.
Check [1Money transaction types](/products/wallet/transactions/raw-signing/1money-transaction-types) before encoding — several
types share a shape, and a few (notably `BatchPayment`) don't.