Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions Cargo-minimal.lock
Original file line number Diff line number Diff line change
Expand Up @@ -470,16 +470,6 @@ dependencies = [
"serde",
]

[[package]]
name = "bitcoin-ffi"
version = "0.1.3"
source = "git+https://github.com/bitcoindevkit/bitcoin-ffi?rev=39cc12b#39cc12bd32d6adf889b48354adcdb0f3c475aad2"
dependencies = [
"bitcoin 0.32.7",
"thiserror 1.0.63",
"uniffi",
]

[[package]]
name = "bitcoin-hpke"
version = "0.13.0"
Expand Down Expand Up @@ -2525,7 +2515,6 @@ name = "payjoin-ffi"
version = "0.24.0"
dependencies = [
"bdk",
"bitcoin-ffi",
"bitcoin-ohttp",
"getrandom 0.2.15",
"lazy_static",
Expand Down
11 changes: 0 additions & 11 deletions Cargo-recent.lock
Original file line number Diff line number Diff line change
Expand Up @@ -470,16 +470,6 @@ dependencies = [
"serde",
]

[[package]]
name = "bitcoin-ffi"
version = "0.1.3"
source = "git+https://github.com/bitcoindevkit/bitcoin-ffi?rev=39cc12b#39cc12bd32d6adf889b48354adcdb0f3c475aad2"
dependencies = [
"bitcoin 0.32.7",
"thiserror 1.0.63",
"uniffi",
]

[[package]]
name = "bitcoin-hpke"
version = "0.13.0"
Expand Down Expand Up @@ -2525,7 +2515,6 @@ name = "payjoin-ffi"
version = "0.24.0"
dependencies = [
"bdk",
"bitcoin-ffi",
"bitcoin-ohttp",
"getrandom 0.2.15",
"lazy_static",
Expand Down
3 changes: 1 addition & 2 deletions payjoin-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"

[dependencies]
bitcoin-ffi = { git = "https://github.com/bitcoindevkit/bitcoin-ffi", rev = "39cc12b" }
getrandom = "0.2"
lazy_static = "1.5.0"
ohttp = { package = "bitcoin-ohttp", version = "0.6.0" }
Expand All @@ -32,7 +31,7 @@ serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.142"
thiserror = "2.0.14"
tokio = { version = "1.47.1", features = ["full"], optional = true }
uniffi = { version = "0.30.0" }
uniffi = { version = "0.30.0", features = ["cli"] }
uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart.git", rev = "5bdcc79", optional = true }
url = "2.5.4"

Expand Down
117 changes: 75 additions & 42 deletions payjoin-ffi/dart/test/test_payjoin_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:test/test.dart';
import "package:convert/convert.dart";

import "package:payjoin/payjoin.dart" as payjoin;
import "package:payjoin/bitcoin.dart" as bitcoin;

late payjoin.BitcoindEnv env;
late payjoin.BitcoindInstance bitcoind;
Expand Down Expand Up @@ -91,18 +90,46 @@ class IsScriptOwnedCallback implements payjoin.IsScriptOwned {
@override
bool callback(Uint8List script) {
try {
final scriptObj = bitcoin.Script(script);
final address = bitcoin.Address.fromScript(
scriptObj,
bitcoin.Network.regtest,
final scriptHex = hex.encode(script);
final decodedScript = jsonDecode(
connection.call("decodescript", [jsonEncode(scriptHex)]),
);
// This is a hack due to toString() not being exposed by dart FFI
final address_str = address.toQrUri().split(":")[1];
final result = connection.call("getaddressinfo", [address_str]);
final decoded = jsonDecode(result);
return decoded["ismine"] == true;

final candidates = <String>[];
final addressField = decodedScript["address"];
if (addressField is String) {
candidates.add(addressField);
}
final addresses = decodedScript["addresses"];
if (addresses is List) {
candidates.addAll(addresses.whereType<String>());
}
final p2sh = decodedScript["p2sh"];
if (p2sh is String) {
candidates.add(p2sh);
}
final segwit = decodedScript["segwit"];
if (segwit is Map) {
final segwitAddress = segwit["address"];
if (segwitAddress is String) {
candidates.add(segwitAddress);
}
final segwitAddresses = segwit["addresses"];
if (segwitAddresses is List) {
candidates.addAll(segwitAddresses.whereType<String>());
}
}

for (final addr in candidates) {
final info = jsonDecode(
connection.call("getaddressinfo", [jsonEncode(addr)]),
);
if (info["ismine"] == true) {
return true;
}
}
return false;
} catch (e) {
print("An error occurred: $e");
return false;
}
}
Expand Down Expand Up @@ -132,7 +159,7 @@ class ProcessPsbtCallback implements payjoin.ProcessPsbt {
}

payjoin.Initialized create_receiver_context(
bitcoin.Address address,
String address,
String directory,
payjoin.OhttpKeys ohttp_keys,
InMemoryReceiverPersister persister,
Expand Down Expand Up @@ -174,17 +201,22 @@ List<payjoin.InputPair> get_inputs(payjoin.RpcClient rpc_connection) {
var utxos = jsonDecode(rpc_connection.call("listunspent", []));
List<payjoin.InputPair> inputs = [];
for (var utxo in utxos) {
var txin = bitcoin.TxIn(
bitcoin.OutPoint(utxo["txid"], utxo["vout"]),
bitcoin.Script(Uint8List.fromList([])),
0,
[],
final txid = utxo["txid"] as String;
final vout = utxo["vout"] as int;
final scriptPubKey = Uint8List.fromList(
hex.decode(utxo["scriptPubKey"] as String),
);
var tx_out = bitcoin.TxOut(
bitcoin.Amount.fromBtc(utxo["amount"]),
bitcoin.Script(Uint8List.fromList(hex.decode(utxo["scriptPubKey"]))),
final amountBtc = utxo["amount"] as num;
final amountSat = (amountBtc * 100000000).round();

final txin = payjoin.PlainTxIn(
payjoin.PlainOutPoint(txid, vout),
Uint8List(0),
0,
<Uint8List>[],
);
var psbt_in = payjoin.PsbtInput(tx_out, null, null);
final witnessUtxo = payjoin.PlainTxOut(amountSat, scriptPubKey);
final psbt_in = payjoin.PlainPsbtInput(witnessUtxo, null, null);
inputs.add(payjoin.InputPair(txin, psbt_in, null));
}

Expand Down Expand Up @@ -346,10 +378,8 @@ void main() {
bitcoind = env.getBitcoind();
receiver = env.getReceiver();
sender = env.getSender();
var receiver_address = bitcoin.Address(
jsonDecode(receiver.call("getnewaddress", [])),
bitcoin.Network.regtest,
);
var receiver_address =
jsonDecode(receiver.call("getnewaddress", [])) as String;
var services = payjoin.TestServices.initialize();

services.waitForServicesReady();
Expand Down Expand Up @@ -443,37 +473,40 @@ void main() {
)
.save(sender_persister);
expect(checked_payjoin_proposal_psbt, isNotNull);
var checked_payjoin_proposal_psbt_inner =
(checked_payjoin_proposal_psbt
as payjoin.ProgressPollingForProposalTransitionOutcome)
.inner;
final progressOutcome =
checked_payjoin_proposal_psbt
as payjoin.ProgressPollingForProposalTransitionOutcome;
var payjoin_psbt = jsonDecode(
sender.call("walletprocesspsbt", [
checked_payjoin_proposal_psbt_inner.serializeBase64(),
]),
sender.call("walletprocesspsbt", [progressOutcome.psbtBase64]),
)["psbt"];
var final_psbt = jsonDecode(
sender.call("finalizepsbt", [payjoin_psbt, jsonEncode(false)]),
)["psbt"];
var payjoin_tx = bitcoin.Psbt.deserializeBase64(final_psbt).extractTx();
sender.call("sendrawtransaction", [
jsonEncode(hex.encode(payjoin_tx.serialize())),
]);
var final_tx_hex = jsonDecode(
sender.call("finalizepsbt", [final_psbt, jsonEncode(true)]),
)["hex"];
sender.call("sendrawtransaction", [jsonEncode(final_tx_hex)]);

// Check resulting transaction and balances
var network_fees = bitcoin.Psbt.deserializeBase64(
final_psbt,
).fee().toBtc();
var decodedTx = jsonDecode(
sender.call("decoderawtransaction", [jsonEncode(final_tx_hex)]),
);
var network_fees =
(jsonDecode(
sender.call("decodepsbt", [jsonEncode(final_psbt)]),
)["fee"]
as num)
.toDouble();
// Sender sent the entire value of their utxo to the receiver (minus fees)
expect(payjoin_tx.input().length, 2);
expect(payjoin_tx.output().length, 1);
expect(decodedTx["vin"].length, 2);
expect(decodedTx["vout"].length, 1);
expect(
jsonDecode(
receiver.call("getbalances", []),
)["mine"]["untrusted_pending"],
100 - network_fees,
);
expect(jsonDecode(sender.call("getbalance", [])), 0.0);
});
}, timeout: const Timeout(Duration(minutes: 5)));
});
}
13 changes: 2 additions & 11 deletions payjoin-ffi/dart/test/test_payjoin_unit_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'package:test/test.dart';
import "package:payjoin/payjoin.dart" as payjoin;
import "package:payjoin/bitcoin.dart" as bitcoin;

class InMemoryReceiverPersister
implements payjoin.JsonReceiverSessionPersister {
Expand Down Expand Up @@ -106,12 +105,8 @@ void main() {
group("Test Persistence", () {
test("Test receiver persistence", () {
var persister = InMemoryReceiverPersister("1");
var address = bitcoin.Address(
"tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4",
bitcoin.Network.signet,
);
payjoin.ReceiverBuilder(
address,
"tb1q6d3a2w975yny0asuvd9a67ner4nks58ff0q8g4",
"https://example.com",
payjoin.OhttpKeys.decode(
Uint8List.fromList(
Expand All @@ -131,12 +126,8 @@ void main() {

test("Test sender persistence", () {
var receiver_persister = InMemoryReceiverPersister("1");
var address = bitcoin.Address(
"2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK",
bitcoin.Network.testnet,
);
var receiver = payjoin.ReceiverBuilder(
address,
"2MuyMrZHkbHbfjudmKUy45dU4P17pjG2szK",
"https://example.com",
payjoin.OhttpKeys.decode(
Uint8List.fromList(
Expand Down
4 changes: 0 additions & 4 deletions payjoin-ffi/javascript/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// Export the generated bindings to the app.
export * as payjoin from "./generated/payjoin.js";
export * as bitcoin from "./generated/bitcoin.js";

// Now import the bindings so we can:
// - initialize them
// - export them as namespaced objects as the default export.
import * as bitcoin from "./generated/bitcoin.js";
import * as payjoin from "./generated/payjoin.js";

let initialized = false;
Expand All @@ -19,14 +17,12 @@ export async function uniffiInitAsync() {
// Initialize the generated bindings: mostly checksums, but also callbacks.
// - the boolean flag ensures this loads exactly once, even if the JS code
// is reloaded (e.g. during development with metro).
bitcoin.default.initialize();
payjoin.default.initialize();

initialized = true;
}

// Export the crates as individually namespaced objects.
export default {
bitcoin,
payjoin,
};
Loading
Loading