Skip to content

FFI callback traits force non async #1389

Open

Description

@xstoicunicornx

Which crate is this feature request for (if any)?

payjoin-ffi

Question

Hey there,

As part of my BOSS 2026 project I am working on proving out the Javascript FFI bindings. My first step has been building out a plain node project that essentially recreates the payjoin-cli functionality just to get a feel for the bindings api (all this bindings stuff is totally foreign to me).

My question is in regards to the callback method within the CanBroadcast trait here:

pub trait CanBroadcast: Send + Sync {
fn callback(&self, tx: Vec<u8>) -> Result<bool, ForeignError>;
}

When the bindings are generated in javascript it is forcing a non async function to be used as the callback. Is it realistic to expect this to be a non async function in javascript?

My current implementation was planning to call the bitcoin-cli testmempoolaccept to implement the callback method but I do not know of a non async way of doing this. Was there better recommended way of implementing callback?

Thanks!

Activity

  1. spacebear21 commented on Mar 6, 2026

    @spacebear21
    Collaborator

    Related discussion here: #816

    tl;dr currently all callback accepting functions are sync-only. This is a known limitation that we get around in Rust by resolving the async calls inside a blocking runtime. I'm not sure if there's a similar workaround available in JS but would check if that's doable. In our JS integration tests we use an internal synchronous client wrapper.

    A better, long-term solution would be the one proposed by Armin in that issue thread.

  2. xstoicunicornx commented on Mar 6, 2026

    @xstoicunicornx
    CollaboratorAuthor

    thanks this is extremely informative!

    feel free to close this as duplicate as I think the related discussion #816 comprehensively covers this question.

  3. spacebear21 commented on Mar 6, 2026

    @spacebear21
    Collaborator

    I'll keep this open because I think the FFI surface deserves its own discussion, thanks for bringing it up!

  4. changed the title [-]Javascript FFI - CanBroadcast trait forces non async callback[/-] [+]FFI callback traits force non async[/+] on Mar 6, 2026
  5. xstoicunicornx commented on Mar 16, 2026

    @xstoicunicornx
    CollaboratorAuthor

    In case you're interested I created a node payjoin POC here:
    https://github.com/xstoicunicornx/node_payjoin

    I was able to work around the lack of async support but its a bit hacky and not sure how viable this strategy is in a production environment.

  6. spacebear21 commented on Mar 16, 2026

    @spacebear21
    Collaborator

    Nice! Yeah this seems like an issue we need to address sooner than later

  7. spacebear21 commented on Mar 19, 2026

    @spacebear21
    Collaborator

    I started going down a async callback wrapper approach but as I got to testing it in downstream languages I realized that tokio's rt-multi-thread feature that enables blocking-in-place is incompatible with wasm32-unknown-unknown, making it unviable for JS bindings 🤦‍♂️

    It seems we really need to expose a split-step API in rust-payjoin to legitimately solve this issue.

  8. xstoicunicornx commented on Mar 19, 2026

    @xstoicunicornx
    CollaboratorAuthor

    Did you get a chance to take a look at my workarounds for the receiver flow at all? Its maybe not ideal (and a bit ugly) but I think it accomplishes the same as a split step API without the need for implementing the split step API.

    https://github.com/xstoicunicornx/node_payjoin/blob/master/src/receiver.ts

    There are definitely still some things that would be needed/nice to have:

    • There would need to be some way to extract the original tx prior to calling CheckBroadcastSuitability so you can check it asynchronously and then pass that result into the synchronous callback (right now my demo is straight up skipping this validation)
    • For checkInputsNotOwned, checkNoInputsSeenBefore, identifyReceiverOutputs it might be nice to have a way to extract the inputs/outputs that need to be checked (right now I'm parsing the tx from the MaybeInputsOwned state's extractTxToScheduleBroadcast() method to find these), but its definitely possible to sufficiently validate these state transitions in Javascript with the API as is
    • For finalizeProposal method I am presigning the psbt taken from the ProvisionalProposal state's psbtToSign(), however I had to do some processing to strip out the senders signature manually, might be good to have psbtToSign() remove this itself?
  9. spacebear21 commented on Mar 19, 2026

    @spacebear21
    Collaborator

    Ohhh, on my first pass I saw the TODO comment and assumed you were skipping/hardcoding all the checks. Now I see the ownership checks and signature are actually happening further down in split step.

    IIRC we use a similar workaround for the finalizeProposal step in the Liana integration, which was the motivation for introducing psbtToSign() in the first place (cc @arminsabouri , @benalleng please correct me if i'm wrong).

    I think it accomplishes the same as a split step API without the need for implementing the split step API.

    I think the "needed/nice to have" helpers you listed are essentially the first half of implementing the split step API. The other half would be to expose a new set of state transition methods that accept the output of the checks, instead of forcing the callback hack. If you're feeling up to it, a PR adding those missing helpers you've identified would be a great start! And we can test it against your node_payjoin POC for end-to-end validation.

  10. xstoicunicornx commented on Mar 19, 2026

    @xstoicunicornx
    CollaboratorAuthor

    I would love to work on that! Still not a rust expert so I may be back with questions. Will start taking a look today.

  11. xstoicunicornx commented on Mar 23, 2026

    @xstoicunicornx
    CollaboratorAuthor

    Just finished up implementation if you want to take a quick look, just to make sure I'm not too off track.

    https://github.com/xstoicunicornx/rust-payjoin/tree/async-compat-api

    I am going to start on tests next and then bindings.

  12. spacebear21 commented on Mar 24, 2026

    @spacebear21
    Collaborator

    This looks like the right direction! Feel free to open a draft PR so that we can leave more specific feedback inline.

  13. xstoicunicornx commented on Mar 26, 2026

    @xstoicunicornx
    CollaboratorAuthor

    Just opened #1446

  14. ConorOkus commented on Apr 28, 2026

    @ConorOkus

    Heads up — we're hitting the same architectural pain on the sender side in a real browser (PDK 1.0-rc.2, uniffi-bindgen-react-native 0.30.0-1, wasm-bindgen --target web). JsonSenderSessionPersister.save and JsonSenderSessionPersisterAsync.saveAsync both trap with memory access out of bounds during FfiConverterArc::try_lift of the foreign callback, before any JS persister method runs. Will file a separate issue with the full repro and stack trace.

    The fix shape #1446 demonstrates for receive-side validation looks like it would apply directly to the send persister too — return the new event(s) from the transition, let the host persist them, feed them back into the next transition. Flagging here so the same pattern can be considered for both sides if a sender-side rework gets scoped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions