Integration example
This page walks through a complete Pharos staking flow with the Staking API (craft → sign → prepare → broadcast), including the signing step. The example uses the delegate action, but the same pattern applies to undelegate, withdraw, claim-rewards, and compound-rewards: 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). Pharos is EVM-compatible, so standard Ethereum tooling applies.
Set X-NETWORK to the chain you want to target (1672 for Pharos mainnet). 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/native/action/delegate', {
address: process.env.ETHEREUM_WALLET_ADDRESS,
amount: 0.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/native/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/native/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
withdraw,claim-rewards,compound-rewards, and balance queries): Endpoints. - The same four-step flow on other networks and the conceptual overview: How it works.