# Raw signing

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.

Disabled by default
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.

API documentation
See our [Wallet-as-a-Service (Palisade) API reference](/products/wallet/api-docs/palisade-api/palisade-api) for information on how to submit raw transactions via the API.

## Transaction Encoding Formats

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.

### 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 use the **XRP Binary Codec** format with `encodeForSigning`.

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

**Key requirements:**

1. Include the `SigningPubKey` field with the wallet's public key
2. Use `encodeForSigning` (not `encode`) for unsigned transactions


**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);
```

**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)
```

### 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)` — 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):**


```python
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 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
);

// The transaction object contains raw_data_hex
const encodedTransaction = tx.raw_data_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.