A blazing fast and easy to use TRPC-like server for Rust.

Overview

rspc

🚧 Work in progress 🚧

A blazing fast and easy to use TRPC-like server for Rust.

Website



Example

You define a trpc router and attach resolvers to it like below. This will be very familiar if you have used trpc or GraphQL before.

let router = <rspc::Router>::new()
    .query("version", |_| "0.0.1")
    .mutation("helloWorld", |_| async { "Hello World!" });

Features:

  • Per Request Context - Great for database connection & authentication data
  • Middleware - With support for context switching
  • Merging routers - Great for separating code between files

Inspiration

This project is based off trpc and was inspired by the bridge system Jamie Pine designed for Spacedrive. A huge thanks to everyone who helped inspire this project!

Comments
  • Add nextjs example

    Add nextjs example

    I've been testing RSPC in a Next.js project and thought I could add an example based on it. Also did some extra changes I thought made sense. Let me know if you want any of them reverted.

    Summary:

    • Made Axum example file into an actual project that can be run by Cargo (bindings output to /examples/bindings.ts)
    • Moved Astro (packages/example) into /examples/astro
    • Added Next.js example to /examples/nextjs

    I'm not fully sure if the changes made to Cargo.toml in root are correct, so please check that.

    Fixes issue https://github.com/oscartbeaumont/rspc/issues/43

    opened by marjorg 5
  • Adding `bigdecimal::BigDecimal` as specta compatible type results in string.

    Adding `bigdecimal::BigDecimal` as specta compatible type results in string.

    I was using sqlx with MySQL through RSPC. The database is connected successfully, however there are some types that are not currently supported for example chrono::NaiveDate, bigdecimal::BigDecimal. A list of what sqlx maps SQL types into rust types can be found here.

    As a workaround, I could get it working by creating another struct and manually mapping the values into the values I need, see following:

    // this struct contains the results fetched from db
    #[derive(Debug, Serialize)]
    struct ExpenseQueryResult {
        id: i32,
        name: String,
        amount: BigDecimal,
        date: chrono::NaiveDate,
        category: String,
        comment: Option<String>,
        tags: Option<String>,
    }
    
    // this is an additional struct for mapping unsupported values into supported values
    #[derive(Debug)]
    struct ListExpensesResponse {
        id: i32,
        name: String,
        amount: f64,
        date: String,
        category: String,
        comment: Option<String>,
        tags: Option<String>,
    }
    
    // my router definition
    let router = rspc::Router::<Ctx>::new()
        .config(Config::new().export_ts_bindings("../generated/bindings.ts"))
        .query("listExpenses", |ctx: Ctx, _: ()| async move {
            let expenses: Vec<ExpenseQueryResult> = sqlx::query_as!(
                ExpenseQueryResult,
                r#"
    select
        e. `id`,
        e. `name`,
        e. `amount`,
        e. `date`,
        ec. `name` as `category`,
        e. `comment`,
        group_concat(t. `name` separator ', ') as `tags`
    from
        `expenses` e
        inner join `expenses_categories` ec on e. `expenses_categories_id` = ec. `id`
        left join `tags_expenses` te on e. `id` = te. `expenses_id`
        left join `tags` t on t. `id` = te. `tags_id`
    group by
        e. `id`,
        e. `name`,
        e. `amount`,
        e. `date`,
        ec. `name`,
        e. `comment`
    order by
        e. `date` desc;
            "#,
            )
            .fetch_all(&ctx.pool)
            .await
            .unwrap();
    
            selection!(
                expenses
                    .into_iter()
                    .map(|e| {
                        ListExpensesResponse {
                            id: e.id,
                            name: e.name,
                            amount: e.amount.to_f64().unwrap_or(-1.0),
                            date: e.date.to_string(),
                            category: e.category,
                            comment: e.comment,
                            tags: e.tags,
                        }
                    })
                    .collect::<Vec<ListExpensesResponse>>(),
                [{ id, name, amount, date, category, comment, tags }]
            )
        })
        .build()
        .arced();
    

    So this code above is working well and fine and produce the following JSON response:

    [
    	{
    		"type": "response",
    		"id": null,
    		"result": [
    			{
    				"amount": 118.00,
    				"category": "Tithe",
    				"comment": "Any comments",
    				"date": "2022-08-06",
    				"id": 4,
    				"name": "August Tithe",
    				"tags": "Touch n' Go"
    			}
    		]
    	}
    ]
    

    However, since I'm learning Rust and wanted to try making chrono::NaiveDate and bigdecimal::BigDecimal works by implementing the rspc::internal::specta::Type trait for it.

    The following shows my implementation

    #[derive(Debug, Serialize)]
    struct SpectaCompatibleNaiveDate(chrono::NaiveDate);
    
    impl From<chrono::NaiveDate> for SpectaCompatibleNaiveDate {
        fn from(date: chrono::NaiveDate) -> Self {
            Self(date)
        }
    }
    
    impl rspc::internal::specta::Type for SpectaCompatibleNaiveDate {
        const NAME: &'static str = "NaiveDate";
    
        fn inline(
            _: rspc::internal::specta::DefOpts,
            _: &[rspc::internal::specta::DataType],
        ) -> rspc::internal::specta::DataType {
            rspc::internal::specta::DataType::Primitive(rspc::internal::specta::PrimitiveType::String)
        }
    
        fn reference(
            _: rspc::internal::specta::DefOpts,
            _: &[rspc::internal::specta::DataType],
        ) -> rspc::internal::specta::DataType {
            rspc::internal::specta::DataType::Primitive(rspc::internal::specta::PrimitiveType::String)
        }
    
        fn definition(_: rspc::internal::specta::DefOpts) -> rspc::internal::specta::DataType {
            panic!()
        }
    }
    
    #[derive(Debug, Serialize)]
    struct SpectaCompatibleBigDecimal(bigdecimal::BigDecimal);
    
    impl From<bigdecimal::BigDecimal> for SpectaCompatibleBigDecimal {
        fn from(decimal: bigdecimal::BigDecimal) -> Self {
            Self(decimal)
        }
    }
    
    impl rspc::internal::specta::Type for SpectaCompatibleBigDecimal {
        const NAME: &'static str = "BigDecimal";
    
        fn inline(
            _: rspc::internal::specta::DefOpts,
            _: &[rspc::internal::specta::DataType],
        ) -> rspc::internal::specta::DataType {
            rspc::internal::specta::DataType::Primitive(rspc::internal::specta::PrimitiveType::f64)
        }
    
        fn reference(
            _: rspc::internal::specta::DefOpts,
            _: &[rspc::internal::specta::DataType],
        ) -> rspc::internal::specta::DataType {
            rspc::internal::specta::DataType::Primitive(rspc::internal::specta::PrimitiveType::f64)
        }
    
        fn definition(_: rspc::internal::specta::DefOpts) -> rspc::internal::specta::DataType {
            panic!()
        }
    }
    

    For SpectaCompatibleNaiveDate, it's working flawlessly. However for SpectaCompatibleBigDecimal, it returned String to the client instead of a number value. Here's the JSON response being returned.

    [
    	{
    		"type": "response",
    		"id": null,
    		"result": [
    			{
    				"amount": "118.00",
    				"category": "Tithe",
    				"comment": "Any comments",
    				"date": "2022-08-06",
    				"id": 4,
    				"name": "August Tithe",
    				"tags": "Touch n' Go"
    			}
    		]
    	}
    ]
    
    opened by marcustut 4
  • Question: Global state in Axum/rspc?

    Question: Global state in Axum/rspc?

    "Per Request Context" seems to create a new context "per request", which is not what i need. In actix-web i can just create a "AppState(WebData)", and that state is shared between all requests.

    A simple example with a global "request hit counter" would be appreciated.

    opened by Trixxi 3
  •  feat: client create helper and enable editing generated file

    feat: client create helper and enable editing generated file

    feat: client create helper and enable editing generated file

    In my project, I've create a new file to hold my client connection, but felt it was redundant, and I figured it might be simpler and consistent if everything can be in rspc's generated file. Therefore, this PR:

    • Enables rspc users to edit the generated rspsc file to add thier own code (either after/before generated setion).
    • Introduce createClient function and TransportKind type to simplify rspc setup.

    Before

    import { createClient, FetchTransport } from '@rspc/client'
    import { Operations } from 'rspc'
    const client = createClient<Operations>({
      transport: new FetchTransport('http://localhost:4000/rspc'),
    })
    

    After

    import { createClient } from 'rspc'
    export const client = createClient('fetch', 'http://localhost:4000/rspc')
    
    opened by kkharji 3
  • Add type_as attribute to specta

    Add type_as attribute to specta

    We were looking into using specta in our project, but ran into the issue of needing an escape hatch for certain types that can't (easily) implement Type. This solution is similar to ts_rs's #[ts(type = "..")] attribute.

    #[specta(type_as = <Ident>)] on a field overrides its type in the generated type definition with the type of the identifier.

    Example:

    use specta::{ts::ts_export, Type};
    
    pub enum MyEnum {
        A,
        B,
        C,
    }
    
    #[derive(Type)]
    pub struct MyCustomType {
        #[specta(type_as=String)]
        pub nested: MyEnum,
    }
    
    fn main() {
        println!("{}", ts_export::<MyCustomType>().unwrap(),);
    }
    

    Any feedback is super appreciated!

    opened by mablin7 2
  • tauri: failed to parse JSON-RPC request

    tauri: failed to parse JSON-RPC request

    https://github.com/tauri-apps/tauri/pull/5492 which was recently merged into the dev branch has broken the tauri integration. All RPC calls fail with the following error message:

    2022-10-30T11:37:47.115378Z ERROR rspc::integrations::tauri: failed to parse JSON-RPC request: invalid type: string "{\"id\":\"b8h6sjydy9l\",\"method\":\"query\",\"params\":{\"path\":\"version\"}}", expected struct Request at line 1 column 83
    

    I tried some hacks to fix the issue on the rust and typescript sides of rspc but wasn't successful. Happy to help PR a fix with a nudge in the right direction.

    Versions: tauri: dev branch rspc: v1.0.2 (also fails with main)

    opened by Gussy 2
  • bug: passing string as second param

    bug: passing string as second param

    Hey @oscartbeaumont, so I've started experimenting with rspc.

    I had odd issue, that I spent quite sometimes to try to fix it myself, but it seems most likely a bug.

    server

        let config = rspc::Config::new().export_ts_bindings("../client/src/rspc");
        let router = rspc::Router::<()>::new()
            .config(config)
            .query("ping", |_, name: String| {
                tokio::time::sleep(std::time::Duration::from_secs(1)).await;
                format!("pong {name}")
            })
            .build()
            .pipe(Arc::new);
        let addr = "[::]:4000".parse::<std::net::SocketAddr>().unwrap();
        println!("listening on http://{addr}/rspc/version?batch=1&input=%7B%7D",);
    
        axum::Server::bind(&addr)
            .serve(
                axum::Router::new()
                    .route("/rspc/:id", router.clone().axum_handler(|| ()))
                    .layer(
                        CorsLayer::new()
                            .allow_methods(Any)
                            .allow_headers(Any)
                            .allow_origin(Any),
                    )
                    .into_make_service(),
            )
            .await
            .unwrap();
    
    
    

    client

    const transport = new FetchTransport('http://localhost:4000/rspc')
    export const client = createClient<Operations>({ transport })
    const message = await client.query('ping', `${id}`)
    console.log(message)
    message
    

    The above seems to send http://localhost:4000/rspc/ping?batch=1&input=32323.

    the console log shows:

    Unhandled Promise Rejection: TypeError: null is not an object (evaluating '(await resp.json())[0].result')
    

    With curl I get the following error:

    │Failed to deserialize query string. Expected something of type `rspc::integrations::axum::GetParams`. Error: missing field `input`[1]
    

    When I used a number u32 type instead of string, it passes. Any ideas

    opened by kkharji 2
  • Exporting dependencies of dependencies

    Exporting dependencies of dependencies

    If you refer to a type directly on the router (Eg. as an argument or result) it will be automatically exported when you export the router bindings however if the type your return depends on another type the dependant type will not be exported.

    As a temporary workaround, you can use the ts_rs derive macros export functionality.

    I can't fix this without changes to ts_rs or moving off of ts_rs due to ts_rs not storing enough information to export the dependencies of each dependency.

    opened by oscartbeaumont 2
  • feat: generate code as side effect of build

    feat: generate code as side effect of build

    Hey @oscartbeaumont, I'm super excited about this library.

    I believe it would be cleaner and simpler if .build() would generate the typescript definitions as side effect instead of having to call router.export("..").unwrap();

    Additionally, it may be a good idea to have a default name for the exported file or dir {ts_root}/rspc/index.ts/{ts_src_root}/rspc.ts;

    For that to be configurable, a method like set_export_ts_root. I'm adding ts here because maybe in the future another languages can be supported, for example I'm hoping to add lua support somehow

    opened by kkharji 2
  • Normalised Caching

    Normalised Caching

    Normalised cache using global IDs + schema build time validate ID's are defined on all types

    Problems:

    • Single item
    • Array of items
    • Realtionships -> Eg. User with list of Pets

    Identifying types and validating the keys exists on types returned from Rust

    opened by oscartbeaumont 2
  • VS Code Extension incompatible with the current version

    VS Code Extension incompatible with the current version

    Hey just just want to let you know, I tried to install the extension, unfortunately I receive the following error.

    Can't install 'oscartbeaumont.rspc-vscode' extension because it is not compatible with the current version of Visual Studio Code (version 1.67.2).

    opened by Nivek92 1
  • Cloudflare Workers Env

    Cloudflare Workers Env

    Come up with a better solution around this. Gonna be annoying because most things are multithreaded. Using the workaround in the example below is dangerous because of the unsafe traits.

    #![allow(non_camel_case_types)]
    #![allow(non_snake_case)]
    #![allow(non_upper_case_globals)]
    
    use std::{ops::Deref, sync::Arc};
    
    use rspc::Router as RspcRouter;
    use worker::*;
    
    mod utils {
        use super::*;
        use cfg_if::cfg_if;
    
        cfg_if! {
            // https://github.com/rustwasm/console_error_panic_hook#readme
            if #[cfg(feature = "console_error_panic_hook")] {
                extern crate console_error_panic_hook;
                pub use self::console_error_panic_hook::set_once as set_panic_hook;
            } else {
                #[inline]
                pub fn set_panic_hook() {}
            }
        }
    
        pub fn log_request(req: &Request) {
            console_log!(
                "{} - [{}], located at: {:?}, within: {}",
                Date::now().to_string(),
                req.path(),
                req.cf().coordinates().unwrap_or_default(),
                req.cf().region().unwrap_or_else(|| "unknown region".into())
            );
        }
    }
    
    type App = RspcRouter<UnsafeEnv, ()>;
    
    #[derive(Clone)]
    pub struct UnsafeEnv(Arc<Env>);
    
    // TODO: Here we are just lying to Rust. I don't know if this is a safe assumption to make but it seems to be working in my quick tests.
    unsafe impl Send for UnsafeEnv {}
    unsafe impl Sync for UnsafeEnv {}
    
    impl Deref for UnsafeEnv {
        type Target = Env;
    
        fn deref(&self) -> &Self::Target {
            &self.0
        }
    }
    
    #[event(fetch)]
    pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
        utils::set_panic_hook();
        utils::log_request(&req);
        let env = UnsafeEnv(Arc::new(env));
    
        App::new()
            .query("version", |t| {
                t(move |env, _: ()| {
                    env.var("WORKERS_RS_VERSION")
                        .map(|v| v.to_string())
                        .unwrap() // TODO: Remove this?
                })
            })
            .build()
            .arced()
            .endpoint(move || env)
            .workers(req)
            .await
    }
    
    opened by oscartbeaumont 0
  • Axum 0.6.0

    Axum 0.6.0

    got this issue when using rspc with Axum 0.6.0

    mismatched types
    expected struct `MethodRouter<(), _>`
       found struct `axum::routing::method_routing::MethodRouter`
    perhaps two different versions of crate `axum` are being used?
    
    opened by ieow 0
  • Added remaining Http Error codes

    Added remaining Http Error codes

    Added all remaining http codes While some are not necessarily relevant for this crate i believe they don't hurt. If there is some reason why not to add some of them i am open to discussion and changes.

    opened by JK2Kgit 0
Releases(v0.1.2)
  • v0.1.2(Oct 1, 2022)

  • v0.1.1(Sep 29, 2022)

    This release comes with a huge amount of breaking changes. These changes are going to allow for many benefits in the future such as a rich plugin ecosystem. If you're having trouble upgrading open a GitHub Issue or jump in the Discord server.

    New vscode extension

    If you use Visual Studio Code and rspc you should definitely check out the new vscode extension!

    Httpz integration

    This update moves from offering a direct Axum integration to using httpz. This change is going to allow rspc to support other HTTP servers and serverless environments in the near future.

    Rust Changes:

    let app = axum::Router::new()
        .route("/", get(|| async { "Hello 'rspc'!" }))
    -   .route("/rspc/:id", router.clone().axum_handler(|| ()))
    -   .route("/rspcws", router.axum_ws_handler(|| ()))
    +   .route("/rspc/:id", router.endpoint(|req, cookies| ()).axum())
    

    Typescript Changes:

    const client = createClient<Operations>({
    -   transport: new WebsocketTransport("ws://localhost:8080/rspcws"),
    +   transport: new WebsocketTransport("ws://localhost:8080/rspc/ws"),
    });
    

    New Typescript bindings format

    The internal format of the generated Typescript bindings has changed. The import has also changed so ensure you update your code as follows.

    - import type { Operations } from "./my-bindings";
    + import type { Procedures } from "./my-bindings";
    

    New middleware syntax

    let router = Router::new()
    - .middleware(|ctx| async move {
    -     println!("MIDDLEWARE TWO");
    -     ctx.next("hello").await
    - })
    + .middleware(|mw| {
    +    mw.middleware(|mw| async move {
    +        let state = (mw.req.clone(), mw.ctx.clone(), mw.input.clone());
    +        // state allows sharing data between the two closures and is optional.
    +        Ok(mw.with_state(state).with_ctx("hello"))
    +    })
    +    // The .resp() part is optional and only required if you need to modify the return value.
    +    // Be aware it will be called for every value in a subscription stream.
    +    .resp(|state, result| async move {
    +        println!(
    +            "[LOG] req='{:?}' ctx='{:?}'  input='{:?}' result='{:?}'",
    +            state.0, state.1, state.2, result
    +        );
    +        Ok(result)
    +    })
    +})
    

    New procedure syntax

    The new procedure syntax is one of the biggest changes with this release. It is reccomended you install the Visual Studio Code extension which will provide many snippets to make working with rspc as easy as possible.

    Query

    let router = Router::new()
    - .query("version", |ctx, input: ()| "1.0.0")
    + .query("version", |t| t(|ctx, input: ()| "1.0.0"))
    

    Mutation

    let router = Router::new()
    - .mutation("demo", |ctx, input: ()| async move { todo!() })
    + .mutation("demo", |t| t(|ctx, input: ()| async move { todo!() })
    

    Subscription

    Rust:

    let router = Router::new()
    - .subscription("version", |ctx, input: ()| stream! { yield 42; })
    + .subscription("version", |t| t(|ctx, input: ()| stream! { yield 42; }))
    

    Typescript:

    rspc.useSubscription(['my.subscription'], {
    -    onNext: (data) => {
    +    onData: (data) => {
            console.log(data)
        }
    });
    

    Minor changes/new features

    • ws, rpc.* and rspc.* are now reserved names for procedures
    • @rspc/solid has been upgraded to the new @tanstack/solid-query.
    • router.execute now returns a serde_json::Value and does not support subscriptions. Use router.execute_subscription to execute a subscription.
    • The Axum body type is now Vec<u8> not hyper::Body. This may cause issues if your extractors aren't generic enough.
    • Support for up to 5 Axum extractors. This limit will increase to 16 in a future release.

    Warning on unstable APIs

    • Support for Axum extractors will likely change or be removed in the future when support for other HTTP servers is added. It exists in this release for backward compatibility.
    Source code(tar.gz)
    Source code(zip)
Owner
Oscar Beaumont
I'm a freelance software developer who loves to build open-source projects!
Oscar Beaumont
Minimal and blazing-fast file server. For real, this time.

Zy Minimal and blazing-fast file server. For real, this time. Features Single Page Application support Partial responses (Range support) Cross-Origin

Miraculous Owonubi 17 Dec 18, 2022
Over-simplified, featherweight, open-source and easy-to-use authentication and authorization server.

concess ⚠️ Early Development: This is not production ready, yet. Do not use it for anything important. Introduction concess is a over-simplified, feat

Dustin Frisch 3 Nov 25, 2022
Yet another lightweight and easy to use HTTP(S) server

Raptor Web server Raptor is a HTTP server written in Rust with aims to use as little memory as possible and an easy configuration. It is built on top

Volham 5 Oct 15, 2022
🚀 A blazingly fast easy to use dotfile and global theme manager written in Rust

GTHEME A blazingly fast easy to use dotfile and global theme manager for *NIX systems written in Rust ?? Demo using wip desktop. To check out more des

David Rodriguez 19 Nov 28, 2022
A robust, customizable, blazingly-fast, efficient and easy-to-use command line application to uwu'ify your text!

uwuifyy A robust, customizable, blazingly-fast, efficient and easy-to-use command line application to uwu'ify your text! Logo Credits: Jade Nelson Tab

Hamothy 43 Dec 12, 2022
A simple, fast, and easy to use Solidity test generator based on the Branching Tree Technique.

bulloak A simple, fast, and easy to use Solidity test generator based on the Branching Tree Technique. Installing cargo install bulloak Usage Basic Us

Alexander González 38 Aug 7, 2023
☄🌌️ The minimal, blazing-fast, and infinitely customizable prompt for any shell

☄??️ The minimal, blazing-fast, and infinitely customizable prompt for any shell

Starship Command 31.6k Dec 30, 2022
Blazing-fast and yet Sleuth cameraman to www* 😎⚡✨

Haylxon ?? ?? SHOOT BEFORE THE BLINK || Haylxon, A tool embodying the K1SS philosophy that allows you to take screenshots of webpages/URLs at lightnin

Nabeen Tiwaree 78 Apr 10, 2023
🚀 Blazing fast and Powerful Discord Token Grabber, no popo made with python

Rusty-Grabber ?? a blazing fast Discord Token Grabber, no popo made with python Fastest Token Grabber ever : Rusty-Grabber> time ./target/release/grab

bishop 5 Sep 1, 2023
A blazing fast command line license generator for your open source projects written in Rust🚀

Overview This is a blazing fast ⚡ , command line license generator for your open source projects written in Rust. I know that GitHub

Shoubhit Dash 43 Dec 30, 2022
⚡ Blazing fast async/await HTTP client for Python written on Rust using reqwests

Reqsnaked Reqsnaked is a blazing fast async/await HTTP client for Python written on Rust using reqwests. Works 15% faster than aiohttp on average RAII

Yan Kurbatov 8 Mar 2, 2023
⚡ Blazing ⚡ fast ⚡ compiler for Cairo, written in 🦀 Rust 🦀

Cairo 1.0 ?? ⚡ Blazing ⚡ fast ⚡ compiler for Cairo, written in ?? Rust ?? Report a Bug - Request a Feature - Ask a Question Getting Started Prerequisi

Darlington Nnam 6 Feb 23, 2023
⚡️Blazing Fast⚡️ CLI tool to get Bible verses with Rust 🦀

bible.rs This is a simple command line tool that accesses bible-api.com to print Bible verses in the terminal Installation brew install wzid/tap/bible

cameron 3 Apr 3, 2023
⚡️ Blazing fast terminal file manager written in Rust, based on async I/O.

Yazi - ⚡️ Blazing Fast Terminal File Manager Yazi ("duck" in Chinese) is a terminal file manager written in Rust, based on non-blocking async I/O. It

三咲雅 · Misaki Masa 189 Aug 1, 2023
zoxide is a blazing fast replacement for your cd command

zoxide A smarter cd command for your terminal zoxide is a blazing fast replacement for your cd command, inspired by z and z.lua. It keeps track of the

Ajeet D'Souza 8.7k Dec 31, 2022
⚡️ A blazing fast way of maintaining powerful notes with connections between them.

Zettl ⚡️ A blazing fast way of maintaining powerful notes with connections between them. Installing Zettl To install Zettl, you will need the Rust too

Tirth Jain 26 Dec 2, 2022
⚡ A Blazing fast alternative to the stock windows folder delete function!

Turbo Delete A blazing fast alternative to the default Windows delete. Turbodelete is a blazing fast alternative to the default Windows delete functio

Tejas Ravishankar 165 Dec 4, 2022
Starkli (/ˈstɑːrklaɪ/), a ⚡ blazing ⚡ fast ⚡ CLI tool for StarkNet powered by 🦀 starknet-rs 🦀

starkli Starkli (/ˈstɑːrklaɪ/), a ⚡ blazing ⚡ fast ⚡ CLI tool for StarkNet powered by ?? starknet-rs ?? Installation The package will be published to

Jonathan LEI 26 Dec 5, 2022
pyrevm Blazing-fast Python bindings to revm

pyrevm Blazing-fast Python bindings to revm Quickstart make install make test Example Usage Here we show how you can fork from Ethereum mainnet and s

Georgios Konstantopoulos 97 Apr 14, 2023