fix(nip46): give sign_event the human-approval timeout leash

Live pairing (Sep 23) proved the signer round-trip works: connect,
get_public_key and the first sign_event all succeeded against real
Amber. Subsequent signs failed because the client half used the 30s
ordinary-RPC leash for sign_event, while every sign waits on a human
approving the prompt on the signer's device. The log shows a valid
signature arriving ~61s after publication, after the waiter had been
removed: 'stale/duplicate response: no waiter ... dropped' — the user
watched failures while Amber was signing correctly.

sign_event now uses a 120s SIGN_TIMEOUT (same leash as the connect
handshake); get_public_key keeps the 30s retry-cadence timeout.
This commit is contained in:
Avi 2026-09-23 20:31:37 -05:00
commit f53bc56497

View file

@ -38,6 +38,13 @@ const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
/// How long the connect handshake can involve a human approving the app on
/// the signer's screen, so it gets a far longer leash than ordinary RPCs.
const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(120);
/// How long a `sign_event` may wait for the signer's answer. Like the
/// handshake, every sign waits for a human to notice and approve the prompt
/// on the signer's device, so it needs the same long leash rather than the
/// 30s ordinary-RPC one: live pairing (Sep 23) showed a perfectly valid
/// signature arriving after the 30s leash expired, discarded as
/// "stale/duplicate response: no waiter" while the user watched failures.
const SIGN_TIMEOUT: Duration = Duration::from_secs(120);
/// How long a pairing QR (client-initiated `nostrconnect://`) stays live
/// while a human opens their signer and scans it.
const PAIRING_TIMEOUT: Duration = Duration::from_secs(300);
@ -1153,12 +1160,16 @@ impl Nip46ClientSigner {
/// return [`SigningError`] rather than falling back to any local key. The
/// waiter is always removed from the pending map, even on timeout, so a
/// late response to an abandoned request finds nothing to wake.
///
/// Uses the human-approval leash ([`SIGN_TIMEOUT`]), not the 30s
/// ordinary-RPC one: every call here asks a human to approve on the
/// signer's device.
async fn send_remote_request(
&self,
method: &str,
params: Vec<String>,
) -> Result<String, SigningError> {
self.send_rpc(method, params, REQUEST_TIMEOUT, true).await
self.send_rpc(method, params, SIGN_TIMEOUT, true).await
}
/// Send an encrypted NIP-46 RPC and await its response.