Integration example
This page walks through a complete Faroo liquid staking flow with the Staking API (craft → sign → prepare → broadcast), including the signing step. The example uses the delegate action (staking PROS and receiving stPROS). The same pattern applies to undelegate and withdraw: only the endpoint and request body change.
Signing happens entirely in your environment; the Stakely Staking API never has access to your private keys. Two signing options are shown below: a local private key and an external custodian (Fireblocks). Faroo runs on Pharos, which is EVM-compatible, so standard Ethereum tooling applies.
Faroo is available on Pharos mainnet only. Set X-NETWORK to 1672. Discover the values enabled for your API key with the networks endpoint.
Setup
This example targets Node.js 24 (LTS). Install the dependencies:
npm install ethereumjs-util dotenv
Fill in your values in .env:
STAKELY_API_KEY=
STAKELY_API_BASE_URL=https://staking-api.stakely.io
STAKELY_NETWORK=1672
EVM_CHAIN_ID=1672
ETHEREUM_WALLET_ADDRESS=
ETHEREUM_WALLET_PRIVATE_KEY=
A small helper to call the API with the required headers:
require('dotenv').config();
const BASE = process.env.STAKELY_API_BASE_URL;
const headers = {
'X-API-KEY': process.env.STAKELY_API_KEY,
'X-NETWORK': process.env.STAKELY_NETWORK,
'Content-Type': 'application/json',
};
// fetch is available globally in Node.js 24
const api = async (path, body) => {
const res = await fetch(`${BASE}${path}`, {
method: 'POST',
headers,
body: JSON.stringify(body),
});
if (!res.ok) throw new Error(`${path} → ${res.status}: ${await res.text()}`);
return res.json();
};
1. Craft the transaction
Ask the API to build the unsigned delegate transaction.
const crafted = await api('/api/v1/pharos/faroo/action/delegate', {
address: process.env.ETHEREUM_WALLET_ADDRESS,
amount: 1,
});
// crafted = { serialized_tx_hex, unsigned_tx_hash_hex, ... }
2. Sign the transaction
Sign the unsigned_tx_hash_hex returned above. Use one of the two options below; both return the EIP-155 signature parts { r, s, v } that the prepare step expects.
Option A: Local private key
const ethUtil = require('ethereumjs-util');
const signWithPrivateKey = ({ unsigned_tx_hash_hex }) => {
const txHashBytes = Buffer.from(unsigned_tx_hash_hex.replace(/^0x/, ''), 'hex');
const privateKeyBuffer = Buffer.from(process.env.ETHEREUM_WALLET_PRIVATE_KEY, 'hex');
// ecsign with the chain id produces the EIP-155 v the API expects
const { r, s, v } = ethUtil.ecsign(
txHashBytes,
privateKeyBuffer,
Number(process.env.EVM_CHAIN_ID),
);
return { r: r.toString('hex'), s: s.toString('hex'), v };
};
const { r, s, v } = signWithPrivateKey(crafted);
Option B: External custodian (Fireblocks)
Private keys are never exposed to your application; signing is delegated to Fireblocks' secure environment. Install and configure the SDK:
npm install fireblocks-sdk
FIREBLOCKS_API_SECRET=
FIREBLOCKS_API_KEY=
FIREBLOCKS_BASE_URL=
FIREBLOCKS_VAULT_ID=
const { FireblocksSDK, TransactionOperation, PeerType, TransactionStatus } = require('fireblocks-sdk');
const fireblocks = new FireblocksSDK(
process.env.FIREBLOCKS_API_SECRET,
process.env.FIREBLOCKS_API_KEY,
process.env.FIREBLOCKS_BASE_URL,
);
const waitForTxCompletion = async (fbTx) => {
let tx = fbTx;
while (tx.status !== TransactionStatus.COMPLETED) {
if (
tx.status === TransactionStatus.BLOCKED ||
tx.status === TransactionStatus.FAILED ||
tx.status === TransactionStatus.CANCELLED
) {
throw new Error(`Fireblocks signer: the transaction has been ${tx.status}`);
} else if (tx.status === TransactionStatus.REJECTED) {
throw new Error('Fireblocks signer: the transaction was rejected; check your TAP security policy');
}
tx = await fireblocks.getTransactionById(fbTx.id);
}
return fireblocks.getTransactionById(fbTx.id);
};
const signWithFireblocks = async ({ unsigned_tx_hash_hex }) => {
const content = unsigned_tx_hash_hex.replace(/^0x/, '');
const fbTx = await fireblocks.createTransaction({
assetId: 'ETH',
operation: TransactionOperation.RAW,
source: { type: PeerType.VAULT_ACCOUNT, id: process.env.FIREBLOCKS_VAULT_ID },
note: 'Sign transaction from Stakely Staking API',
extraParameters: { rawMessageData: { messages: [{ content }] } },
});
const result = await waitForTxCompletion(fbTx);
const signature = result.signedMessages[0].signature;
return { r: '0x' + signature.r, s: '0x' + signature.s, v: 27 + signature.v };
};
const { r, s, v } = await signWithFireblocks(crafted);
3. Prepare the signed transaction
Send the crafted serialized_tx_hex with the signature parts to get the final signed transaction.
const prepared = await api('/api/v1/pharos/faroo/action/prepare', {
serialized_tx_hex: crafted.serialized_tx_hex,
r,
s,
v,
});
// prepared = { signed_tx_hex }
4. Broadcast
Submit the signed transaction. You can use the Stakely broadcast endpoint or your own RPC.
const { tx_hash } = await api('/api/v1/pharos/faroo/action/broadcast', {
signed_tx_hex: prepared.signed_tx_hex,
});
console.log('Broadcasted:', tx_hash);
Broadcasting submits the transaction; confirm it reached finality by polling tx_hash on-chain. See Errors & responses for how to handle results and failures.
Next steps
- Full request/response schemas for every action (including
undelegate,withdraw, and the staking balance): Endpoints. - When to call
withdrawafter anundelegate: Staking Flow. - The same four-step flow on other networks and the conceptual overview: How it works.