use chia_wallet_sdk::driver::{Offer, SpendContext};
use crate::error::Result;
use crate::hydrate::{decode, parse};
use crate::types::{OfferAsset, OfferCost, OfferSummary};
pub fn summarize(offer_str: &str) -> Result<OfferSummary> {
let spend_bundle = decode(offer_str)?;
let mut ctx = SpendContext::new();
let offer = parse(&mut ctx, &spend_bundle)?;
Ok(OfferSummary {
offered: offered_assets(&offer),
requested: requested_assets(&offer),
arbitrage: arbitrage_cost(&offer),
royalties: royalties(&offer),
})
}
fn offered_assets(offer: &Offer) -> Vec<OfferAsset> {
let coins = offer.offered_coins();
let amounts = coins.amounts();
let mut assets = fungible_assets(amounts.xch, amounts.cats.iter().map(|(id, a)| (*id, *a)));
assets.extend(coins.nfts.keys().map(|launcher_id| OfferAsset::Nft {
launcher_id: *launcher_id,
}));
assets
}
fn requested_assets(offer: &Offer) -> Vec<OfferAsset> {
let payments = offer.requested_payments();
let amounts = payments.amounts();
let mut assets = fungible_assets(amounts.xch, amounts.cats.iter().map(|(id, a)| (*id, *a)));
assets.extend(payments.nfts.keys().map(|launcher_id| OfferAsset::Nft {
launcher_id: *launcher_id,
}));
assets
}
fn fungible_assets(
xch: u64,
cats: impl Iterator<Item = (chia_protocol::Bytes32, u64)>,
) -> Vec<OfferAsset> {
let mut assets = Vec::new();
if xch > 0 {
assets.push(OfferAsset::Xch(xch));
}
assets.extend(cats.map(|(asset_id, amount)| OfferAsset::Cat { asset_id, amount }));
assets
}
fn arbitrage_cost(offer: &Offer) -> OfferCost {
let arbitrage = offer.arbitrage();
OfferCost {
xch: arbitrage.offered.xch,
cats: arbitrage
.offered
.cats
.iter()
.map(|(asset_id, amount)| (*asset_id, *amount))
.collect(),
}
}
fn royalties(offer: &Offer) -> Vec<(chia_protocol::Bytes32, u16)> {
offer
.offered_royalties()
.into_iter()
.chain(offer.requested_royalties())
.map(|royalty| (royalty.launcher_id, royalty.basis_points))
.collect()
}
#[cfg(test)]
mod tests {
use std::marker::PhantomData;
use chia_sdk_test::Simulator;
use chia_wallet_sdk::driver::SpendContext;
use crate::test_support::{maker_offer, mint_nft_for, owner_keys, sample_cat_for_xch};
use crate::types::{OfferedSide, RequestedSide};
use crate::{summarize, Error, OfferAsset};
#[test]
fn summarize_rejects_non_offer() {
assert!(matches!(summarize("not an offer"), Err(Error::Decode(_))));
assert!(matches!(summarize(" "), Err(Error::Decode(_))));
}
#[test]
fn summarize_reports_both_sides_and_arbitrage() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let mut ctx = SpendContext::new();
let (offer_str, _maker, asset) = sample_cat_for_xch(&mut sim, &mut ctx, 80_000, 50_000)?;
let summary = summarize(&offer_str)?;
assert_eq!(
summary.offered,
vec![OfferAsset::Cat {
asset_id: asset,
amount: 80_000
}]
);
assert_eq!(summary.requested, vec![OfferAsset::Xch(50_000)]);
assert_eq!(summary.arbitrage.xch, 50_000);
assert!(summary.royalties.is_empty());
Ok(())
}
#[test]
fn summarize_reports_offered_nft_royalty() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let mut ctx = SpendContext::new();
let maker = sim.bls(2);
let nft = mint_nft_for(&mut sim, &mut ctx, &maker, 300)?;
let offered = OfferedSide {
change_puzzle_hash: maker.puzzle_hash,
owner_keys: owner_keys(&maker),
xch_coins: vec![],
cat_coins: vec![],
nfts: vec![nft],
offer_xch: 0,
offer_cats: vec![],
_pd: PhantomData,
};
let requested = RequestedSide {
payee_puzzle_hash: maker.puzzle_hash,
xch: 100_000,
cats: vec![],
nfts: vec![],
};
let offer_str = maker_offer(
&mut ctx,
offered,
requested,
0,
std::slice::from_ref(&maker.sk),
)?;
let summary = summarize(&offer_str)?;
assert_eq!(
summary.royalties,
vec![(nft.info.launcher_id, 300)],
"an offered royalty NFT's royalty must be reported"
);
Ok(())
}
}