From 563051344cc7138ab14ab1ccab168fc9b7716fed Mon Sep 17 00:00:00 2001 From: Rigidity Date: Mon, 11 Nov 2024 16:46:11 -0500 Subject: [PATCH] WIP offers --- crates/sage-api/src/requests/make_offer.rs | 1 + crates/sage-wallet/src/wallet/make_offer.rs | 61 ++++++++++++++++++--- crates/sage-wallet/src/wallet/offer.rs | 2 + src-tauri/src/commands/transactions.rs | 5 ++ src/bindings.ts | 2 +- src/pages/MakeOffer.tsx | 1 + src/pages/Token.tsx | 5 ++ 7 files changed, 69 insertions(+), 8 deletions(-) diff --git a/crates/sage-api/src/requests/make_offer.rs b/crates/sage-api/src/requests/make_offer.rs index ddbd7611..54b530ce 100644 --- a/crates/sage-api/src/requests/make_offer.rs +++ b/crates/sage-api/src/requests/make_offer.rs @@ -7,6 +7,7 @@ use crate::Amount; pub struct MakeOffer { pub requested_assets: Assets, pub offered_assets: Assets, + pub fee: Amount, } #[derive(Debug, Clone, Serialize, Deserialize, Type)] diff --git a/crates/sage-wallet/src/wallet/make_offer.rs b/crates/sage-wallet/src/wallet/make_offer.rs index de89f056..411d0ff0 100644 --- a/crates/sage-wallet/src/wallet/make_offer.rs +++ b/crates/sage-wallet/src/wallet/make_offer.rs @@ -5,12 +5,12 @@ use chia::{ protocol::Bytes32, puzzles::{ cat::CatArgs, - offer::{Payment, SETTLEMENT_PAYMENTS_PUZZLE_HASH}, + offer::{NotarizedPayment, Payment, SETTLEMENT_PAYMENTS_PUZZLE_HASH}, }, }; use chia_wallet_sdk::{ - calculate_nft_trace_price, Condition, Conditions, HashedPtr, Layer, NftInfo, Offer, - OfferBuilder, SpendContext, StandardLayer, TradePrice, + calculate_nft_royalty, calculate_nft_trace_price, payment_assertion, Condition, Conditions, + HashedPtr, Layer, NftInfo, Offer, OfferBuilder, SpendContext, StandardLayer, TradePrice, }; use indexmap::IndexMap; @@ -32,8 +32,10 @@ impl Wallet { let mut coin_ids = Vec::new(); // Select coins for the XCH being offered. - let p2_coins = if offered.xch > 0 { - self.select_p2_coins(offered.xch as u128).await? + let total_xch = offered.xch + offered.fee; + + let p2_coins = if total_xch > 0 { + self.select_p2_coins(total_xch as u128).await? } else { Vec::new() }; @@ -119,6 +121,45 @@ impl Wallet { )?; } + // Add royalty payments for NFTs you are offering. + let mut royalty_assertions = Vec::new(); + + if !nfts.is_empty() { + for (asset_id, amount) in [(None, requested.xch)].into_iter().chain( + requested + .cats + .iter() + .map(|(asset_id, amount)| (Some(*asset_id), *amount)), + ) { + let trade_price = calculate_nft_trace_price(amount, nfts.len()) + .ok_or(WalletError::InvalidTradePrice)?; + + for NftOfferSpend { nft, .. } in &nfts { + let royalty = + calculate_nft_royalty(trade_price, nft.info.royalty_ten_thousandths) + .ok_or(WalletError::InvalidRoyaltyAmount)?; + + let mut puzzle_hash = SETTLEMENT_PAYMENTS_PUZZLE_HASH; + + if let Some(asset_id) = asset_id { + puzzle_hash = CatArgs::curry_tree_hash(asset_id, puzzle_hash); + } + + let notarized_payment = NotarizedPayment { + nonce: nft.info.launcher_id, + payments: vec![Payment::with_memos( + nft.info.royalty_puzzle_hash, + royalty, + vec![nft.info.royalty_puzzle_hash.into()], + )], + }; + + royalty_assertions + .push(payment_assertion(puzzle_hash.into(), ¬arized_payment)); + } + } + } + // Add requested CAT payments. for (asset_id, amount) in requested.cats { builder = builder.request( @@ -161,13 +202,15 @@ impl Wallet { } // Finish the requested payments and get the list of announcement assertions. - let (assertions, builder) = builder.finish(); + let (mut assertions, builder) = builder.finish(); + assertions.extend(royalty_assertions); self.spend_assets( &mut ctx, OfferSpend { p2_coins, p2_amount: offered.xch, + fee: offered.fee, cats: cats .into_iter() .map(|(asset_id, coins)| CatOfferSpend { @@ -252,7 +295,7 @@ impl Wallet { } let total: u128 = spend.p2_coins.iter().map(|coin| coin.amount as u128).sum(); - let change = total - spend.p2_amount as u128; + let change = total - spend.p2_amount as u128 - spend.fee as u128; if change > 0 { conditions = conditions.create_coin( @@ -262,6 +305,10 @@ impl Wallet { ); } + if spend.fee > 0 { + conditions = conditions.reserve_fee(spend.fee); + } + self.spend_p2_coins(ctx, spend.p2_coins, conditions).await?; } diff --git a/crates/sage-wallet/src/wallet/offer.rs b/crates/sage-wallet/src/wallet/offer.rs index e6f7f5a3..0200ccd9 100644 --- a/crates/sage-wallet/src/wallet/offer.rs +++ b/crates/sage-wallet/src/wallet/offer.rs @@ -10,6 +10,7 @@ pub struct OfferedCoins { pub xch: u64, pub cats: IndexMap, pub nfts: IndexSet, + pub fee: u64, } #[derive(Debug, Clone)] @@ -38,6 +39,7 @@ pub struct UnsignedOffer { pub struct OfferSpend { pub p2_coins: Vec, pub p2_amount: u64, + pub fee: u64, pub cats: Vec, pub nfts: Vec, pub assertions: Vec, diff --git a/src-tauri/src/commands/transactions.rs b/src-tauri/src/commands/transactions.rs index 8ffcaa0c..33ad83d6 100644 --- a/src-tauri/src/commands/transactions.rs +++ b/src-tauri/src/commands/transactions.rs @@ -657,12 +657,17 @@ pub async fn make_offer(state: State<'_, AppState>, request: MakeOffer) -> Resul requested_nfts.insert(nft_id, offer_details); } + let Some(fee) = request.fee.to_mojos(state.unit.decimals) else { + return Err(Error::invalid_amount(&request.fee)); + }; + let unsigned = wallet .make_offer( OfferedCoins { xch: offered_xch, cats: offered_cats, nfts: offered_nfts, + fee, }, OfferRequest { xch: requested_xch, diff --git a/src/bindings.ts b/src/bindings.ts index 170c5de1..838b21f0 100644 --- a/src/bindings.ts +++ b/src/bindings.ts @@ -494,7 +494,7 @@ export type GetCollectionNfts = { collection_id: string | null; offset: number; export type GetNftCollections = { offset: number; limit: number; include_hidden: boolean } export type GetNfts = { offset: number; limit: number; sort_mode: NftSortMode; include_hidden: boolean } export type Input = ({ type: "unknown" } | { type: "xch" } | { type: "launcher" } | { type: "cat"; asset_id: string; name: string | null; ticker: string | null; icon_url: string | null } | { type: "did"; launcher_id: string; name: string | null } | { type: "nft"; launcher_id: string; image_data: string | null; image_mime_type: string | null; name: string | null }) & { coin_id: string; amount: Amount; address: string; outputs: Output[] } -export type MakeOffer = { requested_assets: Assets; offered_assets: Assets } +export type MakeOffer = { requested_assets: Assets; offered_assets: Assets; fee: Amount } export type Network = { default_port: number; ticker: string; address_prefix: string; precision: number; genesis_challenge: string; agg_sig_me: string; dns_introducers: string[] } export type NetworkConfig = { network_id?: string; target_peers?: number; discover_peers?: boolean } export type NftCollectionRecord = { collection_id: string; did_id: string; metadata_collection_id: string; visible: boolean; name: string | null; icon: string | null; nfts: number; visible_nfts: number } diff --git a/src/pages/MakeOffer.tsx b/src/pages/MakeOffer.tsx index 4bc477ad..11060671 100644 --- a/src/pages/MakeOffer.tsx +++ b/src/pages/MakeOffer.tsx @@ -30,6 +30,7 @@ export function MakeOffer() { .makeOffer({ offered_assets: offerAssets, requested_assets: requestAssets, + fee, }) .then((result) => { console.log(result); diff --git a/src/pages/Token.tsx b/src/pages/Token.tsx index b7a35f99..95ab33e1 100644 --- a/src/pages/Token.tsx +++ b/src/pages/Token.tsx @@ -1,6 +1,7 @@ import CoinList from '@/components/CoinList'; import ConfirmationDialog from '@/components/ConfirmationDialog'; import Container from '@/components/Container'; +import { CopyBox } from '@/components/CopyBox'; import Header from '@/components/Header'; import { ReceiveAddress } from '@/components/ReceiveAddress'; import { Button } from '@/components/ui/button'; @@ -176,6 +177,10 @@ export default function Token() {
+ {asset?.asset_id !== 'xch' && ( + + )} +