When you use the raw transaction API, 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.
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:
- Include v, r, s signature fields — Set to
0for unsigned transactions - Apply outer RLP wrapper — The typed transaction must be wrapped as an RLP byte string
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:
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:
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)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:
- For single-signed transactions, set
SigningPubKeyto the wallet's public key - Use
encodeForSigning(notencode) for unsigned transactions - For multi-signed transactions, use
encodeForMultisigningwith the signer's address, leaveSigningPubKeyempty, and setsignOnly: true
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:
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:
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):
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);The encodedTransaction field must be a base64-encoded serialized Solana transaction.
Format: base64(transaction.MarshalBinary())
Key requirements:
- Transaction must include a valid recent blockhash
- Account keys must be properly ordered (fee payer first)
- Use standard base64 encoding (not base58)
Python example:
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:
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');The encodedTransaction field must be a hex-encoded TRON Transaction protobuf.
Format: hex(proto.Marshal(Transaction))
Key requirements:
- Transaction must include valid
ref_block_bytesandref_block_hashfrom a recent block - Expiration timestamp must be in the future (typically current time + 60 seconds, in milliseconds)
- Fee limit must be set for TRC-20 transfers (recommended: 15,000,000 SUN = 15 TRX)
- 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):
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 insteadJavaScript example (using TronWeb):
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:
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 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.
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.
The encodedTransaction field must be a hex-encoded JSON object with a transactionType and a payload.
Format: hex(JSON({ transactionType, payload }))
{
"transactionType": "TokenMint",
"payload": {
"chain_id": 1212101,
"nonce": 0,
"recipient": "0x...",
"value": 1000000000000000000,
"token": "0x..."
}
}Key requirements:
- Set
transactionTypeto one of the supported transaction types. (See 1Money transaction types.) - Include the 1Money
chain_idin every payload - Set
nonceto the wallet's current sequence, fromGET /v2/vaults/{vaultId}/wallets/{walletId}/sequence. (See Example: mint tokens.) - Use snake_case payload field names, as shown in the examples.
The envelope is the same for every 1Money transaction, but the payload fields differ. Check 1Money transaction types before encoding — several types share a shape, and a few (notably BatchPayment) don't.