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
|
||||
target
|
||||
*.env
|
||||
|
||||
helios-ts/node_modules
|
||||
helios-ts/dist
|
||||
|
|
|
@ -702,6 +702,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "const-cstr"
|
||||
version = "0.3.0"
|
||||
|
@ -1985,6 +1995,26 @@ dependencies = [
|
|||
"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]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
@ -3884,6 +3914,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
|
|
|
@ -12,6 +12,7 @@ members = [
|
|||
"config",
|
||||
"consensus",
|
||||
"execution",
|
||||
"helios-ts",
|
||||
]
|
||||
|
||||
[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