Create and Authenticate Transactions

In Fordefi, a "transaction" is any object that can be signed cryptographically (with a private key).


Automate transaction signing

Signing programmatic transactions requires running your own API Signer. Learn more about the feature before you get started with creating transactions.

Get started with programmatic transactions

To programmatically create a transaction, you need to call the Create Transaction API by sending a POST request to the /api/v1/transactions endpoint. You can use the following code sample as the basis for any type of transaction. The body of the request differs, based on the specific type of transaction you are creating. Examples appear in the pages of this section. The mandatory x-signature header is used for an additional layer of authentication for programmatic transactions. See Authenticate transactions, below.

ACCESS_TOKEN='enter your API User Access Token'

TIMESTAMP="$(($(date +%s) * 1000))"

SIGNATURE="$(echo -n "${ENDPOINT}|${TIMESTAMP}|${BODY}" | openssl dgst -sha256 -sign ${PRIVATE_KEY_FILE} | base64 | tr -d \\n)"

echo -n "${ENDPOINT}|${TIMESTAMP}|${BODY}"

curl -v "${ENDPOINT}" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "x-signature: ${SIGNATURE}" \
  -H "x-timestamp: ${TIMESTAMP}" \
  -d "${BODY}"
import requests
import base64
import json
import datetime
import ecdsa
import hashlib

request_json = {...}

access_token = "<API User Access Token>"
request_body = json.dumps(request_json)
private_key_file = "private.pem"
path = "/api/v1/transactions"
timestamp ="%s")
payload = f"{path}|{timestamp}|{request_body}"

with open(private_key_file, "r") as f:
    signing_key = ecdsa.SigningKey.from_pem(

signature = signing_key.sign(
    data=payload.encode(), hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_der

resp_tx =
        "Authorization": f"Bearer {access_token}",
        "x-signature": base64.b64encode(signature),
        "x-timestamp": timestamp.encode(),

const crypto = require("crypto");
const fs = require('fs');

const gatewayHost = ""
const accessToken = "<Enter API User access token>"
const requestJson = {

const requestBody = JSON.stringify(requestJson)
const path = "/api/v1/transactions"
const privateKeyFile = "/private.pem"
const timestamp = new Date().getTime();
const payload = `${path}|${timestamp}|${requestBody}`;

const secretPem = fs.readFileSync(privateKeyFile, 'utf8');
const privateKey = crypto.createPrivateKey(secretPem);
const sign = crypto.createSign('SHA256').update(payload, 'utf8').end();
const signature = sign.sign(privateKey, 'base64');

fetch(`https://${gatewayHost}${path}`, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        "Authorization": `Bearer ${accessToken}`,
        'X-Timestamp': timestamp,
        'X-Signature': signature,
    body: requestBody,
}).then((response) => response.text())
  .then((json) => console.log(json));
// Using crate p256 = { version = "0.12.0", features = ["pem"] }
use base64::{engine::general_purpose, Engine as _};
use p256::ecdsa::{signature::Signer, Signature, SigningKey};
use std::fs;
use std::time::{SystemTime, UNIX_EPOCH};

// This is the main function
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let gateway_host = "";
    let access_token = "<Enter API User access token>";

    let request_body = r#"

    let path = "/api/v1/transactions";
  	let private_key_file = "private.pem";
    let timestamp = SystemTime::now()
    let payload = format!("{path}|{timestamp}|{request_body}");

    let priv_pem = fs::read_to_string(private_key_file).expect("Failed to read pem file");
    let private_key = p256::SecretKey::from_sec1_pem(&priv_pem).expect("Failed to decode pem key");
    let signing_key: SigningKey = private_key.into();

    let signature: Signature = signing_key.sign(payload.as_bytes());
    let formatted_signature = general_purpose::STANDARD.encode(signature.to_der().to_bytes());
    let client = reqwest::blocking::Client::new();
    let res = client
        .header("Content-Type", "application/json")
        .header("X-Timestamp", timestamp)
        .header("X-Signature", formatted_signature)

    println!("Result: {res}");

Transaction types

Fordefi supports creating several types of transactions, each with its own format. Since some flows are very common (for example, EVM revoke allowance transactions or Solana transfer transactions), there are special request formats that are used to make the creation process of such transactions quick and easy.

The following list defines which requests are allowed for each transaction type. Links lead to sample code.

  • EVM transaction: A native currency transfer or a smart contract call on an EVM-based chain.
  • EVM message: In which a message is signed for off-chain use on an EVM-based chain.
  • Solana transaction: A list of instructions to invoke on a Solana-based chain.
  • Cosmos transaction: A transaction in the Cosmos ecosystem on Cosmos Hub or one of the supported app chains.
  • Black Box signature: In which a payload is signed for use, external to the Fordefi platform.

Transactions are created in the context of a vault. Hence, not all transaction types are supported for all vaults, and only the following are allowed:

EVM Transaction EVM Message Solana Transaction Cosmos Transaction Black Box Signature
EVM Vault βœ”οΈŽ βœ”οΈŽ ✘ ✘ ✘
Solana Vault ✘ ✘ βœ”οΈŽ ✘ ✘
Cosmos Vault ✘ ✘ ✘ βœ”οΈŽ ✘
Black Box Vault ✘ ✘ ✘ ✘ βœ”οΈŽ

Authenticate transactions

API users must strongly authenticate transaction requests that are created programmatically by signing them. (They must also attach their access token to the transaction-creation request, as they do for any other API request.) Note that the signature is an authentication step which is required only for creating transactions. For more information, see Automatic transaction signing.

The signature is passed as part of the x-signature header, and is comprised of the API endpoint, timestamp, and request body signed by the ECDSA private key, that has been previously registered with the API Signer.

See the code sample at the top of this page.