Skip to content

Commit 0c86852

Browse files
authored
Merge pull request #94 from Arshia-r-m/batch_transfer
feat: expose batch transfer for rgb assets
2 parents 75f9ac5 + 0a93462 commit 0c86852

File tree

6 files changed

+299
-162
lines changed

6 files changed

+299
-162
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,10 @@ The node currently exposes the following APIs:
244244
- `/restore` (POST)
245245
- `/revoketoken` (POST)
246246
- `/rgbinvoice` (POST)
247-
- `/sendasset` (POST)
248247
- `/sendbtc` (POST)
249248
- `/sendonionmessage` (POST)
250249
- `/sendpayment` (POST)
250+
- `/sendrgb` (POST)
251251
- `/shutdown` (POST)
252252
- `/signmessage` (POST)
253253
- `/sync` (POST)

openapi.yaml

Lines changed: 78 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -820,24 +820,6 @@ paths:
820820
application/json:
821821
schema:
822822
$ref: '#/components/schemas/RgbInvoiceResponse'
823-
/sendasset:
824-
post:
825-
tags:
826-
- RGB
827-
summary: Send assets
828-
description: Send RGB assets on-chain
829-
requestBody:
830-
content:
831-
application/json:
832-
schema:
833-
$ref: '#/components/schemas/SendAssetRequest'
834-
responses:
835-
'200':
836-
description: Successful operation
837-
content:
838-
application/json:
839-
schema:
840-
$ref: '#/components/schemas/SendAssetResponse'
841823
/sendbtc:
842824
post:
843825
tags:
@@ -892,6 +874,24 @@ paths:
892874
application/json:
893875
schema:
894876
$ref: '#/components/schemas/SendPaymentResponse'
877+
/sendrgb:
878+
post:
879+
tags:
880+
- RGB
881+
summary: Send RGB assets
882+
description: Send RGB assets on-chain, supporting batch transfers to multiple recipients and/or multiple assets in a single transaction
883+
requestBody:
884+
content:
885+
application/json:
886+
schema:
887+
$ref: '#/components/schemas/SendRgbRequest'
888+
responses:
889+
'200':
890+
description: Successful operation
891+
content:
892+
application/json:
893+
schema:
894+
$ref: '#/components/schemas/SendRgbResponse'
895895
/shutdown:
896896
post:
897897
tags:
@@ -2003,6 +2003,21 @@ components:
20032003
items:
20042004
type: integer
20052005
example: [6, 36, 87, 13, 5, 17]
2006+
Recipient:
2007+
type: object
2008+
properties:
2009+
recipient_id:
2010+
type: string
2011+
example: bcrt:utxob:2FZsSuk-iyVQLVuU4-Gc6J4qkE8-mLS17N4jd-MEx6cWz9F-MFkyE1n
2012+
witness_data:
2013+
$ref: '#/components/schemas/WitnessData'
2014+
assignment:
2015+
$ref: '#/components/schemas/Assignment'
2016+
transport_endpoints:
2017+
type: array
2018+
items:
2019+
type: string
2020+
example: rpc://127.0.0.1:3000/json-rpc
20062021
RecipientType:
20072022
type: string
20082023
enum:
@@ -2072,42 +2087,6 @@ components:
20722087
batch_transfer_idx:
20732088
type: integer
20742089
example: 1
2075-
SendAssetRequest:
2076-
type: object
2077-
properties:
2078-
asset_id:
2079-
type: string
2080-
example: rgb:CJkb4YZw-jRiz2sk-~PARPio-wtVYI1c-XAEYCqO-wTfvRZ8
2081-
assignment:
2082-
$ref: '#/components/schemas/AssignmentFungible'
2083-
recipient_id:
2084-
type: string
2085-
example: bcrt:utxob:2FZsSuk-iyVQLVuU4-Gc6J4qkE8-mLS17N4jd-MEx6cWz9F-MFkyE1n
2086-
witness_data:
2087-
$ref: '#/components/schemas/WitnessData'
2088-
donation:
2089-
type: boolean
2090-
example: false
2091-
fee_rate:
2092-
type: number
2093-
example: 5
2094-
min_confirmations:
2095-
type: integer
2096-
example: 1
2097-
transport_endpoints:
2098-
type: array
2099-
items:
2100-
type: string
2101-
example: rpc://127.0.0.1:3000/json-rpc
2102-
skip_sync:
2103-
type: boolean
2104-
example: false
2105-
SendAssetResponse:
2106-
type: object
2107-
properties:
2108-
txid:
2109-
type: string
2110-
example: 7c2c95b9c2aa0a7d140495b664de7973b76561de833f0dd84def3efa08941664
21112090
SendBtcRequest:
21122091
type: object
21132092
properties:
@@ -2160,6 +2139,51 @@ components:
21602139
example: 777a7756c620868199ed5fdc35bee4095b5709d543e5c2bf0494396bf27d2ea2
21612140
status:
21622141
$ref: '#/components/schemas/HTLCStatus'
2142+
SendRgbRequest:
2143+
type: object
2144+
properties:
2145+
donation:
2146+
type: boolean
2147+
example: false
2148+
fee_rate:
2149+
type: number
2150+
example: 5
2151+
min_confirmations:
2152+
type: integer
2153+
example: 1
2154+
recipient_map:
2155+
type: object
2156+
additionalProperties:
2157+
type: array
2158+
items:
2159+
$ref: '#/components/schemas/Recipient'
2160+
example:
2161+
rgb:CJkb4YZw-jRiz2sk-~PARPio-wtVYI1c-XAEYCqO-wTfvRZ8:
2162+
- recipient_id: "utxob:2FjRqgQ-eEWCVHY5-zmpFtYzT-gGm3MdR-sTnxNcS-7RtUbY9-4NYuuh"
2163+
assignment:
2164+
Fungible: 400
2165+
transport_endpoints:
2166+
- rpc://127.0.0.1:3000/json-rpc
2167+
- recipient_id: "utxob:3GkRrhR-fFXDLIZ6-0anqGuzU-hHn4NeS-tUoyOdT-8SuVcZ0-5OZvvi"
2168+
assignment:
2169+
Fungible: 200
2170+
transport_endpoints:
2171+
- rpc://127.0.0.1:3000/json-rpc
2172+
rgb:d8qDVS5X-ICVG2uM-CPr3yO4-lfBhgjt-7FN1EPE-ApY1LcM:
2173+
- recipient_id: "utxob:4HlSsiS-gGYEMKA7-1borHvaV-iIo5OfT-uVpzPeU-9TvWdA1-6PAwwj"
2174+
assignment:
2175+
Fungible: 100
2176+
transport_endpoints:
2177+
- rpc://127.0.0.1:3000/json-rpc
2178+
skip_sync:
2179+
type: boolean
2180+
example: false
2181+
SendRgbResponse:
2182+
type: object
2183+
properties:
2184+
txid:
2185+
type: string
2186+
example: 7c2c95b9c2aa0a7d140495b664de7973b76561de833f0dd84def3efa08941664
21632187
SignMessageRequest:
21642188
type: object
21652189
properties:

src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ use crate::routes::{
4949
issue_asset_nia, issue_asset_uda, keysend, list_assets, list_channels, list_payments,
5050
list_peers, list_swaps, list_transactions, list_transfers, list_unspents, ln_invoice, lock,
5151
maker_execute, maker_init, network_info, node_info, open_channel, post_asset_media,
52-
refresh_transfers, restore, revoke_token, rgb_invoice, send_asset, send_btc,
53-
send_onion_message, send_payment, shutdown, sign_message, sync, taker, unlock,
52+
refresh_transfers, restore, revoke_token, rgb_invoice, send_btc, send_onion_message,
53+
send_payment, send_rgb, shutdown, sign_message, sync, taker, unlock,
5454
};
5555
use crate::utils::{start_daemon, AppState, LOGS_DIR};
5656

@@ -149,10 +149,10 @@ pub(crate) async fn app(args: UserArgs) -> Result<(Router, Arc<AppState>), AppEr
149149
.route("/restore", post(restore))
150150
.route("/revoketoken", post(revoke_token))
151151
.route("/rgbinvoice", post(rgb_invoice))
152-
.route("/sendasset", post(send_asset))
153152
.route("/sendbtc", post(send_btc))
154153
.route("/sendonionmessage", post(send_onion_message))
155154
.route("/sendpayment", post(send_payment))
155+
.route("/sendrgb", post(send_rgb))
156156
.route("/shutdown", post(shutdown))
157157
.route("/signmessage", post(sign_message))
158158
.route("/sync", post(sync))

src/routes.rs

Lines changed: 77 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ use rgb_lib::{
5252
},
5353
AssetCFA as RgbLibAssetCFA, AssetNIA as RgbLibAssetNIA, AssetUDA as RgbLibAssetUDA,
5454
Balance as RgbLibBalance, EmbeddedMedia as RgbLibEmbeddedMedia, Invoice as RgbLibInvoice,
55-
Media as RgbLibMedia, ProofOfReserves as RgbLibProofOfReserves, Recipient, RecipientInfo,
56-
RecipientType as RgbLibRecipientType, Token as RgbLibToken, TokenLight as RgbLibTokenLight,
57-
WitnessData as RgbLibWitnessData,
55+
Media as RgbLibMedia, ProofOfReserves as RgbLibProofOfReserves,
56+
Recipient as RgbLibRecipient, RecipientInfo, RecipientType as RgbLibRecipientType,
57+
Token as RgbLibToken, TokenLight as RgbLibTokenLight, WitnessData as RgbLibWitnessData,
5858
},
5959
AssetSchema as RgbLibAssetSchema, Assignment as RgbLibAssignment,
6060
BitcoinNetwork as RgbLibNetwork, ContractId, RgbTransport,
@@ -918,6 +918,25 @@ pub(crate) struct RevokeTokenRequest {
918918
pub(crate) token: String,
919919
}
920920

921+
#[derive(Deserialize, Serialize)]
922+
pub(crate) struct Recipient {
923+
pub(crate) recipient_id: String,
924+
pub(crate) witness_data: Option<WitnessData>,
925+
pub(crate) assignment: Assignment,
926+
pub(crate) transport_endpoints: Vec<String>,
927+
}
928+
929+
impl From<Recipient> for RgbLibRecipient {
930+
fn from(value: Recipient) -> Self {
931+
Self {
932+
recipient_id: value.recipient_id,
933+
witness_data: value.witness_data.map(|w| w.into()),
934+
assignment: value.assignment.into(),
935+
transport_endpoints: value.transport_endpoints,
936+
}
937+
}
938+
}
939+
921940
#[derive(Deserialize, Serialize)]
922941
pub(crate) struct RgbAllocation {
923942
pub(crate) asset_id: Option<String>,
@@ -942,24 +961,6 @@ pub(crate) struct RgbInvoiceResponse {
942961
pub(crate) batch_transfer_idx: i32,
943962
}
944963

945-
#[derive(Deserialize, Serialize)]
946-
pub(crate) struct SendAssetRequest {
947-
pub(crate) asset_id: String,
948-
pub(crate) assignment: Assignment,
949-
pub(crate) recipient_id: String,
950-
pub(crate) witness_data: Option<WitnessData>,
951-
pub(crate) donation: bool,
952-
pub(crate) fee_rate: u64,
953-
pub(crate) min_confirmations: u8,
954-
pub(crate) transport_endpoints: Vec<String>,
955-
pub(crate) skip_sync: bool,
956-
}
957-
958-
#[derive(Deserialize, Serialize)]
959-
pub(crate) struct SendAssetResponse {
960-
pub(crate) txid: String,
961-
}
962-
963964
#[derive(Deserialize, Serialize)]
964965
pub(crate) struct SendBtcRequest {
965966
pub(crate) amount: u64,
@@ -994,6 +995,20 @@ pub(crate) struct SendPaymentResponse {
994995
pub(crate) status: HTLCStatus,
995996
}
996997

998+
#[derive(Deserialize, Serialize)]
999+
pub(crate) struct SendRgbRequest {
1000+
pub(crate) donation: bool,
1001+
pub(crate) fee_rate: u64,
1002+
pub(crate) min_confirmations: u8,
1003+
pub(crate) recipient_map: HashMap<String, Vec<Recipient>>,
1004+
pub(crate) skip_sync: bool,
1005+
}
1006+
1007+
#[derive(Deserialize, Serialize)]
1008+
pub(crate) struct SendRgbResponse {
1009+
pub(crate) txid: String,
1010+
}
1011+
9971012
#[derive(Deserialize, Serialize)]
9981013
pub(crate) struct SignMessageRequest {
9991014
pub(crate) message: String,
@@ -3112,7 +3127,7 @@ pub(crate) async fn open_channel(
31123127
};
31133128

31143129
let recipient_map = map! {
3115-
asset_id => vec![Recipient {
3130+
asset_id => vec![RgbLibRecipient {
31163131
recipient_id,
31173132
witness_data: Some(RgbLibWitnessData {
31183133
amount_sat: payload.capacity_sat,
@@ -3357,48 +3372,6 @@ pub(crate) async fn rgb_invoice(
33573372
.await
33583373
}
33593374

3360-
pub(crate) async fn send_asset(
3361-
State(state): State<Arc<AppState>>,
3362-
WithRejection(Json(payload), _): WithRejection<Json<SendAssetRequest>, APIError>,
3363-
) -> Result<Json<SendAssetResponse>, APIError> {
3364-
no_cancel(async move {
3365-
let guard = state.check_unlocked().await?;
3366-
let unlocked_state = guard.as_ref().unwrap();
3367-
3368-
if *unlocked_state.rgb_send_lock.lock().unwrap() {
3369-
return Err(APIError::OpenChannelInProgress);
3370-
}
3371-
3372-
RecipientInfo::new(payload.recipient_id.clone())?;
3373-
let recipient_map = map! {
3374-
payload.asset_id => vec![Recipient {
3375-
recipient_id: payload.recipient_id,
3376-
witness_data: payload.witness_data.map(|w| w.into()),
3377-
assignment: payload.assignment.into(),
3378-
transport_endpoints: payload.transport_endpoints,
3379-
}]
3380-
};
3381-
3382-
let unlocked_state_copy = unlocked_state.clone();
3383-
let send_result = tokio::task::spawn_blocking(move || {
3384-
unlocked_state_copy.rgb_send(
3385-
recipient_map,
3386-
payload.donation,
3387-
payload.fee_rate,
3388-
payload.min_confirmations,
3389-
payload.skip_sync,
3390-
)
3391-
})
3392-
.await
3393-
.unwrap()?;
3394-
3395-
Ok(Json(SendAssetResponse {
3396-
txid: send_result.txid,
3397-
}))
3398-
})
3399-
.await
3400-
}
3401-
34023375
pub(crate) async fn send_btc(
34033376
State(state): State<Arc<AppState>>,
34043377
WithRejection(Json(payload), _): WithRejection<Json<SendBtcRequest>, APIError>,
@@ -3655,6 +3628,46 @@ pub(crate) async fn send_payment(
36553628
.await
36563629
}
36573630

3631+
pub(crate) async fn send_rgb(
3632+
State(state): State<Arc<AppState>>,
3633+
WithRejection(Json(payload), _): WithRejection<Json<SendRgbRequest>, APIError>,
3634+
) -> Result<Json<SendRgbResponse>, APIError> {
3635+
no_cancel(async move {
3636+
let guard = state.check_unlocked().await?;
3637+
let unlocked_state = guard.as_ref().unwrap();
3638+
3639+
if *unlocked_state.rgb_send_lock.lock().unwrap() {
3640+
return Err(APIError::OpenChannelInProgress);
3641+
}
3642+
3643+
let recipient_map: HashMap<String, Vec<RgbLibRecipient>> = payload
3644+
.recipient_map
3645+
.into_iter()
3646+
.map(|(asset_id, recipients)| {
3647+
(asset_id, recipients.into_iter().map(|r| r.into()).collect())
3648+
})
3649+
.collect();
3650+
3651+
let unlocked_state_copy = unlocked_state.clone();
3652+
let send_result = tokio::task::spawn_blocking(move || {
3653+
unlocked_state_copy.rgb_send(
3654+
recipient_map,
3655+
payload.donation,
3656+
payload.fee_rate,
3657+
payload.min_confirmations,
3658+
payload.skip_sync,
3659+
)
3660+
})
3661+
.await
3662+
.unwrap()?;
3663+
3664+
Ok(Json(SendRgbResponse {
3665+
txid: send_result.txid,
3666+
}))
3667+
})
3668+
.await
3669+
}
3670+
36583671
pub(crate) async fn shutdown(
36593672
State(state): State<Arc<AppState>>,
36603673
) -> Result<Json<EmptyResponse>, APIError> {

0 commit comments

Comments
 (0)