operation to TYPED_MESSAGE, and the result is a signature over a readable message rather than an on-chain transfer. For the concepts behind typed messages and how to enable the feature in your workspace, see Typed Message Signing.
Request structure
The following fields are required for any typed message signing request.| Field | Description |
|---|---|
operation | The transaction operation type, which is TYPED_MESSAGE for typed message signing. |
assetId | The network to sign for, which is ETH for Ethereum and other EVM networks, TRX for Tron or BTC for Bitcoin. On a testnet you can use the matching testnet asset ID, such as ETH_TEST5. |
source | The vault account whose key signs the message. |
extraParameters | An object that contains rawMessageData, which holds a messages array. Each entry in the array sets the content to sign and the message type. |
type of each message is one of EIP191, EIP712, TIP191 or BTC_MESSAGE.
type | Used for |
|---|---|
EIP191 | Ethereum and EVM personal messages. |
EIP712 | Ethereum and EVM typed structured data. |
TIP191 | Tron personal messages. |
BTC_MESSAGE | Bitcoin personal messages. |
You do not need to add a prefix to the message before you submit it to the API. The only requirement is that for any message type other than
EIP712 the content must be submitted in hexadecimal format, whereas for EIP712 the content is the typed data object itself.rawMessageData fields see Raw signing objects, and for all request and response schemas see the Create transaction API reference. To avoid creating duplicate requests when you retry, set a unique externalTxId on every request, as described in Transaction idempotency.
Sign an EIP-191 personal message
This example signs the messageHello, Ethereum! from a vault account on the Sepolia testnet, where the message is submitted as a hex string.
- SDK
- API
- CLI
import { TransactionOperation, TransferPeerPathType } from "@fireblocks/ts-sdk";
const response = await fireblocks.transactions.createTransaction({
transactionRequest: {
operation: TransactionOperation.TypedMessage,
assetId: "ETH_TEST5",
source: {
type: TransferPeerPathType.VaultAccount,
id: "<SOURCE_VAULT_ACCOUNT_ID>",
},
note: "EIP-191 message", // Optional - Free-text note shown in your workspace, not sent to the blockchain
extraParameters: {
rawMessageData: {
messages: [
{
content: Buffer.from("Hello, Ethereum!").toString("hex"), // Hex-encoded message
type: "EIP191",
},
],
},
},
externalTxId: "<YOUR_UNIQUE_ID>", // Optional - Your unique ID; Fireblocks rejects later requests with the same value
},
});
console.log(response.data);
from fireblocks.models.transaction_request import TransactionRequest
from fireblocks.models.transaction_operation import TransactionOperation
from fireblocks.models.source_transfer_peer_path import SourceTransferPeerPath
from fireblocks.models.transfer_peer_path_type import TransferPeerPathType
from fireblocks.models.extra_parameters import ExtraParameters
transaction_request = TransactionRequest(
operation=TransactionOperation.TYPED_MESSAGE,
asset_id="ETH_TEST5",
source=SourceTransferPeerPath(
type=TransferPeerPathType.VAULT_ACCOUNT,
id="<SOURCE_VAULT_ACCOUNT_ID>",
),
note="EIP-191 message", # Optional - Free-text note shown in your workspace, not sent to the blockchain
extra_parameters=ExtraParameters(
raw_message_data={
"messages": [
{
"content": "Hello, Ethereum!".encode().hex(), # Hex-encoded message
"type": "EIP191",
}
]
}
),
external_tx_id="<YOUR_UNIQUE_ID>", # Optional - Your unique ID; Fireblocks rejects later requests with the same value
)
future = fireblocks.transactions.create_transaction(
transaction_request=transaction_request,
)
print(future.result().data)
import com.fireblocks.sdk.model.*;
import java.util.List;
import java.util.Map;
TransactionRequest transactionRequest = new TransactionRequest()
.operation(TransactionOperation.TYPED_MESSAGE)
.assetId("ETH_TEST5")
.source(new SourceTransferPeerPath()
.type(TransferPeerPathType.VAULT_ACCOUNT)
.id("<SOURCE_VAULT_ACCOUNT_ID>"))
.note("EIP-191 message") // Optional - Free-text note shown in your workspace, not sent to the blockchain
.extraParameters(new ExtraParameters().rawMessageData(Map.of(
"messages", List.of(Map.of(
"content", "48656c6c6f2c20457468657265756d21", // Hex-encoded "Hello, Ethereum!"
"type", "EIP191")))))
.externalTxId("<YOUR_UNIQUE_ID>"); // Optional - Your unique ID; Fireblocks rejects later requests with the same value
CreateTransactionResponse transaction = fireblocks.transactions()
.createTransaction(
transactionRequest,
null, // Optional - xEndUserWalletId (UUID), only for end-user wallet operations
null) // Optional - Not used for transactions; use externalTxId in the request body
.get()
.getData();
System.out.println(transaction);
POST /v1/transactions
{
"operation": "TYPED_MESSAGE",
"assetId": "ETH_TEST5",
"source": { "type": "VAULT_ACCOUNT", "id": "<SOURCE_VAULT_ACCOUNT_ID>" },
"note": "EIP-191 message",
"extraParameters": {
"rawMessageData": {
"messages": [
{ "content": "48656c6c6f2c20457468657265756d21", "type": "EIP191" }
]
}
},
"externalTxId": "<YOUR_UNIQUE_ID>"
}
# Preview the request without sending it
fireblocks transactions create-transaction \
--data '{"operation":"TYPED_MESSAGE","assetId":"ETH_TEST5","source":{"type":"VAULT_ACCOUNT","id":"<SOURCE_VAULT_ACCOUNT_ID>"},"extraParameters":{"rawMessageData":{"messages":[{"content":"48656c6c6f2c20457468657265756d21","type":"EIP191"}]}},"externalTxId":"<YOUR_UNIQUE_ID>"}' \
--dry-run
# Execute
fireblocks transactions create-transaction \
--data '{"operation":"TYPED_MESSAGE","assetId":"ETH_TEST5","source":{"type":"VAULT_ACCOUNT","id":"<SOURCE_VAULT_ACCOUNT_ID>"},"extraParameters":{"rawMessageData":{"messages":[{"content":"48656c6c6f2c20457468657265756d21","type":"EIP191"}]}},"externalTxId":"<YOUR_UNIQUE_ID>"}' \
--no-confirm
Sign an EIP-712 typed data message
ForEIP712 the content is the typed data object, made up of types, primaryType, domain and message, rather than a hex string. The following example signs a Permit message, using placeholder addresses that you replace with your own values.
- SDK
- API
- CLI
import { TransactionOperation, TransferPeerPathType } from "@fireblocks/ts-sdk";
const eip712Message = {
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
Permit: [
{ name: "holder", type: "address" },
{ name: "spender", type: "address" },
{ name: "nonce", type: "uint256" },
{ name: "expiry", type: "uint256" },
{ name: "allowed", type: "bool" },
],
},
primaryType: "Permit",
domain: {
name: "<TOKEN_NAME>",
version: "1",
chainId: 11155111, // Sepolia; use the chain ID of the EVM network you are signing for
verifyingContract: "<CONTRACT_ADDRESS>",
},
message: {
holder: "<HOLDER_ADDRESS>",
spender: "<SPENDER_ADDRESS>",
nonce: 123,
expiry: 1655467980,
allowed: true,
},
};
const response = await fireblocks.transactions.createTransaction({
transactionRequest: {
operation: TransactionOperation.TypedMessage,
assetId: "ETH_TEST5",
source: {
type: TransferPeerPathType.VaultAccount,
id: "<SOURCE_VAULT_ACCOUNT_ID>",
},
note: "EIP-712 message", // Optional - Free-text note shown in your workspace, not sent to the blockchain
extraParameters: {
rawMessageData: {
messages: [{ content: eip712Message, type: "EIP712" }],
},
},
externalTxId: "<YOUR_UNIQUE_ID>", // Optional - Your unique ID; Fireblocks rejects later requests with the same value
},
});
console.log(response.data);
from fireblocks.models.transaction_request import TransactionRequest
from fireblocks.models.transaction_operation import TransactionOperation
from fireblocks.models.source_transfer_peer_path import SourceTransferPeerPath
from fireblocks.models.transfer_peer_path_type import TransferPeerPathType
from fireblocks.models.extra_parameters import ExtraParameters
eip712_message = {
"types": {
"EIP712Domain": [
{"name": "name", "type": "string"},
{"name": "version", "type": "string"},
{"name": "chainId", "type": "uint256"},
{"name": "verifyingContract", "type": "address"},
],
"Permit": [
{"name": "holder", "type": "address"},
{"name": "spender", "type": "address"},
{"name": "nonce", "type": "uint256"},
{"name": "expiry", "type": "uint256"},
{"name": "allowed", "type": "bool"},
],
},
"primaryType": "Permit",
"domain": {
"name": "<TOKEN_NAME>",
"version": "1",
"chainId": 11155111, # Sepolia; use the chain ID of the EVM network you are signing for
"verifyingContract": "<CONTRACT_ADDRESS>",
},
"message": {
"holder": "<HOLDER_ADDRESS>",
"spender": "<SPENDER_ADDRESS>",
"nonce": 123,
"expiry": 1655467980,
"allowed": True,
},
}
transaction_request = TransactionRequest(
operation=TransactionOperation.TYPED_MESSAGE,
asset_id="ETH_TEST5",
source=SourceTransferPeerPath(
type=TransferPeerPathType.VAULT_ACCOUNT,
id="<SOURCE_VAULT_ACCOUNT_ID>",
),
note="EIP-712 message", # Optional - Free-text note shown in your workspace, not sent to the blockchain
extra_parameters=ExtraParameters(
raw_message_data={
"messages": [{"content": eip712_message, "type": "EIP712"}]
}
),
external_tx_id="<YOUR_UNIQUE_ID>", # Optional - Your unique ID; Fireblocks rejects later requests with the same value
)
future = fireblocks.transactions.create_transaction(
transaction_request=transaction_request,
)
print(future.result().data)
import com.fireblocks.sdk.model.*;
import java.util.List;
import java.util.Map;
Map<String, Object> eip712Message = Map.of(
"types", Map.of(
"EIP712Domain", List.of(
Map.of("name", "name", "type", "string"),
Map.of("name", "version", "type", "string"),
Map.of("name", "chainId", "type", "uint256"),
Map.of("name", "verifyingContract", "type", "address")),
"Permit", List.of(
Map.of("name", "holder", "type", "address"),
Map.of("name", "spender", "type", "address"),
Map.of("name", "nonce", "type", "uint256"),
Map.of("name", "expiry", "type", "uint256"),
Map.of("name", "allowed", "type", "bool"))),
"primaryType", "Permit",
"domain", Map.of(
"name", "<TOKEN_NAME>",
"version", "1",
"chainId", 11155111, // Sepolia; use the chain ID of the EVM network you are signing for
"verifyingContract", "<CONTRACT_ADDRESS>"),
"message", Map.of(
"holder", "<HOLDER_ADDRESS>",
"spender", "<SPENDER_ADDRESS>",
"nonce", 123,
"expiry", 1655467980,
"allowed", true));
TransactionRequest transactionRequest = new TransactionRequest()
.operation(TransactionOperation.TYPED_MESSAGE)
.assetId("ETH_TEST5")
.source(new SourceTransferPeerPath()
.type(TransferPeerPathType.VAULT_ACCOUNT)
.id("<SOURCE_VAULT_ACCOUNT_ID>"))
.note("EIP-712 message") // Optional - Free-text note shown in your workspace, not sent to the blockchain
.extraParameters(new ExtraParameters().rawMessageData(Map.of(
"messages", List.of(Map.of("content", eip712Message, "type", "EIP712")))))
.externalTxId("<YOUR_UNIQUE_ID>"); // Optional - Your unique ID; Fireblocks rejects later requests with the same value
CreateTransactionResponse transaction = fireblocks.transactions()
.createTransaction(
transactionRequest,
null, // Optional - xEndUserWalletId (UUID), only for end-user wallet operations
null) // Optional - Not used for transactions; use externalTxId in the request body
.get()
.getData();
System.out.println(transaction);
POST /v1/transactions
{
"operation": "TYPED_MESSAGE",
"assetId": "ETH_TEST5",
"source": { "type": "VAULT_ACCOUNT", "id": "<SOURCE_VAULT_ACCOUNT_ID>" },
"note": "EIP-712 message",
"extraParameters": {
"rawMessageData": {
"messages": [
{
"type": "EIP712",
"content": {
"types": {
"EIP712Domain": [
{ "name": "name", "type": "string" },
{ "name": "version", "type": "string" },
{ "name": "chainId", "type": "uint256" },
{ "name": "verifyingContract", "type": "address" }
],
"Permit": [
{ "name": "holder", "type": "address" },
{ "name": "spender", "type": "address" },
{ "name": "nonce", "type": "uint256" },
{ "name": "expiry", "type": "uint256" },
{ "name": "allowed", "type": "bool" }
]
},
"primaryType": "Permit",
"domain": {
"name": "<TOKEN_NAME>",
"version": "1",
"chainId": 11155111,
"verifyingContract": "<CONTRACT_ADDRESS>"
},
"message": {
"holder": "<HOLDER_ADDRESS>",
"spender": "<SPENDER_ADDRESS>",
"nonce": 123,
"expiry": 1655467980,
"allowed": true
}
}
}
]
}
},
"externalTxId": "<YOUR_UNIQUE_ID>"
}
# Preview the request without sending it
fireblocks transactions create-transaction \
--data '{"operation":"TYPED_MESSAGE","assetId":"ETH_TEST5","source":{"type":"VAULT_ACCOUNT","id":"<SOURCE_VAULT_ACCOUNT_ID>"},"extraParameters":{"rawMessageData":{"messages":[{"type":"EIP712","content":{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Permit":[{"name":"holder","type":"address"},{"name":"spender","type":"address"},{"name":"nonce","type":"uint256"},{"name":"expiry","type":"uint256"},{"name":"allowed","type":"bool"}]},"primaryType":"Permit","domain":{"name":"<TOKEN_NAME>","version":"1","chainId":11155111,"verifyingContract":"<CONTRACT_ADDRESS>"},"message":{"holder":"<HOLDER_ADDRESS>","spender":"<SPENDER_ADDRESS>","nonce":123,"expiry":1655467980,"allowed":true}}}]}},"externalTxId":"<YOUR_UNIQUE_ID>"}' \
--dry-run
# Execute by re-running the same command with --no-confirm instead of --dry-run
Network-specific guides
Each network has its own message formats and signature structure, which the following pages cover in detail.- Sign typed messages for Ethereum and EVM networks covers
EIP191andEIP712. For any EVM-compatible network other than Ethereum theassetIdyou pass is stillETH. - Sign typed messages in Tron covers
TIP191. - Sign typed messages in Bitcoin covers
BTC_MESSAGE.