Skip to content

Commit

Permalink
New test wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed May 17, 2024
1 parent d9906a8 commit bf58923
Show file tree
Hide file tree
Showing 17 changed files with 259 additions and 454 deletions.
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This is an unofficial wallet SDK for the [Chia blockchain](https://chia.net), en

If you intend on writing an application that uses the Chia blockchain, be it a dApp, a wallet, or even just tooling on top of it, you will most likely need some code to interact with a Chia wallet. The worst case scenario is that you need to write an entire wallet and all of its driver code from scratch every time. This is very challenging to do, takes a lot of time, and can be error prone if anything is done wrong.

To build driver code, you need libraries like `chia-bls` and `clvmr`, for interacting with Chia's native BLS signatures and CLVM runtime. You compose puzzles by currying them, and spend them by constructing solutions. Even with libraries in place to do this (assuming they are tested properly), it can be very tedious and hard to get right. That's what this Wallet SDK is aiming to solve.
To build driver code, you need libraries like `chia-bls` and `clvmr`, for interacting with Chia's native BLS signatures and CLVM runtime. You compose puzzles by currying them, and spend them by constructing solutions. Even with libraries in place to do this (assuming they are tested properly), it can be very tedious and hard to get right. That's what this wallet sdk is aiming to solve.

It's essentially a higher level wrapper over the core primitives that the Chia blockchain provides, and aims to make various things in the lifecycle of wallet development simpler such as state management and signing.

Expand All @@ -18,20 +18,30 @@ This SDK is built on top of the primitives developed in the [chia_rs](https://gi

## Supported primitives

Currently, only a subset of Chia's primitives are supported:
Currently, the following Chia primitives are supported:

### Standard puzzle
### P2 Puzzle (Standard Transaction)

You can spend the standard puzzle, also known as the "standard transaction", "p2 puzzle", or "p2_delegated_puzzle_or_hidden_puzzle". The hidden puzzle functionality is not currently supported.
You can spend the [standard transaction](https://chialisp.com/standard-transactions), either as an inner puzzle or by itself, with a list of conditions.

### CATs
Note that the "hidden puzzle" functionality is not currently supported by the wallet sdk, and the (unspendable) `DEFAULT_HIDDEN_PUZZLE` will be used.

CATs with any asset id can be spent, but only as long as the inner puzzle is the standard puzzle. You can also issue CATs with the "everything with signature" TAIL (multi-issuance).
### CATs (Chia Asset Tokens)

### DIDs
You can spend a CAT with any TAIL (Token Asset Issuance Limitations) program, whose hash is otherwise known as an asset id. You can also issue CATs with the "everything with signature" TAIL (multi-issuance) or "genesis by coin id" TAIL (single-issuance).

You cannot create new DIDs or recover them, and the API for spending them is not as flexible as it will be in the future. But there is a very thin wrapper for spending DIDs, as long as the inner puzzle is the standard puzzle.
Note that it is not currently possible to melt a CAT.

### NFTs
You can also parse an unknown puzzle into the info required to spend a CAT.

You can spend NFTs and mint them in bulk by spending a DID as the parent. You can also change the DID owner of an NFT. As previously mentioned, all primitives assume the standard puzzle is the inner puzzle, which is typically the case at this time.
### DIDs (Decentralized Identifiers)

You can create new DIDs and spend them to mint NFTs with them. You cannot currently update the metadata, recover, or transfer a DID.

You can also parse an unknown puzzle into the info required to spend a DID.

### NFTs (Non-Fungible Tokens)

You can mint NFTs in bulk by spending a DID to creating an intermediate coin that launches the NFT. You can also spend and change the DID owner of an NFT.

Note that you _cannot_ yet parse NFTs from unknown puzzles.
34 changes: 11 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,7 @@ mod condition;
mod parser;
mod spends;
mod ssl;

/// The `prelude` module contains the most commonly used types and traits.
pub mod prelude;

/// The `sqlite` module contains the SQLite storage backend.
#[cfg(any(test, feature = "sqlite"))]
pub mod sqlite;

/// Contains logic needed to glue a wallet together with all of the data stores.
pub mod wallet;
mod wallet;

pub use address::*;
pub use condition::*;
Expand All @@ -23,6 +14,16 @@ pub use spends::*;
pub use ssl::*;
pub use wallet::*;

#[cfg(test)]
mod test;

/// The `sqlite` module contains the SQLite storage backend.
#[cfg(any(test, feature = "sqlite"))]
pub mod sqlite;

#[cfg(any(test, feature = "sqlite"))]
pub use sqlite::*;

/// Removes the leading zeros from a CLVM atom.
pub fn trim_leading_zeros(mut slice: &[u8]) -> &[u8] {
while (!slice.is_empty()) && (slice[0] == 0) {
Expand Down Expand Up @@ -51,16 +52,3 @@ pub fn u16_to_bytes(num: u16) -> Vec<u8> {
let bytes: Vec<u8> = num.to_be_bytes().into();
trim_leading_zeros(bytes.as_slice()).to_vec()
}

#[cfg(test)]
mod testing {
use std::str::FromStr;

use bip39::Mnemonic;
use chia_bls::SecretKey;
use once_cell::sync::Lazy;

const MNEMONIC: &str = "setup update spoil lazy square course ring tell hard eager industry ticket guess amused build reunion woman system cause afraid first material machine morning";
pub(crate) static SECRET_KEY: Lazy<SecretKey> =
Lazy::new(|| SecretKey::from_seed(&Mnemonic::from_str(MNEMONIC).unwrap().to_seed("")));
}
8 changes: 0 additions & 8 deletions src/prelude.rs

This file was deleted.

2 changes: 1 addition & 1 deletion src/spends/puzzles/cat/cat_spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ mod tests {
use clvmr::{serde::node_to_bytes, Allocator};
use hex_literal::hex;

use crate::{testing::SECRET_KEY, Chainable, CreateCoinWithoutMemos, StandardSpend};
use crate::{test::SECRET_KEY, Chainable, CreateCoinWithoutMemos, StandardSpend};

use super::*;

Expand Down
60 changes: 9 additions & 51 deletions src/spends/puzzles/cat/issue_cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,73 +124,31 @@ impl IssueCat {

#[cfg(test)]
mod tests {
use chia_bls::{sign, Signature};
use chia_protocol::SpendBundle;
use chia_puzzles::{
standard::{StandardArgs, STANDARD_PUZZLE_HASH},
DeriveSynthetic,
};
use clvm_utils::ToTreeHash;
use clvmr::Allocator;

use crate::{
testing::SECRET_KEY, Chainable, CreateCoinWithMemos, RequiredSignature, StandardSpend,
WalletSimulator,
};
use crate::{test::TestWallet, Chainable, CreateCoinWithMemos, StandardSpend};

use super::*;

#[tokio::test]
async fn test_cat_issuance() -> anyhow::Result<()> {
let sim = WalletSimulator::new().await;
let peer = sim.peer().await;

let mut allocator = Allocator::new();
let mut ctx = SpendContext::new(&mut allocator);

let sk = SECRET_KEY.derive_synthetic();
let pk = sk.public_key();

let puzzle_hash = CurriedProgram {
program: STANDARD_PUZZLE_HASH,
args: StandardArgs { synthetic_key: pk },
}
.tree_hash()
.into();
let mut wallet = TestWallet::new(&mut allocator, 1).await;
let ctx = &mut wallet.ctx;

let xch_coin = sim.generate_coin(puzzle_hash, 1).await.coin;

let (issue_cat, _cat_info) = IssueCat::new(xch_coin.coin_id())
let (issue_cat, _cat_info) = IssueCat::new(wallet.coin.coin_id())
.condition(ctx.alloc(CreateCoinWithMemos {
puzzle_hash,
puzzle_hash: wallet.puzzle_hash,
amount: 1,
memos: vec![puzzle_hash.to_vec().into()],
memos: vec![wallet.puzzle_hash.to_vec().into()],
})?)
.multi_issuance(&mut ctx, pk, 1)?;
.multi_issuance(ctx, wallet.pk, 1)?;

StandardSpend::new()
.chain(issue_cat)
.finish(&mut ctx, xch_coin, pk)?;

let coin_spends = ctx.take_spends();

let required_signatures = RequiredSignature::from_coin_spends(
&mut allocator,
&coin_spends,
WalletSimulator::AGG_SIG_ME.into(),
)?;

let mut aggregated_signature = Signature::default();

for required in required_signatures {
aggregated_signature += &sign(&sk, required.final_message());
}

let spend_bundle = SpendBundle::new(coin_spends, aggregated_signature);
.finish(ctx, wallet.coin, wallet.pk)?;

let ack = peer.send_transaction(spend_bundle).await?;
assert_eq!(ack.error, None);
assert_eq!(ack.status, 1);
wallet.submit().await?;

Ok(())
}
Expand Down
62 changes: 11 additions & 51 deletions src/spends/puzzles/did.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,70 +10,30 @@ pub use did_spend::*;
mod tests {
use super::*;

use chia_bls::{sign, Signature};
use chia_protocol::SpendBundle;
use chia_puzzles::{
standard::{StandardArgs, STANDARD_PUZZLE_HASH},
DeriveSynthetic,
};
use clvm_utils::{CurriedProgram, ToTreeHash};
use clvmr::Allocator;

use crate::{
testing::SECRET_KEY, Chainable, Launcher, RequiredSignature, SpendContext, StandardSpend,
WalletSimulator,
};
use crate::{test::TestWallet, Chainable, Launcher, StandardSpend};

#[tokio::test]
async fn test_create_did() -> anyhow::Result<()> {
let sim = WalletSimulator::new().await;
let peer = sim.peer().await;

let sk = SECRET_KEY.derive_synthetic();
let pk = sk.public_key();

let puzzle_hash = CurriedProgram {
program: STANDARD_PUZZLE_HASH,
args: StandardArgs { synthetic_key: pk },
}
.tree_hash()
.into();

let parent = sim.generate_coin(puzzle_hash, 1).await.coin;

let mut allocator = Allocator::new();
let mut ctx = SpendContext::new(&mut allocator);
let mut wallet = TestWallet::new(&mut allocator, 1).await;
let ctx = &mut wallet.ctx;

let (launch_singleton, _did_info) = Launcher::new(parent.coin_id(), 1)
.create(&mut ctx)?
.create_standard_did(&mut ctx, pk)?;
let (launch_singleton, _did_info) = Launcher::new(wallet.coin.coin_id(), 1)
.create(ctx)?
.create_standard_did(ctx, wallet.pk)?;

StandardSpend::new()
.chain(launch_singleton)
.finish(&mut ctx, parent, pk)?;

let coin_spends = ctx.take_spends();

let mut spend_bundle = SpendBundle::new(coin_spends, Signature::default());

let required_signatures = RequiredSignature::from_coin_spends(
&mut allocator,
&spend_bundle.coin_spends,
WalletSimulator::AGG_SIG_ME.into(),
)
.unwrap();

for required in required_signatures {
spend_bundle.aggregated_signature += &sign(&sk, required.final_message());
}
.finish(ctx, wallet.coin, wallet.pk)?;

let ack = peer.send_transaction(spend_bundle).await.unwrap();
assert_eq!(ack.error, None);
assert_eq!(ack.status, 1);
wallet.submit().await?;

// Make sure the DID was created.
let found_coins = peer
.register_for_ph_updates(vec![puzzle_hash], 0)
let found_coins = wallet
.peer
.register_for_ph_updates(vec![wallet.puzzle_hash], 0)
.await
.unwrap();
assert_eq!(found_coins.len(), 2);
Expand Down
60 changes: 11 additions & 49 deletions src/spends/puzzles/did/did_spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,74 +145,36 @@ where

#[cfg(test)]
mod tests {
use chia_bls::{sign, Signature};
use chia_protocol::SpendBundle;
use chia_puzzles::{
standard::{StandardArgs, STANDARD_PUZZLE_HASH},
DeriveSynthetic,
};
use clvm_utils::ToTreeHash;
use clvmr::Allocator;

use crate::{testing::SECRET_KEY, CreateDid, Launcher, RequiredSignature, WalletSimulator};
use crate::{test::TestWallet, CreateDid, Launcher};

use super::*;

#[tokio::test]
async fn test_did_recreation() -> anyhow::Result<()> {
let sim = WalletSimulator::new().await;
let peer = sim.peer().await;

let mut allocator = Allocator::new();
let mut ctx = SpendContext::new(&mut allocator);

let sk = SECRET_KEY.derive_synthetic();
let pk = sk.public_key();

let puzzle_hash = CurriedProgram {
program: STANDARD_PUZZLE_HASH,
args: StandardArgs { synthetic_key: pk },
}
.tree_hash()
.into();

let parent = sim.generate_coin(puzzle_hash, 1).await.coin;
let mut wallet = TestWallet::new(&mut allocator, 1).await;
let ctx = &mut wallet.ctx;

let (create_did, mut did_info) = Launcher::new(parent.coin_id(), 1)
.create(&mut ctx)?
.create_standard_did(&mut ctx, pk)?;
let (create_did, mut did_info) = Launcher::new(wallet.coin.coin_id(), 1)
.create(ctx)?
.create_standard_did(ctx, wallet.pk)?;

StandardSpend::new()
.chain(create_did)
.finish(&mut ctx, parent, pk)?;
.finish(ctx, wallet.coin, wallet.pk)?;

for _ in 0..10 {
did_info = StandardDidSpend::new()
.recreate()
.finish(&mut ctx, pk, did_info)?;
}

let coin_spends = ctx.take_spends();

let required_signatures = RequiredSignature::from_coin_spends(
&mut allocator,
&coin_spends,
WalletSimulator::AGG_SIG_ME.into(),
)?;

let mut aggregated_signature = Signature::default();

for required in required_signatures {
aggregated_signature += &sign(&sk, required.final_message());
.finish(ctx, wallet.pk, did_info)?;
}

let ack = peer
.send_transaction(SpendBundle::new(coin_spends, aggregated_signature))
.await?;
assert_eq!(ack.error, None);
assert_eq!(ack.status, 1);
wallet.submit().await?;

let coin_state = peer
let coin_state = wallet
.peer
.register_for_coin_updates(vec![did_info.coin.coin_id()], 0)
.await?
.remove(0);
Expand Down
Loading

0 comments on commit bf58923

Please sign in to comment.