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
- Provision validators
- Submit for Fireblocks signature
- 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();