diff --git a/src/rgb.rs b/src/rgb.rs index b42849b8..31533b95 100644 --- a/src/rgb.rs +++ b/src/rgb.rs @@ -68,16 +68,16 @@ use crate::{ MediaView, NextAddressResponse, NextUtxoResponse, NextUtxosResponse, PsbtFeeRequest, PsbtRequest, PsbtResponse, PublicRgbBidResponse, PublicRgbOfferResponse, PublicRgbOffersResponse, PublishPsbtRequest, ReIssueRequest, ReIssueResponse, - RgbAuctionBidRequest, RgbAuctionBidResponse, RgbBidDetail, RgbBidRequest, RgbBidResponse, - RgbBidsResponse, RgbInternalSaveTransferRequest, RgbInternalTransferResponse, - RgbInvoiceResponse, RgbOfferBidsResponse, RgbOfferDetail, RgbOfferRequest, - RgbOfferResponse, RgbOfferUpdateRequest, RgbOfferUpdateResponse, RgbOffersResponse, - RgbRemoveTransferRequest, RgbReplaceResponse, RgbSaveTransferRequest, RgbSwapRequest, - RgbSwapResponse, RgbSwapStatusResponse, RgbTransferDetail, RgbTransferRequest, - RgbTransferResponse, RgbTransferStatusResponse, RgbTransfersResponse, SchemaDetail, - SchemasResponse, SignPsbtRequest, SignedPsbtResponse, SimpleContractResponse, TransferType, - TxStatus, UtxoResponse, WatcherDetailResponse, WatcherRequest, WatcherResponse, - WatcherUtxoResponse, + RgbAuctionBidRequest, RgbAuctionBidResponse, RgbAuctionOfferRequest, RgbBidDetail, + RgbBidRequest, RgbBidResponse, RgbBidsResponse, RgbInternalSaveTransferRequest, + RgbInternalTransferResponse, RgbInvoiceResponse, RgbOfferBidsResponse, RgbOfferDetail, + RgbOfferRequest, RgbOfferResponse, RgbOfferUpdateRequest, RgbOfferUpdateResponse, + RgbOffersResponse, RgbRemoveTransferRequest, RgbReplaceResponse, RgbSaveTransferRequest, + RgbSwapRequest, RgbSwapResponse, RgbSwapStatusResponse, RgbTransferDetail, + RgbTransferRequest, RgbTransferResponse, RgbTransferStatusResponse, RgbTransfersResponse, + SchemaDetail, SchemasResponse, SignPsbtRequest, SignedPsbtResponse, SimpleContractResponse, + TransferType, TxStatus, UtxoResponse, WatcherDetailResponse, WatcherRequest, + WatcherResponse, WatcherUtxoResponse, }, validators::RGBContext, }; @@ -989,7 +989,7 @@ pub async fn create_seller_offer( pub async fn create_auction_offers( sk: &str, - request: Vec, + request: RgbAuctionOfferRequest, ) -> Result, RgbSwapError> { if let Err(err) = request.validate(&RGBContext::default()) { let errors = err @@ -1012,8 +1012,8 @@ pub async fn create_auction_offers( let mut resp = vec![]; let mut collection = vec![]; let options = RgbOfferOptions::with_bundle_id(sk.to_owned()); - for item in request { - let new_offer = internal_create_seller_offer( + for item in request.offers.clone() { + let mut new_offer = internal_create_seller_offer( sk, item, options.clone(), @@ -1042,6 +1042,18 @@ pub async fn create_auction_offers( let contract_amount = f64::from_str(&contract_amount).expect("Invalid Contract Amount Value"); + let request = SignPsbtRequest { + psbt: seller_psbt, + descriptors: request.sign_keys.clone(), + }; + + let SignedPsbtResponse { + psbt: final_psbt, .. + } = sign_psbt_file(request) + .await + .map_err(|op| RgbSwapError::WrongPsbtFinal(op.to_string()))?; + new_offer.seller_psbt = final_psbt.clone(); + my_offers = my_offers.save_offer(contract_id.clone(), new_offer.clone()); collection.push(RgbOfferSwap::from(new_offer.clone())); @@ -1051,7 +1063,7 @@ pub async fn create_auction_offers( contract_amount, bitcoin_price, seller_address: seller_address.to_string(), - seller_psbt: seller_psbt.clone(), + seller_psbt: final_psbt, }); } diff --git a/src/structs.rs b/src/structs.rs index d29681f2..64200153 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -1309,6 +1309,19 @@ impl UtxoSpentStatus { } } +#[derive(Clone, Serialize, Deserialize, Debug, Display, Default, Validate)] +#[garde(context(RGBContext))] +#[serde(rename_all = "camelCase")] +#[display(doc_comments)] +pub struct RgbAuctionOfferRequest { + #[garde(skip)] + pub sign_keys: Vec, + + /// List of Offers + #[garde(dive)] + pub offers: Vec, +} + #[derive(Clone, Serialize, Deserialize, Debug, Display, Default, Validate)] #[garde(context(RGBContext))] #[serde(rename_all = "camelCase")] diff --git a/tests/rgb/integration/swaps.rs b/tests/rgb/integration/swaps.rs index a42fdba1..a0e83dd1 100644 --- a/tests/rgb/integration/swaps.rs +++ b/tests/rgb/integration/swaps.rs @@ -8,15 +8,16 @@ use bitmask_core::{ sign_and_publish_psbt_file, sign_psbt_file, sync_wallet, }, rgb::{ - accept_transfer, create_buyer_bid, create_seller_offer, create_swap_transfer, - create_watcher, get_contract, import as import_contract, structs::ContractAmount, - swap::RgbSwapStrategy, update_seller_offer, verify_transfers, + accept_transfer, create_auction_bid, create_auction_offers, create_buyer_bid, + create_seller_offer, create_swap_transfer, create_watcher, get_contract, + import as import_contract, structs::ContractAmount, swap::RgbSwapStrategy, + update_seller_offer, verify_transfers, }, structs::{ AcceptRequest, AssetType, ImportRequest, IssueResponse, PsbtFeeRequest, PublishPsbtRequest, - RgbBidRequest, RgbBidResponse, RgbOfferRequest, RgbOfferResponse, RgbOfferUpdateRequest, - RgbSwapRequest, RgbSwapResponse, SecretString, SignPsbtRequest, SignedPsbtResponse, - WatcherRequest, + RgbAuctionBidRequest, RgbAuctionOfferRequest, RgbBidRequest, RgbBidResponse, + RgbOfferRequest, RgbOfferResponse, RgbOfferUpdateRequest, RgbSwapRequest, RgbSwapResponse, + SecretString, SignPsbtRequest, SignedPsbtResponse, WatcherRequest, }, }; @@ -717,3 +718,177 @@ async fn create_p2p_swap() -> anyhow::Result<()> { Ok(()) } + +#[tokio::test] +async fn create_auction_swap() -> anyhow::Result<()> { + // 1. Initial Setup + let seller_keys = new_mnemonic(&SecretString("".to_string())).await?; + let buyer_keys = new_mnemonic(&SecretString("".to_string())).await?; + + let watcher_name = "default"; + let seller_sk = seller_keys.private.nostr_prv.clone(); + let create_watch_req = WatcherRequest { + name: watcher_name.to_string(), + xpub: seller_keys.public.watcher_xpub.clone(), + force: true, + }; + create_watcher(&seller_sk, create_watch_req.clone()).await?; + + let buyer_sk = buyer_keys.private.nostr_prv.clone(); + let create_watch_req = WatcherRequest { + name: watcher_name.to_string(), + xpub: buyer_keys.public.watcher_xpub.clone(), + force: true, + }; + create_watcher(&buyer_sk, create_watch_req.clone()).await?; + + // 2. Setup Wallets (Seller) + let btc_address_1 = get_new_address( + &SecretString(seller_keys.public.btc_descriptor_xpub.clone()), + None, + ) + .await?; + + let default_coins = "0.001"; + send_some_coins(&btc_address_1, default_coins).await; + + let btc_descriptor_xprv = SecretString(seller_keys.private.btc_descriptor_xprv.clone()); + let btc_change_descriptor_xprv = + SecretString(seller_keys.private.btc_change_descriptor_xprv.clone()); + + let assets_address_1 = get_new_address( + &SecretString(seller_keys.public.rgb_assets_descriptor_xpub.clone()), + None, + ) + .await?; + + let uda_address_1 = get_new_address( + &SecretString(seller_keys.public.rgb_udas_descriptor_xpub.clone()), + None, + ) + .await?; + + let btc_wallet = get_wallet(&btc_descriptor_xprv, Some(&btc_change_descriptor_xprv)).await?; + sync_wallet(&btc_wallet).await?; + + let fund_vault = fund_vault( + &btc_descriptor_xprv, + &btc_change_descriptor_xprv, + &assets_address_1, + &uda_address_1, + Some(1.1), + ) + .await?; + + // 3. Send some coins (Buyer) + let btc_address_1 = get_new_address( + &SecretString(buyer_keys.public.btc_descriptor_xpub.clone()), + None, + ) + .await?; + let asset_address_1 = get_new_address( + &SecretString(buyer_keys.public.rgb_assets_descriptor_xpub.clone()), + None, + ) + .await?; + + let default_coins = "0.1"; + send_some_coins(&btc_address_1, default_coins).await; + send_some_coins(&asset_address_1, default_coins).await; + + // 4. Issue Contract (Seller) + let issuer_resp = issuer_issue_contract_v2( + 5, + "RGB20", + ContractAmount::with(1, 0, 2).to_value(), + false, + false, + None, + None, + Some(UtxoFilter::with_outpoint( + fund_vault.assets_output.unwrap_or_default(), + )), + Some(seller_keys.clone()), + ) + .await?; + + for contract in issuer_resp.clone() { + let buyer_import_req = ImportRequest { + import: AssetType::RGB20, + data: contract.contract.strict, + }; + let buyer_import_resp = import_contract(&buyer_sk, buyer_import_req).await; + assert!(buyer_import_resp.is_ok()); + } + + // 5. Create Collection (Seller) + let contract_amount = "1.00".to_string(); + let mut offers_collection = vec![]; + for contract in issuer_resp.clone() { + let IssueResponse { + contract_id, iface, .. + } = contract.clone(); + + let expire_at = (chrono::Local::now() + chrono::Duration::seconds(10)) + .naive_utc() + .timestamp(); + + let desc = SecretString(seller_keys.public.rgb_assets_descriptor_xpub.clone()); + let req = RgbOfferRequest { + contract_id, + iface, + contract_amount: contract_amount.clone(), + bitcoin_price: 1_000, + descriptor: desc, + change_terminal: "/20/1".to_string(), + bitcoin_changes: vec![], + strategy: RgbSwapStrategy::Auction, + expire_at: Some(expire_at), + }; + + offers_collection.push(req); + } + + let offer_auction_req = RgbAuctionOfferRequest { + offers: offers_collection, + sign_keys: vec![ + SecretString(seller_keys.private.btc_descriptor_xprv.clone()), + SecretString(seller_keys.private.btc_change_descriptor_xprv.clone()), + SecretString(seller_keys.private.rgb_assets_descriptor_xprv.clone()), + ], + }; + + let resp = create_auction_offers(&seller_sk, offer_auction_req).await; + assert!(resp.is_ok()); + + let RgbOfferResponse { offer_id, .. } = resp?[0].clone(); + + // 6. Create Bid + let buyer_btc_desc = buyer_keys.public.btc_descriptor_xpub.clone(); + let bid_auction_req = RgbAuctionBidRequest { + offer_id: offer_id.clone(), + asset_amount: contract_amount, + descriptor: SecretString(buyer_btc_desc), + change_terminal: "/1/0".to_string(), + fee: PsbtFeeRequest::Value(1000), + sign_keys: vec![ + SecretString(buyer_keys.private.btc_descriptor_xprv.clone()), + SecretString(buyer_keys.private.btc_change_descriptor_xprv.clone()), + ], + }; + + let resp = create_auction_bid(&buyer_sk, bid_auction_req).await; + assert!(resp.is_ok()); + + // 7. Finish Offer + + // 8. Mine Some Blocks + + // 9. Verify Auctions + + // 10. Verify Transfers + + // 11. Check Balances + + Ok(()) +}