mirror of
https://github.com/jwinterm/grim.git
synced 2026-10-22 15:58:32 +02:00
wallet: auto-reply over Tor when scanning an Invoice1 slatepack
Mirrors the existing Standard1 + Send-task flow for the invoice direction. When a customer scans an Invoice1 slatepack QR with an embedded sender address (now standard for slatepacks produced by grin-wallet's issue_invoice_tx), the patched pay() forwards the sender address through create_slatepack_message, and the OpenMessage handler — if the wallet's Tor service is running or starting — pushes the signed Invoice2 to the sender's foreign-api finalize_tx over Tor. The merchant's wallet finalizes + posts on their side, so no local finalize/post is needed (cf. the existing send_tor closure for Standard1 which does need that). Backward-compatible: if the slatepack has no embedded sender address (older clients) or the Tor service isn't up, the existing write-slatepack-to-disk-for-paste-back fallback runs unchanged. No protocol change, no new dependencies, no new failure modes — the response slatepack file is always written before the Tor send is attempted, so a Tor failure mid-flight is recoverable. Closes the mobile-UX gap that required the customer to manually copy the response slatepack from the wallet and paste it back to the merchant's web/storefront interface. With this patch and a foreign-api listener on the merchant side, scanning a Grin invoice QR is now a single tap: scan → confirm → done. send_tor() gains a `finalize: bool` parameter that selects between the existing receive_tx body (for Standard1 sends) and a new finalize_tx body (for the invoice-flow case). The same Tor SOCKS plumbing handles both. Real-world validation: end-to-end working today on a production BTCPay deployment (Such Software's btcpayserver-plugin-grin v1.3.5) — invoice QR scan with a patched build settles a merchant invoice in ~1 confirmation window with zero customer interaction beyond the scan.
This commit is contained in:
+68
-17
@@ -1070,23 +1070,41 @@ impl Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Send slate to Tor address.
|
||||
async fn send_tor(&self, id: u32, s: &Slate, addr: &SlatepackAddress) -> Result<Slate, Error> {
|
||||
/// Send slate to Tor address. When `finalize` is true, posts the slate to the
|
||||
/// peer's foreign-api `finalize_tx` (used by the invoice-flow payer to push the
|
||||
/// signed Invoice2 back to the merchant for broadcast); otherwise posts to
|
||||
/// `receive_tx` (standard send flow).
|
||||
async fn send_tor(
|
||||
&self,
|
||||
id: u32,
|
||||
s: &Slate,
|
||||
addr: &SlatepackAddress,
|
||||
finalize: bool,
|
||||
) -> Result<Slate, Error> {
|
||||
self.on_tx_action(id, Some(WalletTxAction::SendingTor));
|
||||
|
||||
let tor_addr = OnionV3Address::try_from(addr).unwrap().to_http_str();
|
||||
let url = format!("{}/v2/foreign", tor_addr);
|
||||
let slate_send = VersionedSlate::into_version(s.clone(), SlateVersion::V4)?;
|
||||
let body = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "receive_tx",
|
||||
"id": 1,
|
||||
"params": [
|
||||
slate_send,
|
||||
null,
|
||||
null
|
||||
]
|
||||
})
|
||||
let body = if finalize {
|
||||
json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "finalize_tx",
|
||||
"id": 1,
|
||||
"params": [slate_send]
|
||||
})
|
||||
} else {
|
||||
json!({
|
||||
"jsonrpc": "2.0",
|
||||
"method": "receive_tx",
|
||||
"id": 1,
|
||||
"params": [
|
||||
slate_send,
|
||||
null,
|
||||
null
|
||||
]
|
||||
})
|
||||
}
|
||||
.to_string();
|
||||
// Wait Tor service to launch.
|
||||
while Tor::is_service_starting(&self.identifier()) {
|
||||
@@ -1136,7 +1154,7 @@ impl Wallet {
|
||||
}
|
||||
|
||||
/// Handle message from the invoice issuer to send founds, return response for funds receiver.
|
||||
fn pay(&self, slate: &Slate) -> Result<Slate, Error> {
|
||||
fn pay(&self, slate: &Slate, dest: Option<SlatepackAddress>) -> Result<Slate, Error> {
|
||||
let config = self.get_config();
|
||||
let args = InitTxArgs {
|
||||
src_acct_name: None,
|
||||
@@ -1151,8 +1169,9 @@ impl Wallet {
|
||||
let slate = api.process_invoice_tx(self.keychain_mask().as_ref(), &slate, args)?;
|
||||
api.tx_lock_outputs(self.keychain_mask().as_ref(), &slate)?;
|
||||
|
||||
// Create Slatepack message response.
|
||||
let _ = self.create_slatepack_message(&slate, None)?;
|
||||
// Create Slatepack message response (kept as on-disk paste-back fallback even
|
||||
// when an auto-Tor reply is attempted by the caller).
|
||||
let _ = self.create_slatepack_message(&slate, dest)?;
|
||||
|
||||
Ok(slate)
|
||||
}
|
||||
@@ -1744,7 +1763,7 @@ fn start_sync(wallet: Wallet) -> Thread {
|
||||
/// Handle wallet task.
|
||||
async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
let send_tor = async |tx: TxLogEntry, s: &Slate, r: &SlatepackAddress| match w
|
||||
.send_tor(tx.id, &s, r)
|
||||
.send_tor(tx.id, &s, r, false)
|
||||
.await
|
||||
{
|
||||
Ok(s) => match w.finalize(&s, tx.id) {
|
||||
@@ -1769,6 +1788,23 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
};
|
||||
// Invoice-flow counterpart to send_tor. After signing an Invoice2 the payer
|
||||
// posts the slate to the merchant's foreign-api finalize_tx; the merchant
|
||||
// finalizes + broadcasts on their side, so no local finalize/post is needed.
|
||||
let pay_tor = async |tx: TxLogEntry, s: &Slate, r: &SlatepackAddress| match w
|
||||
.send_tor(tx.id, &s, r, true)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
sync_wallet_data(&w, false);
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("pay tor error: {:?}", e);
|
||||
w.on_tx_error(tx.id, Some(e));
|
||||
w.on_task_result(Some(tx), &t);
|
||||
}
|
||||
};
|
||||
match &t {
|
||||
WalletTask::OpenMessage(m) => {
|
||||
if !w.is_open() || m.is_empty() {
|
||||
@@ -1804,9 +1840,24 @@ async fn handle_task(w: &Wallet, t: WalletTask) {
|
||||
match s.state {
|
||||
SlateState::Standard1 | SlateState::Invoice1 => {
|
||||
if s.state != SlateState::Standard1 {
|
||||
if let Ok(_) = w.pay(&s) {
|
||||
// Invoice1: sign + lock outputs. If the slatepack embedded a
|
||||
// sender address and our Tor service is up, also push the
|
||||
// signed Invoice2 back to the merchant for broadcast — the
|
||||
// on-disk slatepack is still written as a paste-back fallback.
|
||||
if let Ok(signed) = w.pay(&s, dest.clone()) {
|
||||
sync_wallet_data(&w, false);
|
||||
let tx = w.retrieve_tx_by_id(None, Some(s.id));
|
||||
if let Some(addr) = dest {
|
||||
let id = w.identifier();
|
||||
if tx.is_some()
|
||||
&& (Tor::is_service_running(&id)
|
||||
|| Tor::is_service_starting(&id))
|
||||
{
|
||||
w.message_opening.store(false, Ordering::Relaxed);
|
||||
pay_tor(tx.unwrap(), &signed, &addr).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
w.on_task_result(tx, &t);
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user