Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ The node currently exposes the following APIs:
- `/getassetmedia` (POST)
- `/getchannelid` (POST)
- `/getpayment` (POST)
- `/getswap` (POST)
- `/init` (POST)
- `/invoicestatus` (POST)
- `/issueassetcfa` (POST)
Expand Down
32 changes: 32 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,24 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/GetPaymentResponse'
/getswap:
post:
tags:
- Swaps
summary: Get a swap
description: Get a swap by its payment hash
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/GetSwapRequest'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/GetSwapResponse'
/init:
post:
tags:
Expand Down Expand Up @@ -1433,6 +1451,20 @@ components:
properties:
payment:
$ref: '#/components/schemas/Payment'
GetSwapRequest:
type: object
properties:
payment_hash:
type: string
example: 5ca5d81b482b4015e7b14df7a27fe0a38c226273604ffd3b008b752571811938
taker:
type: boolean
example: false
GetSwapResponse:
type: object
properties:
swap:
$ref: '#/components/schemas/Swap'
HTLCStatus:
type: string
enum:
Expand Down
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,9 @@ pub enum APIError {
#[error("Recipient ID already used")]
RecipientIDAlreadyUsed,

#[error("Swap not found: {0}")]
SwapNotFound(String),

#[error("Sync needed")]
SyncNeeded,

Expand Down Expand Up @@ -459,6 +462,7 @@ impl IntoResponse for APIError {
| APIError::OpenChannelInProgress
| APIError::PaymentNotFound(_)
| APIError::RecipientIDAlreadyUsed
| APIError::SwapNotFound(_)
| APIError::SyncNeeded
| APIError::TemporaryChannelIdAlreadyUsed
| APIError::UnknownContractId
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ 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, init, invoice_status, issue_asset_cfa,
get_asset_media, get_channel_id, get_payment, get_swap, init, invoice_status, issue_asset_cfa,
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,
Expand Down Expand Up @@ -120,6 +120,7 @@ pub(crate) async fn app(args: LdkUserInfo) -> Result<(Router, Arc<AppState>), Ap
.route("/getassetmedia", post(get_asset_media))
.route("/getchannelid", post(get_channel_id))
.route("/getpayment", post(get_payment))
.route("/getswap", post(get_swap))
.route("/init", post(init))
.route("/invoicestatus", post(invoice_status))
.route("/issueassetcfa", post(issue_asset_cfa))
Expand Down
76 changes: 74 additions & 2 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,17 @@ pub(crate) struct GetPaymentResponse {
pub(crate) payment: Payment,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct GetSwapRequest {
pub(crate) payment_hash: String,
pub(crate) taker: bool,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct GetSwapResponse {
pub(crate) swap: Swap,
}

#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub(crate) enum HTLCStatus {
Pending,
Expand Down Expand Up @@ -954,7 +965,7 @@ pub(crate) struct SignMessageResponse {
pub(crate) signed_message: String,
}

#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub(crate) struct Swap {
pub(crate) qty_from: u64,
pub(crate) qty_to: u64,
Expand All @@ -968,7 +979,7 @@ pub(crate) struct Swap {
pub(crate) completed_at: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub(crate) enum SwapStatus {
Waiting,
Pending,
Expand Down Expand Up @@ -2210,6 +2221,67 @@ pub(crate) async fn list_swaps(
}))
}

pub(crate) async fn get_swap(
State(state): State<Arc<AppState>>,
WithRejection(Json(payload), _): WithRejection<Json<GetSwapRequest>, APIError>,
) -> Result<Json<GetSwapResponse>, APIError> {
let unlocked_state = state.check_unlocked().await?.clone().unwrap();

let payment_hash_vec = hex_str_to_vec(&payload.payment_hash);
if payment_hash_vec.is_none() || payment_hash_vec.as_ref().unwrap().len() != 32 {
return Err(APIError::InvalidPaymentHash(payload.payment_hash));
}
let requested_ph = PaymentHash(payment_hash_vec.unwrap().try_into().unwrap());

let map_swap = |payment_hash: &PaymentHash, swap_data: &SwapData, taker: bool| {
let mut status = swap_data.status.clone();
if status == SwapStatus::Waiting && get_current_timestamp() > swap_data.swap_info.expiry {
status = SwapStatus::Expired;
} else if status == SwapStatus::Pending
&& get_current_timestamp() > swap_data.initiated_at.unwrap() + 86400
{
status = SwapStatus::Failed;
}
if status != swap_data.status {
if taker {
unlocked_state.update_taker_swap_status(payment_hash, status.clone());
} else {
unlocked_state.update_maker_swap_status(payment_hash, status.clone());
}
}
Swap {
payment_hash: payment_hash.to_string(),
qty_from: swap_data.swap_info.qty_from,
qty_to: swap_data.swap_info.qty_to,
from_asset: swap_data.swap_info.from_asset.map(|c| c.to_string()),
to_asset: swap_data.swap_info.to_asset.map(|c| c.to_string()),
status,
requested_at: swap_data.requested_at,
initiated_at: swap_data.initiated_at,
expires_at: swap_data.swap_info.expiry,
completed_at: swap_data.completed_at,
}
};

if payload.taker {
let taker_swaps = unlocked_state.taker_swaps();
if let Some(sd) = taker_swaps.get(&requested_ph) {
return Ok(Json(GetSwapResponse {
swap: map_swap(&requested_ph, sd, true),
}));
}
} else {
let maker_swaps = unlocked_state.maker_swaps();
if let Some(sd) = maker_swaps.get(&requested_ph) {
return Ok(Json(GetSwapResponse {
swap: map_swap(&requested_ph, sd, false),
}));
}
}

Err(APIError::SwapNotFound(payload.payment_hash))
}

pub(crate) async fn list_transactions(
State(state): State<Arc<AppState>>,
WithRejection(Json(payload), _): WithRejection<Json<ListTransactionsRequest>, APIError>,
Expand Down
45 changes: 33 additions & 12 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,19 @@ use crate::routes::{
DecodeLNInvoiceResponse, DecodeRGBInvoiceRequest, DecodeRGBInvoiceResponse,
DisconnectPeerRequest, EmptyResponse, FailTransfersRequest, FailTransfersResponse,
GetAssetMediaRequest, GetAssetMediaResponse, GetChannelIdRequest, GetChannelIdResponse,
GetPaymentRequest, GetPaymentResponse, HTLCStatus, InitRequest, InitResponse, InvoiceStatus,
InvoiceStatusRequest, InvoiceStatusResponse, IssueAssetCFARequest, IssueAssetCFAResponse,
IssueAssetNIARequest, IssueAssetNIAResponse, IssueAssetUDARequest, IssueAssetUDAResponse,
KeysendRequest, KeysendResponse, LNInvoiceRequest, LNInvoiceResponse, ListAssetsRequest,
ListAssetsResponse, ListChannelsResponse, ListPaymentsResponse, ListPeersResponse,
ListSwapsResponse, ListTransactionsRequest, ListTransactionsResponse, ListTransfersRequest,
ListTransfersResponse, ListUnspentsRequest, ListUnspentsResponse, MakerExecuteRequest,
MakerInitRequest, MakerInitResponse, NetworkInfoResponse, NodeInfoResponse, OpenChannelRequest,
OpenChannelResponse, Payment, Peer, PostAssetMediaResponse, RefreshRequest, RestoreRequest,
RgbInvoiceRequest, RgbInvoiceResponse, SendAssetRequest, SendAssetResponse, SendBtcRequest,
SendBtcResponse, SendPaymentRequest, SendPaymentResponse, SwapStatus, TakerRequest,
Transaction, Transfer, UnlockRequest, Unspent,
GetPaymentRequest, GetPaymentResponse, GetSwapRequest, GetSwapResponse, HTLCStatus,
InitRequest, InitResponse, InvoiceStatus, InvoiceStatusRequest, InvoiceStatusResponse,
IssueAssetCFARequest, IssueAssetCFAResponse, IssueAssetNIARequest, IssueAssetNIAResponse,
IssueAssetUDARequest, IssueAssetUDAResponse, KeysendRequest, KeysendResponse, LNInvoiceRequest,
LNInvoiceResponse, ListAssetsRequest, ListAssetsResponse, ListChannelsResponse,
ListPaymentsResponse, ListPeersResponse, ListSwapsResponse, ListTransactionsRequest,
ListTransactionsResponse, ListTransfersRequest, ListTransfersResponse, ListUnspentsRequest,
ListUnspentsResponse, MakerExecuteRequest, MakerInitRequest, MakerInitResponse,
NetworkInfoResponse, NodeInfoResponse, OpenChannelRequest, OpenChannelResponse, Payment, Peer,
PostAssetMediaResponse, RefreshRequest, RestoreRequest, RgbInvoiceRequest, RgbInvoiceResponse,
SendAssetRequest, SendAssetResponse, SendBtcRequest, SendBtcResponse, SendPaymentRequest,
SendPaymentResponse, Swap, SwapStatus, TakerRequest, Transaction, Transfer, UnlockRequest,
Unspent,
};
use crate::utils::{hex_str_to_vec, ELECTRUM_URL_REGTEST, PROXY_ENDPOINT_LOCAL};

Expand Down Expand Up @@ -809,6 +810,26 @@ async fn list_swaps(node_address: SocketAddr) -> ListSwapsResponse {
_check_response_is_ok(res).await.json().await.unwrap()
}

async fn get_swap(node_address: SocketAddr, payment_hash: &str, taker: bool) -> Swap {
println!("getting swap with payment hash {payment_hash} for node {node_address}");
let payload = GetSwapRequest {
payment_hash: payment_hash.to_string(),
taker,
};
let res = reqwest::Client::new()
.post(format!("http://{}/getswap", node_address))
.json(&payload)
.send()
.await
.unwrap();
_check_response_is_ok(res)
.await
.json::<GetSwapResponse>()
.await
.unwrap()
.swap
}

async fn list_transactions(node_address: SocketAddr) -> Vec<Transaction> {
println!("listing transactions for node {node_address}");
let payload = ListTransactionsRequest { skip_sync: false };
Expand Down
2 changes: 2 additions & 0 deletions src/test/swap_roundtrip_assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ async fn swap_roundtrip_assets() {
assert_eq!(swap_maker.to_asset, Some(asset_id_1.clone()));
assert_eq!(swap_maker.payment_hash, maker_init_response.payment_hash);
assert_eq!(swap_maker.status, SwapStatus::Waiting);
let swap_maker_single = get_swap(maker_addr, &maker_init_response.payment_hash, false).await;
assert_eq!(swap_maker_single, *swap_maker);
let swaps_taker = list_swaps(taker_addr).await;
assert!(swaps_taker.maker.is_empty());
assert_eq!(swaps_taker.taker.len(), 1);
Expand Down