Skip to content

Commit 980e70e

Browse files
committed
feat(SOLNENG-27): add eth staking with fb
1 parent 49f0d70 commit 980e70e

File tree

8 files changed

+3756
-0
lines changed

8 files changed

+3756
-0
lines changed

LICENSE

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Evaluation License Notice:
2+
3+
* Copyright © 2024 Blockdaemon Inc. - All Rights Reserved
4+
* Unauthorized copying of this file, via any medium is strictly prohibited
5+
* Proprietary and confidential, use only granted under applicable License
6+
7+
Permission is hereby granted only under a license to use for testing and evaluating purposes only, to any authorized person obtaining access to this software, associated documentation files, and services (the "Product") to use the Product to support personal or professional operations subject to the License's restrictions. No user may rent, loan, sub-license, license, distribute, copy, decompile, disassemble, merge, modify, adapt, translate, reverse-engineer, make alterations to or derivative works based upon or derive source code from, nor attempt to grant any other rights to or in any whole or part of the Product any whole or part of the Product.
8+
This above copyright notice and this permission notice shall be included in all copies or substantial portions of the software.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ To get started, click on the links for installation and usage instructions.
1010
## Ethereum Staking
1111
- [Stake deposit from Builder Vault wallet with Golang SDK](./ethereum-staking/buildervault/golang/README.md)
1212
- [Stake deposit from Builder Vault wallet with TypeScript SDK](./ethereum-staking/buildervault/nodejs/README.md)
13+
- [Stake deposit from Fireblocks wallet with TypeScript SDK](./ethereum-staking/fireblocks/nodejs/README.md)
1314

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Blockdaemon Stake
2+
BLOCKDAEMON_API_KEY=
3+
BLOCKDAEMON_STAKE_API_KEY=
4+
ETHEREUM_NETWORK=holesky # mainnet | holesky
5+
ETHEREUM_WITHDRAWAL_ADDRESS=
6+
PLAN_ID= # Optional. If provided, will use a specific validator plan.
7+
8+
# Fireblocks
9+
FIREBLOCKS_API_KEY="my-api-key"
10+
FIREBLOCKS_SECRET_KEY="~/fireblocks_secret.key"
11+
FIREBLOCKS_VAULT_ACCOUNT_ID="0"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
# TypeScript Ethereum staking with Fireblocks wallet
3+
4+
```mermaid
5+
sequenceDiagram
6+
autonumber
7+
participant StakeClient as Sample stake<br> client application
8+
participant StakeAPI as Stake Intent API
9+
10+
participant TSM1 as Fireblocks API
11+
12+
StakeClient ->> StakeAPI: get StakeIntent unsigned tx data <br>(amount, withdrawal & recipient address)
13+
StakeClient ->> StakeClient: decode calldata with<br>deposit contract ABI
14+
StakeClient ->> TSM1: register web3.js provider
15+
StakeClient ->> TSM1: invoke web3 smart contract deposit<br> function<br>(amount, calldata, contract address)
16+
17+
TSM1 ->> TSM1: sign & broadcast contract execution
18+
```
19+
20+
### Prerequisites
21+
- [Node.js](https://nodejs.org/en/download/package-manager) or launch in [code-spaces](https://codespaces.new/Blockdaemon/demo-buildervault-stakingAPI?quickstart=1)
22+
- Create Fireblocks [API and Secret key](https://developers.fireblocks.com/docs/manage-api-keys) for use with the [Fireblocks Web3 provider](https://github.com/fireblocks/fireblocks-web3-provider)
23+
- Register free Blockdaemon [RPC API key](https://docs.blockdaemon.com/reference/get-started-rpc#step-1-sign-up-for-an-api-key) and set in .env as BLOCKDAEMON_API_KEY
24+
- Register free Blockdaemon [Staking API key](https://docs.blockdaemon.com/reference/get-started-staking-api#step-1-sign-up-for-an-api-key) and set in .env as BLOCKDAEMON_STAKE_API_KEY
25+
26+
### Step 1. Set environment variables in .env
27+
```shell
28+
cd ethereum-staking/fireblocks/nodejs/
29+
cp .env.example .env
30+
```
31+
- update .env with API keys and Fireblocks Vault details
32+
33+
### Step 2. Install package dependancies
34+
```shell
35+
npm install
36+
```
37+
38+
### Step 3. Launch ethereum-stake-fb.ts to determine the Fireblocks wallet address
39+
```shell
40+
npm run start ethereum-stake-fb.ts
41+
```
42+
- if needed, copy the new Ethereum wallet address and fund the account with https://holesky-faucet.pk910.de/#/
43+
44+
### Step 4. Re-launch ethereum-stake-fb.ts to generate the Stake Intent request, execute the contract with Fireblocks, and broadcast the transaction
45+
```shell
46+
npm run start ethereum-stake-fb.ts
47+
```
48+
- observe the confirmed transaction through the generated blockexplorer link
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import Web3 from "web3";
2+
import 'dotenv/config'
3+
import { readFileSync } from 'fs';
4+
import { FireblocksWeb3Provider, ChainId, ApiBaseUrl } from "@fireblocks/fireblocks-web3-provider";
5+
6+
7+
type CreateStakeIntentRequest = {
8+
stakes: {
9+
fee_recipient: string;
10+
withdrawal_address: string;
11+
amount: string;
12+
}[];
13+
};
14+
15+
type CreateStakeIntentResponse = {
16+
stake_intent_id: string;
17+
ethereum: {
18+
stakes: {
19+
stake_id: string;
20+
amount: string;
21+
validator_public_key: string;
22+
withdrawal_credentials: string;
23+
}[];
24+
contract_address: string;
25+
unsigned_transaction: string;
26+
};
27+
};
28+
29+
30+
function createStakeIntent(
31+
bossApiKey: string,
32+
request: CreateStakeIntentRequest,
33+
): Promise<CreateStakeIntentResponse> {
34+
35+
// * Create a stake intent with the Staking Integration API: https://docs.blockdaemon.com/reference/postethereumstakeintent
36+
const requestOptions = {
37+
method: 'POST',
38+
headers: {
39+
'Content-Type': 'application/json',
40+
Accept: 'application/json',
41+
'X-API-Key': bossApiKey,
42+
'Idempotency-Key': 'FB81DEED-D58B-4948-B51D-99E2E1064B9C',
43+
},
44+
body: JSON.stringify(request),
45+
};
46+
47+
return fetch(
48+
`https://svc.blockdaemon.com/boss/v1/ethereum/${process.env.ETHEREUM_NETWORK}/stake-intents`,
49+
requestOptions,
50+
).then(response => response.json() as Promise<CreateStakeIntentResponse>);
51+
}
52+
53+
async function main() {
54+
55+
const gwei = 10n ** 9n;
56+
57+
// Check for the required environment variables
58+
if (!process.env.BLOCKDAEMON_API_KEY) {
59+
throw new Error('BLOCKDAEMON_API_KEY environment variable not set');
60+
}
61+
62+
if (!process.env.BLOCKDAEMON_STAKE_API_KEY) {
63+
throw new Error('BLOCKDAEMON_STAKE_API_KEY environment variable not set');
64+
}
65+
66+
if (!process.env.ETHEREUM_NETWORK) {
67+
throw new Error('ETHEREUM_NETWORK environment variable not set.');
68+
}
69+
70+
if (!process.env.ETHEREUM_WITHDRAWAL_ADDRESS) {
71+
throw new Error('ETHEREUM_WITHDRAWAL_ADDRESS environment variable not set');
72+
}
73+
74+
if (!process.env.FIREBLOCKS_API_KEY) {
75+
throw new Error('FIREBLOCKS_API_KEY environment variable not set');
76+
}
77+
78+
if (!process.env.FIREBLOCKS_SECRET_KEY) {
79+
throw new Error('FIREBLOCKS_SECRET_KEY environment variable not set');
80+
}
81+
82+
if (!process.env.FIREBLOCKS_VAULT_ACCOUNT_ID) {
83+
throw new Error('FIREBLOCKS_VAULT_ACCOUNT_ID environment variable not set');
84+
}
85+
86+
// Determine FIreblocks Asset ID based on network
87+
const fireblocks_asset_id = process.env.ETHEREUM_NETWORK === "holesky" ? ChainId.HOLESKY : ChainId.MAINNET;
88+
89+
const eip1193Provider = new FireblocksWeb3Provider({
90+
apiBaseUrl: ApiBaseUrl.Production,
91+
privateKey: readFileSync(process.env.FIREBLOCKS_SECRET_KEY, "utf8"),
92+
apiKey: process.env.FIREBLOCKS_API_KEY,
93+
vaultAccountIds: process.env.FIREBLOCKS_VAULT_ACCOUNT_IDS,
94+
chainId: fireblocks_asset_id,
95+
});
96+
97+
const web3 = new Web3(eip1193Provider);
98+
99+
const addresses = await web3.eth.getAccounts();
100+
const address = addresses[0];
101+
console.log("Ethereum addresses:", address);
102+
console.log("Initial balance:", await web3.eth.getBalance(address));
103+
104+
const response = await createStakeIntent(process.env.BLOCKDAEMON_STAKE_API_KEY, {
105+
stakes: [
106+
{
107+
amount: '32000000000',
108+
withdrawal_address: process.env.ETHEREUM_WITHDRAWAL_ADDRESS,
109+
fee_recipient: process.env.ETHEREUM_WITHDRAWAL_ADDRESS,
110+
},
111+
],
112+
});
113+
114+
const { unsigned_transaction, contract_address, stakes } = response.ethereum;
115+
const totalDepositAmount = stakes.reduce((sum, next) => sum + BigInt(next.amount), 0n) * gwei;
116+
117+
// Blockdaemon batch deposit smart contract ABI
118+
const ABI = [{"inputs":[{"internalType":"contract IDepositContract","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"validUntil","type":"uint256"},{"internalType":"bytes","name":"args","type":"bytes"}],"name":"batchDeposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"depositContract","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
119+
const contract = new web3.eth.Contract(ABI, contract_address);
120+
121+
// Strip batchDeposit methodID
122+
const data = unsigned_transaction.split("0x592c0b7d")[1];
123+
const inputData = web3.eth.abi.decodeParameters(["uint256", "bytes"], data);
124+
125+
// Invoke batchDeposit method
126+
const txid = await contract.methods.batchDeposit(inputData[0], inputData[1]).send({
127+
from: address,
128+
value: totalDepositAmount.toString(10),
129+
});
130+
131+
console.log(`Broadcasted transaction hash: https://${process.env.ETHEREUM_NETWORK}.etherscan.io/tx/${txid.transactionHash}`);
132+
}
133+
134+
main()
135+
.then(() => process.exit(0))
136+
.catch(err => {
137+
console.error(err);
138+
process.exit(1);
139+
});

0 commit comments

Comments
 (0)