From 9f22faf4035674408ccfbbd8d508268ac8180313 Mon Sep 17 00:00:00 2001 From: spacebear Date: Thu, 26 Feb 2026 13:34:05 -0500 Subject: [PATCH 1/2] Update uniffi-dart with named params https://github.com/Uniffi-Dart/uniffi-dart/pull/112 introduces named parameters in generated APIs. Update the uniffi-dart version and all dart tests accordingly. --- Cargo-minimal.lock | 25 +- Cargo-recent.lock | 25 +- payjoin-ffi/Cargo.toml | 2 +- .../test/test_payjoin_integration_test.dart | 254 +++++++++++------- .../dart/test/test_payjoin_unit_test.dart | 78 +++--- 5 files changed, 240 insertions(+), 144 deletions(-) diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index dbfe4859c..e19304b0f 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -775,6 +775,20 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.63", +] + [[package]] name = "cargo_metadata" version = "0.19.2" @@ -4704,7 +4718,7 @@ checksum = "c866f627c3f04c3df068b68bb2d725492caaa539dd313e2a9d26bb85b1a32f4e" dependencies = [ "anyhow", "camino", - "cargo_metadata", + "cargo_metadata 0.19.2", "clap 4.5.46", "uniffi_bindgen", "uniffi_build", @@ -4721,7 +4735,7 @@ dependencies = [ "anyhow", "askama", "camino", - "cargo_metadata", + "cargo_metadata 0.19.2", "clap 3.2.25", "extend", "fs-err 2.11.0", @@ -4739,10 +4753,11 @@ dependencies = [ [[package]] name = "uniffi-dart" version = "0.1.0" -source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca" +source = "git+https://github.com/chavic/uniffi-dart.git?rev=2078791#2078791fe1f93e7a71a8a25c9934adb54d97d52e" dependencies = [ "anyhow", "camino", + "cargo_metadata 0.18.1", "genco", "heck 0.5.0", "lazy_static", @@ -4765,7 +4780,7 @@ dependencies = [ "anyhow", "askama", "camino", - "cargo_metadata", + "cargo_metadata 0.19.2", "fs-err 2.11.0", "glob", "goblin", @@ -4809,7 +4824,7 @@ dependencies = [ [[package]] name = "uniffi_dart_macro" version = "0.1.0" -source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca" +source = "git+https://github.com/chavic/uniffi-dart.git?rev=2078791#2078791fe1f93e7a71a8a25c9934adb54d97d52e" dependencies = [ "futures", "proc-macro2", diff --git a/Cargo-recent.lock b/Cargo-recent.lock index dbfe4859c..e19304b0f 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -775,6 +775,20 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 1.0.63", +] + [[package]] name = "cargo_metadata" version = "0.19.2" @@ -4704,7 +4718,7 @@ checksum = "c866f627c3f04c3df068b68bb2d725492caaa539dd313e2a9d26bb85b1a32f4e" dependencies = [ "anyhow", "camino", - "cargo_metadata", + "cargo_metadata 0.19.2", "clap 4.5.46", "uniffi_bindgen", "uniffi_build", @@ -4721,7 +4735,7 @@ dependencies = [ "anyhow", "askama", "camino", - "cargo_metadata", + "cargo_metadata 0.19.2", "clap 3.2.25", "extend", "fs-err 2.11.0", @@ -4739,10 +4753,11 @@ dependencies = [ [[package]] name = "uniffi-dart" version = "0.1.0" -source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca" +source = "git+https://github.com/chavic/uniffi-dart.git?rev=2078791#2078791fe1f93e7a71a8a25c9934adb54d97d52e" dependencies = [ "anyhow", "camino", + "cargo_metadata 0.18.1", "genco", "heck 0.5.0", "lazy_static", @@ -4765,7 +4780,7 @@ dependencies = [ "anyhow", "askama", "camino", - "cargo_metadata", + "cargo_metadata 0.19.2", "fs-err 2.11.0", "glob", "goblin", @@ -4809,7 +4824,7 @@ dependencies = [ [[package]] name = "uniffi_dart_macro" version = "0.1.0" -source = "git+https://github.com/Uniffi-Dart/uniffi-dart.git?rev=f830323#f830323646fb6fbca89f9798dcf425f339f166ca" +source = "git+https://github.com/chavic/uniffi-dart.git?rev=2078791#2078791fe1f93e7a71a8a25c9934adb54d97d52e" dependencies = [ "futures", "proc-macro2", diff --git a/payjoin-ffi/Cargo.toml b/payjoin-ffi/Cargo.toml index 8cb69c299..78ba7782a 100644 --- a/payjoin-ffi/Cargo.toml +++ b/payjoin-ffi/Cargo.toml @@ -34,7 +34,7 @@ thiserror = "2.0.14" tokio = { version = "1.47.1", features = ["full"], optional = true } uniffi = { version = "0.30.0", features = ["cli"] } uniffi-bindgen-cs = { git = "https://github.com/chavic/uniffi-bindgen-cs.git", rev = "878a3d269eacce64beadcd336ade0b7c8da09824", optional = true } -uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart.git", rev = "f830323", optional = true } +uniffi-dart = { git = "https://github.com/chavic/uniffi-dart.git", rev = "2078791", optional = true } url = "2.5.4" # getrandom is ignored here because it's required by the wasm_js feature diff --git a/payjoin-ffi/dart/test/test_payjoin_integration_test.dart b/payjoin-ffi/dart/test/test_payjoin_integration_test.dart index e729de29a..e83b653be 100644 --- a/payjoin-ffi/dart/test/test_payjoin_integration_test.dart +++ b/payjoin-ffi/dart/test/test_payjoin_integration_test.dart @@ -69,7 +69,10 @@ class MempoolAcceptanceCallback implements payjoin.CanBroadcast { bool callback(Uint8List tx) { try { final hexTx = bytesToHex(tx); - final resultJson = connection.call("testmempoolaccept", ['["$hexTx"]']); + final resultJson = connection.call( + method: "testmempoolaccept", + params: ['["$hexTx"]'], + ); final decoded = jsonDecode(resultJson); return decoded[0]['allowed'] == true; } catch (e) { @@ -93,7 +96,10 @@ class IsScriptOwnedCallback implements payjoin.IsScriptOwned { try { final scriptHex = hex.encode(script); final decodedScript = jsonDecode( - connection.call("decodescript", [jsonEncode(scriptHex)]), + connection.call( + method: "decodescript", + params: [jsonEncode(scriptHex)], + ), ); final candidates = []; @@ -123,7 +129,7 @@ class IsScriptOwnedCallback implements payjoin.IsScriptOwned { for (final addr in candidates) { final info = jsonDecode( - connection.call("getaddressinfo", [jsonEncode(addr)]), + connection.call(method: "getaddressinfo", params: [jsonEncode(addr)]), ); if (info["ismine"] == true) { return true; @@ -154,7 +160,9 @@ class ProcessPsbtCallback implements payjoin.ProcessPsbt { @override String callback(String psbt) { - final res = jsonDecode(connection.call("walletprocesspsbt", [psbt])); + final res = jsonDecode( + connection.call(method: "walletprocesspsbt", params: [psbt]), + ); return res["psbt"]; } } @@ -166,10 +174,10 @@ payjoin.Initialized create_receiver_context( InMemoryReceiverPersister persister, ) { var receiver = payjoin.ReceiverBuilder( - address, - directory, - ohttp_keys, - ).build().save(persister); + address: address, + directory: directory, + ohttpKeys: ohttp_keys, + ).build().save(persister: persister); return receiver; } @@ -177,29 +185,32 @@ String build_sweep_psbt(payjoin.RpcClient sender, payjoin.PjUri pj_uri) { var outputs = {}; outputs[pj_uri.address()] = 50; var psbt = jsonDecode( - sender.call("walletcreatefundedpsbt", [ - jsonEncode([]), - jsonEncode(outputs), - jsonEncode(0), - jsonEncode({ - "lockUnspents": true, - "fee_rate": 10, - "subtractFeeFromOutputs": [0], - }), - ]), + sender.call( + method: "walletcreatefundedpsbt", + params: [ + jsonEncode([]), + jsonEncode(outputs), + jsonEncode(0), + jsonEncode({ + "lockUnspents": true, + "fee_rate": 10, + "subtractFeeFromOutputs": [0], + }), + ], + ), )["psbt"]; return jsonDecode( - sender.call("walletprocesspsbt", [ - psbt, - jsonEncode(true), - jsonEncode("ALL"), - jsonEncode(false), - ]), + sender.call( + method: "walletprocesspsbt", + params: [psbt, jsonEncode(true), jsonEncode("ALL"), jsonEncode(false)], + ), )["psbt"]; } List get_inputs(payjoin.RpcClient rpc_connection) { - var utxos = jsonDecode(rpc_connection.call("listunspent", [])); + var utxos = jsonDecode( + rpc_connection.call(method: "listunspent", params: []), + ); List inputs = []; for (var utxo in utxos) { final txid = utxo["txid"] as String; @@ -211,14 +222,23 @@ List get_inputs(payjoin.RpcClient rpc_connection) { final amountSat = (amountBtc * 100000000).round(); final txin = payjoin.PlainTxIn( - payjoin.PlainOutPoint(txid, vout), - Uint8List(0), - 0, - [], + previousOutput: payjoin.PlainOutPoint(txid: txid, vout: vout), + scriptSig: Uint8List(0), + sequence: 0, + witness: [], + ); + final witnessUtxo = payjoin.PlainTxOut( + valueSat: amountSat, + scriptPubkey: scriptPubKey, + ); + final psbt_in = payjoin.PlainPsbtInput( + witnessUtxo: witnessUtxo, + redeemScript: null, + witnessScript: null, + ); + inputs.add( + payjoin.InputPair(txin: txin, psbtin: psbt_in, expectedWeight: null), ); - final witnessUtxo = payjoin.PlainTxOut(amountSat, scriptPubKey); - final psbt_in = payjoin.PlainPsbtInput(witnessUtxo, null, null); - inputs.add(payjoin.InputPair(txin, psbt_in, null)); } return inputs; @@ -229,8 +249,8 @@ Future process_provisional_proposal( InMemoryReceiverPersister recv_persister, ) async { final payjoin_proposal = proposal - .finalizeProposal(ProcessPsbtCallback(receiver)) - .save(recv_persister); + .finalizeProposal(processPsbt: ProcessPsbtCallback(receiver)) + .save(persister: recv_persister); return payjoin.PayjoinProposalReceiveSession(payjoin_proposal); } @@ -238,7 +258,9 @@ Future process_wants_fee_range( payjoin.WantsFeeRange proposal, InMemoryReceiverPersister recv_persister, ) async { - final wants_fee_range = proposal.applyFeeRange(1, 10).save(recv_persister); + final wants_fee_range = proposal + .applyFeeRange(minFeeRateSatPerVb: 1, maxEffectiveFeeRateSatPerVb: 10) + .save(persister: recv_persister); return await process_provisional_proposal(wants_fee_range, recv_persister); } @@ -247,9 +269,9 @@ Future process_wants_inputs( InMemoryReceiverPersister recv_persister, ) async { final provisional_proposal = proposal - .contributeInputs(get_inputs(receiver)) + .contributeInputs(replacementInputs: get_inputs(receiver)) .commitInputs() - .save(recv_persister); + .save(persister: recv_persister); return await process_wants_fee_range(provisional_proposal, recv_persister); } @@ -257,7 +279,7 @@ Future process_wants_outputs( payjoin.WantsOutputs proposal, InMemoryReceiverPersister recv_persister, ) async { - final wants_inputs = proposal.commitOutputs().save(recv_persister); + final wants_inputs = proposal.commitOutputs().save(persister: recv_persister); return await process_wants_inputs(wants_inputs, recv_persister); } @@ -266,8 +288,10 @@ Future process_outputs_unknown( InMemoryReceiverPersister recv_persister, ) async { final wants_outputs = proposal - .identifyReceiverOutputs(IsScriptOwnedCallback(receiver)) - .save(recv_persister); + .identifyReceiverOutputs( + isReceiverOutput: IsScriptOwnedCallback(receiver), + ) + .save(persister: recv_persister); return await process_wants_outputs(wants_outputs, recv_persister); } @@ -276,8 +300,8 @@ Future process_maybe_inputs_seen( InMemoryReceiverPersister recv_persister, ) async { final outputs_unknown = proposal - .checkNoInputsSeenBefore(CheckInputsNotSeenCallback(receiver)) - .save(recv_persister); + .checkNoInputsSeenBefore(isKnown: CheckInputsNotSeenCallback(receiver)) + .save(persister: recv_persister); return await process_outputs_unknown(outputs_unknown, recv_persister); } @@ -286,8 +310,8 @@ Future process_maybe_inputs_owned( InMemoryReceiverPersister recv_persister, ) async { final maybe_inputs_owned = proposal - .checkInputsNotOwned(IsScriptOwnedCallback(receiver)) - .save(recv_persister); + .checkInputsNotOwned(isOwned: IsScriptOwnedCallback(receiver)) + .save(persister: recv_persister); return await process_maybe_inputs_seen(maybe_inputs_owned, recv_persister); } @@ -296,8 +320,11 @@ Future process_unchecked_proposal( InMemoryReceiverPersister recv_persister, ) async { final unchecked_proposal = proposal - .checkBroadcastSuitability(null, MempoolAcceptanceCallback(receiver)) - .save(recv_persister); + .checkBroadcastSuitability( + minFeeRate: null, + canBroadcast: MempoolAcceptanceCallback(receiver), + ) + .save(persister: recv_persister); return await process_maybe_inputs_owned(unchecked_proposal, recv_persister); } @@ -307,15 +334,15 @@ Future retrieve_receiver_proposal( String ohttp_relay, ) async { var agent = http.Client(); - var request = receiver.createPollRequest(ohttp_relay); + var request = receiver.createPollRequest(ohttpRelay: ohttp_relay); var response = await agent.post( Uri.parse(request.request.url), headers: {"Content-Type": request.request.contentType}, body: request.request.body, ); var res = receiver - .processResponse(response.bodyBytes, request.clientResponse) - .save(recv_persister); + .processResponse(body: response.bodyBytes, ctx: request.clientResponse) + .save(persister: recv_persister); if (res is payjoin.StasisInitializedTransitionOutcome) { return null; @@ -378,35 +405,47 @@ void main() { final tooLargeAmount = 21000000 * 100000000 + 1; // Invalid outpoint should fail before amount checks. final txinInvalid = payjoin.PlainTxIn( - payjoin.PlainOutPoint("00" * 64, 0), - Uint8List(0), - 0, - [], + previousOutput: payjoin.PlainOutPoint(txid: "00" * 64, vout: 0), + scriptSig: Uint8List(0), + sequence: 0, + witness: [], ); final psbtInDummy = payjoin.PlainPsbtInput( - payjoin.PlainTxOut(1, Uint8List.fromList([0x6a])), - null, - null, + witnessUtxo: payjoin.PlainTxOut( + valueSat: 1, + scriptPubkey: Uint8List.fromList([0x6a]), + ), + redeemScript: null, + witnessScript: null, ); expect( - () => payjoin.InputPair(txinInvalid, psbtInDummy, null), + () => payjoin.InputPair( + txin: txinInvalid, + psbtin: psbtInDummy, + expectedWeight: null, + ), throwsA(isA()), ); final txin = payjoin.PlainTxIn( // valid 32-byte txid so we exercise amount overflow instead of outpoint parsing - payjoin.PlainOutPoint("00" * 32, 0), - Uint8List(0), - 0, - [], + previousOutput: payjoin.PlainOutPoint(txid: "00" * 32, vout: 0), + scriptSig: Uint8List(0), + sequence: 0, + witness: [], ); final txout = payjoin.PlainTxOut( - tooLargeAmount, - Uint8List.fromList([0x6a]), + valueSat: tooLargeAmount, + scriptPubkey: Uint8List.fromList([0x6a]), + ); + final psbtIn = payjoin.PlainPsbtInput( + witnessUtxo: txout, + redeemScript: null, + witnessScript: null, ); - final psbtIn = payjoin.PlainPsbtInput(txout, null, null); expect( - () => payjoin.InputPair(txin, psbtIn, null), + () => + payjoin.InputPair(txin: txin, psbtin: psbtIn, expectedWeight: null), throwsA(isA()), ); @@ -414,31 +453,32 @@ void main() { final envLocal = test_utils.initBitcoindSenderReceiver(); final receiverRpc = envLocal.getReceiver(); final receiverAddress = - jsonDecode(receiverRpc.call("getnewaddress", [])) as String; + jsonDecode(receiverRpc.call(method: "getnewaddress", params: [])) + as String; final services = test_utils.TestServices.initialize(); services.waitForServicesReady(); final directory = services.directoryUrl(); final ohttpKeys = services.fetchOhttpKeys(); final recvPersister = InMemoryReceiverPersister("prim"); final pjUri = payjoin.ReceiverBuilder( - receiverAddress, - directory, - ohttpKeys, - ).build().save(recvPersister).pjUri(); + address: receiverAddress, + directory: directory, + ohttpKeys: ohttpKeys, + ).build().save(persister: recvPersister).pjUri(); final psbt = test_utils.originalPsbt(); // Large enough to overflow fee * weight but still parsable as Dart int. const overflowFeeRate = 5000000000000; // sat/kwu expect( () => payjoin.SenderBuilder( - psbt, - pjUri, - ).buildRecommended(overflowFeeRate), + psbt: psbt, + uri: pjUri, + ).buildRecommended(minFeeRate: overflowFeeRate), throwsA(isA()), ); expect( - () => pjUri.setAmountSats(tooLargeAmount), + () => pjUri.setAmountSats(amountSats: tooLargeAmount), throwsA(isA()), ); }); @@ -449,7 +489,8 @@ void main() { receiver = env.getReceiver(); sender = env.getSender(); var receiver_address = - jsonDecode(receiver.call("getnewaddress", [])) as String; + jsonDecode(receiver.call(method: "getnewaddress", params: [])) + as String; var services = test_utils.TestServices.initialize(); services.waitForServicesReady(); @@ -481,11 +522,11 @@ void main() { var pj_uri = session.pjUri(); var psbt = build_sweep_psbt(sender, pj_uri); payjoin.WithReplyKey req_ctx = payjoin.SenderBuilder( - psbt, - pj_uri, - ).buildRecommended(1000).save(sender_persister); + psbt: psbt, + uri: pj_uri, + ).buildRecommended(minFeeRate: 1000).save(persister: sender_persister); payjoin.RequestOhttpContext request = req_ctx.createV2PostRequest( - ohttp_relay, + ohttpRelay: ohttp_relay, ); var response = await agent.post( Uri.parse(request.request.url), @@ -493,8 +534,11 @@ void main() { body: request.request.body, ); payjoin.PollingForProposal send_ctx = req_ctx - .processResponse(response.bodyBytes, request.ohttpCtx) - .save(sender_persister); + .processResponse( + response: response.bodyBytes, + postCtx: request.ohttpCtx, + ) + .save(persister: sender_persister); // POST Original PSBT // ********************** @@ -513,7 +557,7 @@ void main() { payjoin.PayjoinProposal proposal = (payjoin_proposal as payjoin.PayjoinProposalReceiveSession).inner; payjoin.RequestResponse request_response = proposal.createPostRequest( - ohttp_relay, + ohttpRelay: ohttp_relay, ); var fallback_response = await agent.post( Uri.parse(request_response.request.url), @@ -521,8 +565,8 @@ void main() { body: request_response.request.body, ); proposal.processResponse( - fallback_response.bodyBytes, - request_response.clientResponse, + body: fallback_response.bodyBytes, + ohttpContext: request_response.clientResponse, ); // ********************** @@ -533,7 +577,7 @@ void main() { var attempts = 0; while (true) { payjoin.RequestOhttpContext ohttp_context_request = send_ctx - .createPollRequest(ohttp_relay); + .createPollRequest(ohttpRelay: ohttp_relay); var final_response = await agent.post( Uri.parse(ohttp_context_request.request.url), headers: {"Content-Type": ohttp_context_request.request.contentType}, @@ -541,10 +585,10 @@ void main() { ); poll_outcome = send_ctx .processResponse( - final_response.bodyBytes, - ohttp_context_request.ohttpCtx, + response: final_response.bodyBytes, + ohttpCtx: ohttp_context_request.ohttpCtx, ) - .save(sender_persister); + .save(persister: sender_persister); if (poll_outcome is payjoin.ProgressPollingForProposalTransitionOutcome) { @@ -561,23 +605,41 @@ void main() { final progressOutcome = poll_outcome as payjoin.ProgressPollingForProposalTransitionOutcome; var payjoin_psbt = jsonDecode( - sender.call("walletprocesspsbt", [progressOutcome.psbtBase64]), + sender.call( + method: "walletprocesspsbt", + params: [progressOutcome.psbtBase64], + ), )["psbt"]; var final_psbt = jsonDecode( - sender.call("finalizepsbt", [payjoin_psbt, jsonEncode(false)]), + sender.call( + method: "finalizepsbt", + params: [payjoin_psbt, jsonEncode(false)], + ), )["psbt"]; var final_tx_hex = jsonDecode( - sender.call("finalizepsbt", [final_psbt, jsonEncode(true)]), + sender.call( + method: "finalizepsbt", + params: [final_psbt, jsonEncode(true)], + ), )["hex"]; - sender.call("sendrawtransaction", [jsonEncode(final_tx_hex)]); + sender.call( + method: "sendrawtransaction", + params: [jsonEncode(final_tx_hex)], + ); // Check resulting transaction and balances var decodedTx = jsonDecode( - sender.call("decoderawtransaction", [jsonEncode(final_tx_hex)]), + sender.call( + method: "decoderawtransaction", + params: [jsonEncode(final_tx_hex)], + ), ); var network_fees = (jsonDecode( - sender.call("decodepsbt", [jsonEncode(final_psbt)]), + sender.call( + method: "decodepsbt", + params: [jsonEncode(final_psbt)], + ), )["fee"] as num) .toDouble(); @@ -586,11 +648,11 @@ void main() { expect(decodedTx["vout"].length, 1); expect( jsonDecode( - receiver.call("getbalances", []), + receiver.call(method: "getbalances", params: []), )["mine"]["untrusted_pending"], 100 - network_fees, ); - expect(jsonDecode(sender.call("getbalance", [])), 0.0); + expect(jsonDecode(sender.call(method: "getbalance", params: [])), 0.0); }, timeout: const Timeout(Duration(minutes: 5))); }); } diff --git a/payjoin-ffi/dart/test/test_payjoin_unit_test.dart b/payjoin-ffi/dart/test/test_payjoin_unit_test.dart index f778c80ca..3b56b729d 100644 --- a/payjoin-ffi/dart/test/test_payjoin_unit_test.dart +++ b/payjoin-ffi/dart/test/test_payjoin_unit_test.dart @@ -103,7 +103,7 @@ void main() { test('Test todo url encoded', () { var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - final result = payjoin.Url.parse(uri); + final result = payjoin.Url.parse(input: uri); expect( result, isA(), @@ -114,14 +114,14 @@ void main() { test('Test valid url', () { var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - final result = payjoin.Url.parse(uri); + final result = payjoin.Url.parse(input: uri); expect(result, isA(), reason: "pj is not a valid url"); }); test('Test missing amount', () { var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://testnet.demo.btcpayserver.org/BTC/pj"; - final result = payjoin.Url.parse(uri); + final result = payjoin.Url.parse(input: uri); expect(result, isA(), reason: "missing amount should be ok"); }); @@ -141,7 +141,7 @@ void main() { for (final pj in pjs) { final uri = "$address?amount=1&pj=$pj"; try { - payjoin.Url.parse(uri); + payjoin.Url.parse(input: uri); } catch (e) { fail("Failed to create a valid Uri for $uri. Error: $e"); } @@ -154,17 +154,17 @@ void main() { test("Test receiver persistence", () { var persister = InMemoryReceiverPersister("1"); payjoin.ReceiverBuilder( - "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4", - "https://example.com", - payjoin.OhttpKeys.decode( - Uint8List.fromList( + address: "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4", + directory: "https://example.com", + ohttpKeys: payjoin.OhttpKeys.decode( + bytes: Uint8List.fromList( hex.decode( "01001604ba48c49c3d4a92a3ad00ecc63a024da10ced02180c73ec12d8a7ad2cc91bb483824fe2bee8d28bfe2eb2fc6453bc4d31cd851e8a6540e86c5382af588d370957000400010003", ), ), ), - ).build().save(persister); - final result = payjoin.replayReceiverEventLog(persister); + ).build().save(persister: persister); + final result = payjoin.replayReceiverEventLog(persister: persister); expect( result.state(), isA(), @@ -175,25 +175,27 @@ void main() { test("Test sender persistence", () { var receiver_persister = InMemoryReceiverPersister("1"); var receiver = payjoin.ReceiverBuilder( - "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK", - "https://example.com", - payjoin.OhttpKeys.decode( - Uint8List.fromList( + address: "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK", + directory: "https://example.com", + ohttpKeys: payjoin.OhttpKeys.decode( + bytes: Uint8List.fromList( hex.decode( "01001604ba48c49c3d4a92a3ad00ecc63a024da10ced02180c73ec12d8a7ad2cc91bb483824fe2bee8d28bfe2eb2fc6453bc4d31cd851e8a6540e86c5382af588d370957000400010003", ), ), ), - ).build().save(receiver_persister); + ).build().save(persister: receiver_persister); var uri = receiver.pjUri(); var sender_persister = InMemorySenderPersister("1"); var psbt = payjoin.originalPsbt(); payjoin.SenderBuilder( - psbt, - uri, - ).buildRecommended(1000).save(sender_persister); - final senderResult = payjoin.replaySenderEventLog(sender_persister); + psbt: psbt, + uri: uri, + ).buildRecommended(minFeeRate: 1000).save(persister: sender_persister); + final senderResult = payjoin.replaySenderEventLog( + persister: sender_persister, + ); expect( senderResult.state(), isA(), @@ -206,17 +208,19 @@ void main() { test("Test receiver async persistence", () async { var persister = InMemoryReceiverPersisterAsync("1"); await payjoin.ReceiverBuilder( - "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4", - "https://example.com", - payjoin.OhttpKeys.decode( - Uint8List.fromList( + address: "tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4", + directory: "https://example.com", + ohttpKeys: payjoin.OhttpKeys.decode( + bytes: Uint8List.fromList( hex.decode( "01001604ba48c49c3d4a92a3ad00ecc63a024da10ced02180c73ec12d8a7ad2cc91bb483824fe2bee8d28bfe2eb2fc6453bc4d31cd851e8a6540e86c5382af588d370957000400010003", ), ), ), - ).build().saveAsync(persister); - final result = await payjoin.replayReceiverEventLogAsync(persister); + ).build().saveAsync(persister: persister); + final result = await payjoin.replayReceiverEventLogAsync( + persister: persister, + ); expect( result.state(), isA(), @@ -227,26 +231,25 @@ void main() { test("Test sender async persistence", () async { var receiver_persister = InMemoryReceiverPersisterAsync("1"); var receiver = await payjoin.ReceiverBuilder( - "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK", - "https://example.com", - payjoin.OhttpKeys.decode( - Uint8List.fromList( + address: "2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK", + directory: "https://example.com", + ohttpKeys: payjoin.OhttpKeys.decode( + bytes: Uint8List.fromList( hex.decode( "01001604ba48c49c3d4a92a3ad00ecc63a024da10ced02180c73ec12d8a7ad2cc91bb483824fe2bee8d28bfe2eb2fc6453bc4d31cd851e8a6540e86c5382af588d370957000400010003", ), ), ), - ).build().saveAsync(receiver_persister); + ).build().saveAsync(persister: receiver_persister); var uri = receiver.pjUri(); var sender_persister = InMemorySenderPersisterAsync("1"); var psbt = payjoin.originalPsbt(); - await payjoin.SenderBuilder( - psbt, - uri, - ).buildRecommended(1000).saveAsync(sender_persister); + await payjoin.SenderBuilder(psbt: psbt, uri: uri) + .buildRecommended(minFeeRate: 1000) + .saveAsync(persister: sender_persister); final senderResult = await payjoin.replaySenderEventLogAsync( - sender_persister, + persister: sender_persister, ); expect( senderResult.state(), @@ -257,10 +260,11 @@ void main() { test("Validation sender builder rejects bad psbt", () { final uri = payjoin.Uri.parse( - "bitcoin:tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4?pj=https://example.com/pj", + uri: + "bitcoin:tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4?pj=https://example.com/pj", ).checkPjSupported(); expect( - () => payjoin.SenderBuilder("not-a-psbt", uri), + () => payjoin.SenderBuilder(psbt: "not-a-psbt", uri: uri), throwsA(isA()), ); }); From d06394ed41479fc79f75a6122e34c9848e8eb2cd Mon Sep 17 00:00:00 2001 From: chavic Date: Thu, 26 Feb 2026 22:32:36 +0200 Subject: [PATCH 2/2] Pin C# CI to .NET 8 SDK Recent ubuntu-latest image updates changed toolchain selection in the C# workflow. Even with setup-dotnet requesting 8.0.x, dotnet test can run under SDK 10 and fail with MSB3030 when apphost is missing. Add a csharp/global.json to pin SDK resolution to .NET 8 and keep test build behavior stable across runner image changes. Also print dotnet version and installed SDKs in CI logs so future drift is immediately visible. --- .github/workflows/csharp.yml | 5 +++++ payjoin-ffi/csharp/global.json | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 payjoin-ffi/csharp/global.json diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index 973a46160..f9dae66db 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -31,6 +31,11 @@ jobs: with: dotnet-version: "8.0.x" + - name: Verify .NET SDK + run: | + dotnet --version + dotnet --list-sdks + - name: Generate bindings and binaries (unix) if: matrix.os != 'windows-latest' run: bash ./scripts/generate_bindings.sh diff --git a/payjoin-ffi/csharp/global.json b/payjoin-ffi/csharp/global.json new file mode 100644 index 000000000..391ba3c2a --- /dev/null +++ b/payjoin-ffi/csharp/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "8.0.100", + "rollForward": "latestFeature" + } +}