Chia Wallet SDK
This is a library designed to make developing on the Chia blockchain as easy as possible. It provides both low-level and high-level functionality for finding, parsing, and spending coins, as well as various other utilities.
Installation
The Wallet SDK is written in Rust, so that's what you should use if you want to get the most out of it. However, there are also bindings provided for WebAssembly, Node.js, and Python. These bindings are roughly the same (aside from naming conventions and language differences), but they do not cover 100% of the API surface of the Rust crate. If anything is missing, feel free to submit an issue or PR on GitHub.
Rust
First, make sure you have installed Rust.
cargo add chia-wallet-sdk chia clvmr
WebAssembly
Currently, in order to use the WASM bindings, you will need a bundler that supports loading .wasm
files. For example, Vite supports this well, with the WASM plugin.
In the future, a plain web version of the WASM bindings may be released, but they are not at this time. You can build from source if you need that sooner.
npm install chia-wallet-sdk-wasm
Node.js
The minimum supported version is Node.js v10.7.0.
npm install chia-wallet-sdk
Python
The minimum supported version is Python 3.8.
pip install chia-wallet-sdk
Coin Set Model
Chia's Coin Set model is similar to Bitcoin's UTXO model. For a more in-depth and general purpose explanation, please refer to the Chia docs on the topic. We're going to focus primarily on what this means for implementing dApps and wallets using the Wallet SDK.
This is the single most important thing to understand when developing on Chia, since it differs pretty heavily from Ethereum's account model. These differences can make learning more complicated, but there is a powerful set of primitives you can build on top of to create secure and auditable code that interacts with the blockchain.
Allocator
The Allocator
is how you interact with the CLVM runtime (which lives in the clvmr crate).
Values that you allocate, which are referenced by NodePtr
, won't be freed until you free the Allocator
itself. You can think of it as a sort of memory arena. All CLVM values are stored contiguously on the heap, to prevent repeated memory allocations, and to improve cache locality. This makes working with the Allocator
very efficient, which is especially desirable for Rust projects.
Low Level
On its own, clvmr doesn't provide many utilities for working with the Allocator
.
For example, this is how you would allocate a string value:
let mut allocator = Allocator::new();
let ptr = allocator.new_atom(b"Hello, world!").unwrap();
And you can read it back like so:
let atom = allocator.atom(ptr).to_vec();
let string = String::from_utf8(atom).unwrap();
You can also create pairs of values:
use clvmr::SExp;
let pair = allocator.new_pair(ptr, ptr).unwrap();
let SExp::Pair(first, rest) = allocator.sexp(pair) else {
panic!("Expected a pair");
};
Type Conversions
As you can see, it gets pretty tedious to work with CLVM values in Rust by hand, especially if you need to allocate and parse complex data structures like lists and curried program arguments.
This is where clvm-traits comes in. This library provides traits and macros for converting between complex CLVM values and Rust values.
The examples above can be rewritten like this:
use clvm_traits::{FromClvm, ToClvm};
let ptr = "Hello.world!".to_clvm(&mut allocator).unwrap();
let string = String::from_clvm(&allocator, ptr).unwrap();
let pair = (ptr, ptr).to_clvm(&mut allocator).unwrap();
let (first, rest) = <(String, String)>::from_clvm(&allocator, ptr).unwrap();
This is much more straightforward now, and you can nest types together instead of parsing individual values repeatedly.
You can read more about how to define your own types in the clvm-traits documentation, but for now we'll move on.
Spend Context
The SpendContext
is an important utility used by the driver code to simplify coin spends. You can think of it as a wrapper around Allocator with these additional benefits:
- A simpler interface for common operations.
- Automatic caching for puzzle pointers, since the size can be large.
- Simple mechanism for currying a puzzle with its arguments.
- Keeps track of coin spends for you, so that they can be collected at the end in one place.
Setup
It's just as easy to create a SpendContext
as an Allocator
:
use chia_sdk_driver::SpendContext;
let mut ctx = SpendContext::new();
You can also create one from an existing Allocator
, although you shouldn't need to do this that often:
use clvmr::Allocator;
let mut ctx = SpendContext::from(Allocator::new());
Simulator
When testing wallet code, it's best not to use mainnet, because you would lose funds if anything went wrong. And while testnet11 would be a fine alternative, it still requires setup and getting test funds to use. Because live networks have a persistent state and every transaction block takes nearly a minute, it can be tedious to test this way. It's also very impractical for unit testing, where you need fast and deterministic results every time.
Chia provides a full node simulator, which works very well for emulating the whole blockchain. But this typically has to run as a standalone service, rather than being embedded in your tests, and even though it's faster than live networks, it's still slower than would be ideal for a unit test.
Because of this, the Wallet SDK provides a Simulator
which allows you to test transactions efficiently with minimal setup. This is the recommended way to test out primitives.
Setup
First you will need to create a simulator instance:
let mut sim = Simulator::new();
let mut ctx = SpendContext::new();
Bindings
CLVM
CLVM
Design
The Wallet SDK is a pretty complex project with a lot of moving pieces. Therefore, the purpose of this section is to try to break down how it's designed, to make it easier to understand how to use and contribute to the project.
Nothing here is set in stone, so if you have a better idea, feel free to suggest it on GitHub issues or make a pull request. We welcome contributions!
Crates
The library is split into a bunch of individual crates in a Cargo workspace. This has a few benefits:
- Enforces a separation of concerns, which makes code easier to manage.
- If needed, you can depend on smaller subsets of the library to reduce dependencies and binary size.
- Bindings can be built as part of the same repository without introducing additional dependencies.
chia-sdk-driver
This is the most important, since it implements all of the wallet driver code for each primitive the Wallet SDK supports. It also provides the SpendContext for managing lots of complex coin spends in an easier way.
chia-sdk-test
A Simulator testing framework for Chia, similar to the official full node simulator but more lightweight and wallet focused. Instead of including a proper mempool, it treats every transaction as its own block and validates the coin spends. This makes unit testing very fast and easy.
Note that this also provides a PeerSimulator, for simulating the wallet protocol via the chia-sdk-client Peer just like you would when connecting to an actual full node. It only implements a subset of the protocol, but it's nice to have for integration testing in wallets for example.
chia-sdk-client
A client implementation of the Chia wallet protocol, with an easy to use interface. Provides both a native-tls and rustls option, to make it more portable to a wider variety of devices. You can connect to introducers, find and connect to peers, and make requests to get coin data or send transactions, entirely peer to peer.
chia-sdk-coinset
As an alternative to chia-sdk-client (and one that can be used on the web as well), you can use the Coinset API. It can also potentially point to your local full node RPC since it has the same (for the most part) endpoints.
chia-sdk-signer
Provides utilities for calculating the required signatures for a given unsigned spend bundle. The usefulness of this varies depending on the type of wallet you are building. For example, with BLS wallets it is common to generate an unsigned spend bundle and sign it later. Whereas with vaults, you usually will want to only put together the spend bundle at the end, once you have all of the required signatures already.
chia-sdk-types
Provides common types for interacting with Chia puzzles, such as conditions and merkle trees. It also currently defines puzzle types that are missing in the chia-puzzle-types library in chia_rs.
chia-sdk-derive
A procedural macro to make implementing the condition types in chia-sdk-types easier. Currently that's all this crate does, so it's not particularly interesting.
chia-sdk-utils
Utilities for addresses and coin selection. Hopefully the scope of this can expand in the future to simplify the development process.
chia-sdk-bindings
The Rust implementation of the Wallet SDK bindings. It's powered by the bindy and bindy-macro crates, which convert JSON files into Node.js, WASM, and Python bindings.
Dependencies
This is not a comprehensive list of every dependency chia-wallet-sdk has, but rather the foundations it's built upon.
chia_rs
This is an entire collection of low level crates for developing on the Chia blockchain, maintained by Chia Network Inc. One of its primary goals is to speed up aspects of the Chia full node through its Python bindings. However, we make use of a lot of the functionality in the Wallet SDK as well, to avoid reinventing the wheel.
For bls12_381 (BLS) signatures, we use chia-bls which itself currently depends on blst to implement the underlying cryptography efficiently and securely.
Similarly, the secp256k1 (K1) and secp256r1 (R1) curves are implemented in chia-secp via k256 and p256.
Common types for coins, spend bundles, and the wallet protocol, are implemented by chia-protocol. They can be serialized as JSON or via Chia's custom streamable binary serialization format.
Finally, clvm-traits, clvm-derive, and clvm-utils provide common utilities for encoding Rust types as CLVM values and vice versa. This makes it trivial to represent complex data structures in puzzles.
clvm_rs
A low level runtime for the Chialisp Virtual Machine (CLVM). It's a necessary component to create and execute puzzles, but it's largely abstracted away by the utilities provided in chia_rs and chia-wallet-sdk.
chia_puzzles
This provides a single location to store standard Chia puzzles, so that they don't have to be copied into every project that relies upon them. While the Wallet SDK contains the actual wallet driver code to use these puzzles, chia_puzzles contains only the puzzle bytecode and hashes. Additionally, chia-puzzle-types (which is part of the chia_rs monorepo) contains data types for working with the arguments and solutions of the most common puzzles (for example XCH, CATs, NFTs, offers, and singletons).