Skip to content

Commit

Permalink
Initial draft
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Aug 31, 2024
1 parent b261936 commit 0936a29
Show file tree
Hide file tree
Showing 5 changed files with 496 additions and 32 deletions.
60 changes: 60 additions & 0 deletions src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,46 @@ impl ToJs<js::Coin> for rust::Coin {
}
}

impl FromJs<js::CoinState> for rust::CoinState {
fn from_js(value: js::CoinState) -> Result<Self> {
Ok(Self {
coin: rust::Coin::from_js(value.coin)?,
spent_height: value
.spent_height
.map(|height| {
u64::from_js(height).and_then(|height| {
height.try_into().map_err(|_| js::err("height exceeds u32"))
})
})
.transpose()?,
created_height: value
.created_height
.map(|height| {
u64::from_js(height).and_then(|height| {
height.try_into().map_err(|_| js::err("height exceeds u32"))
})
})
.transpose()?,
})
}
}

impl ToJs<js::CoinState> for rust::CoinState {
fn to_js(&self) -> Result<js::CoinState> {
Ok(js::CoinState {
coin: self.coin.to_js()?,
spent_height: self
.spent_height
.map(|height| (height as u64).to_js())
.transpose()?,
created_height: self
.created_height
.map(|height| (height as u64).to_js())
.transpose()?,
})
}
}

impl FromJs<js::CoinSpend> for rust::CoinSpend {
fn from_js(value: js::CoinSpend) -> Result<Self> {
Ok(Self {
Expand Down Expand Up @@ -245,3 +285,23 @@ impl ToJs<js::Proof> for rust::Proof {
})
}
}

impl FromJs<js::ServerCoin> for rust::ServerCoin {
fn from_js(value: js::ServerCoin) -> Result<Self> {
Ok(Self {
coin: rust::Coin::from_js(value.coin)?,
p2_puzzle_hash: Bytes32::from_js(value.p2_puzzle_hash)?,
memo_urls: value.memo_urls,
})
}
}

impl ToJs<js::ServerCoin> for rust::ServerCoin {
fn to_js(&self) -> Result<js::ServerCoin> {
Ok(js::ServerCoin {
coin: self.coin.to_js()?,
p2_puzzle_hash: self.p2_puzzle_hash.to_js()?,
memo_urls: self.memo_urls.clone(),
})
}
}
25 changes: 25 additions & 0 deletions src/js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,19 @@ pub struct Coin {
pub amount: BigInt,
}

#[napi(object)]
#[derive(Clone)]
/// Represents a full coin state on the Chia blockchain.
///
/// @property {Coin} coin - The coin.
/// @property {Buffer} spentHeight - The height the coin was spent at, if it was spent.
/// @property {Buffer} createdHeight - The height the coin was created at.
pub struct CoinState {
pub coin: Coin,
pub spent_height: Option<BigInt>,
pub created_height: Option<BigInt>,
}

#[napi(object)]
#[derive(Clone)]
/// Represents a coin spend on the Chia blockchain.
Expand Down Expand Up @@ -61,6 +74,18 @@ pub struct Proof {
pub eve_proof: Option<EveProof>,
}

#[napi(object)]
/// Represents a mirror coin with a potentially morphed launcher id.
///
/// @property {Coin} coin - The coin.
/// @property {Buffer} p2PuzzleHash - The puzzle hash that owns the server coin.
/// @property {Array<string>} memoUrls - The memo URLs that serve the data store being mirrored.
pub struct ServerCoin {
pub coin: Coin,
pub p2_puzzle_hash: Buffer,
pub memo_urls: Vec<String>,
}

pub fn err<T>(error: T) -> napi::Error
where
T: ToString,
Expand Down
159 changes: 148 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,15 @@ use chia_wallet_sdk::{
connect_peer, create_tls_connector, decode_address, encode_address, load_ssl_cert,
DataStore as RustDataStore, DataStoreInfo as RustDataStoreInfo,
DataStoreMetadata as RustDataStoreMetadata, DelegatedPuzzle as RustDelegatedPuzzle, NetworkId,
Peer as RustPeer,
Peer as RustPeer, MAINNET_CONSTANTS, TESTNET11_CONSTANTS,
};
use conversions::{ConversionError, FromJs, ToJs};
use js::{Coin, CoinSpend, EveProof, Proof};
use js::{Coin, CoinSpend, CoinState, EveProof, Proof};
use napi::bindgen_prelude::*;
use napi::Result;
use std::{net::SocketAddr, sync::Arc};
use tokio::sync::Mutex;
use wallet::{
SuccessResponse as RustSuccessResponse, SyncStoreResponse as RustSyncStoreResponse,
UnspentCoinsResponse as RustUnspentCoinsResponse,
};
use wallet::{SuccessResponse as RustSuccessResponse, SyncStoreResponse as RustSyncStoreResponse};

pub use wallet::*;

Expand Down Expand Up @@ -359,9 +356,9 @@ pub struct UnspentCoinsResponse {
pub last_header_hash: Buffer,
}

impl FromJs<UnspentCoinsResponse> for RustUnspentCoinsResponse {
impl FromJs<UnspentCoinsResponse> for rust::UnspentCoinsResponse {
fn from_js(value: UnspentCoinsResponse) -> Result<Self> {
Ok(RustUnspentCoinsResponse {
Ok(rust::UnspentCoinsResponse {
coins: value
.coins
.into_iter()
Expand All @@ -373,7 +370,7 @@ impl FromJs<UnspentCoinsResponse> for RustUnspentCoinsResponse {
}
}

impl ToJs<UnspentCoinsResponse> for RustUnspentCoinsResponse {
impl ToJs<UnspentCoinsResponse> for rust::UnspentCoinsResponse {
fn to_js(&self) -> Result<UnspentCoinsResponse> {
Ok(UnspentCoinsResponse {
coins: self
Expand Down Expand Up @@ -458,18 +455,73 @@ impl Peer {
previous_height: Option<u32>,
previous_header_hash: Buffer,
) -> napi::Result<UnspentCoinsResponse> {
let resp = get_unspent_coins(
let resp: rust::UnspentCoinsResponse = get_unspent_coin_states(
&self.inner.clone(),
RustBytes32::from_js(puzzle_hash)?,
previous_height,
RustBytes32::from_js(previous_header_hash)?,
false,
)
.await
.map_err(js::err)?;
.map_err(js::err)?
.into();

resp.to_js()
}

#[napi]
/// Retrieves all hinted coin states that are unspent on the chain. Note that coins part of spend bundles that are pending in the mempool will also be included.
///
/// @param {Buffer} puzzleHash - Puzzle hash to lookup hinted coins for.
/// @param {bool} forTestnet - True for testnet, false for mainnet.
/// @returns {Promise<Vec<Coin>>} The unspent coins response.
pub async fn get_hinted_coin_states(
&self,
puzzle_hash: Buffer,
for_testnet: bool,
) -> napi::Result<Vec<CoinState>> {
let resp = get_unspent_coin_states(
&self.inner.clone(),
RustBytes32::from_js(puzzle_hash)?,
None,
if for_testnet {
TESTNET11_CONSTANTS.genesis_challenge
} else {
MAINNET_CONSTANTS.genesis_challenge
},
true,
)
.await
.map_err(js::err)?;

resp.coin_states
.into_iter()
.map(|c| c.to_js())
.collect::<Result<Vec<CoinState>>>()
}

#[napi]
/// Fetches the server coin from a given coin state.
///
/// @param {CoinState} coinState - The coin state.
/// @param {BigInt} maxCost - The maximum cost to use when parsing the coin. For example, `11_000_000_000`.
/// @returns {Promise<ServerCoin>} The server coin.
pub async fn fetch_server_coin(
&self,
coin_state: CoinState,
max_cost: BigInt,
) -> napi::Result<js::ServerCoin> {
let coin = wallet::fetch_server_coin(
&self.inner.clone(),
rust::CoinState::from_js(coin_state)?,
u64::from_js(max_cost)?,
)
.await
.map_err(js::err)?;

coin.to_js()
}

#[napi]
/// Synchronizes a datastore.
///
Expand Down Expand Up @@ -638,6 +690,91 @@ pub fn select_coins(all_coins: Vec<Coin>, total_amount: BigInt) -> napi::Result<
.collect::<Result<Vec<Coin>>>()
}

/// Adds an offset to a launcher id to make it deterministically unique from the original.
///
/// @param {Buffer} launcherId - The original launcher id.
/// @param {BigInt} offset - The offset to add.
#[napi]
pub fn morph_launcher_id(launcher_id: Buffer, offset: BigInt) -> napi::Result<Buffer> {
server_coin::morph_launcher_id(
RustBytes32::from_js(launcher_id)?,
&u64::from_js(offset)?.into(),
)
.to_js()
}

/// Creates a new mirror coin with the given URLs.
///
/// @param {Buffer} syntheticKey - The synthetic key used by the wallet.
/// @param {Vec<Coin>} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that, besides the fee, 1 mojo will be used to create the mirror coin.
/// @param {Buffer} hint - The hint for the mirror coin, usually the original or morphed launcher id.
/// @param {Vec<String>} uris - The URIs of the mirrors.
/// @param {BigInt} amount - The amount to use for the created coin.
/// @param {BigInt} fee - The fee to use for the transaction.
#[napi]
pub fn create_server_coin(
synthetic_key: Buffer,
selected_coins: Vec<Coin>,
hint: Buffer,
uris: Vec<String>,
amount: BigInt,
fee: BigInt,
) -> napi::Result<Vec<CoinSpend>> {
let coin = wallet::create_server_coin(
RustPublicKey::from_js(synthetic_key)?,
selected_coins
.into_iter()
.map(RustCoin::from_js)
.collect::<Result<Vec<RustCoin>>>()?,
RustBytes32::from_js(hint)?,
uris,
u64::from_js(amount)?,
u64::from_js(fee)?,
)
.map_err(js::err)?;

coin.into_iter()
.map(|c| c.to_js())
.collect::<Result<Vec<CoinSpend>>>()
}

/// Spends the mirror coins to make them unusable in the future.
///
/// @param {Peer} peer - The peer connection to the Chia node.
/// @param {Buffer} syntheticKey - The synthetic key used by the wallet.
/// @param {Vec<Coin>} selectedCoins - Coins to be used for minting, as retured by `select_coins`. Note that the server coins will count towards the fee.
/// @param {BigInt} fee - The fee to use for the transaction.
/// @param {bool} forTestnet - True for testnet, false for mainnet.
#[napi]
pub async fn lookup_and_spend_server_coins(
peer: &Peer,
synthetic_key: Buffer,
selected_coins: Vec<Coin>,
fee: BigInt,
for_testnet: bool,
) -> napi::Result<Vec<CoinSpend>> {
let coin = wallet::spend_server_coins(
&peer.inner,
RustPublicKey::from_js(synthetic_key)?,
selected_coins
.into_iter()
.map(RustCoin::from_js)
.collect::<Result<Vec<RustCoin>>>()?,
u64::from_js(fee)?,
if for_testnet {
TargetNetwork::Testnet11
} else {
TargetNetwork::Mainnet
},
)
.await
.map_err(js::err)?;

coin.into_iter()
.map(|c| c.to_js())
.collect::<Result<Vec<CoinSpend>>>()
}

#[allow(clippy::too_many_arguments)]
#[napi]
/// Mints a new datastore.
Expand Down
22 changes: 22 additions & 0 deletions src/rust.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,24 @@
pub use crate::server_coin::ServerCoin;
use crate::UnspentCoinStates;
pub use chia::protocol::*;
pub use chia::puzzles::{EveProof, LineageProof, Proof};

pub struct UnspentCoinsResponse {
pub coins: Vec<Coin>,
pub last_height: u32,
pub last_header_hash: Bytes32,
}

impl From<UnspentCoinStates> for UnspentCoinsResponse {
fn from(unspent_coin_states: UnspentCoinStates) -> Self {
Self {
coins: unspent_coin_states
.coin_states
.into_iter()
.map(|cs| cs.coin)
.collect(),
last_height: unspent_coin_states.last_height,
last_header_hash: unspent_coin_states.last_header_hash,
}
}
}
Loading

0 comments on commit 0936a29

Please sign in to comment.