mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-05-05 08:52:14 +00:00
Payjoin Sender are now isolated
This commit is contained in:
parent
4c4fd8e645
commit
8f4dad9134
45 changed files with 452 additions and 295 deletions
cw_bitcoin/lib
bitcoin_payjoin.dartbitcoin_wallet.dartbitcoin_wallet_addresses.dartelectrum_wallet.dart
payjoin
manager.dartpayjoin_receive_worker.dartpayjoin_send_worker.dartpayjoin_session_errors.dartstorage.dart
pending_bitcoin_transaction.dartlib
bitcoin
reactions
src/screens/settings
view_model
send
settings
wallet_address_list
res/values
strings_ar.arbstrings_bg.arbstrings_cs.arbstrings_de.arbstrings_en.arbstrings_es.arbstrings_fr.arbstrings_ha.arbstrings_hi.arbstrings_hr.arbstrings_hy.arbstrings_id.arbstrings_it.arbstrings_ja.arbstrings_ko.arbstrings_my.arbstrings_nl.arbstrings_pl.arbstrings_pt.arbstrings_ru.arbstrings_th.arbstrings_tl.arbstrings_tr.arbstrings_uk.arbstrings_ur.arbstrings_vi.arbstrings_yo.arbstrings_zh.arb
tool
|
@ -143,7 +143,6 @@ class BitcoinPayjoin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PendingBitcoinTransaction> extractPjTx(
|
Future<PendingBitcoinTransaction> extractPjTx(Object wallet, String psbtString) async =>
|
||||||
Object wallet, String psbtString, Object credentials) async =>
|
(wallet as BitcoinWallet).psbtToPendingTx(psbtString);
|
||||||
(wallet as BitcoinWallet).psbtToPendingTx(psbtString, credentials);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
// String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
|
// String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
|
||||||
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
||||||
|
|
||||||
payjoinManager = PayjoinManager(PayjoinStorage(payjoinSessionSources: payjoinBox), this);
|
payjoinManager = PayjoinManager(PayjoinStorage(payjoinBox), this);
|
||||||
walletAddresses = BitcoinWalletAddresses(
|
walletAddresses = BitcoinWalletAddresses(
|
||||||
walletInfo,
|
walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
|
@ -339,43 +339,36 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
@override
|
@override
|
||||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
credentials = credentials as BitcoinTransactionCredentials;
|
credentials = credentials as BitcoinTransactionCredentials;
|
||||||
final tx =
|
|
||||||
super.createTransaction(credentials) as PendingBitcoinTransaction;
|
|
||||||
|
|
||||||
// if (credentials.payjoinUri == null) return tx;
|
final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction;
|
||||||
//
|
|
||||||
// final transaction = await buildPsbt(
|
final payjoinUri = credentials.payjoinUri;
|
||||||
// utxos: tx.utxos,
|
if (payjoinUri == null) return tx;
|
||||||
// outputs: tx.outputs.map((e) => BitcoinOutput(
|
|
||||||
// address: BitcoinBaseAddress.fromString(e.scriptPubKey.toAddress()),
|
final transaction = await buildPsbt(
|
||||||
// value: e.amount,
|
utxos: tx.utxos,
|
||||||
// isSilentPayment: e.isSilentPayment,
|
outputs: tx.outputs.map((e) => BitcoinOutput(
|
||||||
// isChange: e.isChange,
|
address: BitcoinBaseAddress.fromString(e.scriptPubKey.toAddress()),
|
||||||
// )).toList(),
|
value: e.amount,
|
||||||
// fee: BigInt.from(tx.fee),
|
isSilentPayment: e.isSilentPayment,
|
||||||
// network: network,
|
isChange: e.isChange,
|
||||||
// memo: credentials.outputs.first.memo,
|
)).toList(),
|
||||||
// outputOrdering: BitcoinOrdering.none,
|
fee: BigInt.from(tx.fee),
|
||||||
// enableRBF: true,
|
network: network,
|
||||||
// publicKeys: tx.publicKeys,
|
memo: credentials.outputs.first.memo,
|
||||||
// masterFingerprint: Uint8List(0)
|
outputOrdering: BitcoinOrdering.none,
|
||||||
// );
|
enableRBF: true,
|
||||||
//
|
publicKeys: tx.publicKeys!,
|
||||||
// transaction.signWithUTXO(
|
masterFingerprint: Uint8List(0)
|
||||||
// tx.utxos
|
);
|
||||||
// .map((e) =>
|
|
||||||
// UtxoWithPrivateKey.fromUtxo(e, tx.inputPrivKeyInfos))
|
final originalPsbt = await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
|
||||||
// .toList(), (txDigest, utxo, key, sighash) {
|
|
||||||
// if (utxo.utxo.isP2tr()) {
|
tx.commitOverride = () async {
|
||||||
// return key.signTapRoot(
|
final sender =
|
||||||
// txDigest,
|
await payjoinManager.initSender(payjoinUri, originalPsbt, tx.fee);
|
||||||
// sighash: sighash,
|
await payjoinManager.spawnNewSender(sender: sender, pjUrl: payjoinUri);
|
||||||
// tweak: utxo.utxo.isSilentPayment != true,
|
};
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// return key.signInput(txDigest, sigHash: sighash);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
@ -497,8 +490,25 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
.map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this))
|
.map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
Future<PendingBitcoinTransaction> psbtToPendingTx(
|
Future<void> commitPsbt(String finalizedPsbt) {
|
||||||
String preProcessedPsbt, Object credentials) async {
|
final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt));
|
||||||
|
|
||||||
|
final btcTx =
|
||||||
|
BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract()));
|
||||||
|
|
||||||
|
return PendingBitcoinTransaction(
|
||||||
|
btcTx,
|
||||||
|
type,
|
||||||
|
electrumClient: electrumClient,
|
||||||
|
amount: 0,
|
||||||
|
fee: 0,
|
||||||
|
feeRate: "",
|
||||||
|
network: network,
|
||||||
|
hasChange: true,
|
||||||
|
).commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PendingBitcoinTransaction> psbtToPendingTx(String preProcessedPsbt) async {
|
||||||
final psbt = PsbtV2()..deserializeV0(base64.decode(preProcessedPsbt));
|
final psbt = PsbtV2()..deserializeV0(base64.decode(preProcessedPsbt));
|
||||||
|
|
||||||
final inputCount = psbt.getGlobalInputCount();
|
final inputCount = psbt.getGlobalInputCount();
|
||||||
|
@ -575,17 +585,6 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
return base64Encode(psbt.asPsbtV0());
|
return base64Encode(psbt.asPsbtV0());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getPJURl() async {
|
|
||||||
final payjoinBox = await CakeHive.openBox<PayjoinSession>(PayjoinSession.boxName);
|
|
||||||
final manager = PayjoinManager(PayjoinStorage(payjoinSessionSources: payjoinBox), this);
|
|
||||||
|
|
||||||
final receiver = await manager.initReceiver(walletAddresses.primaryAddress);
|
|
||||||
|
|
||||||
manager.spawnNewReceiver(receiver: receiver);
|
|
||||||
|
|
||||||
return receiver.pjUriBuilder().build().pjEndpoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> signMessage(String message, {String? address = null}) async {
|
Future<String> signMessage(String message, {String? address = null}) async {
|
||||||
if (walletInfo.isHardwareWallet) {
|
if (walletInfo.isHardwareWallet) {
|
||||||
|
|
|
@ -57,13 +57,12 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
@override
|
@override
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
await super.init();
|
await super.init();
|
||||||
|
|
||||||
refreshPayjoinReceiver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refreshPayjoinReceiver() async {
|
Future<void> initPayjoin() async {
|
||||||
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
|
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
|
||||||
|
|
||||||
|
await payjoinManager.resumeSessions();
|
||||||
await payjoinManager.spawnNewReceiver(receiver: currentPayjoinReceiver!);
|
await payjoinManager.spawnNewReceiver(receiver: currentPayjoinReceiver!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1181,6 +1181,7 @@ abstract class ElectrumWalletBase
|
||||||
isSendAll: estimatedTx.isSendAll,
|
isSendAll: estimatedTx.isSendAll,
|
||||||
hasTaprootInputs: hasTaprootInputs,
|
hasTaprootInputs: hasTaprootInputs,
|
||||||
utxos: estimatedTx.utxos,
|
utxos: estimatedTx.utxos,
|
||||||
|
publicKeys: estimatedTx.publicKeys
|
||||||
)..addListener((transaction) async {
|
)..addListener((transaction) async {
|
||||||
transactionHistory.addOne(transaction);
|
transactionHistory.addOne(transaction);
|
||||||
if (estimatedTx.spendsSilentPayment) {
|
if (estimatedTx.spendsSilentPayment) {
|
||||||
|
|
|
@ -5,12 +5,16 @@ import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet.dart';
|
||||||
import 'package:cw_bitcoin/payjoin/payjoin_worker.dart';
|
import 'package:cw_bitcoin/payjoin/payjoin_receive_worker.dart';
|
||||||
|
import 'package:cw_bitcoin/payjoin/payjoin_send_worker.dart';
|
||||||
|
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
|
||||||
import 'package:cw_bitcoin/payjoin/storage.dart';
|
import 'package:cw_bitcoin/payjoin/storage.dart';
|
||||||
import 'package:cw_bitcoin/psbt_signer.dart';
|
import 'package:cw_bitcoin/psbt_signer.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:payjoin_flutter/common.dart';
|
import 'package:payjoin_flutter/common.dart';
|
||||||
import 'package:payjoin_flutter/receive.dart';
|
import 'package:payjoin_flutter/receive.dart';
|
||||||
import 'package:payjoin_flutter/uri.dart';
|
import 'package:payjoin_flutter/send.dart';
|
||||||
|
import 'package:payjoin_flutter/uri.dart' as PayjoinUri;
|
||||||
|
|
||||||
class PayjoinManager {
|
class PayjoinManager {
|
||||||
PayjoinManager(this._payjoinStorage, this._wallet);
|
PayjoinManager(this._payjoinStorage, this._wallet);
|
||||||
|
@ -19,20 +23,133 @@ class PayjoinManager {
|
||||||
final BitcoinWalletBase _wallet;
|
final BitcoinWalletBase _wallet;
|
||||||
final Map<String, PayjoinPollerSession> _activePollers = {};
|
final Map<String, PayjoinPollerSession> _activePollers = {};
|
||||||
|
|
||||||
static const List<String> _ohttpRelayUrls = [
|
static const List<String> ohttpRelayUrls = [
|
||||||
'https://pj.bobspacebkk.com',
|
'https://pj.bobspacebkk.com',
|
||||||
'https://ohttp.achow101.com',
|
'https://ohttp.achow101.com',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
static Future<PayjoinUri.Url> randomOhttpRelayUrl() => PayjoinUri.Url.fromStr(
|
||||||
|
ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)]);
|
||||||
|
|
||||||
static const payjoinDirectoryUrl = 'https://payjo.in';
|
static const payjoinDirectoryUrl = 'https://payjo.in';
|
||||||
|
|
||||||
|
Future<void> resumeSessions() async {
|
||||||
|
final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id);
|
||||||
|
|
||||||
|
final spawnedSessions = allSessions.map((session) {
|
||||||
|
if (session.isSenderSession) {
|
||||||
|
return spawnSender(
|
||||||
|
sender: Sender.fromJson(session.sender!),
|
||||||
|
pjUri: session.pjUri!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return spawnReceiver(
|
||||||
|
receiver: Receiver.fromJson(session.receiver!),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.wait(spawnedSessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Sender> initSender(
|
||||||
|
String pjUriString, String originalPsbt, int networkFeesSatPerVb) async {
|
||||||
|
try {
|
||||||
|
final pjUri =
|
||||||
|
(await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported();
|
||||||
|
final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250);
|
||||||
|
final senderBuilder = await SenderBuilder.fromPsbtAndUri(
|
||||||
|
psbtBase64: originalPsbt,
|
||||||
|
pjUri: pjUri,
|
||||||
|
);
|
||||||
|
return senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Error initializing Payjoin Sender: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> spawnNewSender({
|
||||||
|
required Sender sender,
|
||||||
|
required String pjUrl,
|
||||||
|
bool isTestnet = false,
|
||||||
|
}) async {
|
||||||
|
await _payjoinStorage.insertSenderSession(
|
||||||
|
sender,
|
||||||
|
pjUrl,
|
||||||
|
_wallet.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return spawnSender(
|
||||||
|
isTestnet: isTestnet,
|
||||||
|
sender: sender,
|
||||||
|
pjUri: pjUrl,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> spawnSender({
|
||||||
|
required Sender sender,
|
||||||
|
required String pjUri,
|
||||||
|
bool isTestnet = false,
|
||||||
|
}) async {
|
||||||
|
final completer = Completer();
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
|
||||||
|
receivePort.listen((message) async {
|
||||||
|
print('Sender isolate: $message');
|
||||||
|
if (message is Map<String, dynamic>) {
|
||||||
|
try {
|
||||||
|
switch (message['type']) {
|
||||||
|
case PayjoinSenderRequestTypes.requestPosted:
|
||||||
|
//ToDo: Update frontend
|
||||||
|
return;
|
||||||
|
case PayjoinSenderRequestTypes.psbtToSign:
|
||||||
|
final proposalPsbt = message['psbt'] as String;
|
||||||
|
final utxos = _wallet.getUtxoWithPrivateKeys();
|
||||||
|
final finalizedPsbt = await _wallet.signPsbt(proposalPsbt, utxos);
|
||||||
|
_wallet.commitPsbt(finalizedPsbt);
|
||||||
|
|
||||||
|
//ToDo: Update frontend
|
||||||
|
_cleanupSession(pjUri);
|
||||||
|
await _payjoinStorage.markSenderSessionComplete(pjUri);
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_cleanupSession(pjUri);
|
||||||
|
await _payjoinStorage.markReceiverSessionUnrecoverable(pjUri);
|
||||||
|
completer.completeError(e);
|
||||||
|
}
|
||||||
|
} else if (message is PayjoinSessionError) {
|
||||||
|
_cleanupSession(pjUri);
|
||||||
|
if (message is UnrecoverableError) {
|
||||||
|
printV(message.message);
|
||||||
|
await _payjoinStorage.markReceiverSessionUnrecoverable(pjUri);
|
||||||
|
}
|
||||||
|
completer.completeError(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final args = [
|
||||||
|
receivePort.sendPort,
|
||||||
|
sender.toJson(),
|
||||||
|
];
|
||||||
|
|
||||||
|
final isolate = await Isolate.spawn(
|
||||||
|
PayjoinSenderWorker.run,
|
||||||
|
args,
|
||||||
|
);
|
||||||
|
|
||||||
|
_activePollers[pjUri] = PayjoinPollerSession(isolate, receivePort);
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
Future<Receiver> initReceiver(String address,
|
Future<Receiver> initReceiver(String address,
|
||||||
[bool isTestnet = false]) async {
|
[bool isTestnet = false]) async {
|
||||||
try {
|
try {
|
||||||
final payjoinDirectory = await Url.fromStr(payjoinDirectoryUrl);
|
final payjoinDirectory =
|
||||||
|
await PayjoinUri.Url.fromStr(payjoinDirectoryUrl);
|
||||||
|
|
||||||
final ohttpKeys = await fetchOhttpKeys(
|
final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
|
||||||
ohttpRelay: await _randomOhttpRelayUrl(),
|
ohttpRelay: await randomOhttpRelayUrl(),
|
||||||
payjoinDirectory: payjoinDirectory,
|
payjoinDirectory: payjoinDirectory,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,10 +158,10 @@ class PayjoinManager {
|
||||||
network: isTestnet ? Network.testnet : Network.bitcoin,
|
network: isTestnet ? Network.testnet : Network.bitcoin,
|
||||||
directory: payjoinDirectory,
|
directory: payjoinDirectory,
|
||||||
ohttpKeys: ohttpKeys,
|
ohttpKeys: ohttpKeys,
|
||||||
ohttpRelay: await _randomOhttpRelayUrl(),
|
ohttpRelay: await randomOhttpRelayUrl(),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception('Error initializing payjoin Receiver: $e');
|
throw Exception('Error initializing Payjoin Receiver: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,18 +232,19 @@ class PayjoinManager {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PayjoinReceiverRequestTypes.proposalSent:
|
case PayjoinReceiverRequestTypes.proposalSent:
|
||||||
await _cleanupSession(receiver.id());
|
_cleanupSession(receiver.id());
|
||||||
await _payjoinStorage.markReceiverSessionComplete(receiver.id());
|
await _payjoinStorage.markReceiverSessionComplete(receiver.id());
|
||||||
completer.complete();
|
completer.complete();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await _cleanupSession(receiver.id());
|
_cleanupSession(receiver.id());
|
||||||
await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id());
|
await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id());
|
||||||
completer.completeError(e);
|
completer.completeError(e);
|
||||||
}
|
}
|
||||||
} else if (message is PayjoinSessionError) {
|
} else if (message is PayjoinSessionError) {
|
||||||
await _cleanupSession(receiver.id());
|
_cleanupSession(receiver.id());
|
||||||
if (message is UnrecoverableError) {
|
if (message is UnrecoverableError) {
|
||||||
|
printV(message.message);
|
||||||
await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id());
|
await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id());
|
||||||
}
|
}
|
||||||
completer.completeError(message);
|
completer.completeError(message);
|
||||||
|
@ -150,16 +268,16 @@ class PayjoinManager {
|
||||||
return completer.future;
|
return completer.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void cleanupSessions() {
|
||||||
|
for (final sessionId in _activePollers.keys) {
|
||||||
|
_cleanupSession(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _cleanupSession(String sessionId) async {
|
void _cleanupSession(String sessionId) {
|
||||||
_activePollers[sessionId]?.close();
|
_activePollers[sessionId]?.close();
|
||||||
_activePollers.remove(sessionId);
|
_activePollers.remove(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top-level function to generate random OHTTP relay URL
|
|
||||||
Future<Url> _randomOhttpRelayUrl() => Url.fromStr(
|
|
||||||
_ohttpRelayUrls[Random.secure().nextInt(_ohttpRelayUrls.length)],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PayjoinPollerSession {
|
class PayjoinPollerSession {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
|
||||||
import 'package:cw_bitcoin/psbt_signer.dart';
|
import 'package:cw_bitcoin/psbt_signer.dart';
|
||||||
import 'package:cw_core/utils/print_verbose.dart';
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:payjoin_flutter/bitcoin_ffi.dart';
|
import 'package:payjoin_flutter/bitcoin_ffi.dart';
|
||||||
|
@ -19,23 +20,6 @@ enum PayjoinReceiverRequestTypes {
|
||||||
processPsbt;
|
processPsbt;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PayjoinSessionError {
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
const PayjoinSessionError._(this.message);
|
|
||||||
|
|
||||||
factory PayjoinSessionError.recoverable(String message) = RecoverableError;
|
|
||||||
factory PayjoinSessionError.unrecoverable(String message) = UnrecoverableError;
|
|
||||||
}
|
|
||||||
|
|
||||||
class RecoverableError extends PayjoinSessionError {
|
|
||||||
const RecoverableError(super.message) : super._();
|
|
||||||
}
|
|
||||||
|
|
||||||
class UnrecoverableError extends PayjoinSessionError {
|
|
||||||
const UnrecoverableError(super.message) : super._();
|
|
||||||
}
|
|
||||||
|
|
||||||
class PayjoinReceiverWorker {
|
class PayjoinReceiverWorker {
|
||||||
final SendPort sendPort;
|
final SendPort sendPort;
|
||||||
final pendingRequests = <String, Completer<dynamic>>{};
|
final pendingRequests = <String, Completer<dynamic>>{};
|
111
cw_bitcoin/lib/payjoin/payjoin_send_worker.dart
Normal file
111
cw_bitcoin/lib/payjoin/payjoin_send_worker.dart
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:cw_bitcoin/payjoin/manager.dart';
|
||||||
|
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
|
||||||
|
import 'package:payjoin_flutter/common.dart';
|
||||||
|
import 'package:payjoin_flutter/send.dart';
|
||||||
|
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
|
||||||
|
|
||||||
|
enum PayjoinSenderRequestTypes {
|
||||||
|
requestPosted,
|
||||||
|
psbtToSign;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PayjoinSenderWorker {
|
||||||
|
final SendPort sendPort;
|
||||||
|
final pendingRequests = <String, Completer<dynamic>>{};
|
||||||
|
|
||||||
|
PayjoinSenderWorker._(this.sendPort);
|
||||||
|
|
||||||
|
static Future<void> run(List<Object> args) async {
|
||||||
|
await pj.core.init();
|
||||||
|
|
||||||
|
final sendPort = args[0] as SendPort;
|
||||||
|
final senderJson = args[1] as String;
|
||||||
|
|
||||||
|
final sender = Sender.fromJson(senderJson);
|
||||||
|
final worker = PayjoinSenderWorker._(sendPort);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final proposalPsbt = await worker.runSender(sender);
|
||||||
|
sendPort.send({
|
||||||
|
'type': PayjoinSenderRequestTypes.psbtToSign,
|
||||||
|
'psbt': proposalPsbt,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
sendPort.send(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a payjoin sender (V2 protocol first, fallback to V1).
|
||||||
|
Future<String> runSender(Sender sender) async {
|
||||||
|
final httpClient = HttpClient();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await _runSenderV2(sender, httpClient);
|
||||||
|
} catch (e) {
|
||||||
|
if (e is PayjoinException &&
|
||||||
|
// TODO condition on error type instead of message content
|
||||||
|
e.message?.contains('parse receiver public key') == true) {
|
||||||
|
return await _runSenderV1(sender, httpClient);
|
||||||
|
} else if (e is HttpException) {
|
||||||
|
throw Exception(PayjoinSessionError.recoverable(e.toString()));
|
||||||
|
} else {
|
||||||
|
throw Exception(PayjoinSessionError.unrecoverable(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to send payjoin using the V2 of the protocol.
|
||||||
|
Future<String> _runSenderV2(Sender sender, HttpClient httpClient) async {
|
||||||
|
final postRequest = await sender.extractV2(
|
||||||
|
ohttpProxyUrl: await await PayjoinManager.randomOhttpRelayUrl(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final postResult = await _postRequest(httpClient, postRequest.$1);
|
||||||
|
final getContext =
|
||||||
|
await postRequest.$2.processResponse(response: postResult);
|
||||||
|
|
||||||
|
sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
final getRequest = await getContext.extractReq(
|
||||||
|
ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
|
||||||
|
);
|
||||||
|
final getRes = await _postRequest(httpClient, getRequest.$1);
|
||||||
|
final proposalPsbt = await getContext.processResponse(
|
||||||
|
response: getRes,
|
||||||
|
ohttpCtx: getRequest.$2,
|
||||||
|
);
|
||||||
|
if (proposalPsbt != null) return proposalPsbt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to send payjoin using the V1 of the protocol.
|
||||||
|
Future<String> _runSenderV1(Sender sender, HttpClient httpClient) async {
|
||||||
|
try {
|
||||||
|
final postRequest = await sender.extractV1();
|
||||||
|
final response = await _postRequest(httpClient, postRequest.$1);
|
||||||
|
|
||||||
|
sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted});
|
||||||
|
|
||||||
|
return await postRequest.$2.processResponse(response: response);
|
||||||
|
} catch (e) {
|
||||||
|
throw PayjoinSessionError.unrecoverable('Send V1 payjoin error: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<int>> _postRequest(HttpClient client, Request req) async {
|
||||||
|
final httpRequest = await client.postUrl(Uri.parse(req.url.asString()));
|
||||||
|
httpRequest.headers.set('Content-Type', req.contentType);
|
||||||
|
httpRequest.add(req.body);
|
||||||
|
|
||||||
|
final response = await httpRequest.close();
|
||||||
|
return response.fold<List<int>>(
|
||||||
|
[],
|
||||||
|
(previous, element) => previous..addAll(element),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
16
cw_bitcoin/lib/payjoin/payjoin_session_errors.dart
Normal file
16
cw_bitcoin/lib/payjoin/payjoin_session_errors.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
class PayjoinSessionError {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const PayjoinSessionError._(this.message);
|
||||||
|
|
||||||
|
factory PayjoinSessionError.recoverable(String message) = RecoverableError;
|
||||||
|
factory PayjoinSessionError.unrecoverable(String message) = UnrecoverableError;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RecoverableError extends PayjoinSessionError {
|
||||||
|
const RecoverableError(super.message) : super._();
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnrecoverableError extends PayjoinSessionError {
|
||||||
|
const UnrecoverableError(super.message) : super._();
|
||||||
|
}
|
|
@ -1,194 +1,75 @@
|
||||||
import 'package:cw_core/payjoin_session.dart';
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:payjoin_flutter/receive.dart';
|
import 'package:payjoin_flutter/receive.dart';
|
||||||
|
import 'package:payjoin_flutter/send.dart';
|
||||||
|
|
||||||
class PayjoinStorage {
|
class PayjoinStorage {
|
||||||
PayjoinStorage({required Box<PayjoinSession> payjoinSessionSources})
|
PayjoinStorage(this._payjoinSessionSources);
|
||||||
: _payjoinSessionSources = payjoinSessionSources;
|
|
||||||
final Box<PayjoinSession> _payjoinSessionSources;
|
final Box<PayjoinSession> _payjoinSessionSources;
|
||||||
|
|
||||||
static const String receiverPrefix = 'pj_recv_';
|
static const String _receiverPrefix = 'pj_recv_';
|
||||||
static const String senderPrefix = 'pj_send_';
|
static const String _senderPrefix = 'pj_send_';
|
||||||
|
|
||||||
Future<void> insertReceiverSession(
|
Future<void> insertReceiverSession(
|
||||||
Receiver receiver,
|
Receiver receiver,
|
||||||
String walletId,
|
String walletId,
|
||||||
) async {
|
) =>
|
||||||
final receiverSession =
|
_payjoinSessionSources.put(
|
||||||
PayjoinSession(walletId: walletId, receiver: receiver.toJson());
|
"$_receiverPrefix${receiver.id()}",
|
||||||
|
PayjoinSession(
|
||||||
await _payjoinSessionSources.put(
|
walletId: walletId,
|
||||||
"$receiverPrefix${receiver.id()}", receiverSession);
|
receiver: receiver.toJson(),
|
||||||
}
|
),
|
||||||
|
);
|
||||||
// Future<(RecvSession?, Err?)> readReceiverSession(String sessionId) async {
|
|
||||||
// try {
|
|
||||||
// final (jsn, err) =
|
|
||||||
// await _hiveStorage.getValue(receiverPrefix + sessionId);
|
|
||||||
// if (err != null) throw err;
|
|
||||||
// final obj = jsonDecode(jsn!) as Map<String, dynamic>;
|
|
||||||
// final session = RecvSession.fromJson(obj);
|
|
||||||
// return (session, null);
|
|
||||||
// } catch (e) {
|
|
||||||
// return (
|
|
||||||
// null,
|
|
||||||
// Err(
|
|
||||||
// e.toString(),
|
|
||||||
// expected: e.toString() == 'No Receiver with id $sessionId',
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> markReceiverSessionComplete(String sessionId) async {
|
Future<void> markReceiverSessionComplete(String sessionId) async {
|
||||||
final session =
|
final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!;
|
||||||
await _payjoinSessionSources.get("$receiverPrefix${sessionId}")!;
|
|
||||||
|
|
||||||
session.status = "success";
|
session.status = "success";
|
||||||
await session.save();
|
await session.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> markReceiverSessionUnrecoverable(String sessionId) async {
|
Future<void> markReceiverSessionUnrecoverable(String sessionId) async {
|
||||||
final session =
|
final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!;
|
||||||
await _payjoinSessionSources.get("$receiverPrefix${sessionId}")!;
|
|
||||||
|
|
||||||
session.status = "unrecoverable";
|
session.status = "unrecoverable";
|
||||||
await session.save();
|
await session.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<(List<RecvSession>, Err?)> readAllReceivers() async {
|
Future<void> insertSenderSession(
|
||||||
// //deleteAllSessions();
|
Sender sender,
|
||||||
// try {
|
String pjUrl,
|
||||||
// final (allData, err) = await _hiveStorage.getAll();
|
String walletId,
|
||||||
// if (err != null) return (List<RecvSession>.empty(), err);
|
) =>
|
||||||
//
|
_payjoinSessionSources.put(
|
||||||
// final List<RecvSession> receivers = [];
|
"$_senderPrefix$pjUrl",
|
||||||
// allData!.forEach((key, value) {
|
PayjoinSession(
|
||||||
// if (key.startsWith(receiverPrefix)) {
|
walletId: walletId,
|
||||||
// try {
|
pjUri: pjUrl,
|
||||||
// final obj = jsonDecode(value) as Map<String, dynamic>;
|
sender: sender.toJson(),
|
||||||
// receivers.add(RecvSession.fromJson(obj));
|
),
|
||||||
// } catch (e) {
|
);
|
||||||
// // Skip invalid entries
|
|
||||||
// debugPrint('Error: $e');
|
Future<void> markSenderSessionComplete(String pjUrl) async {
|
||||||
// }
|
final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!;
|
||||||
// }
|
|
||||||
// });
|
session.status = "success";
|
||||||
// return (receivers, null);
|
await session.save();
|
||||||
// } catch (e) {
|
}
|
||||||
// return (List<RecvSession>.empty(), Err(e.toString()));
|
|
||||||
// }
|
Future<void> markSenderSessionUnrecoverable(String pjUrl) async {
|
||||||
// }
|
final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!;
|
||||||
//
|
|
||||||
// Future<Err?> insertSenderSession(
|
session.status = "unrecoverable";
|
||||||
// Sender sender,
|
await session.save();
|
||||||
// String pjUrl,
|
}
|
||||||
// String walletId,
|
|
||||||
// bool isTestnet,
|
List<PayjoinSession> readAllOpenSessions(String walletId) =>
|
||||||
// ) async {
|
_payjoinSessionSources.values
|
||||||
// try {
|
.where((session) =>
|
||||||
// final sendSession = SendSession(
|
session.walletId == walletId &&
|
||||||
// isTestnet,
|
session.status != "success" &&
|
||||||
// sender,
|
session.status != "unrecoverable")
|
||||||
// walletId,
|
.toList();
|
||||||
// pjUrl,
|
|
||||||
// PayjoinSessionStatus.pending,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// await _hiveStorage.saveValue(
|
|
||||||
// key: senderPrefix + pjUrl,
|
|
||||||
// value: jsonEncode(sendSession.toJson()),
|
|
||||||
// );
|
|
||||||
// return null;
|
|
||||||
// } catch (e) {
|
|
||||||
// return Err(e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<(SendSession?, Err?)> readSenderSession(String pjUrl) async {
|
|
||||||
// try {
|
|
||||||
// final (jsn, err) = await _hiveStorage.getValue(senderPrefix + pjUrl);
|
|
||||||
// if (err != null) throw err;
|
|
||||||
// final obj = jsonDecode(jsn!) as Map<String, dynamic>;
|
|
||||||
// final session = SendSession.fromJson(obj);
|
|
||||||
// return (session, null);
|
|
||||||
// } catch (e) {
|
|
||||||
// return (
|
|
||||||
// null,
|
|
||||||
// Err(
|
|
||||||
// e.toString(),
|
|
||||||
// expected: e.toString() == 'No Sender with id $pjUrl',
|
|
||||||
// )
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<Err?> markSenderSessionComplete(String pjUrl) async {
|
|
||||||
// try {
|
|
||||||
// final (session, err) = await readSenderSession(pjUrl);
|
|
||||||
// if (err != null) return err;
|
|
||||||
//
|
|
||||||
// final updatedSession = SendSession(
|
|
||||||
// session!.isTestnet,
|
|
||||||
// session.sender,
|
|
||||||
// session.walletId,
|
|
||||||
// session.pjUri,
|
|
||||||
// PayjoinSessionStatus.success,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// await _hiveStorage.saveValue(
|
|
||||||
// key: senderPrefix + pjUrl,
|
|
||||||
// value: jsonEncode(updatedSession.toJson()),
|
|
||||||
// );
|
|
||||||
// return null;
|
|
||||||
// } catch (e) {
|
|
||||||
// return Err(e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<Err?> markSenderSessionUnrecoverable(String pjUri) async {
|
|
||||||
// try {
|
|
||||||
// final (session, err) = await readSenderSession(pjUri);
|
|
||||||
// if (err != null) return err;
|
|
||||||
//
|
|
||||||
// final updatedSession = SendSession(
|
|
||||||
// session!.isTestnet,
|
|
||||||
// session.sender,
|
|
||||||
// session.walletId,
|
|
||||||
// session.pjUri,
|
|
||||||
// PayjoinSessionStatus.unrecoverable,
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// await _hiveStorage.saveValue(
|
|
||||||
// key: senderPrefix + pjUri,
|
|
||||||
// value: jsonEncode(updatedSession.toJson()),
|
|
||||||
// );
|
|
||||||
// return null;
|
|
||||||
// } catch (e) {
|
|
||||||
// return Err(e.toString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Future<(List<SendSession>, Err?)> readAllSenders() async {
|
|
||||||
// try {
|
|
||||||
// final (allData, err) = await _hiveStorage.getAll();
|
|
||||||
// if (err != null) return (List<SendSession>.empty(), err);
|
|
||||||
//
|
|
||||||
// final List<SendSession> senders = [];
|
|
||||||
// allData!.forEach((key, value) {
|
|
||||||
// if (key.startsWith(senderPrefix)) {
|
|
||||||
// try {
|
|
||||||
// final obj = jsonDecode(value) as Map<String, dynamic>;
|
|
||||||
// senders.add(SendSession.fromJson(obj));
|
|
||||||
// } catch (e) {
|
|
||||||
// // Skip invalid entries
|
|
||||||
// debugPrint('Error: $e');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// return (senders, null);
|
|
||||||
// } catch (e) {
|
|
||||||
// return (List<SendSession>.empty(), Err(e.toString()));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
import 'package:cw_bitcoin/exceptions.dart';
|
import 'package:cw_bitcoin/exceptions.dart';
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
@ -25,6 +26,8 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
this.hasTaprootInputs = false,
|
this.hasTaprootInputs = false,
|
||||||
this.isMweb = false,
|
this.isMweb = false,
|
||||||
this.utxos = const [],
|
this.utxos = const [],
|
||||||
|
this.publicKeys,
|
||||||
|
this.commitOverride,
|
||||||
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||||
|
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
|
@ -43,6 +46,8 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
String? idOverride;
|
String? idOverride;
|
||||||
String? hexOverride;
|
String? hexOverride;
|
||||||
List<String>? outputAddresses;
|
List<String>? outputAddresses;
|
||||||
|
final Map<String, PublicKeyWithDerivationPath>? publicKeys;
|
||||||
|
Future<void> Function()? commitOverride;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id => idOverride ?? _tx.txId();
|
String get id => idOverride ?? _tx.txId();
|
||||||
|
@ -129,6 +134,10 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> commit() async {
|
Future<void> commit() async {
|
||||||
|
if (commitOverride != null) {
|
||||||
|
return commitOverride?.call();
|
||||||
|
}
|
||||||
|
|
||||||
if (isMweb) {
|
if (isMweb) {
|
||||||
await _ltcCommit();
|
await _ltcCommit();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -721,11 +721,20 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String buildV2PjStr({
|
String getPayjoinEndpoint(Object wallet) {
|
||||||
required Object receiverWallet
|
final _wallet = wallet as BitcoinWallet;
|
||||||
}) {
|
return (_wallet.walletAddresses as BitcoinWalletAddresses).payjoinEndpoint ?? '';
|
||||||
final wallet = receiverWallet as BitcoinWallet;
|
}
|
||||||
return (wallet.walletAddresses as BitcoinWalletAddresses).payjoinEndpoint ?? '';
|
|
||||||
|
@override
|
||||||
|
void updatePayjoinState(Object wallet, bool value) {
|
||||||
|
final _wallet = wallet as ElectrumWallet;
|
||||||
|
if (value) {
|
||||||
|
(_wallet.walletAddresses as BitcoinWalletAddresses).initPayjoin();
|
||||||
|
} else {
|
||||||
|
(_wallet.walletAddresses as BitcoinWalletAddresses).payjoinManager.cleanupSessions();
|
||||||
|
(_wallet.walletAddresses as BitcoinWalletAddresses).currentPayjoinReceiver = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -755,12 +764,10 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingBitcoinTransaction> extractPjTx(
|
Future<PendingBitcoinTransaction> extractPjTx(Object wallet, String psbtString) {
|
||||||
Object wallet, String psbtString, Object credentials) {
|
|
||||||
return payjoin.extractPjTx(
|
return payjoin.extractPjTx(
|
||||||
wallet,
|
wallet,
|
||||||
psbtString,
|
psbtString
|
||||||
credentials
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||||
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
import 'package:cake_wallet/entities/update_haven_rate.dart';
|
||||||
|
@ -74,6 +75,10 @@ void startCurrentWalletChangeReaction(
|
||||||
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
|
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wallet.type == WalletType.bitcoin) {
|
||||||
|
bitcoin!.updatePayjoinState(wallet, settingsStore.usePayjoin);
|
||||||
|
}
|
||||||
|
|
||||||
await wallet.connectToNode(node: node);
|
await wallet.connectToNode(node: node);
|
||||||
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
|
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
|
||||||
final powNode = settingsStore.getCurrentPowNode(wallet.type);
|
final powNode = settingsStore.getCurrentPowNode(wallet.type);
|
||||||
|
|
|
@ -51,7 +51,7 @@ class PrivacyPage extends BasePage {
|
||||||
),
|
),
|
||||||
if (_privacySettingsViewModel.canUsePayjoin)
|
if (_privacySettingsViewModel.canUsePayjoin)
|
||||||
SettingsSwitcherCell(
|
SettingsSwitcherCell(
|
||||||
title: 'Use Payjoin', // ToDo: localize
|
title: S.of(context).use_payjoin,
|
||||||
value: _privacySettingsViewModel.usePayjoin,
|
value: _privacySettingsViewModel.usePayjoin,
|
||||||
onValueChange: (BuildContext _, bool value) {
|
onValueChange: (BuildContext _, bool value) {
|
||||||
_privacySettingsViewModel.setUsePayjoin(value);
|
_privacySettingsViewModel.setUsePayjoin(value);
|
||||||
|
|
|
@ -395,7 +395,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
|
|
||||||
if (wallet.isHardwareWallet) state = IsAwaitingDeviceResponseState();
|
if (wallet.isHardwareWallet) state = IsAwaitingDeviceResponseState();
|
||||||
|
|
||||||
pendingTransaction = await (pjUri != null ? performPayjoinSend() : wallet.createTransaction(_credentials())); // ToDo: Remove move Payjoin into create tx
|
pendingTransaction = await wallet.createTransaction(_credentials());
|
||||||
|
|
||||||
if (provider is ThorChainExchangeProvider) {
|
if (provider is ThorChainExchangeProvider) {
|
||||||
final outputCount = pendingTransaction?.outputCount ?? 0;
|
final outputCount = pendingTransaction?.outputCount ?? 0;
|
||||||
|
@ -850,10 +850,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a proposal is received, finalize the payjoin
|
// If a proposal is received, finalize the payjoin
|
||||||
return bitcoin!.extractPjTx(
|
return bitcoin!.extractPjTx(wallet, psbt);
|
||||||
wallet,
|
|
||||||
psbt,
|
|
||||||
_credentials()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,5 +172,8 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
_settingsStore.useMempoolFeeAPI = value;
|
_settingsStore.useMempoolFeeAPI = value;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setUsePayjoin(bool value) => _settingsStore.usePayjoin = value;
|
void setUsePayjoin(bool value) {
|
||||||
|
_settingsStore.usePayjoin = value;
|
||||||
|
bitcoin!.updatePayjoinState(_wallet, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,7 +284,7 @@ abstract class WalletAddressListViewModelBase
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
return HavenURI(amount: amount, address: address.address);
|
return HavenURI(amount: amount, address: address.address);
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
final pjEndpoint = bitcoin!.buildV2PjStr(receiverWallet: wallet);
|
final pjEndpoint = bitcoin!.getPayjoinEndpoint(wallet);
|
||||||
return BitcoinURI(amount: amount, address: address.address, pjUri: pjEndpoint);
|
return BitcoinURI(amount: amount, address: address.address, pjUri: pjEndpoint);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return LitecoinURI(amount: amount, address: address.address);
|
return LitecoinURI(amount: amount, address: address.address);
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "التبديل إلى",
|
"use": "التبديل إلى",
|
||||||
"use_card_info_three": "استخدم البطاقة الرقمية عبر الإنترنت أو مع طرق الدفع غير التلامسية.",
|
"use_card_info_three": "استخدم البطاقة الرقمية عبر الإنترنت أو مع طرق الدفع غير التلامسية.",
|
||||||
"use_card_info_two": "يتم تحويل الأموال إلى الدولار الأمريكي عند الاحتفاظ بها في الحساب المدفوع مسبقًا ، وليس بالعملات الرقمية.",
|
"use_card_info_two": "يتم تحويل الأموال إلى الدولار الأمريكي عند الاحتفاظ بها في الحساب المدفوع مسبقًا ، وليس بالعملات الرقمية.",
|
||||||
|
"use_payjoin": "يستخدم ٪٪٪",
|
||||||
"use_ssl": "استخدم SSL",
|
"use_ssl": "استخدم SSL",
|
||||||
"use_suggested": "استخدام المقترح",
|
"use_suggested": "استخدام المقترح",
|
||||||
"use_testnet": "استخدم testnet",
|
"use_testnet": "استخدم testnet",
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Смяна на ",
|
"use": "Смяна на ",
|
||||||
"use_card_info_three": "Използвайте дигиталната карта онлайн или чрез безконтактен метод на плащане.",
|
"use_card_info_three": "Използвайте дигиталната карта онлайн или чрез безконтактен метод на плащане.",
|
||||||
"use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.",
|
"use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.",
|
||||||
|
"use_payjoin": "Използвайте %%",
|
||||||
"use_ssl": "Използване на SSL",
|
"use_ssl": "Използване на SSL",
|
||||||
"use_suggested": "Използване на предложеното",
|
"use_suggested": "Използване на предложеното",
|
||||||
"use_testnet": "Използвайте TestNet",
|
"use_testnet": "Използвайте TestNet",
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Přepnout na ",
|
"use": "Přepnout na ",
|
||||||
"use_card_info_three": "Použijte tuto digitální kartu online nebo bezkontaktními platebními metodami.",
|
"use_card_info_three": "Použijte tuto digitální kartu online nebo bezkontaktními platebními metodami.",
|
||||||
"use_card_info_two": "Prostředky jsou převedeny na USD, když jsou drženy na předplaceném účtu, nikoliv na digitální měnu.",
|
"use_card_info_two": "Prostředky jsou převedeny na USD, když jsou drženy na předplaceném účtu, nikoliv na digitální měnu.",
|
||||||
|
"use_payjoin": "Použijte %%TE",
|
||||||
"use_ssl": "Použít SSL",
|
"use_ssl": "Použít SSL",
|
||||||
"use_suggested": "Použít doporučený",
|
"use_suggested": "Použít doporučený",
|
||||||
"use_testnet": "Použijte testNet",
|
"use_testnet": "Použijte testNet",
|
||||||
|
|
|
@ -907,6 +907,7 @@
|
||||||
"use": "Wechsel zu ",
|
"use": "Wechsel zu ",
|
||||||
"use_card_info_three": "Verwenden Sie die digitale Karte online oder mit kontaktlosen Zahlungsmethoden.",
|
"use_card_info_three": "Verwenden Sie die digitale Karte online oder mit kontaktlosen Zahlungsmethoden.",
|
||||||
"use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.",
|
"use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.",
|
||||||
|
"use_payjoin": "Benutze Payjoin",
|
||||||
"use_ssl": "SSL verwenden",
|
"use_ssl": "SSL verwenden",
|
||||||
"use_suggested": "Vorgeschlagen verwenden",
|
"use_suggested": "Vorgeschlagen verwenden",
|
||||||
"use_testnet": "TESTNET verwenden",
|
"use_testnet": "TESTNET verwenden",
|
||||||
|
@ -991,4 +992,4 @@
|
||||||
"you_will_get": "Konvertieren zu",
|
"you_will_get": "Konvertieren zu",
|
||||||
"you_will_send": "Konvertieren von",
|
"you_will_send": "Konvertieren von",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Switch to ",
|
"use": "Switch to ",
|
||||||
"use_card_info_three": "Use the digital card online or with contactless payment methods.",
|
"use_card_info_three": "Use the digital card online or with contactless payment methods.",
|
||||||
"use_card_info_two": "Funds are converted to USD when they're held in the prepaid account, not in digital currencies.",
|
"use_card_info_two": "Funds are converted to USD when they're held in the prepaid account, not in digital currencies.",
|
||||||
|
"use_payjoin": "Use Payjoin",
|
||||||
"use_ssl": "Use SSL",
|
"use_ssl": "Use SSL",
|
||||||
"use_suggested": "Use Suggested",
|
"use_suggested": "Use Suggested",
|
||||||
"use_testnet": "Use Testnet",
|
"use_testnet": "Use Testnet",
|
||||||
|
@ -988,4 +989,4 @@
|
||||||
"you_will_get": "Convert to",
|
"you_will_get": "Convert to",
|
||||||
"you_will_send": "Convert from",
|
"you_will_send": "Convert from",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -906,6 +906,7 @@
|
||||||
"use": "Utilizar a ",
|
"use": "Utilizar a ",
|
||||||
"use_card_info_three": "Utiliza la tarjeta digital en línea o con métodos de pago sin contacto.",
|
"use_card_info_three": "Utiliza la tarjeta digital en línea o con métodos de pago sin contacto.",
|
||||||
"use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
|
"use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
|
||||||
|
"use_payjoin": "Usar Payjoin",
|
||||||
"use_ssl": "Utiliza SSL",
|
"use_ssl": "Utiliza SSL",
|
||||||
"use_suggested": "Usar sugerido",
|
"use_suggested": "Usar sugerido",
|
||||||
"use_testnet": "Usar TestNet",
|
"use_testnet": "Usar TestNet",
|
||||||
|
@ -989,4 +990,4 @@
|
||||||
"you_will_get": "Convertir a",
|
"you_will_get": "Convertir a",
|
||||||
"you_will_send": "Convertir de",
|
"you_will_send": "Convertir de",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Changer vers code PIN à ",
|
"use": "Changer vers code PIN à ",
|
||||||
"use_card_info_three": "Utilisez la carte numérique en ligne ou avec des méthodes de paiement sans contact.",
|
"use_card_info_three": "Utilisez la carte numérique en ligne ou avec des méthodes de paiement sans contact.",
|
||||||
"use_card_info_two": "Les fonds sont convertis en USD lorsqu'ils sont détenus sur le compte prépayé, et non en devises numériques.",
|
"use_card_info_two": "Les fonds sont convertis en USD lorsqu'ils sont détenus sur le compte prépayé, et non en devises numériques.",
|
||||||
|
"use_payjoin": "Utiliser Payjoin",
|
||||||
"use_ssl": "Utiliser SSL",
|
"use_ssl": "Utiliser SSL",
|
||||||
"use_suggested": "Suivre la suggestion",
|
"use_suggested": "Suivre la suggestion",
|
||||||
"use_testnet": "Utiliser TestNet",
|
"use_testnet": "Utiliser TestNet",
|
||||||
|
@ -988,4 +989,4 @@
|
||||||
"you_will_get": "Convertir vers",
|
"you_will_get": "Convertir vers",
|
||||||
"you_will_send": "Convertir depuis",
|
"you_will_send": "Convertir depuis",
|
||||||
"yy": "AA"
|
"yy": "AA"
|
||||||
}
|
}
|
||||||
|
|
|
@ -907,6 +907,7 @@
|
||||||
"use": "Canja zuwa",
|
"use": "Canja zuwa",
|
||||||
"use_card_info_three": "Yi amfani da katin dijital akan layi ko tare da hanyoyin biyan kuɗi mara lamba.",
|
"use_card_info_three": "Yi amfani da katin dijital akan layi ko tare da hanyoyin biyan kuɗi mara lamba.",
|
||||||
"use_card_info_two": "Ana canza kuɗi zuwa dalar Amurka lokacin da ake riƙe su a cikin asusun da aka riga aka biya, ba cikin agogon dijital ba.",
|
"use_card_info_two": "Ana canza kuɗi zuwa dalar Amurka lokacin da ake riƙe su a cikin asusun da aka riga aka biya, ba cikin agogon dijital ba.",
|
||||||
|
"use_payjoin": "Yi amfani da %%",
|
||||||
"use_ssl": "Yi amfani da SSL",
|
"use_ssl": "Yi amfani da SSL",
|
||||||
"use_suggested": "Amfani da Shawarwari",
|
"use_suggested": "Amfani da Shawarwari",
|
||||||
"use_testnet": "Amfani da gwaji",
|
"use_testnet": "Amfani da gwaji",
|
||||||
|
|
|
@ -907,6 +907,7 @@
|
||||||
"use": "उपयोग ",
|
"use": "उपयोग ",
|
||||||
"use_card_info_three": "डिजिटल कार्ड का ऑनलाइन या संपर्क रहित भुगतान विधियों के साथ उपयोग करें।",
|
"use_card_info_three": "डिजिटल कार्ड का ऑनलाइन या संपर्क रहित भुगतान विधियों के साथ उपयोग करें।",
|
||||||
"use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
|
"use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
|
||||||
|
"use_payjoin": "उपयोग %%%",
|
||||||
"use_ssl": "उपयोग SSL",
|
"use_ssl": "उपयोग SSL",
|
||||||
"use_suggested": "सुझाए गए का प्रयोग करें",
|
"use_suggested": "सुझाए गए का प्रयोग करें",
|
||||||
"use_testnet": "टेस्टनेट का उपयोग करें",
|
"use_testnet": "टेस्टनेट का उपयोग करें",
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Prebaci na",
|
"use": "Prebaci na",
|
||||||
"use_card_info_three": "Koristite digitalnu karticu online ili s beskontaktnim metodama plaćanja.",
|
"use_card_info_three": "Koristite digitalnu karticu online ili s beskontaktnim metodama plaćanja.",
|
||||||
"use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.",
|
"use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.",
|
||||||
|
"use_payjoin": "Koristite Payjoin",
|
||||||
"use_ssl": "Koristi SSL",
|
"use_ssl": "Koristi SSL",
|
||||||
"use_suggested": "Koristite predloženo",
|
"use_suggested": "Koristite predloženo",
|
||||||
"use_testnet": "Koristite TestNet",
|
"use_testnet": "Koristite TestNet",
|
||||||
|
@ -988,4 +989,4 @@
|
||||||
"you_will_get": "Razmijeni u",
|
"you_will_get": "Razmijeni u",
|
||||||
"you_will_send": "Razmijeni iz",
|
"you_will_send": "Razmijeni iz",
|
||||||
"yy": "GG"
|
"yy": "GG"
|
||||||
}
|
}
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Փոխեք ",
|
"use": "Փոխեք ",
|
||||||
"use_card_info_three": "Օգտագործեք թվային քարտը առցանց կամ անշփման վճարման մեթոդներով։",
|
"use_card_info_three": "Օգտագործեք թվային քարտը առցանց կամ անշփման վճարման մեթոդներով։",
|
||||||
"use_card_info_two": "Միջոցները փոխարկվում են ԱՄՆ դոլար երբ դրանք պահվում են կանխավճարային հաշվեկշռում, ոչ թե թվային արժույթներում։",
|
"use_card_info_two": "Միջոցները փոխարկվում են ԱՄՆ դոլար երբ դրանք պահվում են կանխավճարային հաշվեկշռում, ոչ թե թվային արժույթներում։",
|
||||||
|
"use_payjoin": "Օգտագործեք Payjoin",
|
||||||
"use_ssl": "Օգտագործել SSL",
|
"use_ssl": "Օգտագործել SSL",
|
||||||
"use_suggested": "Օգտագործել առաջարկվածը",
|
"use_suggested": "Օգտագործել առաջարկվածը",
|
||||||
"use_testnet": "Օգտագործել Testnet",
|
"use_testnet": "Օգտագործել Testnet",
|
||||||
|
@ -988,4 +989,4 @@
|
||||||
"you_will_get": "Ստացեք",
|
"you_will_get": "Ստացեք",
|
||||||
"you_will_send": "Փոխանակեք",
|
"you_will_send": "Փոխանակեք",
|
||||||
"yy": "ՏՏ"
|
"yy": "ՏՏ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -908,6 +908,7 @@
|
||||||
"use": "Beralih ke ",
|
"use": "Beralih ke ",
|
||||||
"use_card_info_three": "Gunakan kartu digital secara online atau dengan metode pembayaran tanpa kontak.",
|
"use_card_info_three": "Gunakan kartu digital secara online atau dengan metode pembayaran tanpa kontak.",
|
||||||
"use_card_info_two": "Dana dikonversi ke USD ketika disimpan dalam akun pra-bayar, bukan dalam mata uang digital.",
|
"use_card_info_two": "Dana dikonversi ke USD ketika disimpan dalam akun pra-bayar, bukan dalam mata uang digital.",
|
||||||
|
"use_payjoin": "Menggunakan Payjoin",
|
||||||
"use_ssl": "Gunakan SSL",
|
"use_ssl": "Gunakan SSL",
|
||||||
"use_suggested": "Gunakan yang Disarankan",
|
"use_suggested": "Gunakan yang Disarankan",
|
||||||
"use_testnet": "Gunakan TestNet",
|
"use_testnet": "Gunakan TestNet",
|
||||||
|
@ -991,4 +992,4 @@
|
||||||
"you_will_get": "Konversi ke",
|
"you_will_get": "Konversi ke",
|
||||||
"you_will_send": "Konversi dari",
|
"you_will_send": "Konversi dari",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -907,6 +907,7 @@
|
||||||
"use": "Passa a ",
|
"use": "Passa a ",
|
||||||
"use_card_info_three": "Utilizza la carta digitale online o con metodi di pagamento contactless.",
|
"use_card_info_three": "Utilizza la carta digitale online o con metodi di pagamento contactless.",
|
||||||
"use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.",
|
"use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.",
|
||||||
|
"use_payjoin": "Utilizzo Payjoin",
|
||||||
"use_ssl": "Usa SSL",
|
"use_ssl": "Usa SSL",
|
||||||
"use_suggested": "Usa suggerito",
|
"use_suggested": "Usa suggerito",
|
||||||
"use_testnet": "Usa TestNet",
|
"use_testnet": "Usa TestNet",
|
||||||
|
@ -991,4 +992,4 @@
|
||||||
"you_will_get": "Converti a",
|
"you_will_get": "Converti a",
|
||||||
"you_will_send": "Conveti da",
|
"you_will_send": "Conveti da",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -906,6 +906,7 @@
|
||||||
"use": "使用する ",
|
"use": "使用する ",
|
||||||
"use_card_info_three": "デジタルカードをオンラインまたは非接触型決済方法で使用してください。",
|
"use_card_info_three": "デジタルカードをオンラインまたは非接触型決済方法で使用してください。",
|
||||||
"use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。",
|
"use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。",
|
||||||
|
"use_payjoin": "使用 %%%",
|
||||||
"use_ssl": "SSLを使用する",
|
"use_ssl": "SSLを使用する",
|
||||||
"use_suggested": "推奨を使用",
|
"use_suggested": "推奨を使用",
|
||||||
"use_testnet": "テストネットを使用します",
|
"use_testnet": "テストネットを使用します",
|
||||||
|
|
|
@ -906,6 +906,7 @@
|
||||||
"use": "사용하다 ",
|
"use": "사용하다 ",
|
||||||
"use_card_info_three": "디지털 카드를 온라인 또는 비접촉식 결제 수단으로 사용하십시오.",
|
"use_card_info_three": "디지털 카드를 온라인 또는 비접촉식 결제 수단으로 사용하십시오.",
|
||||||
"use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.",
|
"use_card_info_two": "디지털 화폐가 아닌 선불 계정에 보유하면 자금이 USD로 변환됩니다.",
|
||||||
|
"use_payjoin": "사용 Payjoin",
|
||||||
"use_ssl": "SSL 사용",
|
"use_ssl": "SSL 사용",
|
||||||
"use_suggested": "추천 사용",
|
"use_suggested": "추천 사용",
|
||||||
"use_testnet": "TestNet을 사용하십시오",
|
"use_testnet": "TestNet을 사용하십시오",
|
||||||
|
@ -990,4 +991,4 @@
|
||||||
"you_will_send": "다음에서 변환",
|
"you_will_send": "다음에서 변환",
|
||||||
"YY": "YY",
|
"YY": "YY",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "သို့ပြောင်းပါ။",
|
"use": "သို့ပြောင်းပါ။",
|
||||||
"use_card_info_three": "ဒစ်ဂျစ်တယ်ကတ်ကို အွန်လိုင်း သို့မဟုတ် ထိတွေ့မှုမဲ့ ငွေပေးချေမှုနည်းလမ်းများဖြင့် အသုံးပြုပါ။",
|
"use_card_info_three": "ဒစ်ဂျစ်တယ်ကတ်ကို အွန်လိုင်း သို့မဟုတ် ထိတွေ့မှုမဲ့ ငွေပေးချေမှုနည်းလမ်းများဖြင့် အသုံးပြုပါ။",
|
||||||
"use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
|
"use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
|
||||||
|
"use_payjoin": "%% Chair ကိုအသုံးပြုပါ",
|
||||||
"use_ssl": "SSL ကိုသုံးပါ။",
|
"use_ssl": "SSL ကိုသုံးပါ။",
|
||||||
"use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။",
|
"use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။",
|
||||||
"use_testnet": "testnet ကိုသုံးပါ",
|
"use_testnet": "testnet ကိုသုံးပါ",
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Gebruik ",
|
"use": "Gebruik ",
|
||||||
"use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
|
"use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
|
||||||
"use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
|
"use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
|
||||||
|
"use_payjoin": "Gebruik Payjoin",
|
||||||
"use_ssl": "Gebruik SSL",
|
"use_ssl": "Gebruik SSL",
|
||||||
"use_suggested": "Gebruik aanbevolen",
|
"use_suggested": "Gebruik aanbevolen",
|
||||||
"use_testnet": "Gebruik testnet",
|
"use_testnet": "Gebruik testnet",
|
||||||
|
@ -989,4 +990,4 @@
|
||||||
"you_will_get": "Converteren naar",
|
"you_will_get": "Converteren naar",
|
||||||
"you_will_send": "Converteren van",
|
"you_will_send": "Converteren van",
|
||||||
"yy": "JJ"
|
"yy": "JJ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Użyj ",
|
"use": "Użyj ",
|
||||||
"use_card_info_three": "Użyj cyfrowej karty online lub za pomocą zbliżeniowych metod płatności.",
|
"use_card_info_three": "Użyj cyfrowej karty online lub za pomocą zbliżeniowych metod płatności.",
|
||||||
"use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.",
|
"use_card_info_two": "Środki są przeliczane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w walutach cyfrowych.",
|
||||||
|
"use_payjoin": "Używać Payjoin",
|
||||||
"use_ssl": "Użyj SSL",
|
"use_ssl": "Użyj SSL",
|
||||||
"use_suggested": "Użyj sugerowane",
|
"use_suggested": "Użyj sugerowane",
|
||||||
"use_testnet": "Użyj testne",
|
"use_testnet": "Użyj testne",
|
||||||
|
@ -988,4 +989,4 @@
|
||||||
"you_will_get": "Konwertuj na",
|
"you_will_get": "Konwertuj na",
|
||||||
"you_will_send": "Konwertuj z",
|
"you_will_send": "Konwertuj z",
|
||||||
"yy": "RR"
|
"yy": "RR"
|
||||||
}
|
}
|
||||||
|
|
|
@ -907,6 +907,7 @@
|
||||||
"use": "Use PIN de ",
|
"use": "Use PIN de ",
|
||||||
"use_card_info_three": "Use o cartão digital online ou com métodos de pagamento sem contato.",
|
"use_card_info_three": "Use o cartão digital online ou com métodos de pagamento sem contato.",
|
||||||
"use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.",
|
"use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.",
|
||||||
|
"use_payjoin": "Usar Payjoin",
|
||||||
"use_ssl": "Use SSL",
|
"use_ssl": "Use SSL",
|
||||||
"use_suggested": "Uso sugerido",
|
"use_suggested": "Uso sugerido",
|
||||||
"use_testnet": "Use testNet",
|
"use_testnet": "Use testNet",
|
||||||
|
@ -991,4 +992,4 @@
|
||||||
"you_will_get": "Converter para",
|
"you_will_get": "Converter para",
|
||||||
"you_will_send": "Converter de",
|
"you_will_send": "Converter de",
|
||||||
"yy": "aa"
|
"yy": "aa"
|
||||||
}
|
}
|
||||||
|
|
|
@ -906,6 +906,7 @@
|
||||||
"use": "Использовать ",
|
"use": "Использовать ",
|
||||||
"use_card_info_three": "Используйте цифровую карту онлайн или с помощью бесконтактных способов оплаты.",
|
"use_card_info_three": "Используйте цифровую карту онлайн или с помощью бесконтактных способов оплаты.",
|
||||||
"use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.",
|
"use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.",
|
||||||
|
"use_payjoin": "Использовать %%%",
|
||||||
"use_ssl": "Использовать SSL",
|
"use_ssl": "Использовать SSL",
|
||||||
"use_suggested": "Использовать предложенный",
|
"use_suggested": "Использовать предложенный",
|
||||||
"use_testnet": "Используйте Testnet",
|
"use_testnet": "Используйте Testnet",
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "สลับไปที่ ",
|
"use": "สลับไปที่ ",
|
||||||
"use_card_info_three": "ใช้บัตรดิจิตอลออนไลน์หรือผ่านวิธีการชำระเงินแบบไม่ต้องใช้บัตรกระดาษ",
|
"use_card_info_three": "ใช้บัตรดิจิตอลออนไลน์หรือผ่านวิธีการชำระเงินแบบไม่ต้องใช้บัตรกระดาษ",
|
||||||
"use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล",
|
"use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล",
|
||||||
|
"use_payjoin": "ใช้ %%%",
|
||||||
"use_ssl": "ใช้ SSL",
|
"use_ssl": "ใช้ SSL",
|
||||||
"use_suggested": "ใช้ที่แนะนำ",
|
"use_suggested": "ใช้ที่แนะนำ",
|
||||||
"use_testnet": "ใช้ testnet",
|
"use_testnet": "ใช้ testnet",
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Lumipat sa ",
|
"use": "Lumipat sa ",
|
||||||
"use_card_info_three": "Gamitin ang digital card online o sa mga paraan ng pagbabayad na walang contact.",
|
"use_card_info_three": "Gamitin ang digital card online o sa mga paraan ng pagbabayad na walang contact.",
|
||||||
"use_card_info_two": "Ang mga pondo ay na-convert sa USD kapag hawak sa prepaid account, hindi sa mga digital na pera.",
|
"use_card_info_two": "Ang mga pondo ay na-convert sa USD kapag hawak sa prepaid account, hindi sa mga digital na pera.",
|
||||||
|
"use_payjoin": "Gumamit ng Payjoin",
|
||||||
"use_ssl": "Gumamit ng SSL",
|
"use_ssl": "Gumamit ng SSL",
|
||||||
"use_suggested": "Gumamit ng iminungkahing",
|
"use_suggested": "Gumamit ng iminungkahing",
|
||||||
"use_testnet": "Gumamit ng testnet",
|
"use_testnet": "Gumamit ng testnet",
|
||||||
|
@ -988,4 +989,4 @@
|
||||||
"you_will_get": "I-convert sa",
|
"you_will_get": "I-convert sa",
|
||||||
"you_will_send": "I-convert mula sa",
|
"you_will_send": "I-convert mula sa",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "Şuna geç: ",
|
"use": "Şuna geç: ",
|
||||||
"use_card_info_three": "Dijital kartı çevrimiçi olarak veya temassız ödeme yöntemleriyle kullanın.",
|
"use_card_info_three": "Dijital kartı çevrimiçi olarak veya temassız ödeme yöntemleriyle kullanın.",
|
||||||
"use_card_info_two": "Paralar, dijital para birimlerinde değil, ön ödemeli hesapta tutulduğunda USD'ye dönüştürülür.",
|
"use_card_info_two": "Paralar, dijital para birimlerinde değil, ön ödemeli hesapta tutulduğunda USD'ye dönüştürülür.",
|
||||||
|
"use_payjoin": "Kullanmak Payjoin",
|
||||||
"use_ssl": "SSL kullan",
|
"use_ssl": "SSL kullan",
|
||||||
"use_suggested": "Önerileni Kullan",
|
"use_suggested": "Önerileni Kullan",
|
||||||
"use_testnet": "TestNet kullanın",
|
"use_testnet": "TestNet kullanın",
|
||||||
|
@ -988,4 +989,4 @@
|
||||||
"you_will_get": "Biçimine dönüştür:",
|
"you_will_get": "Biçimine dönüştür:",
|
||||||
"you_will_send": "Biçiminden dönüştür:",
|
"you_will_send": "Biçiminden dönüştür:",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -906,6 +906,7 @@
|
||||||
"use": "Використати ",
|
"use": "Використати ",
|
||||||
"use_card_info_three": "Використовуйте цифрову картку онлайн або за допомогою безконтактних методів оплати.",
|
"use_card_info_three": "Використовуйте цифрову картку онлайн або за допомогою безконтактних методів оплати.",
|
||||||
"use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.",
|
"use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.",
|
||||||
|
"use_payjoin": "Використовуйте %%",
|
||||||
"use_ssl": "Використати SSL",
|
"use_ssl": "Використати SSL",
|
||||||
"use_suggested": "Використати запропоноване",
|
"use_suggested": "Використати запропоноване",
|
||||||
"use_testnet": "Використовуйте тестову мережу",
|
"use_testnet": "Використовуйте тестову мережу",
|
||||||
|
|
|
@ -907,6 +907,7 @@
|
||||||
"use": "تبدیل کرنا",
|
"use": "تبدیل کرنا",
|
||||||
"use_card_info_three": "ڈیجیٹل کارڈ آن لائن یا کنٹیکٹ لیس ادائیگی کے طریقوں کے ساتھ استعمال کریں۔",
|
"use_card_info_three": "ڈیجیٹل کارڈ آن لائن یا کنٹیکٹ لیس ادائیگی کے طریقوں کے ساتھ استعمال کریں۔",
|
||||||
"use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
|
"use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
|
||||||
|
"use_payjoin": "٪٪٪ کا استعمال کریں",
|
||||||
"use_ssl": "SSL استعمال کریں۔",
|
"use_ssl": "SSL استعمال کریں۔",
|
||||||
"use_suggested": "تجویز کردہ استعمال کریں۔",
|
"use_suggested": "تجویز کردہ استعمال کریں۔",
|
||||||
"use_testnet": "ٹیسٹ نیٹ استعمال کریں",
|
"use_testnet": "ٹیسٹ نیٹ استعمال کریں",
|
||||||
|
|
|
@ -904,6 +904,7 @@
|
||||||
"use": "Chuyển sang",
|
"use": "Chuyển sang",
|
||||||
"use_card_info_three": "Sử dụng thẻ kỹ thuật số trực tuyến hoặc với các phương thức thanh toán không tiếp xúc.",
|
"use_card_info_three": "Sử dụng thẻ kỹ thuật số trực tuyến hoặc với các phương thức thanh toán không tiếp xúc.",
|
||||||
"use_card_info_two": "Các khoản tiền được chuyển đổi thành USD khi chúng được giữ trong tài khoản trả trước, không phải trong các loại tiền kỹ thuật số.",
|
"use_card_info_two": "Các khoản tiền được chuyển đổi thành USD khi chúng được giữ trong tài khoản trả trước, không phải trong các loại tiền kỹ thuật số.",
|
||||||
|
"use_payjoin": "Sử dụng Payjoin",
|
||||||
"use_ssl": "Sử dụng SSL",
|
"use_ssl": "Sử dụng SSL",
|
||||||
"use_suggested": "Sử dụng đề xuất",
|
"use_suggested": "Sử dụng đề xuất",
|
||||||
"use_testnet": "Sử dụng Testnet",
|
"use_testnet": "Sử dụng Testnet",
|
||||||
|
@ -987,4 +988,4 @@
|
||||||
"you_will_get": "Chuyển đổi thành",
|
"you_will_get": "Chuyển đổi thành",
|
||||||
"you_will_send": "Chuyển đổi từ",
|
"you_will_send": "Chuyển đổi từ",
|
||||||
"yy": "YY"
|
"yy": "YY"
|
||||||
}
|
}
|
||||||
|
|
|
@ -906,6 +906,7 @@
|
||||||
"use": "Lo",
|
"use": "Lo",
|
||||||
"use_card_info_three": "Ẹ lo káàdí ayélujára lórí wẹ́ẹ̀bù tàbí ẹ lò ó lórí àwọn ẹ̀rọ̀ ìrajà tíwọn kò kò.",
|
"use_card_info_three": "Ẹ lo káàdí ayélujára lórí wẹ́ẹ̀bù tàbí ẹ lò ó lórí àwọn ẹ̀rọ̀ ìrajà tíwọn kò kò.",
|
||||||
"use_card_info_two": "A pààrọ̀ owó sí owó Amẹ́ríkà tó bá wà nínú àkanti t'á ti fikún tẹ́lẹ̀tẹ́lẹ̀. A kò kó owó náà nínú owó ayélujára.",
|
"use_card_info_two": "A pààrọ̀ owó sí owó Amẹ́ríkà tó bá wà nínú àkanti t'á ti fikún tẹ́lẹ̀tẹ́lẹ̀. A kò kó owó náà nínú owó ayélujára.",
|
||||||
|
"use_payjoin": "Lo %%",
|
||||||
"use_ssl": "Lo SSL",
|
"use_ssl": "Lo SSL",
|
||||||
"use_suggested": "Lo àbá",
|
"use_suggested": "Lo àbá",
|
||||||
"use_testnet": "Lo tele",
|
"use_testnet": "Lo tele",
|
||||||
|
|
|
@ -905,6 +905,7 @@
|
||||||
"use": "切换使用",
|
"use": "切换使用",
|
||||||
"use_card_info_three": "在线使用电子卡或使用非接触式支付方式。",
|
"use_card_info_three": "在线使用电子卡或使用非接触式支付方式。",
|
||||||
"use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。",
|
"use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。",
|
||||||
|
"use_payjoin": "使用 %%%",
|
||||||
"use_ssl": "使用SSL",
|
"use_ssl": "使用SSL",
|
||||||
"use_suggested": "使用建议",
|
"use_suggested": "使用建议",
|
||||||
"use_testnet": "使用TestNet",
|
"use_testnet": "使用TestNet",
|
||||||
|
|
|
@ -240,11 +240,12 @@ abstract class Bitcoin {
|
||||||
String? getUnusedMwebAddress(Object wallet);
|
String? getUnusedMwebAddress(Object wallet);
|
||||||
String? getUnusedSegwitAddress(Object wallet);
|
String? getUnusedSegwitAddress(Object wallet);
|
||||||
|
|
||||||
String buildV2PjStr({required Object receiverWallet});
|
void updatePayjoinState(Object wallet, bool state);
|
||||||
|
String getPayjoinEndpoint(Object wallet);
|
||||||
Future<Sender> buildPayjoinRequest(String originalPsbt, dynamic pjUri, int fee);
|
Future<Sender> buildPayjoinRequest(String originalPsbt, dynamic pjUri, int fee);
|
||||||
Future<String> buildOriginalPsbt(Object wallet, int fee, double amount, Object credentials);
|
Future<String> buildOriginalPsbt(Object wallet, int fee, double amount, Object credentials);
|
||||||
Future<String> requestAndPollV2Proposal(Sender sender);
|
Future<String> requestAndPollV2Proposal(Sender sender);
|
||||||
Future<PendingBitcoinTransaction> extractPjTx(Object wallet, String psbtString, Object credentials);
|
Future<PendingBitcoinTransaction> extractPjTx(Object wallet, String psbtString);
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue