-
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 BuiilderVault web3 provider example
- Loading branch information
Showing
8 changed files
with
2,253 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
32 changes: 32 additions & 0 deletions
32
ethereum-staking/buildervault/nodejs-web3provider/.env.example
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,32 @@ | ||
BLOCKDAEMON_STAKE_API_KEY=zpka_... | ||
ETHEREUM_NETWORK=holesky # mainnet | holesky | ||
ETHEREUM_WITHDRAWAL_ADDRESS=0x... | ||
|
||
BLOCKDAEMON_RPC_URL="https://svc.blockdaemon.com/native/v1/ethereum/holesky?apiKey=zpka_..." | ||
BUILDERVAULT_PLAYER0_URL="https://tsm-sandbox.prd.wallet.blockdaemon.app:8080" | ||
BUILDERVAULT_PLAYER0_MPCPUBLICKEY="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtDFBfanInAMHNKKDG2RW/DiSnYeI7scVvfHIwUIRdbPH0gBrsilqxlvsKZTakN8om/Psc6igO+224X8T0J9eMg==" | ||
|
||
BUILDERVAULT_PLAYER1_URL="https://tsm-sandbox.prd.wallet.blockdaemon.app:8081" | ||
BUILDERVAULT_PLAYER1_MPCPUBLICKEY="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqvSkhonTeNhlETse8v3X7g4p100EW9xIqg4aRpD8yDXgB0UYjhd+gFtOCsRT2lRhuqNForqqC+YnBsJeZ4ANxg==" | ||
|
||
BUILDERVAULT_PLAYER2_URL="https://tsm-sandbox.prd.wallet.blockdaemon.app:8082" | ||
BUILDERVAULT_PLAYER2_MPCPUBLICKEY="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBaHCIiViexaVaPuER4tE6oJE3IBA0U//GlB51C1kXkT07liVc51uWuYk78wi4e1unxC95QbeIfnDCG2i43fW3g==" | ||
|
||
BUILDERVAULT_MASTERKEY_ID="Ap7..." | ||
BUILDERVAULT_ACCOUNT_ID=0 | ||
BUILDERVAULT_ADDRESS_INDEX=0 | ||
|
||
BUILDERVAULT_PLAYER0_MTLSPUBLICKEY="-----BEGIN CERTIFICATE-----\nMIICMTCCAdegAwIBAgICB+MwCgYIKoZIzj0EAwIwgaAxCzAJBgNVBAYTAlVTMRMw\nEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQHDAtMb3MgQW5nZWxlczEUMBIGA1UE\nCgwLQmxvY2tkYWVtb24xFDASBgNVBAsMC0Jsb2NrZGFlbW9uMRQwEgYDVQQDDAtC\nbG9ja2RhZW1vbjEkMCIGCSqGSIb3DQEJARYVYWRtaW5AYmxvY2tkYWVtb24uY29t\nMB4XDTI0MDIxMzE3MjE0OFoXDTI5MDIxMzE3MjE0OFowTjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMRQwEgYD\nVQQKEwtCbG9ja2RhZW1vbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGlixcUc\nYC0ByeutoHHdi3zxWCg5iPAJcxVLvzBUdD2+XdCWEgS/xwFEef9Tl3xFdfK4iWSQ\nnjmtYMTaHMM6mfWjUjBQMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEF\nBQcDAgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUW6ouasv5oWo7MZ4ZzlE/mpbDrIMw\nCgYIKoZIzj0EAwIDSAAwRQIgSDKHZmsnylzL8kopFSeo8L6LQGxyd/NsBRb+8STI\n1cECIQChi4cl5nJgTXCBzJEHicnRk/0vl+9zq6iABMV+KTXJxA==\n-----END CERTIFICATE-----" | ||
BUILDERVAULT_PLAYER0_CLIENT_CERT="./client.crt" | ||
BUILDERVAULT_PLAYER0_CLIENT_KEY="./client.key" | ||
BUILDERVAULT_PLAYER1_MTLSPUBLICKEY="-----BEGIN CERTIFICATE-----\nMIICMjCCAdegAwIBAgICB+MwCgYIKoZIzj0EAwIwgaAxCzAJBgNVBAYTAlVTMRMw\nEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQHDAtMb3MgQW5nZWxlczEUMBIGA1UE\nCgwLQmxvY2tkYWVtb24xFDASBgNVBAsMC0Jsb2NrZGFlbW9uMRQwEgYDVQQDDAtC\nbG9ja2RhZW1vbjEkMCIGCSqGSIb3DQEJARYVYWRtaW5AYmxvY2tkYWVtb24uY29t\nMB4XDTI0MDIxMzE3MjEzMloXDTI5MDIxMzE3MjEzMlowTjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMRQwEgYD\nVQQKEwtCbG9ja2RhZW1vbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABKz8yGcE\nYIhaQYCA2As30cRIL2rLrB2uKpcFpydE55RoI3Hw+QaeNCfR5znZQZM4bVVquT4i\nxDGhVnQKU5EQU/WjUjBQMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEF\nBQcDAgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUW6ouasv5oWo7MZ4ZzlE/mpbDrIMw\nCgYIKoZIzj0EAwIDSQAwRgIhAO9yXpssqar6IdgmEOIfAsha0ZIWG56nwE8/GbyN\nBiTaAiEAhhEClrSm/TzmWxODXamBz0pmQ9qNFsrtbGsDhLOe8O8=\n-----END CERTIFICATE-----" | ||
BUILDERVAULT_PLAYER1_CLIENT_CERT="./client.crt" | ||
BUILDERVAULT_PLAYER1_CLIENT_KEY="./client.key" | ||
BUILDERVAULT_PLAYER2_MTLSPUBLICKEY="-----BEGIN CERTIFICATE-----\nMIICMTCCAdegAwIBAgICB+MwCgYIKoZIzj0EAwIwgaAxCzAJBgNVBAYTAlVTMRMw\nEQYDVQQIDApDYWxpZm9ybmlhMRQwEgYDVQQHDAtMb3MgQW5nZWxlczEUMBIGA1UE\nCgwLQmxvY2tkYWVtb24xFDASBgNVBAsMC0Jsb2NrZGFlbW9uMRQwEgYDVQQDDAtC\nbG9ja2RhZW1vbjEkMCIGCSqGSIb3DQEJARYVYWRtaW5AYmxvY2tkYWVtb24uY29t\nMB4XDTI0MDIxMzE3MjEzMVoXDTI5MDIxMzE3MjEzMVowTjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgTCkNhbGlmb3JuaWExFDASBgNVBAcTC0xvcyBBbmdlbGVzMRQwEgYD\nVQQKEwtCbG9ja2RhZW1vbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABMmi8FGO\nAPfRA76hjIwU9kPzN88nZjp4hTmYnJ8Hb5yUzIF8PCwKi3nvtFRSFGW8UT07zdXN\nTkaA/PtsSdFT4JajUjBQMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAUBggrBgEF\nBQcDAgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUW6ouasv5oWo7MZ4ZzlE/mpbDrIMw\nCgYIKoZIzj0EAwIDSAAwRQIgew3hpT6O48N2WvccxDyNniAbwNV4NgCw8KXnLgkN\nR/4CIQD0/mV7K8i7HivST09bbSY4zakZVyGQpu+uUUq+6AzvDg==\n-----END CERTIFICATE-----" | ||
BUILDERVAULT_PLAYER2_CLIENT_CERT="./client.crt" | ||
BUILDERVAULT_PLAYER2_CLIENT_KEY="./client.key" | ||
|
||
## Optional APIkey-based authentication | ||
# BUILDERVAULT_PLAYER0_APIKEY="apikey0" | ||
# BUILDERVAULT_PLAYER1_APIKEY="apikey1" | ||
# BUILDERVAULT_PLAYER1_APIKEY="apikey2" |
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 @@ | ||
*.crt | ||
*.key | ||
.vscode | ||
init | ||
**/key.txt | ||
**/.env | ||
**/node_modules | ||
/dist/ |
63 changes: 63 additions & 0 deletions
63
ethereum-staking/buildervault/nodejs-web3provider/README.md
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,63 @@ | ||
|
||
# TypeScript Ethereum staking with BuilderVault wallet | ||
|
||
```mermaid | ||
sequenceDiagram | ||
autonumber | ||
participant StakeClient as Sample stake<br> client application | ||
participant StakeAPI as Stake Intent API | ||
participant RPC as Blockdaemon RPC | ||
box Builder Vault | ||
participant TSM1 as MPC Wallet <br>(private key share 1) | ||
participant TSM2 as MPC Wallet <br>(private key share 2) | ||
end | ||
StakeClient ->> StakeAPI: get StakeIntent unsigned tx data <br>(amount, withdrawal & recipient address) | ||
StakeClient ->> StakeClient: decode calldata with<br>deposit contract ABI | ||
critical BuilderVault Web3 Provider | ||
StakeClient ->> StakeClient: init web3 provider | ||
StakeClient ->> StakeClient: invoke web3 smart contract deposit<br> function<br>(amount, calldata, contract address) | ||
StakeClient ->> TSM1: partial sign | ||
StakeClient ->> TSM2: partial sign | ||
StakeClient ->> StakeClient: combine partial signatures | ||
StakeClient ->> RPC: broadcast contract execution | ||
end | ||
``` | ||
|
||
### Prerequisites | ||
- [Node.js](https://nodejs.org/en/download/package-manager) or launch in [code-spaces](https://codespaces.new/Blockdaemon/demo-buildervault-stakingAPI?quickstart=1) | ||
- Register for a demo Builder Vault tenant: https://www.blockdaemon.com/get-started/builder-vault-sandbox-registration | ||
- Download SDK bundle provided in registration email (extract authentication certificates) | ||
- Execute the go sample `go run main.go keygen` to generate an ECDSA master key ID across the 3 MPC players of the BuilderVault. This Master Key ID will be used to generate wallet addresses and sign operations. | ||
- Place Builder Vault authentication certificate key-pair `client.crt` & `client.key` in this nodejs folder | ||
- Register for 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 a 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/buildervault/nodejs-web3provider/ | ||
cp .env.example .env | ||
``` | ||
- update .env with API keys and BuilderVault Vault details | ||
|
||
### Step 2. Install package dependancies | ||
```shell | ||
npm config set @sepior:registry=https://gitlab.com/api/v4/projects/56306653/packages/npm/ # Builder Vault nodejsSDK public repository | ||
npm config set @blockdaemon:registry=https://npm.pkg.github.com/ | ||
npm install @blockdaemon/buildervault-web3-provider | ||
``` | ||
|
||
### Step 3. Launch ethereum-stake-bv.ts to determine the BuilderVault wallet address | ||
```shell | ||
ts-node ethereum-stake-bv.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-bv.ts to generate the Stake Intent request, execute the contract with BuilderVault, and broadcast the transaction | ||
```shell | ||
ts-node ethereum-stake-bv.ts | ||
``` | ||
- observe the confirmed transaction through the generated blockexplorer link |
142 changes: 142 additions & 0 deletions
142
ethereum-staking/buildervault/nodejs-web3provider/ethereum-stake-bv.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,142 @@ | ||
import Web3 from "web3"; | ||
import 'dotenv/config' | ||
import { BuildervaultWeb3Provider } from "@blockdaemon/buildervault-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_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'); | ||
} | ||
|
||
const eip1193Provider = new BuildervaultWeb3Provider({ | ||
rpcUrl: process.env.BLOCKDAEMON_RPC_URL, | ||
playerCount: Number(process.env.BUILDERVAULT_PLAYER_COUNT), | ||
|
||
player0Url: process.env.BUILDERVAULT_PLAYER0_URL, | ||
player0MPCpublicKey: process.env.BUILDERVAULT_PLAYER0_MPCPUBLICKEY, | ||
|
||
player1Url: process.env.BUILDERVAULT_PLAYER1_URL, | ||
player1MPCpublicKey: process.env.BUILDERVAULT_PLAYER1_MPCPUBLICKEY, | ||
|
||
player2Url: process.env.BUILDERVAULT_PLAYER2_URL, | ||
player2MPCpublicKey: process.env.BUILDERVAULT_PLAYER2_MPCPUBLICKEY, | ||
|
||
player0ClientCert: process.env.BUILDERVAULT_PLAYER0_CLIENT_CERT, | ||
player0ClientKey: process.env.BUILDERVAULT_PLAYER0_CLIENT_KEY, | ||
player0mTLSpublicKey: process.env.BUILDERVAULT_PLAYER0_MTLSPUBLICKEY, | ||
|
||
player1ClientCert: process.env.BUILDERVAULT_PLAYER1_CLIENT_CERT, | ||
player1ClientKey: process.env.BUILDERVAULT_PLAYER1_CLIENT_KEY, | ||
player1mTLSpublicKey: process.env.BUILDERVAULT_PLAYER1_MTLSPUBLICKEY, | ||
|
||
player2ClientCert: process.env.BUILDERVAULT_PLAYER2_CLIENT_CERT, | ||
player2ClientKey: process.env.BUILDERVAULT_PLAYER2_CLIENT_KEY, | ||
player2mTLSpublicKey: process.env.BUILDERVAULT_PLAYER2_MTLSPUBLICKEY, | ||
|
||
masterKeyId: process.env.BUILDERVAULT_MASTERKEY_ID, | ||
accountId: Number(process.env.BUILDERVAULT_ACCOUNT_ID), | ||
addressIndex: Number(process.env.BUILDERVAULT_ADDRESS_INDEX), | ||
logRequestsAndResponses: false // Verbose logging | ||
}) | ||
|
||
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.