Skip to content

Commit

Permalink
feat(SOLNENG-27): add eth staking with fb
Browse files Browse the repository at this point in the history
  • Loading branch information
MnrGreg committed Aug 21, 2024
1 parent 49f0d70 commit 980e70e
Show file tree
Hide file tree
Showing 8 changed files with 3,756 additions and 0 deletions.
8 changes: 8 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Evaluation License Notice:

* Copyright © 2024 Blockdaemon Inc. - All Rights Reserved
* Unauthorized copying of this file, via any medium is strictly prohibited
* Proprietary and confidential, use only granted under applicable License

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.
This above copyright notice and this permission notice shall be included in all copies or substantial portions of the software.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ To get started, click on the links for installation and usage instructions.
## Ethereum Staking
- [Stake deposit from Builder Vault wallet with Golang SDK](./ethereum-staking/buildervault/golang/README.md)
- [Stake deposit from Builder Vault wallet with TypeScript SDK](./ethereum-staking/buildervault/nodejs/README.md)
- [Stake deposit from Fireblocks wallet with TypeScript SDK](./ethereum-staking/fireblocks/nodejs/README.md)

11 changes: 11 additions & 0 deletions ethereum-staking/fireblocks/nodejs/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Blockdaemon Stake
BLOCKDAEMON_API_KEY=
BLOCKDAEMON_STAKE_API_KEY=
ETHEREUM_NETWORK=holesky # mainnet | holesky
ETHEREUM_WITHDRAWAL_ADDRESS=
PLAN_ID= # Optional. If provided, will use a specific validator plan.

# Fireblocks
FIREBLOCKS_API_KEY="my-api-key"
FIREBLOCKS_SECRET_KEY="~/fireblocks_secret.key"
FIREBLOCKS_VAULT_ACCOUNT_ID="0"
48 changes: 48 additions & 0 deletions ethereum-staking/fireblocks/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

# TypeScript Ethereum staking with Fireblocks wallet

```mermaid
sequenceDiagram
autonumber
participant StakeClient as Sample stake<br> client application
participant StakeAPI as Stake Intent API
participant TSM1 as Fireblocks API
StakeClient ->> StakeAPI: get StakeIntent unsigned tx data <br>(amount, withdrawal & recipient address)
StakeClient ->> StakeClient: decode calldata with<br>deposit contract ABI
StakeClient ->> TSM1: register web3.js provider
StakeClient ->> TSM1: invoke web3 smart contract deposit<br> function<br>(amount, calldata, contract address)
TSM1 ->> TSM1: sign & broadcast contract execution
```

### Prerequisites
- [Node.js](https://nodejs.org/en/download/package-manager) or launch in [code-spaces](https://codespaces.new/Blockdaemon/demo-buildervault-stakingAPI?quickstart=1)
- 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)
- 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
- 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

### Step 1. Set environment variables in .env
```shell
cd ethereum-staking/fireblocks/nodejs/
cp .env.example .env
```
- update .env with API keys and Fireblocks Vault details

### Step 2. Install package dependancies
```shell
npm install
```

### Step 3. Launch ethereum-stake-fb.ts to determine the Fireblocks wallet address
```shell
npm run start ethereum-stake-fb.ts
```
- if needed, copy the new Ethereum wallet address and fund the account with https://holesky-faucet.pk910.de/#/

### Step 4. Re-launch ethereum-stake-fb.ts to generate the Stake Intent request, execute the contract with Fireblocks, and broadcast the transaction
```shell
npm run start ethereum-stake-fb.ts
```
- observe the confirmed transaction through the generated blockexplorer link
139 changes: 139 additions & 0 deletions ethereum-staking/fireblocks/nodejs/ethereum-stake-fb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import Web3 from "web3";
import 'dotenv/config'
import { readFileSync } from 'fs';
import { FireblocksWeb3Provider, ChainId, ApiBaseUrl } from "@fireblocks/fireblocks-web3-provider";


type CreateStakeIntentRequest = {
stakes: {
fee_recipient: string;
withdrawal_address: string;
amount: string;
}[];
};

type CreateStakeIntentResponse = {
stake_intent_id: string;
ethereum: {
stakes: {
stake_id: string;
amount: string;
validator_public_key: string;
withdrawal_credentials: string;
}[];
contract_address: string;
unsigned_transaction: string;
};
};


function createStakeIntent(
bossApiKey: string,
request: CreateStakeIntentRequest,
): Promise<CreateStakeIntentResponse> {

// * Create a stake intent with the Staking Integration API: https://docs.blockdaemon.com/reference/postethereumstakeintent
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'X-API-Key': bossApiKey,
'Idempotency-Key': 'FB81DEED-D58B-4948-B51D-99E2E1064B9C',
},
body: JSON.stringify(request),
};

return fetch(
`https://svc.blockdaemon.com/boss/v1/ethereum/${process.env.ETHEREUM_NETWORK}/stake-intents`,
requestOptions,
).then(response => response.json() as Promise<CreateStakeIntentResponse>);
}

async function main() {

const gwei = 10n ** 9n;

// Check for the required environment variables
if (!process.env.BLOCKDAEMON_API_KEY) {
throw new Error('BLOCKDAEMON_API_KEY environment variable not set');
}

if (!process.env.BLOCKDAEMON_STAKE_API_KEY) {
throw new Error('BLOCKDAEMON_STAKE_API_KEY environment variable not set');
}

if (!process.env.ETHEREUM_NETWORK) {
throw new Error('ETHEREUM_NETWORK environment variable not set.');
}

if (!process.env.ETHEREUM_WITHDRAWAL_ADDRESS) {
throw new Error('ETHEREUM_WITHDRAWAL_ADDRESS environment variable not set');
}

if (!process.env.FIREBLOCKS_API_KEY) {
throw new Error('FIREBLOCKS_API_KEY environment variable not set');
}

if (!process.env.FIREBLOCKS_SECRET_KEY) {
throw new Error('FIREBLOCKS_SECRET_KEY environment variable not set');
}

if (!process.env.FIREBLOCKS_VAULT_ACCOUNT_ID) {
throw new Error('FIREBLOCKS_VAULT_ACCOUNT_ID environment variable not set');
}

// Determine FIreblocks Asset ID based on network
const fireblocks_asset_id = process.env.ETHEREUM_NETWORK === "holesky" ? ChainId.HOLESKY : ChainId.MAINNET;

const eip1193Provider = new FireblocksWeb3Provider({
apiBaseUrl: ApiBaseUrl.Production,
privateKey: readFileSync(process.env.FIREBLOCKS_SECRET_KEY, "utf8"),
apiKey: process.env.FIREBLOCKS_API_KEY,
vaultAccountIds: process.env.FIREBLOCKS_VAULT_ACCOUNT_IDS,
chainId: fireblocks_asset_id,
});

const web3 = new Web3(eip1193Provider);

const addresses = await web3.eth.getAccounts();
const address = addresses[0];
console.log("Ethereum addresses:", address);
console.log("Initial balance:", await web3.eth.getBalance(address));

const response = await createStakeIntent(process.env.BLOCKDAEMON_STAKE_API_KEY, {
stakes: [
{
amount: '32000000000',
withdrawal_address: process.env.ETHEREUM_WITHDRAWAL_ADDRESS,
fee_recipient: process.env.ETHEREUM_WITHDRAWAL_ADDRESS,
},
],
});

const { unsigned_transaction, contract_address, stakes } = response.ethereum;
const totalDepositAmount = stakes.reduce((sum, next) => sum + BigInt(next.amount), 0n) * gwei;

// Blockdaemon batch deposit smart contract ABI
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"}]
const contract = new web3.eth.Contract(ABI, contract_address);

// Strip batchDeposit methodID
const data = unsigned_transaction.split("0x592c0b7d")[1];
const inputData = web3.eth.abi.decodeParameters(["uint256", "bytes"], data);

// Invoke batchDeposit method
const txid = await contract.methods.batchDeposit(inputData[0], inputData[1]).send({
from: address,
value: totalDepositAmount.toString(10),
});

console.log(`Broadcasted transaction hash: https://${process.env.ETHEREUM_NETWORK}.etherscan.io/tx/${txid.transactionHash}`);
}

main()
.then(() => process.exit(0))
.catch(err => {
console.error(err);
process.exit(1);
});
Loading

0 comments on commit 980e70e

Please sign in to comment.