diff --git a/README.md b/README.md index 5a89e408..fdceec66 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,7 @@ The node currently exposes the following APIs: - `/createutxos` (POST) - `/decodelninvoice` (POST) - `/decodergbinvoice` (POST) +- `/decodeswapstring` (POST) - `/disconnectpeer` (POST) - `/estimatefee` (POST) - `/failtransfers` (POST) diff --git a/openapi.yaml b/openapi.yaml index 16f81d0d..dda8f1d5 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -259,6 +259,24 @@ paths: application/json: schema: $ref: '#/components/schemas/DecodeRGBInvoiceResponse' + /decodeswapstring: + post: + tags: + - Swaps + summary: Decode a swapstring + description: Decode the provided swapstring + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DecodeSwapstringRequest' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/DecodeSwapstringResponse' /disconnectpeer: post: tags: @@ -1690,6 +1708,44 @@ components: items: type: string example: rpcs://proxy.iriswallet.com/0.2/json-rpc + DecodeSwapstringRequest: + type: object + required: + - swapstring + properties: + swapstring: + type: string + example: 30/rgb:CJkb4YZw-jRiz2sk-~PARPio-wtVYI1c-XAEYCqO-wTfvRZ8/10/rgb:icfqnK9y-wObZKTu-XJcDL98-sKbE5Mh-OuDJhiI-brRJrzE/1715896416/9d342c6ba006e24abee84a2e034a22d5e30c1f2599fb9c3574d46d3cde3d65a2 + DecodeSwapstringResponse: + type: object + required: + - qty_from + - qty_to + - expiry + - payment_hash + properties: + qty_from: + type: integer + example: 30 + qty_to: + type: integer + example: 10 + from_asset: + type: + - string + - 'null' + example: rgb:CJkb4YZw-jRiz2sk-~PARPio-wtVYI1c-XAEYCqO-wTfvRZ8 + to_asset: + type: + - string + - 'null' + example: rgb:icfqnK9y-wObZKTu-XJcDL98-sKbE5Mh-OuDJhiI-brRJrzE + expiry: + type: integer + example: 1715896416 + payment_hash: + type: string + example: 9d342c6ba006e24abee84a2e034a22d5e30c1f2599fb9c3574d46d3cde3d65a2 DisconnectPeerRequest: type: object required: diff --git a/src/auth.rs b/src/auth.rs index 65bd3a33..17f64fa0 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -16,7 +16,7 @@ use crate::{ const REVOKED_TOKENS_FILE: &str = "revoked_tokens.txt"; -const READ_ONLY_OPS: [&str; 23] = [ +const READ_ONLY_OPS: [&str; 24] = [ "/assetbalance", "/assetmetadata", "/btcbalance", @@ -24,6 +24,7 @@ const READ_ONLY_OPS: [&str; 23] = [ "/checkproxyendpoint", "/decodelninvoice", "/decodergbinvoice", + "/decodeswapstring", "/estimatefee", "/getassetmedia", "/getchannelid", diff --git a/src/main.rs b/src/main.rs index 4fcc5bbd..eec77875 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,14 +44,14 @@ use crate::ldk::stop_ldk; use crate::routes::{ address, asset_balance, asset_metadata, backup, btc_balance, change_password, check_indexer_url, check_proxy_endpoint, close_channel, connect_peer, create_utxos, - decode_ln_invoice, decode_rgb_invoice, disconnect_peer, estimate_fee, fail_transfers, - get_asset_media, get_channel_id, get_payment, get_swap, inflate, init, invoice_status, - issue_asset_cfa, issue_asset_ifa, issue_asset_nia, issue_asset_uda, keysend, list_assets, - list_channels, list_payments, list_peers, list_swaps, list_transactions, list_transfers, - list_unspents, ln_invoice, lock, maker_execute, maker_init, network_info, node_info, - open_channel, post_asset_media, refresh_transfers, restore, revoke_token, rgb_invoice, - send_btc, send_onion_message, send_payment, send_rgb, shutdown, sign_message, sync, taker, - unlock, + decode_ln_invoice, decode_rgb_invoice, decode_swapstring, disconnect_peer, estimate_fee, + fail_transfers, get_asset_media, get_channel_id, get_payment, get_swap, inflate, init, + invoice_status, issue_asset_cfa, issue_asset_ifa, issue_asset_nia, issue_asset_uda, keysend, + list_assets, list_channels, list_payments, list_peers, list_swaps, list_transactions, + list_transfers, list_unspents, ln_invoice, lock, maker_execute, maker_init, network_info, + node_info, open_channel, post_asset_media, refresh_transfers, restore, revoke_token, + rgb_invoice, send_btc, send_onion_message, send_payment, send_rgb, shutdown, sign_message, + sync, taker, unlock, }; use crate::utils::{start_daemon, AppState, LOGS_DIR}; @@ -118,6 +118,7 @@ pub(crate) async fn app(args: UserArgs) -> Result<(Router, Arc), AppEr .route("/createutxos", post(create_utxos)) .route("/decodelninvoice", post(decode_ln_invoice)) .route("/decodergbinvoice", post(decode_rgb_invoice)) + .route("/decodeswapstring", post(decode_swapstring)) .route("/disconnectpeer", post(disconnect_peer)) .route("/estimatefee", post(estimate_fee)) .route("/failtransfers", post(fail_transfers)) diff --git a/src/routes.rs b/src/routes.rs index ae6c8ed0..dd6e1ef8 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -521,6 +521,21 @@ pub(crate) struct DecodeRGBInvoiceResponse { pub(crate) transport_endpoints: Vec, } +#[derive(Deserialize, Serialize)] +pub(crate) struct DecodeSwapstringRequest { + pub(crate) swapstring: String, +} + +#[derive(Deserialize, Serialize)] +pub(crate) struct DecodeSwapstringResponse { + pub(crate) qty_from: u64, + pub(crate) qty_to: u64, + pub(crate) from_asset: Option, + pub(crate) to_asset: Option, + pub(crate) expiry: u64, + pub(crate) payment_hash: String, +} + #[derive(Deserialize, Serialize)] pub(crate) struct DisconnectPeerRequest { pub(crate) peer_pubkey: String, @@ -1736,6 +1751,23 @@ pub(crate) async fn decode_rgb_invoice( })) } +pub(crate) async fn decode_swapstring( + WithRejection(Json(payload), _): WithRejection, APIError>, +) -> Result, APIError> { + let swapstring = SwapString::from_str(&payload.swapstring) + .map_err(|e| APIError::InvalidSwapString(payload.swapstring, e.to_string()))?; + let swap_info = swapstring.swap_info; + + Ok(Json(DecodeSwapstringResponse { + qty_from: swap_info.qty_from, + qty_to: swap_info.qty_to, + from_asset: swap_info.from_asset.map(|a| a.to_string()), + to_asset: swap_info.to_asset.map(|a| a.to_string()), + expiry: swap_info.expiry, + payment_hash: hex_str(&swapstring.payment_hash.0), + })) +} + pub(crate) async fn disconnect_peer( State(state): State>, WithRejection(Json(payload), _): WithRejection, APIError>, diff --git a/src/test/mod.rs b/src/test/mod.rs index 0e3d9c66..f7d46cac 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -34,10 +34,11 @@ use crate::routes::{ AssetUDA, Assignment, BackupRequest, BtcBalanceRequest, BtcBalanceResponse, ChangePasswordRequest, Channel, CloseChannelRequest, ConnectPeerRequest, CreateUtxosRequest, DecodeLNInvoiceRequest, DecodeLNInvoiceResponse, DecodeRGBInvoiceRequest, - DecodeRGBInvoiceResponse, DisconnectPeerRequest, EmptyResponse, FailTransfersRequest, - FailTransfersResponse, GetAssetMediaRequest, GetAssetMediaResponse, GetChannelIdRequest, - GetChannelIdResponse, GetPaymentRequest, GetPaymentResponse, GetSwapRequest, GetSwapResponse, - HTLCStatus, InflateRequest, InflateResponse, InitRequest, InitResponse, InvoiceStatus, + DecodeRGBInvoiceResponse, DecodeSwapstringRequest, DecodeSwapstringResponse, + DisconnectPeerRequest, EmptyResponse, FailTransfersRequest, FailTransfersResponse, + GetAssetMediaRequest, GetAssetMediaResponse, GetChannelIdRequest, GetChannelIdResponse, + GetPaymentRequest, GetPaymentResponse, GetSwapRequest, GetSwapResponse, HTLCStatus, + InflateRequest, InflateResponse, InitRequest, InitResponse, InvoiceStatus, InvoiceStatusRequest, InvoiceStatusResponse, IssueAssetCFARequest, IssueAssetCFAResponse, IssueAssetIFARequest, IssueAssetIFAResponse, IssueAssetNIARequest, IssueAssetNIAResponse, IssueAssetUDARequest, IssueAssetUDAResponse, KeysendRequest, KeysendResponse, LNInvoiceRequest, @@ -530,6 +531,24 @@ async fn decode_rgb_invoice(node_address: SocketAddr, invoice: &str) -> DecodeRG .unwrap() } +async fn decode_swapstring(node_address: SocketAddr, swapstring: &str) -> DecodeSwapstringResponse { + println!("decoding swapstring {swapstring} for node {node_address}"); + let payload = DecodeSwapstringRequest { + swapstring: swapstring.to_string(), + }; + let res = reqwest::Client::new() + .post(format!("http://{node_address}/decodeswapstring")) + .json(&payload) + .send() + .await + .unwrap(); + check_response_is_ok(res) + .await + .json::() + .await + .unwrap() +} + async fn disconnect_peer(node_address: SocketAddr, peer_pubkey: &str) { println!("disconnecting peer {peer_pubkey} from node {node_address}"); let payload = DisconnectPeerRequest { diff --git a/src/test/swap_roundtrip_assets.rs b/src/test/swap_roundtrip_assets.rs index e7d7c753..c1a4a952 100644 --- a/src/test/swap_roundtrip_assets.rs +++ b/src/test/swap_roundtrip_assets.rs @@ -81,6 +81,36 @@ async fn swap_roundtrip_assets() { 3600, ) .await; + // check /decodeswapstring + let decoded_swapstring = decode_swapstring(maker_addr, &maker_init_response.swapstring).await; + assert_eq!(decoded_swapstring.qty_from, qty_from); + assert_eq!(decoded_swapstring.qty_to, qty_to); + assert_eq!(decoded_swapstring.from_asset, Some(asset_id_2.clone())); + assert_eq!(decoded_swapstring.to_asset, Some(asset_id_1.clone())); + assert!(decoded_swapstring.expiry > 0); + assert_eq!( + decoded_swapstring.payment_hash, + maker_init_response.payment_hash + ); + + // check /decodeswapstring invalid swapstring error + let payload = DecodeSwapstringRequest { + swapstring: s!("not_a_valid_swapstring"), + }; + let res = reqwest::Client::new() + .post(format!("http://{maker_addr}/decodeswapstring")) + .json(&payload) + .send() + .await + .unwrap(); + check_response_is_nok( + res, + reqwest::StatusCode::BAD_REQUEST, + "Invalid swap string 'not_a_valid_swapstring'", + "InvalidSwapString", + ) + .await; + taker(taker_addr, maker_init_response.swapstring.clone()).await; let swaps_maker = list_swaps(maker_addr).await;