feat: typescript bindings (#191)
* basic ethers provider setup * add getCode * add call * add estimateGas * add gas pricing methods * add sendRawTransaction * add getTransactionReceipt * add getLogs * add net_version * decouple ethers from lib * add config options * fmt
This commit is contained in:
parent
4066828387
commit
8e006d623b
|
@ -1,3 +1,6 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
target
|
target
|
||||||
*.env
|
*.env
|
||||||
|
|
||||||
|
helios-ts/node_modules
|
||||||
|
helios-ts/dist
|
||||||
|
|
|
@ -702,6 +702,16 @@ dependencies = [
|
||||||
"wasm-timer",
|
"wasm-timer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console_error_panic_hook"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const-cstr"
|
name = "const-cstr"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -1985,6 +1995,26 @@ dependencies = [
|
||||||
"tracing-test",
|
"tracing-test",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "helios-ts"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"client",
|
||||||
|
"common",
|
||||||
|
"config",
|
||||||
|
"consensus",
|
||||||
|
"console_error_panic_hook",
|
||||||
|
"ethers",
|
||||||
|
"execution",
|
||||||
|
"hex",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"serde_json",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
|
@ -3884,6 +3914,17 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-wasm-bindgen"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.152"
|
version = "1.0.152"
|
||||||
|
|
|
@ -12,6 +12,7 @@ members = [
|
||||||
"config",
|
"config",
|
||||||
"consensus",
|
"consensus",
|
||||||
"execution",
|
"execution",
|
||||||
|
"helios-ts",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "helios-ts"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = "0.2.83"
|
||||||
|
wasm-bindgen-futures = "0.4.33"
|
||||||
|
serde-wasm-bindgen = "0.4.5"
|
||||||
|
console_error_panic_hook = "0.1.7"
|
||||||
|
|
||||||
|
ethers = "1.0.0"
|
||||||
|
hex = "0.4.3"
|
||||||
|
serde = { version = "1.0.143", features = ["derive"] }
|
||||||
|
serde_json = "1.0.85"
|
||||||
|
|
||||||
|
client = { path = "../client" }
|
||||||
|
common = { path = "../common" }
|
||||||
|
consensus = { path = "../consensus" }
|
||||||
|
execution = { path = "../execution" }
|
||||||
|
config = { path = "../config" }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3"
|
||||||
|
features = [
|
||||||
|
"console",
|
||||||
|
]
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>hello-wasm example</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="./dist/bundle.js"></script>
|
||||||
|
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"></script>
|
||||||
|
<script>
|
||||||
|
helios.then((helios) => {
|
||||||
|
const config = {
|
||||||
|
executionRpc:"http://localhost:9001/proxy",
|
||||||
|
consensusRpc: "http://localhost:9002/proxy",
|
||||||
|
checkpoint: "0x372342db81e3a42527e08dc19e33cd4f91f440f45b9ddb0a9865d407eceb08e4",
|
||||||
|
}
|
||||||
|
|
||||||
|
const heliosProvider = new helios.HeliosProvider(config);
|
||||||
|
|
||||||
|
heliosProvider.sync().then(() => {
|
||||||
|
window.provider = new ethers.providers.Web3Provider(heliosProvider);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { Client } from "./pkg";
|
||||||
|
|
||||||
|
/// An EIP-1193 compliant Ethereum provider. Treat this the same as you
|
||||||
|
/// would window.ethereum when constructing an ethers or web3 provider.
|
||||||
|
export class HeliosProvider {
|
||||||
|
#client;
|
||||||
|
#chainId;
|
||||||
|
|
||||||
|
constructor(config: Config) {
|
||||||
|
const executionRpc = config.executionRpc;
|
||||||
|
const consensusRpc = config.consensusRpc;
|
||||||
|
const checkpoint = config.checkpoint;
|
||||||
|
const network = config.network ?? Network.MAINNET;
|
||||||
|
|
||||||
|
this.#client = new Client(executionRpc, consensusRpc, network, checkpoint);
|
||||||
|
this.#chainId = this.#client.chain_id();
|
||||||
|
}
|
||||||
|
|
||||||
|
async sync() {
|
||||||
|
await this.#client.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(req: Request): Promise<any> {
|
||||||
|
switch(req.method) {
|
||||||
|
case "eth_getBalance": {
|
||||||
|
return this.#client.get_balance(req.params[0], req.params[1]);
|
||||||
|
};
|
||||||
|
case "eth_chainId": {
|
||||||
|
return this.#chainId;
|
||||||
|
};
|
||||||
|
case "eth_blockNumber": {
|
||||||
|
return this.#client.get_block_number();
|
||||||
|
};
|
||||||
|
case "eth_getTransactionByHash": {
|
||||||
|
let tx = await this.#client.get_transaction_by_hash(req.params[0]);
|
||||||
|
return mapToObj(tx);
|
||||||
|
};
|
||||||
|
case "eth_getTransactionCount": {
|
||||||
|
return this.#client.get_transaction_count(req.params[0], req.params[1]);
|
||||||
|
};
|
||||||
|
case "eth_getBlockTransactionCountByHash": {
|
||||||
|
return this.#client.get_block_transaction_count_by_hash(req.params[0]);
|
||||||
|
};
|
||||||
|
case "eth_getBlockTransactionCountByNumber": {
|
||||||
|
return this.#client.get_block_transaction_count_by_number(req.params[0]);
|
||||||
|
};
|
||||||
|
case "eth_getCode": {
|
||||||
|
return this.#client.get_code(req.params[0], req.params[1]);
|
||||||
|
};
|
||||||
|
case "eth_call": {
|
||||||
|
return this.#client.call(req.params[0], req.params[1]);
|
||||||
|
};
|
||||||
|
case "eth_estimateGas": {
|
||||||
|
return this.#client.estimate_gas(req.params[0]);
|
||||||
|
};
|
||||||
|
case "eth_gasPrice": {
|
||||||
|
return this.#client.gas_price();
|
||||||
|
};
|
||||||
|
case "eth_maxPriorityFeePerGas": {
|
||||||
|
return this.#client.max_priority_fee_per_gas();
|
||||||
|
};
|
||||||
|
case "eth_sendRawTransaction": {
|
||||||
|
return this.#client.send_raw_transaction(req.params[0]);
|
||||||
|
};
|
||||||
|
case "eth_getTransactionReceipt": {
|
||||||
|
return this.#client.get_transaction_receipt(req.params[0]);
|
||||||
|
};
|
||||||
|
case "eth_getLogs": {
|
||||||
|
return this.#client.get_logs(req.params[0]);
|
||||||
|
};
|
||||||
|
case "net_version": {
|
||||||
|
return this.#chainId;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Config = {
|
||||||
|
executionRpc: string,
|
||||||
|
consensusRpc?: string,
|
||||||
|
checkpoint?: string,
|
||||||
|
network?: Network,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Network {
|
||||||
|
MAINNET = "mainnet",
|
||||||
|
GOERLI = "goerli",
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request = {
|
||||||
|
method: string,
|
||||||
|
params: any[],
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapToObj(map: Map<any, any> | undefined): Object | undefined {
|
||||||
|
if(!map) return undefined;
|
||||||
|
|
||||||
|
return Array.from(map).reduce((obj: any, [key, value]) => {
|
||||||
|
obj[key] = value;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "helios",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "lib.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"ts-loader": "^9.4.1",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"webpack": "^5.75.0",
|
||||||
|
"webpack-cli": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ethers": "^5.7.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
set -e
|
||||||
|
|
||||||
|
(&>/dev/null lcp --proxyUrl https://eth-mainnet.g.alchemy.com/v2/23IavJytUwkTtBMpzt_TZKwgwAarocdT --port 9001 &)
|
||||||
|
(&>/dev/null lcp --proxyUrl https://www.lightclientdata.org --port 9002 &)
|
||||||
|
|
||||||
|
wasm-pack build
|
||||||
|
npm run build
|
||||||
|
simple-http-server
|
|
@ -0,0 +1,185 @@
|
||||||
|
extern crate console_error_panic_hook;
|
||||||
|
extern crate web_sys;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use common::types::BlockTag;
|
||||||
|
use ethers::types::{Address, Filter, H256};
|
||||||
|
use execution::types::CallOpts;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
use client::database::ConfigDB;
|
||||||
|
use config::{networks, Config};
|
||||||
|
|
||||||
|
#[allow(unused_macros)]
|
||||||
|
macro_rules! log {
|
||||||
|
( $( $t:tt )* ) => {
|
||||||
|
web_sys::console::log_1(&format!( $( $t )* ).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub struct Client {
|
||||||
|
inner: client::Client<ConfigDB>,
|
||||||
|
chain_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
impl Client {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(
|
||||||
|
execution_rpc: String,
|
||||||
|
consensus_rpc: Option<String>,
|
||||||
|
network: String,
|
||||||
|
checkpoint: Option<String>,
|
||||||
|
) -> Self {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
let base = match network.as_str() {
|
||||||
|
"mainnet" => networks::mainnet(),
|
||||||
|
"goerli" => networks::goerli(),
|
||||||
|
_ => panic!("invalid network"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let chain_id = base.chain.chain_id;
|
||||||
|
|
||||||
|
let checkpoint = Some(
|
||||||
|
checkpoint
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.strip_prefix("0x").unwrap_or(c.as_str()))
|
||||||
|
.map(|c| hex::decode(c).unwrap())
|
||||||
|
.unwrap_or(base.default_checkpoint),
|
||||||
|
);
|
||||||
|
|
||||||
|
let consensus_rpc = consensus_rpc.unwrap_or(base.consensus_rpc.unwrap());
|
||||||
|
|
||||||
|
let config = Config {
|
||||||
|
execution_rpc,
|
||||||
|
consensus_rpc,
|
||||||
|
checkpoint,
|
||||||
|
|
||||||
|
chain: base.chain,
|
||||||
|
forks: base.forks,
|
||||||
|
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let inner: client::Client<ConfigDB> =
|
||||||
|
client::ClientBuilder::new().config(config).build().unwrap();
|
||||||
|
|
||||||
|
Self { inner, chain_id }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn sync(&mut self) {
|
||||||
|
self.inner.start().await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn chain_id(&self) -> u32 {
|
||||||
|
self.chain_id as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_block_number(&self) -> u32 {
|
||||||
|
self.inner.get_block_number().await.unwrap() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_balance(&self, addr: JsValue, block: JsValue) -> String {
|
||||||
|
let addr: Address = serde_wasm_bindgen::from_value(addr).unwrap();
|
||||||
|
let block: BlockTag = serde_wasm_bindgen::from_value(block).unwrap();
|
||||||
|
self.inner
|
||||||
|
.get_balance(&addr, block)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_transaction_by_hash(&self, hash: String) -> JsValue {
|
||||||
|
let hash = H256::from_str(&hash).unwrap();
|
||||||
|
let tx = self.inner.get_transaction_by_hash(&hash).await.unwrap();
|
||||||
|
serde_wasm_bindgen::to_value(&tx).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_transaction_count(&self, addr: JsValue, block: JsValue) -> u32 {
|
||||||
|
let addr: Address = serde_wasm_bindgen::from_value(addr).unwrap();
|
||||||
|
let block: BlockTag = serde_wasm_bindgen::from_value(block).unwrap();
|
||||||
|
self.inner.get_nonce(&addr, block).await.unwrap() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_block_transaction_count_by_hash(&self, hash: JsValue) -> u32 {
|
||||||
|
let hash: H256 = serde_wasm_bindgen::from_value(hash).unwrap();
|
||||||
|
self.inner
|
||||||
|
.get_block_transaction_count_by_hash(&hash.as_bytes().to_vec())
|
||||||
|
.await
|
||||||
|
.unwrap() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_block_transaction_count_by_number(&self, block: JsValue) -> u32 {
|
||||||
|
let block: BlockTag = serde_wasm_bindgen::from_value(block).unwrap();
|
||||||
|
self.inner
|
||||||
|
.get_block_transaction_count_by_number(block)
|
||||||
|
.await
|
||||||
|
.unwrap() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_code(&self, addr: JsValue, block: JsValue) -> String {
|
||||||
|
let addr: Address = serde_wasm_bindgen::from_value(addr).unwrap();
|
||||||
|
let block: BlockTag = serde_wasm_bindgen::from_value(block).unwrap();
|
||||||
|
let code = self.inner.get_code(&addr, block).await.unwrap();
|
||||||
|
format!("0x{}", hex::encode(code))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn call(&self, opts: JsValue, block: JsValue) -> String {
|
||||||
|
let opts: CallOpts = serde_wasm_bindgen::from_value(opts).unwrap();
|
||||||
|
let block: BlockTag = serde_wasm_bindgen::from_value(block).unwrap();
|
||||||
|
let res = self.inner.call(&opts, block).await.unwrap();
|
||||||
|
format!("0x{}", hex::encode(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn estimate_gas(&self, opts: JsValue) -> u32 {
|
||||||
|
let opts: CallOpts = serde_wasm_bindgen::from_value(opts).unwrap();
|
||||||
|
self.inner.estimate_gas(&opts).await.unwrap() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn gas_price(&self) -> JsValue {
|
||||||
|
let price = self.inner.get_gas_price().await.unwrap();
|
||||||
|
serde_wasm_bindgen::to_value(&price).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn max_priority_fee_per_gas(&self) -> JsValue {
|
||||||
|
let price = self.inner.get_priority_fee().await.unwrap();
|
||||||
|
serde_wasm_bindgen::to_value(&price).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn send_raw_transaction(&self, tx: String) -> JsValue {
|
||||||
|
let tx = hex::decode(tx).unwrap();
|
||||||
|
let hash = self.inner.send_raw_transaction(&tx).await.unwrap();
|
||||||
|
serde_wasm_bindgen::to_value(&hash).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_transaction_receipt(&self, tx: JsValue) -> JsValue {
|
||||||
|
let tx: H256 = serde_wasm_bindgen::from_value(tx).unwrap();
|
||||||
|
let receipt = self.inner.get_transaction_receipt(&tx).await.unwrap();
|
||||||
|
serde_wasm_bindgen::to_value(&receipt).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn get_logs(&self, filter: JsValue) -> JsValue {
|
||||||
|
let filter: Filter = serde_wasm_bindgen::from_value(filter).unwrap();
|
||||||
|
let logs = self.inner.get_logs(&filter).await.unwrap();
|
||||||
|
serde_wasm_bindgen::to_value(&logs).unwrap()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"module": "es6",
|
||||||
|
"target": "es6",
|
||||||
|
"jsx": "react",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: "./lib.ts",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ts?$/,
|
||||||
|
use: 'ts-loader',
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
filename: "bundle.js",
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
library: {
|
||||||
|
name: "helios",
|
||||||
|
type: "umd",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
experiments: {
|
||||||
|
asyncWebAssembly: true,
|
||||||
|
}
|
||||||
|
};
|
Loading…
Reference in New Issue