-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(SOLNENG-27): add eth staking with fb
- Loading branch information
Showing
8 changed files
with
3,756 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
139
ethereum-staking/fireblocks/nodejs/ethereum-stake-fb.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
Oops, something went wrong.