Raw signing with Wallet-as-a-Service (Palisade) offers the ability to sign any transaction type available to a blockchain. It can be used on any transaction type if enabled.
This is an important feature of the Wallet-as-a-Service (Palisade) platform as it means customers can benefit from all natively supported transaction types of a blockchain as soon as they become available, regardless of whether that blockchain or transaction type is currently supported by Wallet-as-a-Service (Palisade).
Raw signing can be enabled and disabled for a wallet from wallet settings.
Raw signing is a powerful and insecure signing method. It is therefore disabled by default. Please only enable raw signing for individual wallets if you fully understand this feature.
See our Wallet-as-a-Service (Palisade) API reference for information on how to submit raw transactions via the API.
When using the Raw Transaction API, the encodedTransaction field must be encoded in a blockchain-specific format. This section details the exact encoding requirements for each supported blockchain.
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 use the XRP Binary Codec format with encodeForSigning.
Format: Hex-encoded binary codec output of the transaction JSON
Key requirements:
- Include the
SigningPubKeyfield with the wallet's public key - Use
encodeForSigning(notencode) for unsigned transactions
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);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)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)— Wallet-as-a-Service (Palisade) 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 tronpy):
from tronpy import Tron
client = Tron(network='shasta') # or 'mainnet'
# Build a TRX transfer transaction
txn = (
client.trx.transfer(
from_="TSourceAddress...",
to="TDestAddress...",
amount=1_000_000 # 1 TRX in SUN
)
.fee_limit(1_000_000) # 1 TRX fee limit
.build()
)
# Get the raw transaction bytes (protobuf serialized)
raw_bytes = txn._raw_data.SerializeToString()
# Hex encode for Wallet-as-a-Service (Palisade)
encoded_transaction = raw_bytes.hex()
# For TRC-20 transfers, use trigger_smart_contract 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
);
// The transaction object contains raw_data_hex
const encodedTransaction = tx.raw_data_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.