Skip to content
Open
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ curl -X POST -H "Content-type: application/json" \
The node currently exposes the following APIs:
- `/address` (POST)
- `/assetbalance` (POST)
- `/assetidfromhexbytes` (POST)
- `/assetidtohexbytes` (POST)
- `/assetmetadata` (POST)
- `/backup` (POST)
- `/btcbalance` (POST)
Expand Down
62 changes: 62 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,44 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/AssetBalanceResponse'
/assetidfromhexbytes:
post:
tags:
- RGB
summary: Convert hex-encoded bytes to RGB asset ID
description: Convert asset ID bytes in hexadecimal format to the corresponding baid64 string form
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AssetIdFromHexBytesRequest'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/AssetIdFromHexBytesResponse'
/assetidtohexbytes:
post:
tags:
- RGB
summary: Convert RGB asset ID to hex-encoded bytes
description: Convert RGB asset ID in baid64 string form to its bytes in hexadecimal format
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AssetIdToHexBytesRequest'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/AssetIdToHexBytesResponse'
/assetmetadata:
post:
tags:
Expand Down Expand Up @@ -986,6 +1024,30 @@ components:
offchain_inbound:
type: integer
example: 0
AssetIdFromHexBytesRequest:
type: object
properties:
hex_bytes:
type: string
example: 08991be186708d18b3dac93f3c044f8a8c2d558235717004602a8ec137ef459f
AssetIdFromHexBytesResponse:
type: object
properties:
asset_id:
type: string
example: rgb:CJkb4YZw-jRiz2sk-~PARPio-wtVYI1c-XAEYCqO-wTfvRZ8
AssetIdToHexBytesRequest:
type: object
properties:
asset_id:
type: string
example: rgb:CJkb4YZw-jRiz2sk-~PARPio-wtVYI1c-XAEYCqO-wTfvRZ8
AssetIdToHexBytesResponse:
type: object
properties:
hex_bytes:
type: string
example: 08991be186708d18b3dac93f3c044f8a8c2d558235717004602a8ec137ef459f
AssetMetadataRequest:
type: object
properties:
Expand Down
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ pub enum APIError {
#[error("Invalid asset ID: {0}")]
InvalidAssetID(String),

#[error("Invalid hex bytes")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[error("Invalid hex bytes")]
#[error("Invalid asset ID hex bytes")]

InvalidAssetIDBytes,

#[error("Invalid attachments: {0}")]
InvalidAttachments(String),

Expand All @@ -129,6 +132,9 @@ pub enum APIError {
#[error("Invalid fee rate: {0}")]
InvalidFeeRate(String),

#[error("Invalid hex string: {0}")]
InvalidHexString(String),

#[error("Invalid indexer: {0}")]
InvalidIndexer(String),

Expand Down Expand Up @@ -405,12 +411,14 @@ impl IntoResponse for APIError {
| APIError::InvalidAnnounceAddresses(_)
| APIError::InvalidAnnounceAlias(_)
| APIError::InvalidAssetID(_)
| APIError::InvalidAssetIDBytes
| APIError::InvalidAttachments(_)
| APIError::InvalidBackupPath
| APIError::InvalidChannelID
| APIError::InvalidDetails(_)
| APIError::InvalidEstimationBlocks
| APIError::InvalidFeeRate(_)
| APIError::InvalidHexString(_)
| APIError::InvalidInvoice(_)
| APIError::InvalidMediaDigest
| APIError::InvalidName(_)
Expand Down
20 changes: 11 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ use crate::args::LdkUserInfo;
use crate::error::AppError;
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, 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,
refresh_transfers, restore, rgb_invoice, send_asset, send_btc, send_onion_message,
send_payment, shutdown, sign_message, sync, taker, unlock,
address, asset_balance, asset_id_from_hex_bytes, asset_id_to_hex_bytes, 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, 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, refresh_transfers, restore, rgb_invoice, send_asset, send_btc,
send_onion_message, send_payment, shutdown, sign_message, sync, taker, unlock,
};
use crate::utils::{start_daemon, AppState, LOGS_DIR};

Expand Down Expand Up @@ -103,6 +103,8 @@ pub(crate) async fn app(args: LdkUserInfo) -> Result<(Router, Arc<AppState>), Ap
.layer(DefaultBodyLimit::disable())
.route("/address", post(address))
.route("/assetbalance", post(asset_balance))
.route("/assetidfromhexbytes", post(asset_id_from_hex_bytes))
.route("/assetidtohexbytes", post(asset_id_to_hex_bytes))
.route("/assetmetadata", post(asset_metadata))
.route("/backup", post(backup))
.route("/btcbalance", post(btc_balance))
Expand Down
43 changes: 43 additions & 0 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,26 @@
}
}

#[derive(Deserialize, Serialize)]
pub(crate) struct AssetIdToHexBytesRequest {
pub(crate) asset_id: String,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct AssetIdToHexBytesResponse {
pub(crate) hex_bytes: String,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct AssetIdFromHexBytesRequest {
pub(crate) hex_bytes: String,
}

#[derive(Deserialize, Serialize)]
pub(crate) struct AssetIdFromHexBytesResponse {
pub(crate) asset_id: String,
}

Comment on lines +144 to +163
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please sort these alphabetically

#[derive(Deserialize, Serialize)]
pub(crate) struct AssetMetadataRequest {
pub(crate) asset_id: String,
Expand Down Expand Up @@ -1137,7 +1157,7 @@
}

async fn check_locked(
&self,

Check warning on line 1160 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, macos)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1160 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, linux)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1160 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, windows)

lifetime flowing from input to output with different syntax can be confusing
) -> Result<TokioMutexGuard<Option<Arc<UnlockedAppState>>>, APIError> {
let unlocked_app_state = self.get_unlocked_app_state().await;
if unlocked_app_state.is_some() {
Expand All @@ -1149,7 +1169,7 @@
}

async fn check_unlocked(
&self,

Check warning on line 1172 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, macos)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1172 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, linux)

lifetime flowing from input to output with different syntax can be confusing

Check warning on line 1172 in src/routes.rs

View workflow job for this annotation

GitHub Actions / build (nightly, windows)

lifetime flowing from input to output with different syntax can be confusing
) -> Result<TokioMutexGuard<Option<Arc<UnlockedAppState>>>, APIError> {
let unlocked_app_state = self.get_unlocked_app_state().await;
if unlocked_app_state.is_none() {
Expand Down Expand Up @@ -1224,6 +1244,29 @@
}))
}

pub(crate) async fn asset_id_from_hex_bytes(
WithRejection(Json(payload), _): WithRejection<Json<AssetIdFromHexBytesRequest>, APIError>,
) -> Result<Json<AssetIdFromHexBytesResponse>, APIError> {
let hex_bytes = hex_str_to_vec(&payload.hex_bytes)
.ok_or_else(|| APIError::InvalidHexString(payload.hex_bytes))?;

let contract_id =
ContractId::copy_from_slice(&hex_bytes).map_err(|_| APIError::InvalidAssetIDBytes)?;
let asset_id = contract_id.to_string();

Ok(Json(AssetIdFromHexBytesResponse { asset_id }))
}

pub(crate) async fn asset_id_to_hex_bytes(
WithRejection(Json(payload), _): WithRejection<Json<AssetIdToHexBytesRequest>, APIError>,
) -> Result<Json<AssetIdToHexBytesResponse>, APIError> {
let contract_id = ContractId::from_str(&payload.asset_id)
.map_err(|_| APIError::InvalidAssetID(payload.asset_id))?;
let hex_bytes = hex_str(&contract_id.to_byte_array());

Ok(Json(AssetIdToHexBytesResponse { hex_bytes }))
}

pub(crate) async fn asset_metadata(
State(state): State<Arc<AppState>>,
WithRejection(Json(payload), _): WithRejection<Json<AssetMetadataRequest>, APIError>,
Expand Down
36 changes: 36 additions & 0 deletions src/test/asset_id_hex_bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use super::*;

const TEST_DIR_BASE: &str = "tmp/asset_id_hex_bytes/";

#[serial_test::serial]
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[traced_test]
async fn success() {
initialize();

let test_dir_node1 = format!("{TEST_DIR_BASE}node1");
let (node1_addr, _) = start_node(&test_dir_node1, NODE1_PEER_PORT, false).await;

fund_and_create_utxos(node1_addr, None).await;

// issue assets
let asset_cfa = issue_asset_cfa(node1_addr, None).await;
let asset_nia = issue_asset_nia(node1_addr).await;
let asset_uda = issue_asset_uda(node1_addr, None).await;

// check
let cfa_decoded_result = asset_id_to_hex_bytes(node1_addr, asset_cfa.asset_id.clone()).await;
let nia_decoded_result = asset_id_to_hex_bytes(node1_addr, asset_nia.asset_id.clone()).await;
let uda_decoded_result = asset_id_to_hex_bytes(node1_addr, asset_uda.asset_id.clone()).await;

let cfa_encoded_result =
asset_id_from_hex_bytes(node1_addr, cfa_decoded_result.hex_bytes.clone()).await;
let nia_encoded_result =
asset_id_from_hex_bytes(node1_addr, nia_decoded_result.hex_bytes.clone()).await;
let uda_encoded_result =
asset_id_from_hex_bytes(node1_addr, uda_decoded_result.hex_bytes.clone()).await;

assert_eq!(cfa_encoded_result.asset_id, asset_cfa.asset_id);
assert_eq!(nia_encoded_result.asset_id, asset_nia.asset_id);
assert_eq!(uda_encoded_result.asset_id, asset_uda.asset_id);
}
78 changes: 59 additions & 19 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,26 @@ use tracing_test::traced_test;
use crate::error::APIErrorResponse;
use crate::ldk::FEE_RATE;
use crate::routes::{
AddressResponse, AssetBalanceRequest, AssetBalanceResponse, AssetCFA, AssetNIA, AssetUDA,
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,
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,
AddressResponse, AssetBalanceRequest, AssetBalanceResponse, AssetCFA,
AssetIdFromHexBytesRequest, AssetIdFromHexBytesResponse, AssetIdToHexBytesRequest,
AssetIdToHexBytesResponse, AssetNIA, AssetUDA, 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, 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 @@ -228,6 +229,44 @@ async fn asset_balance_spendable(node_address: SocketAddr, asset_id: &str) -> u6
asset_balance(node_address, asset_id).await.spendable
}

async fn asset_id_from_hex_bytes(
node_address: SocketAddr,
hex_bytes: String,
) -> AssetIdFromHexBytesResponse {
println!("converting hex bytes {hex_bytes} to asset ID for node {node_address}");
let payload = AssetIdFromHexBytesRequest { hex_bytes };
let res = reqwest::Client::new()
.post(format!("http://{}/assetidfromhexbytes", node_address))
.json(&payload)
.send()
.await
.unwrap();
_check_response_is_ok(res)
.await
.json::<AssetIdFromHexBytesResponse>()
.await
.unwrap()
}

async fn asset_id_to_hex_bytes(
node_address: SocketAddr,
asset_id: String,
) -> AssetIdToHexBytesResponse {
println!("converting asset ID {asset_id} to hex bytes for node {node_address}");
let payload = AssetIdToHexBytesRequest { asset_id };
let res = reqwest::Client::new()
.post(format!("http://{}/assetidtohexbytes", node_address))
.json(&payload)
.send()
.await
.unwrap();
_check_response_is_ok(res)
.await
.json::<AssetIdToHexBytesResponse>()
.await
.unwrap()
}

async fn backup(node_address: SocketAddr, backup_path: &str, password: &str) {
println!("performing backup for node {node_address} on {backup_path}");
let payload = BackupRequest {
Expand Down Expand Up @@ -1658,6 +1697,7 @@ pub fn mock_fee(fee: u32) -> u32 {
}
}

mod asset_id_hex_bytes;
mod backup_and_restore;
mod close_coop_nobtc_acceptor;
mod close_coop_other_side;
Expand Down
Loading