Stake ADA from Fireblocks & Broadcast via Figment API

Recipes

Stake ETH From Fireblocks

Creates new validator keys and returns a staking transaction to deposit ETH to those validators. Then the staking transaction gets signed w Fireblocks API.

In this Recipe

  1. Provision validators
  2. Submit for Fireblocks signature
  3. Wait for Fireblocks signature

JavaScript Code

async function createValidators() {
  const url = '<https://api.figment.io/ethereum/validators>';
  const data = {
    network: 'hoodi',
    validators_count: 1,
    withdrawal_address: '0x8C025F5EdaBE788A7f14Db861420aaE12CDf979f',
    funding_address: '0x8C025F5EdaBE788A7f14Db861420aaE12CDf979f',
    fee_recipient_address: '0x8C025F5EdaBE788A7f14Db861420aaE12CDf979f',
    region: 'ca-central-1'
  };

try {
    const response = await axios.post(url, data, {
      headers: {
        'accept': 'application/json',
        'content-type': 'application/json',
        'x-api-key': process.env.FIGMENT_API_KEY
      }
    });
    if (response.data && response.data.meta && response.data.meta.staking_transaction && response.data.meta.staking_transaction.contract_call_data) {
      return response.data.meta.staking_transaction.contract_call_data;
    } else {
      console.log('Contract Call Data not found in the response.');
      return null;
    }
  } catch (error) {
    console.error('Error:', error.response ? error.response.data : error);
    return null;
  }
}

async function signWithFireblocks(contractAddress, contractCallData) {
  console.log("Contract Call Data:", contractCallData);
  return fireblocksApiClient.createTransaction({
    operation: TransactionOperation.CONTRACT_CALL,
    assetId: process.env.FIREBLOCKS_ASSET_ID,
    source: {
      type: PeerType.VAULT_ACCOUNT,
      id: "1"
    },
    destination: {
      type: PeerType.ONE_TIME_ADDRESS,
      oneTimeAddress: {
        address: contractAddress
      }
    },
    note: "Contract Call Transaction",
    amount: "32",
    extraParameters: {
      contractCallData
    }
  }).then(res => {
    console.log(`✔ Submitted to Fireblocks for approval & signature. ID: ${res.id}`);
    return res;
  }).catch(e => {
    console.error(`Fireblocks API Error: ${e.message}`);
  });
}

async function waitForTxCompletion(fbTx) {
  let tx = fbTx;
  while (tx.status !== TransactionStatus.COMPLETED) {
    if ([TransactionStatus.BLOCKED, TransactionStatus.FAILED, TransactionStatus.REJECTED, TransactionStatus.CANCELLED].includes(tx.status)) {
      throw new Error(`Exiting the operation due to error. Transaction status: ${tx.status}`);
    }
    console.log("Transaction's status:", (await fireblocksApiClient.getTransactionById(fbTx.id)).status);
    await new Promise(resolve => setTimeout(resolve, 4000));
    tx = await fireblocksApiClient.getTransactionById(fbTx.id);
  }
  return (await fireblocksApiClient.getTransactionById(fbTx.id));
}

async function processValidators() {
  const contractCallData = await createValidators();
  if (contractCallData) {
    const fbTx = await signWithFireblocks('0xA627f94a8F94E4713d38F52aC3a6377B0a111d47', contractCallData); // Use the actual contract address
    if (fbTx) {
      const fbRes = await waitForTxCompletion(fbTx);
      console.log(fbRes);
    }
  }
}

// Execute the combined function
processValidators();

## Stake ADA from Fireblocks & Broadcast via Figment API

Recipe Description

Follow the Fireblocks developer quickstart guide to set up the SDK and set up your TAP rules for ADA staking with Fireblocks.

### Code Example

```javascript
const fs = require('fs');
const axios = require('axios');
const { FireblocksSDK, TransactionStatus, PeerType, TransactionOperation } = require("fireblocks-sdk");

// Configuration
const FIGMENT_API_KEY = fs.readFileSync('./figment_api.key', "utf8").trim();
const FIREBLOCKS_SECRET_KEY = fs.readFileSync('./fireblocks_secret.key', "utf8");
const FIREBLOCKS_API_KEY = fs.readFileSync('./fireblocks_api.key', "utf8");
const fireblocks = new FireblocksSDK(FIREBLOCKS_SECRET_KEY, FIREBLOCKS_API_KEY);

const TESTNET = false;
const VAULT_ACCOUNT_ID = 1;
const NETWORK = TESTNET ? "testnet" : "preprod";
const DELEGATOR_ADDRESS = "addr_test1qrswqv7wx5lq9d7p00qmm80ntnja38wm2rq7xd8s0cs767yy4n25eghca4kf20q9tkfun39j3fz03vf9d8nkrvskpchsdw7hqq";
const VALIDATOR_ADDRESS = "pool13m26ky08vz205232k20u8ft5nrg8u68klhn0xfsk9m4gsqsc44v";

const HEADERS = {
  'accept': 'application/json',
  'content-type': 'application/json',
  'x-api-key': FIGMENT_API_KEY
};

async function waitForTxCompletion(fbTx) {
  const errorStatuses = [TransactionStatus.BLOCKED, TransactionStatus.FAILED, TransactionStatus.REJECTED, TransactionStatus.CANCELLED];
  while (fbTx.status !== TransactionStatus.COMPLETED) {
    if (errorStatuses.includes(fbTx.status)) throw new Error(`Transaction ${fbTx.status}`);
    console.log(`Transaction status: ${fbTx.status}`);
    await new Promise(resolve => setTimeout(resolve, 4000));
    fbTx = await fireblocks.getTransactionById(fbTx.id);
  }
  return await fireblocks.getTransactionById(fbTx.id);
}

async function createDelegationTransaction() {
  const response = await axios.post('https://api.figment.io/cardano/delegate',
    { network: NETWORK, delegator_address: DELEGATOR_ADDRESS, validator_address: VALIDATOR_ADDRESS },
    { headers: HEADERS });
  const { signing_payload: signingPayload, unsigned_transaction_serialized: unsignedTx } = response.data.data || {};
  if (!signingPayload || !unsignedTx) throw new Error('Invalid response from delegate endpoint');
  return { signingPayload, unsignedTx };
}

async function signTransaction(signingPayload) {
  const fbTx = await fireblocks.createTransaction({
    assetId: "ADA_TEST",
    operation: TransactionOperation.RAW,
    source: { type: PeerType.VAULT_ACCOUNT, id: String(VAULT_ACCOUNT_ID) },
    note: "Cardano Delegation - Figment API",
    extraParameters: {
      rawMessageData: {
        messages: [{ content: signingPayload }, { content: signingPayload, bip44change: 2 }]
      }
    }
  });
  const tx = await waitForTxCompletion(fbTx);
  if (!tx.signedMessages?.length) throw new Error('No signed messages received from Fireblocks');
  return tx.signedMessages;
}

async function broadcastTransaction(unsignedTx, signedMessages) {
  const response = await axios.post('https://api.figment.io/cardano/broadcast/fireblocks',
    { network: NETWORK, unsigned_transaction_serialized: unsignedTx, signed_messages: signedMessages },
    { headers: HEADERS });
  return response.data?.data?.transaction_hash || response.data?.transaction_hash || response.data;
}

(async () => {
  try {
    console.log(`Starting Cardano Delegation Flow\nNetwork: ${NETWORK}\nDelegator: ${DELEGATOR_ADDRESS}\nValidator: ${VALIDATOR_ADDRESS}\n${'='.repeat(60)}`);
    const startTime = Date.now();
    console.log('Creating transaction...');
    const { signingPayload, unsignedTx } = await createDelegationTransaction();
    const creationTime = Date.now();
    console.log(`Transaction created (${((creationTime - startTime) / 1000).toFixed(2)}s)`);
    console.log('Signing with Fireblocks...');
    const signedMessages = await signTransaction(signingPayload);
    const signingTime = Date.now();
    console.log(`Signed (${((signingTime - creationTime) / 1000).toFixed(2)}s, ${signedMessages.length} signature${signedMessages.length !== 1 ? 's' : ''})`);
    console.log('Broadcasting...');
    const txHash = await broadcastTransaction(unsignedTx, signedMessages);
    const totalTime = ((Date.now() - startTime) / 1000).toFixed(2);
    console.log(`\nDelegation completed! (${totalTime}s)\nTransaction Hash: ${typeof txHash === 'string' ? txHash : JSON.stringify(txHash)}\nAll done!`);
  } catch (error) {
    console.error('\nDelegation failed');
    console.error('Error:', error.response?.data?.error?.message || error.message || error);
    if (error.response && error.response.data && !error.response.data.error) {
      console.error('Response:', JSON.stringify(error.response.data, null, 2));
    }
    process.exit(1);
  }
})();