Typed Message Signing

Prerequisites

Overview

Typed Message is a popular activity within the Ethereum ecosystem. This basically lets you sign any arbitrary message you would like. You can read more about ERC-712 (Typed Structure Data Hashing & Signing) here.

Transaction Authorization Policy (TAP) Requirements

In order to execute Typed Message transactions from Fireblocks, the issuer must be authorized to do so via a "Typed Message" TAP rule, that explicitly allows for typed messages, as shown here:

Typed message EIP-712 example

See below the use of typed message in the EIP-712 standard:

const { FireblocksSDK, PeerType, TransactionOperation, TransactionStatus } = require("fireblocks-sdk");
const fs = require('fs');
const path = require('path');
const apiSecret = fs.readFileSync(path.resolve(__dirname, "fireblocks_secret.key"), "utf8");
const apiKey = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"; // Your API Key
const fireblocks = new FireblocksSDK(apiSecret, apiKey);

async function signEIP712Message(vaultAccountId, exampleSignRequest) {     
    const { status, id } = await fireblocks.createTransaction({
        operation: TransactionOperation.TYPED_MESSAGE,
        assetId: "ETH",
        source: { 
            type: PeerType.VAULT_ACCOUNT,
            id: vaultAccountId
        },
        amount: "0",
        note: "Test EIP-712 Message",
        extraParameters: {
            rawMessageData: {
                messages: [exampleSignRequest],
            },
        },
    });
    let currentStatus = status;
    let txInfo;

    while (currentStatus != TransactionStatus.COMPLETED && currentStatus != TransactionStatus.FAILED) { 
        console.log("keep polling for tx " + id + "; status: " + currentStatus);
        txInfo = await fireblocks.getTransactionById(id);
        currentStatus = txInfo.status; 
        await new Promise(r => setTimeout(r, 1000));
    };
}


const exampleSignRequest = {
    type: "EIP712",
    index: 0,
    content: {
        types: {
          EIP712Domain: [
            {
              name: "name",
              type: "string"
            },
            {
              name: "version",
              type: "string"
            },
            {
              name: "chainId",
              type: "uint256"
            },
            {
              name: "verifyingContract",
              type: "address"
            }
          ],
          Permit: [
            {
              name: "owner",
              type: "address"
            },
            {
              name: "spender",
              type: "address"
            },
            {
              name: "value",
              type: "uint256"
            },
            {
              name: "nonce",
              type: "uint256"
            },
            {
              name: "deadline",
              type: "uint256"
            }
          ]
        },
        primaryType: "Permit",
        domain: {
          name: "USDC",
          version: "1",
          chainId: 9,
          verifyingContract: "0x6e11530D05DF9BcA475Ba23eA26AcCCab9122d4c"
        },
        message: {
          owner: "0x74ehEb032057CF42bDA226F132AF771ADc415D32",
          spender: "0x7a6E1C5cBe4F7B1f863b2251Cb801b4dEE905c2c",
          value: 2,
          nonce: 0,
          deadline: 1923318233
        }
      }
};

signEIP712Message("0", exampleSignRequest);

Typed message example

See below the use of typed message signing API for Ethereum message signing.

async function signArbitraryMessage(fireblocks: FireblocksSDK, vaultAccountId: string, message: string, addressIndex = 0) { 
    
    const { status, id } = await fireblocks.createTransaction({
        operation: TransactionOperation.TYPED_MESSAGE,
        assetId: "ETH",
        source: { 
            type: PeerType.VAULT_ACCOUNT,
            id: vaultAccountId
        },
        note: `Test Message`,
        extraParameters: {
            rawMessageData: {
                messages: [{
                    content: Buffer.from(message).toString("hex"),
                    index: addressIndex,
                    type: "ETH_MESSAGE"
                }]
            }
        }
    });
    let currentStatus = status;
    let txInfo:any;

    while (currentStatus != TransactionStatus.COMPLETED && currentStatus != TransactionStatus.FAILED) { 
        console.log("keep polling for tx " + id + "; status: " + currentStatus);
        txInfo = await fireblocks.getTransactionById(id);
        currentStatus = txInfo.status; 
        await new Promise(r => setTimeout(r, 1000));
    };
   
    const walletAddresses = await fireblocks.getDepositAddresses(vaultAccountId, "ETH");

    console.log(walletAddresses);
        
    console.log("Address: ", walletAddresses[0].address); 
    console.log("Message: ", message);
        
    const signature = txInfo.signedMessages[0].signature;

    const v = 27 + signature.v;
    console.log("Signature: ", "0x" + signature.r + signature.s + v.toString(16));
}

signArbitraryMessage(fireblocks, "0", "INSERT TEXT HERE");
def sign_arbitrary_message(fireblocks: FireblocksSDK, vault_id: str, message: str, address_index: int = 0):
    status, id = fireblocks.create_transaction(
        tx_type="TYPED_MESSAGE",
        asset_id="ETH",
        source=TransferPeerPath(VAULT_ACCOUNT, vault_id),
        note="ETH Test Message",
        extra_parameters={
            "rawMessageData":
                RawMessage([UnsignedMessage(
                    content=bytes(message).hex(),
                    bip44addressIndex=address_index
                )])
        }
    ).values()

    wallet_address = fireblocks.get_deposit_addresses(vault_id, "ETH")
    print(wallet_address)

    print(f"Address: {wallet_address[0]['address']}\nMessage: {message}")
    
    while status != TRANSACTION_STATUS_COMPLETED and status != TRANSACTION_STATUS_FAILED:
        tx_info = fireblocks.get_transaction_by_id(id)
        status = tx_info['status']
        
    signature = tx_info["signedMessages"][0]["signature"]
    
    v = 27 + int(signature["v"])
    
    print(f"Signature: 0x{signature['r']}{signature['s']}{v}")
    
sign_arbitrary_message(fireblocks, "0", "INSERT TEXT HERE")