Stake SOL from Fireblocks

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

🔥Open Recipe

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 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.

### 3 steps

---

## Stake SOL from Fireblocks

Use Fireblocks Solana Program Calls with Figment's API to stake SOL

Follow the [Fireblocks developer quickstart guide](https://developers.fireblocks.com/reference/quickstart) to set up the SDK and [set up your TAP rules](https://support.fireblocks.io/hc/en-us/articles/18322418553500-Building-TAP-rules-for-Solana-program-calls) for SOL staking with Fireblocks

These are your staking UI form fields. Ask for just the stake amount for simplest UX

Call [Figment's API](https://docs.figment.io/reference/solana-stake) from your backend using user input and some metadata. Receive your transaction, ready for signature

Using a [Fireblocks Program Call](https://developers.fireblocks.com/reference/interact-with-solana-programs) makes the transaction will follow your TAP rules and if approved, sign and broadcast, returning a transaction hash. Make sure to whitelist the Stake Program (Stake11111111111111111111111111111111111111)

Chef, the stake's ready

```javascript
require('dotenv').config({ path: __dirname + '/.env' });
const axios = require('axios');
const secretKey = process.env.FIREBLOCKS_SECRET_KEY;
const apiKey = process.env.FIREBLOCKS_API_KEY;
const { FireblocksSDK, TransactionStatus, PeerType } = require("fireblocks-sdk");
const fireblocks = new FireblocksSDK(secretKey, apiKey);
/* Configuration */
const NETWORK = 'devnet';
const EXPLORER_BASE_URL = 'https://explorer.solana.com/tx/';
const FIGMENT_API_URL = 'https://api.figment.io/solana';
const VAULT_ACCOUNT_ID = 1;
const STAKE_AMOUNT = 0.01;
const VALIDATOR_VOTE_ACCOUNT = '21Jxcw74j5SvajRKE3PvNifu26CVorF7DF8HyanKNzZ3';
const FIREBLOCKS_ASSET_ID = "SOL_TEST";
const API_HEADERS = {
  'x-api-key': process.env.FIGMENT_API_KEY
};
async function createStakeTransaction(walletAddress, validatorVoteAccount) {
    const response = await axios.post(`${FIGMENT_API_URL}/stake`, {
        funding_account: walletAddress,
        vote_account: validatorVoteAccount,
        amount_sol: STAKE_AMOUNT,
        network: NETWORK
    }, { headers: API_HEADERS });
    return response.data.data.unsigned_tx_serialized_base64;
}
async function waitForTxCompletion(fbTx) {
  let tx = fbTx;
  while (tx.status != TransactionStatus.COMPLETED) {
      if(tx.status == TransactionStatus.BLOCKED ||
         tx.status == TransactionStatus.FAILED ||
         tx.status == TransactionStatus.REJECTED ||
         tx.status == TransactionStatus.CANCELLED) {
          console.log("Transaction's status: " + tx.status);
          throw Error("Exiting the operation due to error");
      }
      console.log("Transaction's status:",(await fireblocks.getTransactionById(fbTx.id)).status);
      setTimeout(() => { }, 4000);
      tx = await fireblocks.getTransactionById(fbTx.id);
  }
  return (await fireblocks.getTransactionById(fbTx.id));
}
async function signWithFireblocks(base64EncodedTransaction) {
    const note = `Stake ${STAKE_AMOUNT} SOL to ${VALIDATOR_VOTE_ACCOUNT} on ${NETWORK}`;
    const fbTx = await fireblocks.createTransaction({
        assetId: FIREBLOCKS_ASSET_ID,
        operation: 'PROGRAM_CALL',
        source: {
            type: PeerType.VAULT_ACCOUNT,
            id: String(VAULT_ACCOUNT_ID)
        },
        note,
        extraParameters: {
            programCallData: base64EncodedTransaction
        }
    });
    return (await waitForTxCompletion(fbTx));
}
async function main() {
    try {
        const walletInfo = await fireblocks.getDepositAddresses(VAULT_ACCOUNT_ID, FIREBLOCKS_ASSET_ID);
        const walletAddress = walletInfo[0].address;
        const unsignedTranasctionSerializedBase64 = await createStakeTransaction(walletAddress, VALIDATOR_VOTE_ACCOUNT);
        const signedTx = await signWithFireblocks(unsignedTranasctionSerializedBase64);
        let txHash = signedTx.txHash;
        const explorerUrl = `${EXPLORER_BASE_URL}${txHash}${NETWORK === 'devnet' ? '?cluster=devnet' : ''}`;
        console.log(`Staked ${STAKE_AMOUNT} SOL to ${VALIDATOR_VOTE_ACCOUNT} successfully!`);
        console.log('View transaction on explorer:', explorerUrl);
    } catch (error) {
        console.log(error);
        console.error('Error:', error.message);
    }
}
main();