You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every wallet that integrated payjoin runs two parsers over one URI string: its own BIP 21 parser for display and routing, and payjoin-ffi's Uri for the protocol. The bugs that produces are the same everywhere. + decoded as a space, endpoints dropped on reparse, pj= built by string concatenation, and none of them can parse a body-less URI, which is what a silent payment URI is.
The fix is one parser that wallets reach through the bindings they already load. Almost nobody consumes this from Rust. The Rust parser is scoped in payjoin/bitcoin_uri#12; this issue tracks the part that reaches wallets, which is payjoin-ffi's Uri surface.
PjParam::from_uri.PjParam::from_uri(&bitcoin_uri::Uri<impl NetworkValidation>) -> Result<Option<PjParam>, PjParseError> plus pjos handling, behind an optional bitcoin-uri feature so bitcoin_uri version changes stay out of payjoin's semver surface until that crate is 1.0. This replaces the private adapter impls in payjoin/src/core/uri/mod.rs, which implement traits 0.2 removes. A malformed pj becomes an error from this call rather than a failure of the whole URI parse, so Cove can delete its strip-and-retry workaround.
Bump the dependency and rebase Uri / PjUri on 0.2.Insulate bitcoin uri #1756 moved the bitcoin_uri types behind newtypes, so this is a minor for payjoin. One decision first: Uri::address() returns &Address and a body-less URI has none. Either a new Option-returning accessor with the existing one kept, or a type split where v1/v2 PjUri requires a body and the silent-payment static form is its own type.
payjoin-ffi Uri becomes the full parser. This is the deliverable wallets can use. Additive on the existing object: params() as an ordered list of {key, baseKey, required, rawValue, value} records, get(baseKey), getAll(baseKey), instructions() as a record enum in document order, unsatisfiedRequirements(understood), sourceStr(), isValidForNetwork(network), and a UriBuilder object. Typed errors, never bare strings. PjParam.fromUri(uri) on the same object. No new namespace and no new native library, so nothing depends on external-type support in the C# or Dart generators.
Emit through the builder. Receiver-side URI construction goes through bitcoin_uri::UriBuilder so encoding is RFC 3986 in one place, in Rust and in the bindings.
Static-session parser. The silent-payment static session URI is body-less (bitcoin:?sp=…&pj=…). That parser has only been exercised against body-address URIs and needs a follow-up once 0.2 lands.
Not in this issue: a standalone bitcoin-uri-ffi crate. Every wallet this is for already loads payjoin-ffi. A separate crate waits until a wallet that does not use payjoin asks for one.
What done looks like
A wallet's bitcoin: branch collapses to one Uri.parse call plus handoffs: PjParam.fromUri for payjoin, the invoice string from instructions() to whatever BOLT 11 decoder it already has, the sp string to its silent payments library. The wallet keeps its scheme dispatcher for liquid:, LNURL, PSBT, and bare pasted strings. It deletes its BIP 21 parser, its + workaround, its network sniffing, and its hand-built pj=. The measure is lines deleted in Bull Bitcoin, Cake, and Wasabi. Migrations themselves are tracked in integrations-tracker.
Acceptance
rust-payjoin depends on bitcoin_uri 0.2 with no change to its own public API beyond additions
PjParam::from_uri exists and a URI with a malformed pj still parses as a plain Uri
No bitcoin_uri trait impls remain in rust-payjoin
bitcoin:?sp=…&pj=… parses through the payjoin Uri newtype and through payjoin-ffi
payjoin-ffi Uri exposes params, instructions, requirements, source string, network check, and a builder, with typed errors
Receiver-emitted URIs are produced by UriBuilder
A + inside a pj value survives Dart and C# round trips unchanged
Every wallet that integrated payjoin runs two parsers over one URI string: its own BIP 21 parser for display and routing, and payjoin-ffi's
Urifor the protocol. The bugs that produces are the same everywhere.+decoded as a space, endpoints dropped on reparse,pj=built by string concatenation, and none of them can parse a body-less URI, which is what a silent payment URI is.The fix is one parser that wallets reach through the bindings they already load. Almost nobody consumes this from Rust. The Rust parser is scoped in payjoin/bitcoin_uri#12; this issue tracks the part that reaches wallets, which is payjoin-ffi's
Urisurface.Order of work
PjParam::from_uri.PjParam::from_uri(&bitcoin_uri::Uri<impl NetworkValidation>) -> Result<Option<PjParam>, PjParseError>pluspjoshandling, behind an optionalbitcoin-urifeature sobitcoin_uriversion changes stay out of payjoin's semver surface until that crate is 1.0. This replaces the private adapter impls inpayjoin/src/core/uri/mod.rs, which implement traits 0.2 removes. A malformedpjbecomes an error from this call rather than a failure of the whole URI parse, so Cove can delete its strip-and-retry workaround.Uri/PjUrion 0.2. Insulate bitcoin uri #1756 moved thebitcoin_uritypes behind newtypes, so this is a minor for payjoin. One decision first:Uri::address()returns&Addressand a body-less URI has none. Either a newOption-returning accessor with the existing one kept, or a type split where v1/v2PjUrirequires a body and the silent-payment static form is its own type.Uribecomes the full parser. This is the deliverable wallets can use. Additive on the existing object:params()as an ordered list of{key, baseKey, required, rawValue, value}records,get(baseKey),getAll(baseKey),instructions()as a record enum in document order,unsatisfiedRequirements(understood),sourceStr(),isValidForNetwork(network), and aUriBuilderobject. Typed errors, never bare strings.PjParam.fromUri(uri)on the same object. No new namespace and no new native library, so nothing depends on external-type support in the C# or Dart generators.bitcoin_uri::UriBuilderso encoding is RFC 3986 in one place, in Rust and in the bindings.bitcoin:?sp=…&pj=…). That parser has only been exercised against body-address URIs and needs a follow-up once 0.2 lands.Not in this issue: a standalone
bitcoin-uri-fficrate. Every wallet this is for already loads payjoin-ffi. A separate crate waits until a wallet that does not use payjoin asks for one.What done looks like
A wallet's
bitcoin:branch collapses to oneUri.parsecall plus handoffs:PjParam.fromUrifor payjoin, the invoice string frominstructions()to whatever BOLT 11 decoder it already has, thespstring to its silent payments library. The wallet keeps its scheme dispatcher forliquid:, LNURL, PSBT, and bare pasted strings. It deletes its BIP 21 parser, its+workaround, its network sniffing, and its hand-builtpj=. The measure is lines deleted in Bull Bitcoin, Cake, and Wasabi. Migrations themselves are tracked in integrations-tracker.Acceptance
bitcoin_uri0.2 with no change to its own public API beyond additionsPjParam::from_uriexists and a URI with a malformedpjstill parses as a plainUribitcoin_uritrait impls remain in rust-payjoinbitcoin:?sp=…&pj=…parses through the payjoinUrinewtype and through payjoin-ffiUriexposes params, instructions, requirements, source string, network check, and a builder, with typed errorsUriBuilder+inside apjvalue survives Dart and C# round trips unchanged