- Built with TypeScript
- State-management with Redux Toolkit (
- CRA (create-react-app) scripts v5 with Craco to enable yarn workspaces (monorepo package management)
wasm-react-scripts- enabling WebAssembly files into the Webpack pipeline
- Styled-Componenents for all application/component styling
Much of the core functionality of the web app requires either direct interfacing with types from the Anoma codebase, or other Rust libraries that provide encryption, key-management, mnemonic-generation, etc., that are more easily and robustly handled in the Rust ecosystem than that of TypeScript.
The primary functionality that we currently pull from
anoma involves constructing transactions. The web wallet interface should be able to serialize the data broadcast to the ledger for different transactions, and this requires items to be serialized within the WebAssembly code. We created
anoma-lib, which houses wrapped Anoma types (wrapped when some work is needed to get it to work well with wasm), and the logic needed for us to be able to interface with it from TypeScript.
The Rust source code
anoma-lib is structured as follows:
. ├── types │ ├── address.rs │ ├── keypair.rs │ ├── mod.rs │ ├── transaction.rs │ ├── tx.rs │ └── wrapper.rs ├── account.rs ├── lib.rs ├── transfer.rs ├── utils.rs
Here, we have several types that are essentially built on top of
anoma types, allowing us to interface easily from the client app, such as
wrapper, then a generic
transaction type that handles the logic common to all transactions. Essentially, we want these types to handle any serialization that the
anoma types require entirely within the wasm, then later translate the results into something the client can understand.
Outside of types, we have an
account.rs file that allows us to call account functions, such as
initialize (to construct an "init-account" transaction), from the client app.
transfer.rs is similar, in that it provides the bridge for the client to issue a transfer transaction. Additional transactions can be easily created in this way, with a specific differences being handled in a top level Rust source file, the common logic of transactions handled by
types/transaction, and any types that need extra work in order to be useful to the client being added as well to
When compiling the
wasm-pack target to
web, we get an additional exported
init function, which is a promise that resolves when the wasm is fully loaded, exposing the
memory variable. In most cases we shouldn't need to interact directly with the memory of the wasm, but by awaiting the
init() call, we can immediately execute any of the wasm methods.
In the case of
anoma-lib, there is a corresponding class that initializes and exposes the features of the wasm in
AnomaClient. (NOTE: This is one use case for wasm, but we may have any number of wasm projects that the wallet can utilize). Exposing the features through a TypeScript class is a good opportunity to move from Rust-style "snake-casing" to camel-casing (most common in TypeScript), and any additional type definitions we can add at this level as well.
The goal of bridging wasm and the client TypeScript application should be to make its usage as straightforward as any TypeScript class. It should also be fairly easy for the developer to add new features to the Rust source and quickly bring that into the client app.
Vec<u8> makes this straight-forward. (More to come on this topic in the future)
There is much more nuance to handling types from Rust wasm in TypeScript when working with
wasm-bindgen, and more information can be found at the following URL:
The wallet-interface should be able to run within the Jest testing framework. This is made possibly by switching our
wasm-pack target and rebuilding before the test is run, as tests run within NodeJS. So, instead of the following:
wasm-pack build ../anoma-lib/ --out-dir ../anoma-wallet/src/lib/anoma --out-name anoma --target web