Stake AVAX 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
JavaScript
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) {
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 => {
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 AVAX From Fireblocks
Use Fireblocks raw signing to bridge AVAX from C->P and delegate to Figment
Follow the [Fireblocks developer quickstart](https://developers.fireblocks.com/reference/quickstart) guide to set up the SDK
These are your staking UI form fields. Ask for just the delegation amount and period for simplest UX
Call [Figment's API](https://docs.figment.io/reference/build-avalanche-export-tx) from your backend using user input and some metadata. Receive your transaction, ready for signature
Requires Fireblocks raw signing. Approve on the Fireblocks mobile app
Broadcast your signed transaction to the C-chain. Your AVAX will then be importable on the P-chain.
Call [Figment's API](https://docs.figment.io/reference/build-avalanche-import-tx) from your backend using user input and some metadata. This retrieves all AVAX exported from the C-Chain. Receive your transaction, ready for signature
Requires Fireblocks raw signing. Approve on the Fireblocks mobile app
Broadcast your import transaction to the P-chain. All AXAX exported from C are imported
Call [Figment's API](https://docs.figment.io/reference/build-avalanche-delegate-tx) from your backend using user input and some metadata. This locks your AVAX and delegates it to the validator. Receive your transaction, ready for signature
Requires Fireblocks raw signing. Approve on the Fireblocks mobile app
Broadcast your import transaction to the P-chain. Your AVAX is now locked for delegation.
Chef, the stake's ready.
```javascript
require('dotenv').config({ path: __dirname + '/.env' });
const { FireblocksSDK, TransactionStatus, PeerType, TransactionOperation, } = require('fireblocks-sdk');
const axios = require('axios');
const avalanche = require('@avalabs/avalanchejs');
const crypto = require('crypto');
const AMOUNT_TO_BRIDGE = 1;
const AMOUNT_TO_DELEGATE = 1;
const VALIDATOR_ADDRESS = 'NodeID-PmN1QWcH3MY4DuVUMsbx9QysvgyGrpCPZ';
const DELEGATION_START_TIME = Math.floor(Date.now() / 1000) + 5 * 60 * 60; // 5 hours from now
const DELEGATION_END_TIME = Math.floor(Date.now() / 1000) + 29 * 60 * 60; // 29 hours from now, for a 24 hour staking period
const TESTNET = true;
const ASSET_ID = TESTNET ? 'AVAXTEST' : 'AVAX';
const VAULT_ACCOUNT_ID = 1;
const NETWORK = TESTNET ? 'fuji' : 'mainnet';
const HEADERS = {
'Content-Type': 'application/json',
'x-api-key': process.env.FIGMENT_API_KEY,
};
const fireblocks = new FireblocksSDK(
process.env.FIREBLOCKS_SECRET_KEY,
process.env.FIREBLOCKS_API_KEY
);
async function exportFromC(fromAddress, toAddress, amount, network) {
try {
const response = await axios.post(
'https://api.figment.io/avalanche/export',
{
from_address: fromAddress,
to_address: toAddress,
amount: amount,
network: network,
},
{
headers: HEADERS,
}
);
if (!response.data || !response.data.data) {
throw new Error('Invalid response format from Figment API');
}
return {
signingPayload: response.data.data.signing_payload,
unsignedTransactionSerialized: response.data.data.unsigned_transaction_serialized,
};
} catch (error) {
console.error('Error exporting from C-chain:');
throw error;
}
}
async function importToP(fromAddress, toAddress, network) {
try {
const response = await axios.post(
'https://api.figment.io/avalanche/import',
{
from_address: fromAddress,
to_address: toAddress,
network: network,
},
{
headers: HEADERS,
}
);
return {
signingPayload: response.data.data.signing_payload,
unsignedTransactionSerialized: response.data.data.unsigned_transaction_serialized,
};
} catch (error) {
console.error('Error importing to C-chain:', error.err || error.message);
throw error;
}
}
async function delegate(fromAddress, network, nodeId, amount, startTime, endTime) {
try {
const response = await axios.post(
'https://api.figment.io/avalanche/delegate',
{
from_address: fromAddress,
node_id: nodeId,
amount: amount,
start: startTime,
end: endTime,
network: network,
},
{
headers: HEADERS,
}
);
return {
signingPayload: response.data.data.signing_payload,
unsignedTransactionSerialized: response.data.data.unsigned_transaction_serialized,
};
} catch (error) {
console.error('Error creating delegation transaction:', error);
throw error;
}
}
async function broadcastTx(network, signedPayload, unsignedTransactionSerialized) {
try {
const response = await axios.post(
'https://api.figment.io/avalanche/broadcast',
{
network: network,
signed_payload: signedPayload,
unsigned_transaction_serialized: unsignedTransactionSerialized,
},
{
headers: HEADERS,
}
);
return response.data;
} catch (error) {
console.log('Error broadcasting transaction: ', error);
throw error;
}
}
(async () => {
try {
const cChainAddress = await getFireblocksDepositAddress();
console.log('C-chain address: ', cChainAddress);
const pChainAddress = await translateAddress(true);
console.log('P-chain address: ', pChainAddress);
console.log('\n=== Exporting from C-chain ===');
const { signingPayload: exportSigningPayload, unsignedTransactionSerialized: exportUnsignedTx } = await exportFromC(cChainAddress, pChainAddress, AMOUNT_TO_BRIDGE, NETWORK);
const exportSignedPayload = addVToSignature(await signWithFireblocks(exportSigningPayload));
const exportTxHash = (await broadcastTx(NETWORK, exportSignedPayload, exportUnsignedTx)).data.transaction_hash;
console.log('Export from C-chain transaction successful! View here: ', explorerUrl(exportTxHash));
console.log('\n=== Importing to P-chain ===');
const { signingPayload: importSigningPayload, unsignedTransactionSerialized: importUnsignedTx } = await importToP(cChainAddress, pChainAddress, NETWORK);
const importSignedPayload = addVToSignature(await signWithFireblocks(importSigningPayload));
const importTxHash = (await broadcastTx(NETWORK, importSignedPayload, importUnsignedTx)).data.transaction_hash;
console.log('Import to P-chain transaction successful! View here: ', explorerUrl(importTxHash, true));
console.log('\n=== Delegating to validator ===');
const { signingPayload: delegateSigningPayload, unsignedTransactionSerialized: delegateUnsignedTx } = await delegate(pChainAddress, NETWORK, VALIDATOR_ADDRESS, AMOUNT_TO_DELEGATE, DELEGATION_START_TIME, DELEGATION_END_TIME);
const delegateSignedPayload = addVToSignature(await signWithFireblocks(delegateSigningPayload));
const delegateTxHash = (await broadcastTx(NETWORK, delegateSignedPayload, delegateUnsignedTx)).data.transaction_hash;
console.log('Delegate to validator transaction successful! View here: ', explorerUrl(delegateTxHash, true));
} catch (error) {
console.error('Fatal error:', error);
process.exit(1);
}
})();