mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
implement-payjoin (#1949)
* Initial Payjoin * Initial Payjoin * More payjoin stuff * Minor fixes * Minor fixes * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Fix minor bug causes by data inconsistency in the btc utxos * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Initial Payjoin * Initial Payjoin * More payjoin stuff * Minor fixes * Minor fixes * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Fix minor bug causes by data inconsistency in the btc utxos * Minor cleanup * Minor cleanup * Minor cleanup * Minor cleanup * Fix Rebase issues * Move PJ Receiver to isolate * Add Payjoin Setting * Payjoin Sender are now isolated * Added Payjoin sessions to tx overview. Fix Fee issue with payjoin * Clean up code * Fix taproot for payjoin * Fix CI Errors * Add Payjoin UI elements and details page * Add Payjoin UI elements and details page * Fix Translations * feat: Detect Payjoin URIs in pasted text and show to the User sending Payjoin * feat: rename pjUri to payjoinURI for more code clarity * Update res/values/strings_pl.arb Co-authored-by: cyan <cyjan@mrcyjanek.net> * Update cw_bitcoin/lib/payjoin/manager.dart Co-authored-by: cyan <cyjan@mrcyjanek.net> * Update cw_bitcoin/lib/payjoin/manager.dart Co-authored-by: cyan <cyjan@mrcyjanek.net> * feat: Disable Payjoin per default * feat: Disable Payjoin fully if disabled or no Inputs available * feat: Resume Payjoin if app comes back to foreground * chore: Revert overly aggressive code formats * feat: show correct Payjoin amount for receivers * feat: Improved payjoin status * feat: Show payjoin errors on payjoin details screen * deps: update flutter to 3.27.4 * feat: Revert localisations * bug: Remove duplicate transaction id on payjoin details * style: remove double await in payjoin sender * refactor(cw_bitcoin): Refactor method signatures and convert constructor to factory * refactor(cw_bitcoin): Refactor wallet service and PSBT signer for cleaner code Removed unnecessary `CakeHive` dependency and refactored `BitcoinWallet` initialization to use `payjoinSessionSource`. Improved code readability in `PsbtSigner` by reformatting lines and simplifying constructor methods for `UtxoWithPrivateKey`. * fix: Resume Payjoin Sessions and load PJUri after sleep * feat: Add "Copy Payjoin URL button" to receive screen * fix: Add "Payjoin enabled"-Box below QR Code on the receive screen * fix: Set payjoin_enabled color to black independent of the theme * refactor: Payjoin session management and cleanup unused code. --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> Co-authored-by: cyan <cyjan@mrcyjanek.net>
This commit is contained in:
parent
4a08e18f00
commit
82e3ebf4fa
84 changed files with 2622 additions and 198 deletions
BIN
assets/images/payjoin.png
Normal file
BIN
assets/images/payjoin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -2,22 +2,36 @@ import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
|
||||||
String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
|
String addressFromOutputScript(Script script, BasedUtxoNetwork network) {
|
||||||
try {
|
try {
|
||||||
switch (script.getAddressType()) {
|
return addressFromScript(script, network).toAddress(network);
|
||||||
case P2pkhAddressType.p2pkh:
|
|
||||||
return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case P2shAddressType.p2pkInP2sh:
|
|
||||||
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case SegwitAddresType.p2wpkh:
|
|
||||||
return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case P2shAddressType.p2pkhInP2sh:
|
|
||||||
return P2shAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case SegwitAddresType.p2wsh:
|
|
||||||
return P2wshAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
case SegwitAddresType.p2tr:
|
|
||||||
return P2trAddress.fromScriptPubkey(script: script).toAddress(network);
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BitcoinBaseAddress addressFromScript(Script script,
|
||||||
|
[BasedUtxoNetwork network = BitcoinNetwork.mainnet]) {
|
||||||
|
final addressType = script.getAddressType();
|
||||||
|
if (addressType == null) {
|
||||||
|
throw ArgumentError("Invalid script");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (addressType) {
|
||||||
|
case P2pkhAddressType.p2pkh:
|
||||||
|
return P2pkhAddress.fromScriptPubkey(
|
||||||
|
script: script, network: BitcoinNetwork.mainnet);
|
||||||
|
case P2shAddressType.p2pkhInP2sh:
|
||||||
|
return P2shAddress.fromScriptPubkey(
|
||||||
|
script: script, network: BitcoinNetwork.mainnet);
|
||||||
|
case SegwitAddresType.p2wpkh:
|
||||||
|
return P2wpkhAddress.fromScriptPubkey(
|
||||||
|
script: script, network: BitcoinNetwork.mainnet);
|
||||||
|
case SegwitAddresType.p2wsh:
|
||||||
|
return P2wshAddress.fromScriptPubkey(
|
||||||
|
script: script, network: BitcoinNetwork.mainnet);
|
||||||
|
case SegwitAddresType.p2tr:
|
||||||
|
return P2trAddress.fromScriptPubkey(
|
||||||
|
script: script, network: BitcoinNetwork.mainnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw ArgumentError("Invalid script");
|
||||||
|
}
|
||||||
|
|
|
@ -3,11 +3,17 @@ import 'package:cw_core/output_info.dart';
|
||||||
import 'package:cw_core/unspent_coin_type.dart';
|
import 'package:cw_core/unspent_coin_type.dart';
|
||||||
|
|
||||||
class BitcoinTransactionCredentials {
|
class BitcoinTransactionCredentials {
|
||||||
BitcoinTransactionCredentials(this.outputs,
|
BitcoinTransactionCredentials(
|
||||||
{required this.priority, this.feeRate, this.coinTypeToSpendFrom = UnspentCoinType.any});
|
this.outputs, {
|
||||||
|
required this.priority,
|
||||||
|
this.feeRate,
|
||||||
|
this.coinTypeToSpendFrom = UnspentCoinType.any,
|
||||||
|
this.payjoinUri,
|
||||||
|
});
|
||||||
|
|
||||||
final List<OutputInfo> outputs;
|
final List<OutputInfo> outputs;
|
||||||
final BitcoinTransactionPriority? priority;
|
final BitcoinTransactionPriority? priority;
|
||||||
final int? feeRate;
|
final int? feeRate;
|
||||||
final UnspentCoinType coinTypeToSpendFrom;
|
final UnspentCoinType coinTypeToSpendFrom;
|
||||||
|
final String? payjoinUri;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,22 +3,33 @@ import 'dart:convert';
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:cw_bitcoin/address_from_output.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
|
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||||
|
import 'package:cw_bitcoin/payjoin/manager.dart';
|
||||||
|
import 'package:cw_bitcoin/payjoin/storage.dart';
|
||||||
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||||
|
import 'package:cw_bitcoin/psbt/signer.dart';
|
||||||
|
import 'package:cw_bitcoin/psbt/transaction_builder.dart';
|
||||||
|
import 'package:cw_bitcoin/psbt/v0_deserialize.dart';
|
||||||
|
import 'package:cw_bitcoin/psbt/v0_finalizer.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_core/wallet_keys_file.dart';
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||||
|
import 'package:ledger_bitcoin/psbt.dart';
|
||||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
@ -31,6 +42,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required Box<PayjoinSession> payjoinBox,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
Uint8List? seedBytes,
|
Uint8List? seedBytes,
|
||||||
String? mnemonic,
|
String? mnemonic,
|
||||||
|
@ -71,8 +83,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
||||||
// 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);
|
||||||
walletAddresses = BitcoinWalletAddresses(
|
|
||||||
walletInfo,
|
payjoinManager = PayjoinManager(PayjoinStorage(payjoinBox), this);
|
||||||
|
walletAddresses = BitcoinWalletAddresses(walletInfo,
|
||||||
initialAddresses: initialAddresses,
|
initialAddresses: initialAddresses,
|
||||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
|
@ -84,7 +97,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
masterHd:
|
masterHd:
|
||||||
seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
|
seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
|
||||||
isHardwareWallet: walletInfo.isHardwareWallet,
|
isHardwareWallet: walletInfo.isHardwareWallet,
|
||||||
);
|
payjoinManager: payjoinManager);
|
||||||
|
|
||||||
autorun((_) {
|
autorun((_) {
|
||||||
this.walletAddresses.isEnabledAutoGenerateSubaddress =
|
this.walletAddresses.isEnabledAutoGenerateSubaddress =
|
||||||
|
@ -100,6 +113,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String password,
|
required String password,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required Box<PayjoinSession> payjoinBox,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
String? addressPageType,
|
String? addressPageType,
|
||||||
|
@ -122,9 +136,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
break;
|
break;
|
||||||
case DerivationType.electrum:
|
case DerivationType.electrum:
|
||||||
default:
|
default:
|
||||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
seedBytes =
|
||||||
|
await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return BitcoinWallet(
|
return BitcoinWallet(
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
passphrase: passphrase ?? "",
|
passphrase: passphrase ?? "",
|
||||||
|
@ -141,6 +157,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||||
addressPageType: addressPageType,
|
addressPageType: addressPageType,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
|
payjoinBox: payjoinBox,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +165,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
required String name,
|
required String name,
|
||||||
required WalletInfo walletInfo,
|
required WalletInfo walletInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||||
|
required Box<PayjoinSession> payjoinBox,
|
||||||
required String password,
|
required String password,
|
||||||
required EncryptionFileUtils encryptionFileUtils,
|
required EncryptionFileUtils encryptionFileUtils,
|
||||||
required bool alwaysScan,
|
required bool alwaysScan,
|
||||||
|
@ -204,7 +222,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
if (mnemonic != null) {
|
if (mnemonic != null) {
|
||||||
switch (walletInfo.derivationInfo!.derivationType) {
|
switch (walletInfo.derivationInfo!.derivationType) {
|
||||||
case DerivationType.electrum:
|
case DerivationType.electrum:
|
||||||
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
seedBytes =
|
||||||
|
await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
|
||||||
break;
|
break;
|
||||||
case DerivationType.bip39:
|
case DerivationType.bip39:
|
||||||
default:
|
default:
|
||||||
|
@ -234,7 +253,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
addressPageType: snp?.addressPageType,
|
addressPageType: snp?.addressPageType,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
);
|
payjoinBox: payjoinBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
LedgerConnection? _ledgerConnection;
|
LedgerConnection? _ledgerConnection;
|
||||||
|
@ -247,20 +266,25 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
late final PayjoinManager payjoinManager;
|
||||||
Future<BtcTransaction> buildHardwareWalletTransaction({
|
|
||||||
|
bool get isPayjoinAvailable => unspentCoinsInfo.values
|
||||||
|
.where((element) =>
|
||||||
|
element.walletId == id && element.isSending && !element.isFrozen)
|
||||||
|
.isNotEmpty;
|
||||||
|
|
||||||
|
Future<PsbtV2> buildPsbt({
|
||||||
required List<BitcoinBaseOutput> outputs,
|
required List<BitcoinBaseOutput> outputs,
|
||||||
required BigInt fee,
|
required BigInt fee,
|
||||||
required BasedUtxoNetwork network,
|
required BasedUtxoNetwork network,
|
||||||
required List<UtxoWithAddress> utxos,
|
required List<UtxoWithAddress> utxos,
|
||||||
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
||||||
|
required Uint8List masterFingerprint,
|
||||||
String? memo,
|
String? memo,
|
||||||
bool enableRBF = false,
|
bool enableRBF = false,
|
||||||
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
||||||
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
||||||
}) async {
|
}) async {
|
||||||
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
|
|
||||||
|
|
||||||
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
||||||
for (final utxo in utxos) {
|
for (final utxo in utxos) {
|
||||||
final rawTx =
|
final rawTx =
|
||||||
|
@ -278,13 +302,128 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
final psbt = PSBTTransactionBuild(
|
return PSBTTransactionBuild(
|
||||||
inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF)
|
||||||
|
.psbt;
|
||||||
|
}
|
||||||
|
|
||||||
final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt);
|
@override
|
||||||
|
Future<BtcTransaction> buildHardwareWalletTransaction({
|
||||||
|
required List<BitcoinBaseOutput> outputs,
|
||||||
|
required BigInt fee,
|
||||||
|
required BasedUtxoNetwork network,
|
||||||
|
required List<UtxoWithAddress> utxos,
|
||||||
|
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
||||||
|
String? memo,
|
||||||
|
bool enableRBF = false,
|
||||||
|
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
||||||
|
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
||||||
|
}) async {
|
||||||
|
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
|
||||||
|
|
||||||
|
final psbt = await buildPsbt(
|
||||||
|
outputs: outputs,
|
||||||
|
fee: fee,
|
||||||
|
network: network,
|
||||||
|
utxos: utxos,
|
||||||
|
publicKeys: publicKeys,
|
||||||
|
masterFingerprint: masterFingerprint,
|
||||||
|
memo: memo,
|
||||||
|
enableRBF: enableRBF,
|
||||||
|
inputOrdering: inputOrdering,
|
||||||
|
outputOrdering: outputOrdering,
|
||||||
|
);
|
||||||
|
|
||||||
|
final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt);
|
||||||
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
credentials = credentials as BitcoinTransactionCredentials;
|
||||||
|
|
||||||
|
final tx = (await super.createTransaction(credentials))
|
||||||
|
as PendingBitcoinTransaction;
|
||||||
|
|
||||||
|
final payjoinUri = credentials.payjoinUri;
|
||||||
|
if (payjoinUri == null) return tx;
|
||||||
|
|
||||||
|
final transaction = await buildPsbt(
|
||||||
|
utxos: tx.utxos,
|
||||||
|
outputs: tx.outputs
|
||||||
|
.map((e) => BitcoinOutput(
|
||||||
|
address: addressFromScript(e.scriptPubKey),
|
||||||
|
value: e.amount,
|
||||||
|
isSilentPayment: e.isSilentPayment,
|
||||||
|
isChange: e.isChange,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
fee: BigInt.from(tx.fee),
|
||||||
|
network: network,
|
||||||
|
memo: credentials.outputs.first.memo,
|
||||||
|
outputOrdering: BitcoinOrdering.none,
|
||||||
|
enableRBF: true,
|
||||||
|
publicKeys: tx.publicKeys!,
|
||||||
|
masterFingerprint: Uint8List(0));
|
||||||
|
|
||||||
|
final originalPsbt = await signPsbt(
|
||||||
|
base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
|
||||||
|
|
||||||
|
tx.commitOverride = () async {
|
||||||
|
final sender = await payjoinManager.initSender(
|
||||||
|
payjoinUri, originalPsbt, int.parse(tx.feeRate));
|
||||||
|
payjoinManager.spawnNewSender(
|
||||||
|
sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount));
|
||||||
|
};
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UtxoWithPrivateKey> getUtxoWithPrivateKeys() => unspentCoins
|
||||||
|
.where((e) => (e.isSending && !e.isFrozen))
|
||||||
|
.map((unspent) => UtxoWithPrivateKey.fromUnspent(unspent, this))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
Future<void> commitPsbt(String finalizedPsbt) {
|
||||||
|
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<String> signPsbt(
|
||||||
|
String preProcessedPsbt, List<UtxoWithPrivateKey> utxos) async {
|
||||||
|
final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt));
|
||||||
|
|
||||||
|
await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) {
|
||||||
|
return utxo.utxo.isP2tr()
|
||||||
|
? key.signTapRoot(
|
||||||
|
txDigest,
|
||||||
|
sighash: sighash,
|
||||||
|
tweak: utxo.utxo.isSilentPayment != true,
|
||||||
|
)
|
||||||
|
: key.signInput(txDigest, sigHash: sighash);
|
||||||
|
}, (txId, vout) async {
|
||||||
|
final txHex = await electrumClient.getTransactionHex(hash: txId);
|
||||||
|
final output = BtcTransaction.fromRaw(txHex).outputs[vout];
|
||||||
|
return TaprootAmountScriptPair(output.amount, output.scriptPubKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
psbt.finalizeV0();
|
||||||
|
return base64Encode(psbt.asPsbtV0());
|
||||||
|
}
|
||||||
|
|
||||||
@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) {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:blockchain_utils/bip/bip/bip32/bip32.dart';
|
import 'package:blockchain_utils/bip/bip/bip32/bip32.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
|
import 'package:cw_bitcoin/payjoin/manager.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
import 'package:cw_core/unspent_coin_type.dart';
|
import 'package:cw_core/unspent_coin_type.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:payjoin_flutter/receive.dart' as payjoin;
|
||||||
|
|
||||||
part 'bitcoin_wallet_addresses.g.dart';
|
part 'bitcoin_wallet_addresses.g.dart';
|
||||||
|
|
||||||
|
@ -17,6 +20,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
required super.sideHd,
|
required super.sideHd,
|
||||||
required super.network,
|
required super.network,
|
||||||
required super.isHardwareWallet,
|
required super.isHardwareWallet,
|
||||||
|
required this.payjoinManager,
|
||||||
super.initialAddresses,
|
super.initialAddresses,
|
||||||
super.initialRegularAddressIndex,
|
super.initialRegularAddressIndex,
|
||||||
super.initialChangeAddressIndex,
|
super.initialChangeAddressIndex,
|
||||||
|
@ -25,6 +29,15 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
super.masterHd,
|
super.masterHd,
|
||||||
}) : super(walletInfo);
|
}) : super(walletInfo);
|
||||||
|
|
||||||
|
final PayjoinManager payjoinManager;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
payjoin.Receiver? currentPayjoinReceiver;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
String? get payjoinEndpoint =>
|
||||||
|
currentPayjoinReceiver?.pjUriBuilder().build().pjEndpoint();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAddress(
|
String getAddress(
|
||||||
{required int index,
|
{required int index,
|
||||||
|
@ -45,4 +58,17 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
||||||
|
|
||||||
return generateP2WPKHAddress(hd: hd, index: index, network: network);
|
return generateP2WPKHAddress(hd: hd, index: index, network: network);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initPayjoin() async {
|
||||||
|
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
|
||||||
|
|
||||||
|
payjoinManager.resumeSessions();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> newPayjoinReceiver() async {
|
||||||
|
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress);
|
||||||
|
|
||||||
|
printV("Initializing new Payjoin Receiver");
|
||||||
|
payjoinManager.spawnNewReceiver(receiver: currentPayjoinReceiver!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
||||||
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
import 'package:cw_core/encryption_file_utils.dart';
|
import 'package:cw_core/encryption_file_utils.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_service.dart';
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
@ -21,10 +22,12 @@ class BitcoinWalletService extends WalletService<
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials,
|
BitcoinRestoreWalletFromWIFCredentials,
|
||||||
BitcoinRestoreWalletFromHardware> {
|
BitcoinRestoreWalletFromHardware> {
|
||||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource,
|
||||||
|
this.payjoinSessionSource, this.alwaysScan, this.isDirect);
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
final Box<WalletInfo> walletInfoSource;
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
|
final Box<PayjoinSession> payjoinSessionSource;
|
||||||
final bool alwaysScan;
|
final bool alwaysScan;
|
||||||
final bool isDirect;
|
final bool isDirect;
|
||||||
|
|
||||||
|
@ -55,6 +58,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
passphrase: credentials.passphrase,
|
passphrase: credentials.passphrase,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
payjoinBox: payjoinSessionSource,
|
||||||
network: network,
|
network: network,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
@ -79,6 +83,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
name: name,
|
name: name,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
payjoinBox: payjoinSessionSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
@ -92,6 +97,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
name: name,
|
name: name,
|
||||||
walletInfo: walletInfo,
|
walletInfo: walletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
payjoinBox: payjoinSessionSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
@ -126,6 +132,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
name: currentName,
|
name: currentName,
|
||||||
walletInfo: currentWalletInfo,
|
walletInfo: currentWalletInfo,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
payjoinBox: payjoinSessionSource,
|
||||||
alwaysScan: alwaysScan,
|
alwaysScan: alwaysScan,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
@ -147,7 +154,6 @@ class BitcoinWalletService extends WalletService<
|
||||||
credentials.walletInfo?.network = network.value;
|
credentials.walletInfo?.network = network.value;
|
||||||
credentials.walletInfo?.derivationInfo?.derivationPath =
|
credentials.walletInfo?.derivationInfo?.derivationPath =
|
||||||
credentials.hwAccountData.derivationPath;
|
credentials.hwAccountData.derivationPath;
|
||||||
|
|
||||||
final wallet = await BitcoinWallet(
|
final wallet = await BitcoinWallet(
|
||||||
password: credentials.password!,
|
password: credentials.password!,
|
||||||
xpub: credentials.hwAccountData.xpub,
|
xpub: credentials.hwAccountData.xpub,
|
||||||
|
@ -155,6 +161,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
networkParam: network,
|
networkParam: network,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
|
payjoinBox: payjoinSessionSource,
|
||||||
);
|
);
|
||||||
await wallet.save();
|
await wallet.save();
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
@ -182,6 +189,7 @@ class BitcoinWalletService extends WalletService<
|
||||||
mnemonic: credentials.mnemonic,
|
mnemonic: credentials.mnemonic,
|
||||||
walletInfo: credentials.walletInfo!,
|
walletInfo: credentials.walletInfo!,
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||||
|
payjoinBox: payjoinSessionSource,
|
||||||
network: network,
|
network: network,
|
||||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1188,6 +1188,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) {
|
||||||
|
@ -1965,6 +1966,11 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isMine(Script script) {
|
||||||
|
final derivedAddress = addressFromOutputScript(script, network);
|
||||||
|
return addressesSet.contains(derivedAddress);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||||
try {
|
try {
|
||||||
|
|
298
cw_bitcoin/lib/payjoin/manager.dart
Normal file
298
cw_bitcoin/lib/payjoin/manager.dart
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:isolate';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.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/psbt/signer.dart';
|
||||||
|
import 'package:cw_bitcoin/psbt/utils.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
|
import 'package:payjoin_flutter/common.dart';
|
||||||
|
import 'package:payjoin_flutter/receive.dart';
|
||||||
|
import 'package:payjoin_flutter/send.dart';
|
||||||
|
import 'package:payjoin_flutter/uri.dart' as PayjoinUri;
|
||||||
|
|
||||||
|
class PayjoinManager {
|
||||||
|
PayjoinManager(this._payjoinStorage, this._wallet);
|
||||||
|
|
||||||
|
final PayjoinStorage _payjoinStorage;
|
||||||
|
final BitcoinWalletBase _wallet;
|
||||||
|
final Map<String, PayjoinPollerSession> _activePollers = {};
|
||||||
|
|
||||||
|
static const List<String> ohttpRelayUrls = [
|
||||||
|
'https://pj.bobspacebkk.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';
|
||||||
|
|
||||||
|
Future<void> resumeSessions() async {
|
||||||
|
final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id);
|
||||||
|
|
||||||
|
final spawnedSessions = allSessions.map((session) {
|
||||||
|
if (session.isSenderSession) {
|
||||||
|
printV("Resuming Payjoin Sender Session ${session.pjUri!}");
|
||||||
|
return _spawnSender(
|
||||||
|
sender: Sender.fromJson(session.sender!),
|
||||||
|
pjUri: session.pjUri!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final receiver = Receiver.fromJson(session.receiver!);
|
||||||
|
printV("Resuming Payjoin Receiver Session ${receiver.id()}");
|
||||||
|
return _spawnReceiver(receiver: receiver);
|
||||||
|
});
|
||||||
|
|
||||||
|
printV("Resumed ${spawnedSessions.length} Payjoin Sessions");
|
||||||
|
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,
|
||||||
|
required BigInt amount,
|
||||||
|
bool isTestnet = false,
|
||||||
|
}) async {
|
||||||
|
final pjUri = Uri.parse(pjUrl).queryParameters['pj']!;
|
||||||
|
await _payjoinStorage.insertSenderSession(
|
||||||
|
sender, pjUri, _wallet.id, amount);
|
||||||
|
|
||||||
|
return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _spawnSender({
|
||||||
|
required Sender sender,
|
||||||
|
required String pjUri,
|
||||||
|
bool isTestnet = false,
|
||||||
|
}) async {
|
||||||
|
final completer = Completer();
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
|
||||||
|
receivePort.listen((message) async {
|
||||||
|
if (message is Map<String, dynamic>) {
|
||||||
|
try {
|
||||||
|
switch (message['type'] as PayjoinSenderRequestTypes) {
|
||||||
|
case PayjoinSenderRequestTypes.requestPosted:
|
||||||
|
return;
|
||||||
|
case PayjoinSenderRequestTypes.psbtToSign:
|
||||||
|
final proposalPsbt = message['psbt'] as String;
|
||||||
|
final utxos = _wallet.getUtxoWithPrivateKeys();
|
||||||
|
final finalizedPsbt = await _wallet.signPsbt(proposalPsbt, utxos);
|
||||||
|
final txId = getTxIdFromPsbtV0(finalizedPsbt);
|
||||||
|
_wallet.commitPsbt(finalizedPsbt);
|
||||||
|
|
||||||
|
_cleanupSession(pjUri);
|
||||||
|
await _payjoinStorage.markSenderSessionComplete(pjUri, txId);
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_cleanupSession(pjUri);
|
||||||
|
printV(e);
|
||||||
|
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri);
|
||||||
|
completer.completeError(e);
|
||||||
|
}
|
||||||
|
} else if (message is PayjoinSessionError) {
|
||||||
|
_cleanupSession(pjUri);
|
||||||
|
if (message is UnrecoverableError) {
|
||||||
|
printV(message.message);
|
||||||
|
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri);
|
||||||
|
completer.complete();
|
||||||
|
} else if (message is RecoverableError) {
|
||||||
|
completer.complete();
|
||||||
|
} else {
|
||||||
|
completer.completeError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final isolate = await Isolate.spawn(
|
||||||
|
PayjoinSenderWorker.run,
|
||||||
|
[receivePort.sendPort, sender.toJson(), pjUri],
|
||||||
|
);
|
||||||
|
|
||||||
|
_activePollers[pjUri] = PayjoinPollerSession(isolate, receivePort);
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Receiver> initReceiver(String address,
|
||||||
|
[bool isTestnet = false]) async {
|
||||||
|
try {
|
||||||
|
final payjoinDirectory =
|
||||||
|
await PayjoinUri.Url.fromStr(payjoinDirectoryUrl);
|
||||||
|
|
||||||
|
final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
|
||||||
|
ohttpRelay: await randomOhttpRelayUrl(),
|
||||||
|
payjoinDirectory: payjoinDirectory,
|
||||||
|
);
|
||||||
|
|
||||||
|
final receiver = await Receiver.create(
|
||||||
|
address: address,
|
||||||
|
network: isTestnet ? Network.testnet : Network.bitcoin,
|
||||||
|
directory: payjoinDirectory,
|
||||||
|
ohttpKeys: ohttpKeys,
|
||||||
|
ohttpRelay: await randomOhttpRelayUrl(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
|
||||||
|
|
||||||
|
return receiver;
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Error initializing Payjoin Receiver: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> spawnNewReceiver({
|
||||||
|
required Receiver receiver,
|
||||||
|
bool isTestnet = false,
|
||||||
|
}) async {
|
||||||
|
await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
|
||||||
|
return _spawnReceiver(isTestnet: isTestnet, receiver: receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _spawnReceiver({
|
||||||
|
required Receiver receiver,
|
||||||
|
bool isTestnet = false,
|
||||||
|
}) async {
|
||||||
|
final completer = Completer();
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
|
||||||
|
SendPort? mainToIsolateSendPort;
|
||||||
|
List<UtxoWithPrivateKey> utxos = [];
|
||||||
|
String rawAmount = '0';
|
||||||
|
|
||||||
|
receivePort.listen((message) async {
|
||||||
|
if (message is Map<String, dynamic>) {
|
||||||
|
try {
|
||||||
|
switch (message['type'] as PayjoinReceiverRequestTypes) {
|
||||||
|
case PayjoinReceiverRequestTypes.processOriginalTx:
|
||||||
|
final tx = message['tx'] as String;
|
||||||
|
rawAmount = getOutputAmountFromTx(tx, _wallet);
|
||||||
|
break;
|
||||||
|
case PayjoinReceiverRequestTypes.checkIsOwned:
|
||||||
|
(_wallet.walletAddresses as BitcoinWalletAddresses).newPayjoinReceiver();
|
||||||
|
_payjoinStorage.markReceiverSessionInProgress(receiver.id());
|
||||||
|
|
||||||
|
final inputScript = message['input_script'] as Uint8List;
|
||||||
|
final isOwned =
|
||||||
|
_wallet.isMine(Script.fromRaw(byteData: inputScript));
|
||||||
|
mainToIsolateSendPort?.send({
|
||||||
|
'requestId': message['requestId'],
|
||||||
|
'result': isOwned,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PayjoinReceiverRequestTypes.checkIsReceiverOutput:
|
||||||
|
final outputScript = message['output_script'] as Uint8List;
|
||||||
|
final isReceiverOutput =
|
||||||
|
_wallet.isMine(Script.fromRaw(byteData: outputScript));
|
||||||
|
mainToIsolateSendPort?.send({
|
||||||
|
'requestId': message['requestId'],
|
||||||
|
'result': isReceiverOutput,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PayjoinReceiverRequestTypes.getCandidateInputs:
|
||||||
|
utxos = _wallet.getUtxoWithPrivateKeys();
|
||||||
|
mainToIsolateSendPort?.send({
|
||||||
|
'requestId': message['requestId'],
|
||||||
|
'result': utxos,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PayjoinReceiverRequestTypes.processPsbt:
|
||||||
|
final psbt = message['psbt'] as String;
|
||||||
|
final signedPsbt = await _wallet.signPsbt(psbt, utxos);
|
||||||
|
mainToIsolateSendPort?.send({
|
||||||
|
'requestId': message['requestId'],
|
||||||
|
'result': signedPsbt,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PayjoinReceiverRequestTypes.proposalSent:
|
||||||
|
_cleanupSession(receiver.id());
|
||||||
|
final psbt = message['psbt'] as String;
|
||||||
|
await _payjoinStorage.markReceiverSessionComplete(
|
||||||
|
receiver.id(), getTxIdFromPsbtV0(psbt), rawAmount);
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_cleanupSession(receiver.id());
|
||||||
|
await _payjoinStorage.markReceiverSessionUnrecoverable(
|
||||||
|
receiver.id(), e.toString());
|
||||||
|
completer.completeError(e);
|
||||||
|
}
|
||||||
|
} else if (message is PayjoinSessionError) {
|
||||||
|
_cleanupSession(receiver.id());
|
||||||
|
if (message is UnrecoverableError) {
|
||||||
|
await _payjoinStorage.markReceiverSessionUnrecoverable(
|
||||||
|
receiver.id(), message.message);
|
||||||
|
completer.complete();
|
||||||
|
} else if (message is RecoverableError) {
|
||||||
|
completer.complete();
|
||||||
|
} else {
|
||||||
|
completer.completeError(message);
|
||||||
|
}
|
||||||
|
} else if (message is SendPort) {
|
||||||
|
mainToIsolateSendPort = message;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final isolate = await Isolate.spawn(
|
||||||
|
PayjoinReceiverWorker.run,
|
||||||
|
[receivePort.sendPort, receiver.toJson()],
|
||||||
|
);
|
||||||
|
|
||||||
|
_activePollers[receiver.id()] = PayjoinPollerSession(isolate, receivePort);
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanupSessions() {
|
||||||
|
final sessionIds = _activePollers.keys.toList();
|
||||||
|
for (final sessionId in sessionIds) {
|
||||||
|
_cleanupSession(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _cleanupSession(String sessionId) {
|
||||||
|
_activePollers[sessionId]?.close();
|
||||||
|
_activePollers.remove(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PayjoinPollerSession {
|
||||||
|
final Isolate isolate;
|
||||||
|
final ReceivePort port;
|
||||||
|
|
||||||
|
PayjoinPollerSession(this.isolate, this.port);
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
isolate.kill();
|
||||||
|
port.close();
|
||||||
|
}
|
||||||
|
}
|
219
cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart
Normal file
219
cw_bitcoin/lib/payjoin/payjoin_receive_worker.dart
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
|
||||||
|
import 'package:cw_bitcoin/psbt/signer.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:payjoin_flutter/bitcoin_ffi.dart';
|
||||||
|
import 'package:payjoin_flutter/common.dart';
|
||||||
|
import 'package:payjoin_flutter/receive.dart';
|
||||||
|
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
|
||||||
|
|
||||||
|
enum PayjoinReceiverRequestTypes {
|
||||||
|
processOriginalTx,
|
||||||
|
proposalSent,
|
||||||
|
getCandidateInputs,
|
||||||
|
checkIsOwned,
|
||||||
|
checkIsReceiverOutput,
|
||||||
|
processPsbt;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PayjoinReceiverWorker {
|
||||||
|
final SendPort sendPort;
|
||||||
|
final pendingRequests = <String, Completer<dynamic>>{};
|
||||||
|
|
||||||
|
PayjoinReceiverWorker._(this.sendPort);
|
||||||
|
|
||||||
|
static Future<void> run(List<Object> args) async {
|
||||||
|
await pj.core.init();
|
||||||
|
|
||||||
|
final sendPort = args[0] as SendPort;
|
||||||
|
final receiverJson = args[1] as String;
|
||||||
|
|
||||||
|
final worker = PayjoinReceiverWorker._(sendPort);
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
|
||||||
|
sendPort.send(receivePort.sendPort);
|
||||||
|
receivePort.listen(worker.handleMessage);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final httpClient = http.Client();
|
||||||
|
final receiver = Receiver.fromJson(receiverJson);
|
||||||
|
|
||||||
|
final uncheckedProposal =
|
||||||
|
await worker.receiveUncheckedProposal(httpClient, receiver);
|
||||||
|
|
||||||
|
final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast();
|
||||||
|
sendPort.send({
|
||||||
|
'type': PayjoinReceiverRequestTypes.processOriginalTx,
|
||||||
|
'tx': BytesUtils.toHexString(originalTx),
|
||||||
|
});
|
||||||
|
|
||||||
|
final payjoinProposal = await worker.processPayjoinProposal(
|
||||||
|
uncheckedProposal,
|
||||||
|
);
|
||||||
|
final psbt = await worker.sendFinalProposal(httpClient, payjoinProposal);
|
||||||
|
sendPort.send({
|
||||||
|
'type': PayjoinReceiverRequestTypes.proposalSent,
|
||||||
|
'psbt': psbt,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e is HttpException ||
|
||||||
|
(e is http.ClientException &&
|
||||||
|
e.message.contains("Software caused connection abort"))) {
|
||||||
|
sendPort.send(PayjoinSessionError.recoverable(e.toString()));
|
||||||
|
} else {
|
||||||
|
sendPort.send(PayjoinSessionError.unrecoverable(e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleMessage(dynamic message) async {
|
||||||
|
if (message is Map<String, dynamic>) {
|
||||||
|
final requestId = message['requestId'] as String?;
|
||||||
|
if (requestId != null && pendingRequests.containsKey(requestId)) {
|
||||||
|
pendingRequests[requestId]!.complete(message['result']);
|
||||||
|
pendingRequests.remove(requestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _sendRequest(PayjoinReceiverRequestTypes type,
|
||||||
|
[Map<String, dynamic> data = const {}]) async {
|
||||||
|
final completer = Completer<dynamic>();
|
||||||
|
final requestId = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
pendingRequests[requestId] = completer;
|
||||||
|
|
||||||
|
sendPort.send({
|
||||||
|
...data,
|
||||||
|
'type': type,
|
||||||
|
'requestId': requestId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<UncheckedProposal> receiveUncheckedProposal(
|
||||||
|
http.Client httpClient, Receiver session) async {
|
||||||
|
while (true) {
|
||||||
|
printV("Polling for Proposal (${session.id()})");
|
||||||
|
final extractReq = await session.extractReq();
|
||||||
|
final request = extractReq.$1;
|
||||||
|
|
||||||
|
final url = Uri.parse(request.url.asString());
|
||||||
|
final httpRequest = await httpClient.post(url,
|
||||||
|
headers: {'Content-Type': request.contentType}, body: request.body);
|
||||||
|
|
||||||
|
final proposal = await session.processRes(
|
||||||
|
body: httpRequest.bodyBytes, ctx: extractReq.$2);
|
||||||
|
if (proposal != null) return proposal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> sendFinalProposal(
|
||||||
|
http.Client httpClient, PayjoinProposal finalProposal) async {
|
||||||
|
final req = await finalProposal.extractV2Req();
|
||||||
|
final proposalReq = req.$1;
|
||||||
|
final proposalCtx = req.$2;
|
||||||
|
|
||||||
|
final request = await httpClient.post(
|
||||||
|
Uri.parse(proposalReq.url.asString()),
|
||||||
|
headers: {"Content-Type": proposalReq.contentType},
|
||||||
|
body: proposalReq.body,
|
||||||
|
);
|
||||||
|
|
||||||
|
await finalProposal.processRes(
|
||||||
|
res: request.bodyBytes,
|
||||||
|
ohttpContext: proposalCtx,
|
||||||
|
);
|
||||||
|
|
||||||
|
return await finalProposal.psbt();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PayjoinProposal> processPayjoinProposal(
|
||||||
|
UncheckedProposal proposal) async {
|
||||||
|
await proposal.extractTxToScheduleBroadcast();
|
||||||
|
// TODO Handle this. send to the main port on a timer?
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Receive Check 1: can broadcast
|
||||||
|
final pj1 = await proposal.assumeInteractiveReceiver();
|
||||||
|
|
||||||
|
// Receive Check 2: original PSBT has no receiver-owned inputs
|
||||||
|
final pj2 = await pj1.checkInputsNotOwned(
|
||||||
|
isOwned: (inputScript) async {
|
||||||
|
final result = await _sendRequest(
|
||||||
|
PayjoinReceiverRequestTypes.checkIsOwned,
|
||||||
|
{'input_script': inputScript},
|
||||||
|
);
|
||||||
|
return result as bool;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Receive Check 3: sender inputs have not been seen before (prevent probing attacks)
|
||||||
|
final pj3 = await pj2.checkNoInputsSeenBefore(isKnown: (input) => false);
|
||||||
|
|
||||||
|
// Identify receiver outputs
|
||||||
|
final pj4 = await pj3.identifyReceiverOutputs(
|
||||||
|
isReceiverOutput: (outputScript) async {
|
||||||
|
final result = await _sendRequest(
|
||||||
|
PayjoinReceiverRequestTypes.checkIsReceiverOutput,
|
||||||
|
{'output_script': outputScript},
|
||||||
|
);
|
||||||
|
return result as bool;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
final pj5 = await pj4.commitOutputs();
|
||||||
|
|
||||||
|
final listUnspent =
|
||||||
|
await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs);
|
||||||
|
final unspent = listUnspent as List<UtxoWithPrivateKey>;
|
||||||
|
if (unspent.isEmpty) throw Exception('No unspent outputs available');
|
||||||
|
|
||||||
|
final selectedUtxo = await _inputPairFromUtxo(unspent[0]);
|
||||||
|
final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]);
|
||||||
|
final pj7 = await pj6.commitInputs();
|
||||||
|
|
||||||
|
// Finalize proposal
|
||||||
|
final payjoinProposal = await pj7.finalizeProposal(
|
||||||
|
processPsbt: (String psbt) async {
|
||||||
|
final result = await _sendRequest(
|
||||||
|
PayjoinReceiverRequestTypes.processPsbt, {'psbt': psbt});
|
||||||
|
return result as String;
|
||||||
|
},
|
||||||
|
// TODO set maxFeeRateSatPerVb
|
||||||
|
maxFeeRateSatPerVb: BigInt.from(10000),
|
||||||
|
);
|
||||||
|
return payjoinProposal;
|
||||||
|
} catch (e) {
|
||||||
|
printV('Error occurred while finalizing proposal: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<InputPair> _inputPairFromUtxo(UtxoWithPrivateKey utxo) async {
|
||||||
|
final txout = TxOut(
|
||||||
|
value: utxo.utxo.value,
|
||||||
|
scriptPubkey: Uint8List.fromList(
|
||||||
|
utxo.ownerDetails.address.toScriptPubKey().toBytes()),
|
||||||
|
);
|
||||||
|
|
||||||
|
final psbtin =
|
||||||
|
PsbtInput(witnessUtxo: txout, redeemScript: null, witnessScript: null);
|
||||||
|
|
||||||
|
final previousOutput =
|
||||||
|
OutPoint(txid: utxo.utxo.txHash, vout: utxo.utxo.vout);
|
||||||
|
|
||||||
|
final txin = TxIn(
|
||||||
|
previousOutput: previousOutput,
|
||||||
|
scriptSig: await Script.newInstance(rawOutputScript: []),
|
||||||
|
witness: [],
|
||||||
|
sequence: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
return InputPair.newInstance(txin, psbtin);
|
||||||
|
}
|
||||||
|
}
|
119
cw_bitcoin/lib/payjoin/payjoin_send_worker.dart
Normal file
119
cw_bitcoin/lib/payjoin/payjoin_send_worker.dart
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
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:cw_core/utils/print_verbose.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
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>>{};
|
||||||
|
final String pjUrl;
|
||||||
|
|
||||||
|
PayjoinSenderWorker._(this.sendPort, this.pjUrl);
|
||||||
|
|
||||||
|
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 pjUrl = args[2] as String;
|
||||||
|
|
||||||
|
final sender = Sender.fromJson(senderJson);
|
||||||
|
final worker = PayjoinSenderWorker._(sendPort, pjUrl);
|
||||||
|
|
||||||
|
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 = http.Client();
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await _runSenderV2(sender, httpClient);
|
||||||
|
} catch (e) {
|
||||||
|
printV(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) {
|
||||||
|
printV(e);
|
||||||
|
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, http.Client httpClient) async {
|
||||||
|
try {
|
||||||
|
final postRequest = await sender.extractV2(
|
||||||
|
ohttpProxyUrl: await PayjoinManager.randomOhttpRelayUrl(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final postResult = await _postRequest(httpClient, postRequest.$1);
|
||||||
|
final getContext =
|
||||||
|
await postRequest.$2.processResponse(response: postResult);
|
||||||
|
|
||||||
|
sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted, "pj": pjUrl});
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
printV('Polling V2 Proposal Request (${pjUrl})');
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
printV("$proposalPsbt");
|
||||||
|
if (proposalPsbt != null) return proposalPsbt;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to send payjoin using the V1 of the protocol.
|
||||||
|
Future<String> _runSenderV1(Sender sender, http.Client 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(http.Client client, Request req) async {
|
||||||
|
final httpRequest = await client.post(Uri.parse(req.url.asString()),
|
||||||
|
headers: {'Content-Type': req.contentType}, body: req.body);
|
||||||
|
|
||||||
|
return httpRequest.bodyBytes;
|
||||||
|
}
|
||||||
|
}
|
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._();
|
||||||
|
}
|
95
cw_bitcoin/lib/payjoin/storage.dart
Normal file
95
cw_bitcoin/lib/payjoin/storage.dart
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:payjoin_flutter/receive.dart';
|
||||||
|
import 'package:payjoin_flutter/send.dart';
|
||||||
|
|
||||||
|
class PayjoinStorage {
|
||||||
|
PayjoinStorage(this._payjoinSessionSources);
|
||||||
|
|
||||||
|
final Box<PayjoinSession> _payjoinSessionSources;
|
||||||
|
|
||||||
|
static const String _receiverPrefix = 'pj_recv_';
|
||||||
|
static const String _senderPrefix = 'pj_send_';
|
||||||
|
|
||||||
|
Future<void> insertReceiverSession(
|
||||||
|
Receiver receiver,
|
||||||
|
String walletId,
|
||||||
|
) =>
|
||||||
|
_payjoinSessionSources.put(
|
||||||
|
"$_receiverPrefix${receiver.id()}",
|
||||||
|
PayjoinSession(
|
||||||
|
walletId: walletId,
|
||||||
|
receiver: receiver.toJson(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> markReceiverSessionComplete(
|
||||||
|
String sessionId, String txId, String amount) async {
|
||||||
|
final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!;
|
||||||
|
|
||||||
|
session.status = PayjoinSessionStatus.success.name;
|
||||||
|
session.txId = txId;
|
||||||
|
session.rawAmount = amount;
|
||||||
|
await session.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> markReceiverSessionUnrecoverable(
|
||||||
|
String sessionId, String reason) async {
|
||||||
|
final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!;
|
||||||
|
|
||||||
|
session.status = PayjoinSessionStatus.unrecoverable.name;
|
||||||
|
session.error = reason;
|
||||||
|
await session.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> markReceiverSessionInProgress(String sessionId) async {
|
||||||
|
final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!;
|
||||||
|
|
||||||
|
session.status = PayjoinSessionStatus.inProgress.name;
|
||||||
|
session.inProgressSince = DateTime.now();
|
||||||
|
await session.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> insertSenderSession(
|
||||||
|
Sender sender,
|
||||||
|
String pjUrl,
|
||||||
|
String walletId,
|
||||||
|
BigInt amount,
|
||||||
|
) =>
|
||||||
|
_payjoinSessionSources.put(
|
||||||
|
"$_senderPrefix$pjUrl",
|
||||||
|
PayjoinSession(
|
||||||
|
walletId: walletId,
|
||||||
|
pjUri: pjUrl,
|
||||||
|
sender: sender.toJson(),
|
||||||
|
status: PayjoinSessionStatus.inProgress.name,
|
||||||
|
inProgressSince: DateTime.now(),
|
||||||
|
rawAmount: amount.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> markSenderSessionComplete(String pjUrl, String txId) async {
|
||||||
|
final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!;
|
||||||
|
|
||||||
|
session.status = PayjoinSessionStatus.success.name;
|
||||||
|
session.txId = txId;
|
||||||
|
await session.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> markSenderSessionUnrecoverable(String pjUrl) async {
|
||||||
|
final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!;
|
||||||
|
|
||||||
|
session.status = PayjoinSessionStatus.unrecoverable.name;
|
||||||
|
await session.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PayjoinSession> readAllOpenSessions(String walletId) =>
|
||||||
|
_payjoinSessionSources.values
|
||||||
|
.where((session) =>
|
||||||
|
session.walletId == walletId &&
|
||||||
|
![
|
||||||
|
PayjoinSessionStatus.success.name,
|
||||||
|
PayjoinSessionStatus.unrecoverable.name
|
||||||
|
].contains(session.status))
|
||||||
|
.toList();
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
263
cw_bitcoin/lib/psbt/signer.dart
Normal file
263
cw_bitcoin/lib/psbt/signer.dart
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
|
import 'package:ledger_bitcoin/psbt.dart';
|
||||||
|
import 'package:ledger_bitcoin/src/utils/buffer_writer.dart';
|
||||||
|
|
||||||
|
extension PsbtSigner on PsbtV2 {
|
||||||
|
Uint8List extractUnsignedTX({bool getSegwit = true}) {
|
||||||
|
final tx = BufferWriter()..writeUInt32(getGlobalTxVersion());
|
||||||
|
|
||||||
|
final isSegwit = getInputWitnessUtxo(0) != null;
|
||||||
|
if (isSegwit && getSegwit) {
|
||||||
|
tx.writeSlice(Uint8List.fromList([0, 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
final inputCount = getGlobalInputCount();
|
||||||
|
tx.writeVarInt(inputCount);
|
||||||
|
|
||||||
|
for (var i = 0; i < inputCount; i++) {
|
||||||
|
tx
|
||||||
|
..writeSlice(getInputPreviousTxid(i))
|
||||||
|
..writeUInt32(getInputOutputIndex(i))
|
||||||
|
..writeVarSlice(Uint8List(0))
|
||||||
|
..writeUInt32(getInputSequence(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
final outputCount = getGlobalOutputCount();
|
||||||
|
tx.writeVarInt(outputCount);
|
||||||
|
for (var i = 0; i < outputCount; i++) {
|
||||||
|
tx.writeUInt64(getOutputAmount(i));
|
||||||
|
tx.writeVarSlice(getOutputScript(i));
|
||||||
|
}
|
||||||
|
tx.writeUInt32(getGlobalFallbackLocktime() ?? 0);
|
||||||
|
return tx.buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> signWithUTXO(
|
||||||
|
List<UtxoWithPrivateKey> utxos, UTXOSignerCallBack signer,
|
||||||
|
[UTXOGetterCallBack? getTaprootPair]) async {
|
||||||
|
final raw = BytesUtils.toHexString(extractUnsignedTX(getSegwit: false));
|
||||||
|
final tx = BtcTransaction.fromRaw(raw);
|
||||||
|
|
||||||
|
/// when the transaction is taproot and we must use getTaproot transaction
|
||||||
|
/// digest we need all of inputs amounts and owner script pub keys
|
||||||
|
List<BigInt> taprootAmounts = [];
|
||||||
|
List<Script> taprootScripts = [];
|
||||||
|
|
||||||
|
if (utxos.any((e) => e.utxo.isP2tr())) {
|
||||||
|
for (final input in tx.inputs) {
|
||||||
|
final utxo = utxos.firstWhereOrNull(
|
||||||
|
(u) => u.utxo.txHash == input.txId && u.utxo.vout == input.txIndex);
|
||||||
|
|
||||||
|
if (utxo == null) {
|
||||||
|
final trPair = await getTaprootPair!.call(input.txId, input.txIndex);
|
||||||
|
taprootAmounts.add(trPair.value);
|
||||||
|
taprootScripts.add(trPair.script);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
taprootAmounts.add(utxo.utxo.value);
|
||||||
|
taprootScripts.add(_findLockingScript(utxo, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < tx.inputs.length; i++) {
|
||||||
|
final utxo = utxos.firstWhereOrNull((e) =>
|
||||||
|
e.utxo.txHash == tx.inputs[i].txId &&
|
||||||
|
e.utxo.vout == tx.inputs[i].txIndex); // ToDo: More robust verify
|
||||||
|
if (utxo == null) continue;
|
||||||
|
|
||||||
|
/// We receive the owner's ScriptPubKey
|
||||||
|
final script = _findLockingScript(utxo, false);
|
||||||
|
|
||||||
|
final int sighash = utxo.utxo.isP2tr()
|
||||||
|
? BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL
|
||||||
|
: BitcoinOpCodeConst.SIGHASH_ALL;
|
||||||
|
|
||||||
|
/// We generate transaction digest for current input
|
||||||
|
final digest = _generateTransactionDigest(
|
||||||
|
script, i, utxo.utxo, tx, taprootAmounts, taprootScripts);
|
||||||
|
|
||||||
|
/// now we need sign the transaction digest
|
||||||
|
final sig = signer(digest, utxo, utxo.privateKey, sighash);
|
||||||
|
|
||||||
|
if (utxo.utxo.isP2tr()) {
|
||||||
|
setInputTapKeySig(i, Uint8List.fromList(BytesUtils.fromHexString(sig)));
|
||||||
|
} else {
|
||||||
|
setInputPartialSig(
|
||||||
|
i,
|
||||||
|
Uint8List.fromList(BytesUtils.fromHexString(utxo.public().toHex())),
|
||||||
|
Uint8List.fromList(BytesUtils.fromHexString(sig)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> _generateTransactionDigest(
|
||||||
|
Script scriptPubKeys,
|
||||||
|
int input,
|
||||||
|
BitcoinUtxo utxo,
|
||||||
|
BtcTransaction transaction,
|
||||||
|
List<BigInt> taprootAmounts,
|
||||||
|
List<Script> tapRootPubKeys) {
|
||||||
|
if (utxo.isSegwit()) {
|
||||||
|
if (utxo.isP2tr()) {
|
||||||
|
return transaction.getTransactionTaprootDigset(
|
||||||
|
txIndex: input,
|
||||||
|
scriptPubKeys: tapRootPubKeys,
|
||||||
|
amounts: taprootAmounts,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return transaction.getTransactionSegwitDigit(
|
||||||
|
txInIndex: input, script: scriptPubKeys, amount: utxo.value);
|
||||||
|
}
|
||||||
|
return transaction.getTransactionDigest(
|
||||||
|
txInIndex: input, script: scriptPubKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
Script _findLockingScript(UtxoWithAddress utxo, bool isTaproot) {
|
||||||
|
if (utxo.isMultiSig()) {
|
||||||
|
throw Exception("MultiSig is not supported yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
final senderPub = utxo.public();
|
||||||
|
switch (utxo.utxo.scriptType) {
|
||||||
|
case PubKeyAddressType.p2pk:
|
||||||
|
return senderPub.toRedeemScript();
|
||||||
|
case SegwitAddresType.p2wsh:
|
||||||
|
if (isTaproot) {
|
||||||
|
return senderPub.toP2wshAddress().toScriptPubKey();
|
||||||
|
}
|
||||||
|
return senderPub.toP2wshRedeemScript();
|
||||||
|
case P2pkhAddressType.p2pkh:
|
||||||
|
return senderPub.toP2pkhAddress().toScriptPubKey();
|
||||||
|
case SegwitAddresType.p2wpkh:
|
||||||
|
if (isTaproot) {
|
||||||
|
return senderPub.toP2wpkhAddress().toScriptPubKey();
|
||||||
|
}
|
||||||
|
return senderPub.toP2pkhAddress().toScriptPubKey();
|
||||||
|
case SegwitAddresType.p2tr:
|
||||||
|
return senderPub
|
||||||
|
.toTaprootAddress(tweak: utxo.utxo.isSilentPayment != true)
|
||||||
|
.toScriptPubKey();
|
||||||
|
case SegwitAddresType.mweb:
|
||||||
|
return Script(script: []);
|
||||||
|
case P2shAddressType.p2pkhInP2sh:
|
||||||
|
if (isTaproot) {
|
||||||
|
return senderPub.toP2pkhInP2sh().toScriptPubKey();
|
||||||
|
}
|
||||||
|
return senderPub.toP2pkhAddress().toScriptPubKey();
|
||||||
|
case P2shAddressType.p2wpkhInP2sh:
|
||||||
|
if (isTaproot) {
|
||||||
|
return senderPub.toP2wpkhInP2sh().toScriptPubKey();
|
||||||
|
}
|
||||||
|
return senderPub.toP2pkhAddress().toScriptPubKey();
|
||||||
|
case P2shAddressType.p2wshInP2sh:
|
||||||
|
if (isTaproot) {
|
||||||
|
return senderPub.toP2wshInP2sh().toScriptPubKey();
|
||||||
|
}
|
||||||
|
return senderPub.toP2wshRedeemScript();
|
||||||
|
case P2shAddressType.p2pkInP2sh:
|
||||||
|
if (isTaproot) {
|
||||||
|
return senderPub.toP2pkInP2sh().toScriptPubKey();
|
||||||
|
}
|
||||||
|
return senderPub.toRedeemScript();
|
||||||
|
}
|
||||||
|
throw Exception("invalid bitcoin address type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef UTXOSignerCallBack = String Function(List<int> trDigest,
|
||||||
|
UtxoWithAddress utxo, ECPrivate privateKey, int sighash);
|
||||||
|
|
||||||
|
typedef UTXOGetterCallBack = Future<TaprootAmountScriptPair> Function(
|
||||||
|
String txId, int vout);
|
||||||
|
|
||||||
|
class TaprootAmountScriptPair {
|
||||||
|
final BigInt value;
|
||||||
|
final Script script;
|
||||||
|
|
||||||
|
const TaprootAmountScriptPair(this.value, this.script);
|
||||||
|
}
|
||||||
|
|
||||||
|
class UtxoWithPrivateKey extends UtxoWithAddress {
|
||||||
|
final ECPrivate privateKey;
|
||||||
|
|
||||||
|
UtxoWithPrivateKey({
|
||||||
|
required super.utxo,
|
||||||
|
required super.ownerDetails,
|
||||||
|
required this.privateKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory UtxoWithPrivateKey.fromUtxo(
|
||||||
|
UtxoWithAddress input, List<ECPrivateInfo> inputPrivateKeyInfos) {
|
||||||
|
ECPrivateInfo? key;
|
||||||
|
|
||||||
|
if (inputPrivateKeyInfos.isEmpty) {
|
||||||
|
throw Exception("No private keys generated.");
|
||||||
|
} else {
|
||||||
|
key = inputPrivateKeyInfos.firstWhereOrNull((element) {
|
||||||
|
final elemPubkey = element.privkey.getPublic().toHex();
|
||||||
|
if (elemPubkey == input.public().toHex()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key == null) {
|
||||||
|
throw Exception("${input.utxo.txHash} No Key found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return UtxoWithPrivateKey(
|
||||||
|
utxo: input.utxo,
|
||||||
|
ownerDetails: input.ownerDetails,
|
||||||
|
privateKey: key.privkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
factory UtxoWithPrivateKey.fromUnspent(
|
||||||
|
BitcoinUnspent input, BitcoinWalletBase wallet) {
|
||||||
|
final address =
|
||||||
|
RegexUtils.addressTypeFromStr(input.address, BitcoinNetwork.mainnet);
|
||||||
|
|
||||||
|
final newHd =
|
||||||
|
input.bitcoinAddressRecord.isHidden ? wallet.sideHd : wallet.hd;
|
||||||
|
|
||||||
|
ECPrivate privkey;
|
||||||
|
if (input.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||||
|
final unspentAddress =
|
||||||
|
input.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
|
||||||
|
privkey = wallet.walletAddresses.silentAddress!.b_spend.tweakAdd(
|
||||||
|
BigintUtils.fromBytes(
|
||||||
|
BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
privkey = generateECPrivate(
|
||||||
|
hd: newHd,
|
||||||
|
index: input.bitcoinAddressRecord.index,
|
||||||
|
network: BitcoinNetwork.mainnet);
|
||||||
|
}
|
||||||
|
|
||||||
|
return UtxoWithPrivateKey(
|
||||||
|
utxo: BitcoinUtxo(
|
||||||
|
txHash: input.hash,
|
||||||
|
value: BigInt.from(input.value),
|
||||||
|
vout: input.vout,
|
||||||
|
scriptType: input.bitcoinAddressRecord.type,
|
||||||
|
isSilentPayment:
|
||||||
|
input.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord,
|
||||||
|
),
|
||||||
|
ownerDetails: UtxoAddressDetails(
|
||||||
|
publicKey: privkey.getPublic().toHex(),
|
||||||
|
address: address,
|
||||||
|
),
|
||||||
|
privateKey: privkey);
|
||||||
|
}
|
||||||
|
}
|
41
cw_bitcoin/lib/psbt/utils.dart
Normal file
41
cw_bitcoin/lib/psbt/utils.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_wallet.dart';
|
||||||
|
import 'package:cw_bitcoin/psbt/v0_deserialize.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
|
import 'package:ledger_bitcoin/psbt.dart';
|
||||||
|
|
||||||
|
String getTxIdFromPsbtV0(String psbt) {
|
||||||
|
final psbtV2 = PsbtV2()..deserializeV0(base64.decode(psbt));
|
||||||
|
|
||||||
|
return BtcTransaction.fromRaw(
|
||||||
|
BytesUtils.toHexString(psbtV2.extractUnsignedTX(false)))
|
||||||
|
.txId();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getOutputAmountFromPsbt(String psbtV0, BitcoinWalletBase wallet) {
|
||||||
|
printV(psbtV0);
|
||||||
|
final psbt = PsbtV2()..deserializeV0(base64.decode(psbtV0));
|
||||||
|
int amount = 0;
|
||||||
|
for (var i = 0; i < psbt.getGlobalOutputCount(); i++) {
|
||||||
|
final script = psbt.getOutputScript(i);
|
||||||
|
if (wallet.isMine(Script.fromRaw(byteData: script))) {
|
||||||
|
amount += psbt.getOutputAmount(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return amount.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getOutputAmountFromTx(String originalTx, BitcoinWalletBase wallet) {
|
||||||
|
final tx = BtcTransaction.fromRaw(originalTx);
|
||||||
|
BigInt amount = BigInt.zero;
|
||||||
|
for (final output in tx.outputs) {
|
||||||
|
if (wallet.isMine(output.scriptPubKey)) {
|
||||||
|
amount += output.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printV(amount);
|
||||||
|
return amount.toString();
|
||||||
|
}
|
52
cw_bitcoin/lib/psbt/v0_deserialize.dart
Normal file
52
cw_bitcoin/lib/psbt/v0_deserialize.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
|
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:ledger_bitcoin/psbt.dart';
|
||||||
|
import 'package:ledger_bitcoin/src/psbt/map_extension.dart';
|
||||||
|
import 'package:ledger_bitcoin/src/utils/buffer_reader.dart';
|
||||||
|
import 'package:ledger_bitcoin/src/utils/uint8list_extension.dart' as ext;
|
||||||
|
|
||||||
|
extension PsbtSigner on PsbtV2 {
|
||||||
|
|
||||||
|
void deserializeV0(Uint8List psbt) {
|
||||||
|
final bufferReader = BufferReader(psbt);
|
||||||
|
if (!listEquals(bufferReader.readSlice(5), Uint8List.fromList([0x70, 0x73, 0x62, 0x74, 0xff]))) {
|
||||||
|
throw Exception("Invalid magic bytes");
|
||||||
|
}
|
||||||
|
while (_readKeyPair(globalMap, bufferReader)) {}
|
||||||
|
|
||||||
|
final tx = BtcTransaction.fromRaw(BytesUtils.toHexString(globalMap['00']!));
|
||||||
|
|
||||||
|
setGlobalInputCount(tx.inputs.length);
|
||||||
|
setGlobalOutputCount(tx.outputs.length);
|
||||||
|
setGlobalTxVersion(Uint8List.fromList(tx.version).readUint32LE(0));
|
||||||
|
|
||||||
|
for (var i = 0; i < getGlobalInputCount(); i++) {
|
||||||
|
inputMaps.insert(i, <String, Uint8List>{});
|
||||||
|
while (_readKeyPair(inputMaps[i], bufferReader)) {}
|
||||||
|
final input = tx.inputs[i];
|
||||||
|
setInputOutputIndex(i, input.txIndex);
|
||||||
|
setInputPreviousTxId(i, Uint8List.fromList(BytesUtils.fromHexString(input.txId).reversed.toList()));
|
||||||
|
setInputSequence(i, Uint8List.fromList(input.sequence).readUint32LE(0));
|
||||||
|
}
|
||||||
|
for (var i = 0; i < getGlobalOutputCount(); i++) {
|
||||||
|
outputMaps.insert(i, <String, Uint8List>{});
|
||||||
|
while (_readKeyPair(outputMaps[i], bufferReader)) {}
|
||||||
|
final output = tx.outputs[i];
|
||||||
|
setOutputAmount(i, output.amount.toInt());
|
||||||
|
setOutputScript(i, Uint8List.fromList(output.scriptPubKey.toBytes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _readKeyPair(Map<String, Uint8List> map, BufferReader bufferReader) {
|
||||||
|
final keyLen = bufferReader.readVarInt();
|
||||||
|
if (keyLen == 0) return false;
|
||||||
|
|
||||||
|
final keyType = bufferReader.readUInt8();
|
||||||
|
final keyData = bufferReader.readSlice(keyLen - 1);
|
||||||
|
final value = bufferReader.readVarSlice();
|
||||||
|
|
||||||
|
map.set(keyType, keyData, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
143
cw_bitcoin/lib/psbt/v0_finalizer.dart
Normal file
143
cw_bitcoin/lib/psbt/v0_finalizer.dart
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
|
import "package:ledger_bitcoin/src/psbt/constants.dart";
|
||||||
|
import "package:ledger_bitcoin/src/psbt/psbtv2.dart";
|
||||||
|
import "package:ledger_bitcoin/src/utils/buffer_writer.dart";
|
||||||
|
|
||||||
|
/// This roughly implements the "input finalizer" role of BIP370 (PSBTv2
|
||||||
|
/// https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki). However
|
||||||
|
/// the role is documented in BIP174 (PSBTv0
|
||||||
|
/// https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki).
|
||||||
|
///
|
||||||
|
/// Verify that all inputs have a signature, and set inputFinalScriptwitness
|
||||||
|
/// and/or inputFinalScriptSig depending on the type of the spent outputs. Clean
|
||||||
|
/// fields that aren't useful anymore, partial signatures, redeem script and
|
||||||
|
/// derivation paths.
|
||||||
|
///
|
||||||
|
/// @param psbt The psbt with all signatures added as partial sigs, either
|
||||||
|
/// through PSBT_IN_PARTIAL_SIG or PSBT_IN_TAP_KEY_SIG
|
||||||
|
extension InputFinalizer on PsbtV2 {
|
||||||
|
void finalizeV0() {
|
||||||
|
|
||||||
|
// First check that each input has a signature
|
||||||
|
for (var i = 0; i < getGlobalInputCount(); i++) {
|
||||||
|
if (_isFinalized(i)) continue;
|
||||||
|
|
||||||
|
final legacyPubkeys = getInputKeyDatas(i, PSBTIn.partialSig);
|
||||||
|
final taprootSig = getInputTapKeySig(i);
|
||||||
|
if (legacyPubkeys.isEmpty && taprootSig == null) {
|
||||||
|
continue;
|
||||||
|
// throw Exception('No signature for input $i present');
|
||||||
|
}
|
||||||
|
if (legacyPubkeys.isNotEmpty) {
|
||||||
|
if (legacyPubkeys.length > 1) {
|
||||||
|
throw Exception(
|
||||||
|
'Expected exactly one signature, got ${legacyPubkeys.length}');
|
||||||
|
}
|
||||||
|
if (taprootSig != null) {
|
||||||
|
throw Exception('Both taproot and non-taproot signatures present.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final isSegwitV0 = getInputWitnessUtxo(i) != null;
|
||||||
|
final redeemScript = getInputRedeemScript(i);
|
||||||
|
final isWrappedSegwit = redeemScript != null;
|
||||||
|
final signature = getInputPartialSig(i, legacyPubkeys[0]);
|
||||||
|
if (signature == null) {
|
||||||
|
throw Exception('Expected partial signature for input $i');
|
||||||
|
}
|
||||||
|
if (isSegwitV0) {
|
||||||
|
final witnessBuf = BufferWriter()
|
||||||
|
..writeVarInt(2)
|
||||||
|
..writeVarInt(signature.length)
|
||||||
|
..writeSlice(signature)
|
||||||
|
..writeVarInt(legacyPubkeys[0].length)
|
||||||
|
..writeSlice(legacyPubkeys[0]);
|
||||||
|
setInputFinalScriptwitness(i, witnessBuf.buffer());
|
||||||
|
if (isWrappedSegwit) {
|
||||||
|
if (redeemScript.isEmpty) {
|
||||||
|
throw Exception(
|
||||||
|
"Expected non-empty redeemscript. Can't finalize intput $i");
|
||||||
|
}
|
||||||
|
final scriptSigBuf = BufferWriter()
|
||||||
|
..writeUInt8(redeemScript.length) // Push redeemScript length
|
||||||
|
..writeSlice(redeemScript);
|
||||||
|
setInputFinalScriptsig(i, scriptSigBuf.buffer());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Legacy input
|
||||||
|
final scriptSig = BufferWriter();
|
||||||
|
_writePush(scriptSig, signature);
|
||||||
|
_writePush(scriptSig, legacyPubkeys[0]);
|
||||||
|
setInputFinalScriptsig(i, scriptSig.buffer());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Taproot input
|
||||||
|
final signature = getInputTapKeySig(i);
|
||||||
|
if (signature == null) {
|
||||||
|
throw Exception("No taproot signature found");
|
||||||
|
}
|
||||||
|
if (signature.length != 64 && signature.length != 65) {
|
||||||
|
throw Exception("Unexpected length of schnorr signature.");
|
||||||
|
}
|
||||||
|
final witnessBuf = BufferWriter()
|
||||||
|
..writeVarInt(1)
|
||||||
|
..writeVarSlice(signature);
|
||||||
|
setInputFinalScriptwitness(i, witnessBuf.buffer());
|
||||||
|
}
|
||||||
|
clearFinalizedInput(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes fields that are no longer neccesary from the psbt.
|
||||||
|
///
|
||||||
|
/// Note, the spec doesn't say anything about removing ouput fields
|
||||||
|
/// like PSBT_OUT_BIP32_DERIVATION_PATH and others, so we keep them
|
||||||
|
/// without actually knowing why. I think we should remove them too.
|
||||||
|
void clearFinalizedInput(int inputIndex) {
|
||||||
|
final keyTypes = [
|
||||||
|
PSBTIn.bip32Derivation,
|
||||||
|
PSBTIn.partialSig,
|
||||||
|
PSBTIn.tapBip32Derivation,
|
||||||
|
PSBTIn.tapKeySig,
|
||||||
|
];
|
||||||
|
final witnessUtxoAvailable = getInputWitnessUtxo(inputIndex) != null;
|
||||||
|
final nonWitnessUtxoAvailable = getInputNonWitnessUtxo(inputIndex) != null;
|
||||||
|
if (witnessUtxoAvailable && nonWitnessUtxoAvailable) {
|
||||||
|
// Remove NON_WITNESS_UTXO for segwit v0 as it's only needed while signing.
|
||||||
|
// Segwit v1 doesn't have NON_WITNESS_UTXO set.
|
||||||
|
// See https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#cite_note-7
|
||||||
|
keyTypes.add(PSBTIn.nonWitnessUTXO);
|
||||||
|
}
|
||||||
|
deleteInputEntries(inputIndex, keyTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes a script push operation to buf, which looks different
|
||||||
|
/// depending on the size of the data. See
|
||||||
|
/// https://en.bitcoin.it/wiki/Script#finalants
|
||||||
|
///
|
||||||
|
/// [buf] the BufferWriter to write to
|
||||||
|
/// [data] the Buffer to be pushed.
|
||||||
|
void _writePush(BufferWriter buf, Uint8List data) {
|
||||||
|
if (data.length <= 75) {
|
||||||
|
buf.writeUInt8(data.length);
|
||||||
|
} else if (data.length <= 256) {
|
||||||
|
buf.writeUInt8(76);
|
||||||
|
buf.writeUInt8(data.length);
|
||||||
|
} else if (data.length <= 256 * 256) {
|
||||||
|
buf.writeUInt8(77);
|
||||||
|
final b = ByteData(2)..setUint16(0, data.length, Endian.little);
|
||||||
|
buf.writeSlice(b.buffer.asUint8List());
|
||||||
|
}
|
||||||
|
buf.writeSlice(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isFinalized(int i) {
|
||||||
|
if (getInputFinalScriptsig(i) != null) return true;
|
||||||
|
try {
|
||||||
|
getInputFinalScriptwitness(i);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -125,6 +125,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.1"
|
||||||
|
build_cli_annotations:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_cli_annotations
|
||||||
|
sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
build_config:
|
build_config:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -377,6 +385,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
flutter_rust_bridge:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_rust_bridge
|
||||||
|
sha256: "3292ad6085552987b8b3b9a7e5805567f4013372d302736b702801acb001ee00"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.1"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -395,6 +411,14 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
freezed_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: freezed_annotation
|
||||||
|
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.4"
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -559,7 +583,7 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "packages/ledger-bitcoin"
|
path: "packages/ledger-bitcoin"
|
||||||
ref: HEAD
|
ref: trunk
|
||||||
resolved-ref: e93254f3ff3f996fb91f65a1e7ceffb9f510b4c8
|
resolved-ref: e93254f3ff3f996fb91f65a1e7ceffb9f510b4c8
|
||||||
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
|
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
|
||||||
source: git
|
source: git
|
||||||
|
@ -726,6 +750,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
payjoin_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: "6a3eb32fb9467ac12e7b75d3de47de4ca44fd88c"
|
||||||
|
resolved-ref: "6a3eb32fb9467ac12e7b75d3de47de4ca44fd88c"
|
||||||
|
url: "https://github.com/konstantinullrich/payjoin-flutter"
|
||||||
|
source: git
|
||||||
|
version: "0.21.0"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -940,6 +973,14 @@ packages:
|
||||||
url: "https://github.com/cake-tech/sp_scanner"
|
url: "https://github.com/cake-tech/sp_scanner"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
sprintf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sprintf
|
||||||
|
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1036,6 +1077,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
version: "0.3.0"
|
||||||
|
uuid:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uuid
|
||||||
|
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.1"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1118,4 +1167,4 @@ packages:
|
||||||
version: "2.2.2"
|
version: "2.2.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -40,11 +40,16 @@ dependencies:
|
||||||
bech32:
|
bech32:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bech32.git
|
url: https://github.com/cake-tech/bech32.git
|
||||||
|
payjoin_flutter:
|
||||||
|
git:
|
||||||
|
url: https://github.com/konstantinullrich/payjoin-flutter
|
||||||
|
ref: 6a3eb32fb9467ac12e7b75d3de47de4ca44fd88c #cake-v1
|
||||||
ledger_flutter_plus: ^1.4.1
|
ledger_flutter_plus: ^1.4.1
|
||||||
ledger_bitcoin:
|
ledger_bitcoin:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
||||||
path: packages/ledger-bitcoin
|
path: packages/ledger-bitcoin
|
||||||
|
ref: trunk
|
||||||
ledger_litecoin:
|
ledger_litecoin:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
||||||
|
|
|
@ -21,3 +21,4 @@ const HARDWARE_WALLET_TYPE_TYPE_ID = 19;
|
||||||
const MWEB_UTXO_TYPE_ID = 20;
|
const MWEB_UTXO_TYPE_ID = 20;
|
||||||
const HAVEN_SEED_STORE_TYPE_ID = 21;
|
const HAVEN_SEED_STORE_TYPE_ID = 21;
|
||||||
const ZANO_ASSET_TYPE_ID = 22;
|
const ZANO_ASSET_TYPE_ID = 22;
|
||||||
|
const PAYJOIN_SESSION_TYPE_ID = 23;
|
||||||
|
|
67
cw_core/lib/payjoin_session.dart
Normal file
67
cw_core/lib/payjoin_session.dart
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:cw_core/hive_type_ids.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
|
part 'payjoin_session.g.dart';
|
||||||
|
|
||||||
|
@HiveType(typeId: PAYJOIN_SESSION_TYPE_ID)
|
||||||
|
class PayjoinSession extends HiveObject {
|
||||||
|
PayjoinSession({
|
||||||
|
required this.walletId,
|
||||||
|
this.receiver,
|
||||||
|
this.sender,
|
||||||
|
this.pjUri,
|
||||||
|
this.status = "created",
|
||||||
|
this.inProgressSince,
|
||||||
|
this.rawAmount,
|
||||||
|
}) {
|
||||||
|
if (receiver == null) {
|
||||||
|
assert(sender != null);
|
||||||
|
assert(pjUri != null);
|
||||||
|
} else {
|
||||||
|
assert(receiver != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const typeId = PAYJOIN_SESSION_TYPE_ID;
|
||||||
|
static const boxName = 'PayjoinSessions';
|
||||||
|
|
||||||
|
@HiveField(0)
|
||||||
|
final String walletId;
|
||||||
|
|
||||||
|
@HiveField(1)
|
||||||
|
final String? sender;
|
||||||
|
|
||||||
|
@HiveField(2)
|
||||||
|
final String? receiver;
|
||||||
|
|
||||||
|
@HiveField(3)
|
||||||
|
final String? pjUri;
|
||||||
|
|
||||||
|
@HiveField(4)
|
||||||
|
String status;
|
||||||
|
|
||||||
|
@HiveField(5)
|
||||||
|
DateTime? inProgressSince;
|
||||||
|
|
||||||
|
@HiveField(6)
|
||||||
|
String? txId;
|
||||||
|
|
||||||
|
@HiveField(7)
|
||||||
|
String? rawAmount;
|
||||||
|
|
||||||
|
@HiveField(8)
|
||||||
|
String? error;
|
||||||
|
|
||||||
|
bool get isSenderSession => sender != null;
|
||||||
|
|
||||||
|
BigInt get amount => BigInt.parse(rawAmount ?? "0");
|
||||||
|
set amount(BigInt amount) => rawAmount = amount.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PayjoinSessionStatus {
|
||||||
|
created,
|
||||||
|
inProgress,
|
||||||
|
success,
|
||||||
|
unrecoverable,
|
||||||
|
}
|
|
@ -810,4 +810,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -849,4 +849,4 @@ packages:
|
||||||
version: "2.2.2"
|
version: "2.2.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -978,4 +978,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -946,4 +946,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -845,4 +845,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -842,4 +842,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -109,9 +109,12 @@ class CWBitcoin extends Bitcoin {
|
||||||
required TransactionPriority priority,
|
required TransactionPriority priority,
|
||||||
int? feeRate,
|
int? feeRate,
|
||||||
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
|
||||||
|
String? payjoinUri,
|
||||||
}) {
|
}) {
|
||||||
final bitcoinFeeRate =
|
final bitcoinFeeRate =
|
||||||
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
|
priority == BitcoinTransactionPriority.custom && feeRate != null
|
||||||
|
? feeRate
|
||||||
|
: null;
|
||||||
return BitcoinTransactionCredentials(
|
return BitcoinTransactionCredentials(
|
||||||
outputs
|
outputs
|
||||||
.map((out) => OutputInfo(
|
.map((out) => OutputInfo(
|
||||||
|
@ -128,7 +131,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
priority: priority as BitcoinTransactionPriority,
|
priority: priority as BitcoinTransactionPriority,
|
||||||
feeRate: bitcoinFeeRate,
|
feeRate: bitcoinFeeRate,
|
||||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||||
);
|
payjoinUri: payjoinUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -224,9 +227,14 @@ class CWBitcoin extends Bitcoin {
|
||||||
await bitcoinWallet.updateAllUnspents();
|
await bitcoinWallet.updateAllUnspents();
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource,
|
WalletService createBitcoinWalletService(
|
||||||
Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect) {
|
Box<WalletInfo> walletInfoSource,
|
||||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan, isDirect);
|
Box<UnspentCoinsInfo> unspentCoinSource,
|
||||||
|
Box<PayjoinSession> payjoinSessionSource,
|
||||||
|
bool alwaysScan,
|
||||||
|
bool isDirect) {
|
||||||
|
return BitcoinWalletService(walletInfoSource, unspentCoinSource,
|
||||||
|
payjoinSessionSource, alwaysScan, isDirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource,
|
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource,
|
||||||
|
@ -550,6 +558,10 @@ class CWBitcoin extends Bitcoin {
|
||||||
return option is BitcoinReceivePageOption;
|
return option is BitcoinReceivePageOption;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isPayjoinAvailable(Object wallet) =>
|
||||||
|
(wallet is BitcoinWallet) && (wallet as BitcoinWallet).isPayjoinAvailable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BitcoinAddressType getOptionToType(ReceivePageOption option) {
|
BitcoinAddressType getOptionToType(ReceivePageOption option) {
|
||||||
return (option as BitcoinReceivePageOption).toType();
|
return (option as BitcoinReceivePageOption).toType();
|
||||||
|
@ -706,4 +718,34 @@ class CWBitcoin extends Bitcoin {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getPayjoinEndpoint(Object wallet) {
|
||||||
|
final _wallet = wallet as ElectrumWallet;
|
||||||
|
if (!isPayjoinAvailable(wallet)) return '';
|
||||||
|
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 {
|
||||||
|
stopPayjoinSessions(wallet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resumePayjoinSessions(Object wallet) {
|
||||||
|
final _wallet = wallet as ElectrumWallet;
|
||||||
|
(_wallet.walletAddresses as BitcoinWalletAddresses).initPayjoin();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stopPayjoinSessions(Object wallet) {
|
||||||
|
final _wallet = wallet as ElectrumWallet;
|
||||||
|
(_wallet.walletAddresses as BitcoinWalletAddresses).payjoinManager.cleanupSessions();
|
||||||
|
(_wallet.walletAddresses as BitcoinWalletAddresses).currentPayjoinReceiver = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
25
lib/di.dart
25
lib/di.dart
|
@ -51,6 +51,7 @@ import 'package:cake_wallet/entities/wallet_manager.dart';
|
||||||
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
|
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
|
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
|
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_external_send_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/payjoin_details/payjoin_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
|
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
|
||||||
|
@ -58,7 +59,10 @@ import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
|
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
|
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/welcome/welcome_page.dart';
|
import 'package:cake_wallet/src/screens/welcome/welcome_page.dart';
|
||||||
|
import 'package:cake_wallet/store/dashboard/payjoin_transactions_store.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
|
||||||
|
import 'package:cake_wallet/view_model/payjoin_details_view_model.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
|
||||||
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
|
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
|
@ -286,6 +290,7 @@ late Box<ExchangeTemplate> _exchangeTemplates;
|
||||||
late Box<TransactionDescription> _transactionDescriptionBox;
|
late Box<TransactionDescription> _transactionDescriptionBox;
|
||||||
late Box<Order> _ordersSource;
|
late Box<Order> _ordersSource;
|
||||||
late Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
|
late Box<UnspentCoinsInfo> _unspentCoinsInfoSource;
|
||||||
|
late Box<PayjoinSession> _payjoinSessionSource;
|
||||||
late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
|
late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
|
||||||
|
|
||||||
Future<void> setup({
|
Future<void> setup({
|
||||||
|
@ -299,6 +304,7 @@ Future<void> setup({
|
||||||
required Box<TransactionDescription> transactionDescriptionBox,
|
required Box<TransactionDescription> transactionDescriptionBox,
|
||||||
required Box<Order> ordersSource,
|
required Box<Order> ordersSource,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
|
required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
|
||||||
|
required Box<PayjoinSession> payjoinSessionSource,
|
||||||
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
|
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfoSource,
|
||||||
required SecureStorage secureStorage,
|
required SecureStorage secureStorage,
|
||||||
required GlobalKey<NavigatorState> navigatorKey,
|
required GlobalKey<NavigatorState> navigatorKey,
|
||||||
|
@ -313,6 +319,7 @@ Future<void> setup({
|
||||||
_transactionDescriptionBox = transactionDescriptionBox;
|
_transactionDescriptionBox = transactionDescriptionBox;
|
||||||
_ordersSource = ordersSource;
|
_ordersSource = ordersSource;
|
||||||
_unspentCoinsInfoSource = unspentCoinsInfoSource;
|
_unspentCoinsInfoSource = unspentCoinsInfoSource;
|
||||||
|
_payjoinSessionSource = payjoinSessionSource;
|
||||||
_anonpayInvoiceInfoSource = anonpayInvoiceInfoSource;
|
_anonpayInvoiceInfoSource = anonpayInvoiceInfoSource;
|
||||||
|
|
||||||
if (!_isSetupFinished) {
|
if (!_isSetupFinished) {
|
||||||
|
@ -354,6 +361,8 @@ Future<void> setup({
|
||||||
TradesStore(tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
|
TradesStore(tradesSource: _tradesSource, settingsStore: getIt.get<SettingsStore>()));
|
||||||
getIt.registerSingleton<OrdersStore>(
|
getIt.registerSingleton<OrdersStore>(
|
||||||
OrdersStore(ordersSource: _ordersSource, settingsStore: getIt.get<SettingsStore>()));
|
OrdersStore(ordersSource: _ordersSource, settingsStore: getIt.get<SettingsStore>()));
|
||||||
|
getIt.registerFactory(() =>
|
||||||
|
PayjoinTransactionsStore(payjoinSessionSource: _payjoinSessionSource));
|
||||||
getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
|
getIt.registerSingleton<TradeFilterStore>(TradeFilterStore());
|
||||||
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore(getIt.get<AppStore>()));
|
getIt.registerSingleton<TransactionFilterStore>(TransactionFilterStore(getIt.get<AppStore>()));
|
||||||
getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
|
getIt.registerSingleton<FiatConversionStore>(FiatConversionStore());
|
||||||
|
@ -507,6 +516,7 @@ Future<void> setup({
|
||||||
yatStore: getIt.get<YatStore>(),
|
yatStore: getIt.get<YatStore>(),
|
||||||
ordersStore: getIt.get<OrdersStore>(),
|
ordersStore: getIt.get<OrdersStore>(),
|
||||||
anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
|
anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
|
||||||
|
payjoinTransactionsStore: getIt.get<PayjoinTransactionsStore>(),
|
||||||
sharedPreferences: getIt.get<SharedPreferences>(),
|
sharedPreferences: getIt.get<SharedPreferences>(),
|
||||||
keyService: getIt.get<KeyService>()));
|
keyService: getIt.get<KeyService>()));
|
||||||
|
|
||||||
|
@ -1095,6 +1105,7 @@ Future<void> setup({
|
||||||
return bitcoin!.createBitcoinWalletService(
|
return bitcoin!.createBitcoinWalletService(
|
||||||
_walletInfoSource,
|
_walletInfoSource,
|
||||||
_unspentCoinsInfoSource,
|
_unspentCoinsInfoSource,
|
||||||
|
_payjoinSessionSource,
|
||||||
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
|
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
|
||||||
SettingsStoreBase.walletPasswordDirectInput,
|
SettingsStoreBase.walletPasswordDirectInput,
|
||||||
);
|
);
|
||||||
|
@ -1423,6 +1434,15 @@ Future<void> setup({
|
||||||
settingsStore: getIt.get<SettingsStore>(),
|
settingsStore: getIt.get<SettingsStore>(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
getIt.registerFactoryParam<PayjoinDetailsViewModel, String, TransactionInfo?>(
|
||||||
|
(String sessionId, TransactionInfo? transactionInfo) =>
|
||||||
|
PayjoinDetailsViewModel(
|
||||||
|
sessionId,
|
||||||
|
transactionInfo,
|
||||||
|
payjoinSessionSource: _payjoinSessionSource,
|
||||||
|
settingsStore: getIt.get<SettingsStore>(),
|
||||||
|
));
|
||||||
|
|
||||||
getIt.registerFactoryParam<AnonPayReceivePage, AnonpayInfoBase, void>(
|
getIt.registerFactoryParam<AnonPayReceivePage, AnonpayInfoBase, void>(
|
||||||
(AnonpayInfoBase anonpayInvoiceInfo, _) =>
|
(AnonpayInfoBase anonpayInvoiceInfo, _) =>
|
||||||
AnonPayReceivePage(invoiceInfo: anonpayInvoiceInfo));
|
AnonPayReceivePage(invoiceInfo: anonpayInvoiceInfo));
|
||||||
|
@ -1431,6 +1451,11 @@ Future<void> setup({
|
||||||
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
|
(AnonpayInvoiceInfo anonpayInvoiceInfo, _) => AnonpayDetailsPage(
|
||||||
anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
|
anonpayDetailsViewModel: getIt.get<AnonpayDetailsViewModel>(param1: anonpayInvoiceInfo)));
|
||||||
|
|
||||||
|
getIt.registerFactoryParam<PayjoinDetailsPage, String, TransactionInfo?>(
|
||||||
|
(String sessionId, TransactionInfo? transactionInfo) => PayjoinDetailsPage(
|
||||||
|
payjoinDetailsViewModel: getIt.get<PayjoinDetailsViewModel>(
|
||||||
|
param1: sessionId, param2: transactionInfo)));
|
||||||
|
|
||||||
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
|
||||||
HomeSettingsPage(getIt.get<HomeSettingsViewModel>(param1: balanceViewModel)));
|
HomeSettingsPage(getIt.get<HomeSettingsViewModel>(param1: balanceViewModel)));
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ class PreferencesKey {
|
||||||
static const lookupsOpenAlias = 'looks_up_open_alias';
|
static const lookupsOpenAlias = 'looks_up_open_alias';
|
||||||
static const lookupsENS = 'looks_up_ens';
|
static const lookupsENS = 'looks_up_ens';
|
||||||
static const lookupsWellKnown = 'looks_up_well_known';
|
static const lookupsWellKnown = 'looks_up_well_known';
|
||||||
|
static const usePayjoin = 'use_payjoin';
|
||||||
static const showCameraConsent = 'show_camera_consent';
|
static const showCameraConsent = 'show_camera_consent';
|
||||||
static const showDecredInfoCard = 'show_decred_info_card';
|
static const showDecredInfoCard = 'show_decred_info_card';
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ import 'package:cw_core/cake_hive.dart';
|
||||||
import 'package:cw_core/hive_type_ids.dart';
|
import 'package:cw_core/hive_type_ids.dart';
|
||||||
import 'package:cw_core/mweb_utxo.dart';
|
import 'package:cw_core/mweb_utxo.dart';
|
||||||
import 'package:cw_core/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cw_core/utils/print_verbose.dart';
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
@ -178,6 +179,10 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||||
CakeHive.registerAdapter(MwebUtxoAdapter());
|
CakeHive.registerAdapter(MwebUtxoAdapter());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CakeHive.isAdapterRegistered(PayjoinSession.typeId)) {
|
||||||
|
CakeHive.registerAdapter(PayjoinSessionAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
final secureStorage = secureStorageShared;
|
final secureStorage = secureStorageShared;
|
||||||
final transactionDescriptionsBoxKey =
|
final transactionDescriptionsBoxKey =
|
||||||
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
|
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
|
||||||
|
@ -197,6 +202,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||||
final exchangeTemplates = await CakeHive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
|
final exchangeTemplates = await CakeHive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
|
||||||
final anonpayInvoiceInfo = await CakeHive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
|
final anonpayInvoiceInfo = await CakeHive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
|
||||||
final unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
final unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
||||||
|
final payjoinSessionSource = await CakeHive.openBox<PayjoinSession>(PayjoinSession.boxName);
|
||||||
|
|
||||||
final havenSeedStoreBoxKey =
|
final havenSeedStoreBoxKey =
|
||||||
await getEncryptionKey(secureStorage: secureStorage, forKey: HavenSeedStore.boxKey);
|
await getEncryptionKey(secureStorage: secureStorage, forKey: HavenSeedStore.boxKey);
|
||||||
|
@ -219,6 +225,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||||
exchangeTemplates: exchangeTemplates,
|
exchangeTemplates: exchangeTemplates,
|
||||||
transactionDescriptions: transactionDescriptions,
|
transactionDescriptions: transactionDescriptions,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
|
payjoinSessionSource: payjoinSessionSource,
|
||||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||||
havenSeedStore: havenSeedStore,
|
havenSeedStore: havenSeedStore,
|
||||||
initialMigrationVersion: 49,
|
initialMigrationVersion: 49,
|
||||||
|
@ -241,6 +248,7 @@ Future<void> initialSetup(
|
||||||
required SecureStorage secureStorage,
|
required SecureStorage secureStorage,
|
||||||
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo,
|
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo,
|
||||||
required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
|
required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
|
||||||
|
required Box<PayjoinSession> payjoinSessionSource,
|
||||||
required Box<HavenSeedStore> havenSeedStore,
|
required Box<HavenSeedStore> havenSeedStore,
|
||||||
int initialMigrationVersion = 15, }) async {
|
int initialMigrationVersion = 15, }) async {
|
||||||
LanguageService.loadLocaleList();
|
LanguageService.loadLocaleList();
|
||||||
|
@ -266,6 +274,7 @@ Future<void> initialSetup(
|
||||||
ordersSource: ordersSource,
|
ordersSource: ordersSource,
|
||||||
anonpayInvoiceInfoSource: anonpayInvoiceInfo,
|
anonpayInvoiceInfoSource: anonpayInvoiceInfo,
|
||||||
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
unspentCoinsInfoSource: unspentCoinsInfoSource,
|
||||||
|
payjoinSessionSource: payjoinSessionSource,
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
secureStorage: secureStorage,
|
secureStorage: secureStorage,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/di.dart';
|
import 'package:cake_wallet/di.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';
|
||||||
|
@ -78,6 +79,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);
|
||||||
|
|
|
@ -58,6 +58,7 @@ import 'package:cake_wallet/src/screens/new_wallet/wallet_group_existing_seed_de
|
||||||
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
|
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
|
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/payjoin_details/payjoin_details_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
|
||||||
|
@ -719,6 +720,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
|
builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
|
||||||
|
|
||||||
|
case Routes.payjoinDetails:
|
||||||
|
final arguments = settings.arguments as List;
|
||||||
|
final sessionId = arguments.first as String;
|
||||||
|
final transactionInfo = arguments[1] as TransactionInfo?;
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
builder: (_) => getIt.get<PayjoinDetailsPage>(
|
||||||
|
param1: sessionId, param2: transactionInfo));
|
||||||
|
|
||||||
case Routes.desktop_actions:
|
case Routes.desktop_actions:
|
||||||
return PageRouteBuilder(
|
return PageRouteBuilder(
|
||||||
opaque: false,
|
opaque: false,
|
||||||
|
|
|
@ -15,6 +15,7 @@ class Routes {
|
||||||
static const dashboard = '/dashboard';
|
static const dashboard = '/dashboard';
|
||||||
static const send = '/send';
|
static const send = '/send';
|
||||||
static const transactionDetails = '/transaction_info';
|
static const transactionDetails = '/transaction_info';
|
||||||
|
static const payjoinDetails = '/transaction_info/payjoin';
|
||||||
static const bumpFeePage = '/bump_fee_page';
|
static const bumpFeePage = '/bump_fee_page';
|
||||||
static const receive = '/receive';
|
static const receive = '/receive';
|
||||||
static const newSubaddress = '/new_subaddress';
|
static const newSubaddress = '/new_subaddress';
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/dashboard/widgets/payjoin_transaction_row.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
|
import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
|
||||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cw_core/sync_status.dart';
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
@ -143,6 +145,25 @@ class TransactionsPage extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is PayjoinTransactionListItem) {
|
||||||
|
final session = item.session;
|
||||||
|
|
||||||
|
return PayjoinTransactionRow(
|
||||||
|
key: item.key,
|
||||||
|
onTap: () => Navigator.of(context).pushNamed(
|
||||||
|
Routes.payjoinDetails,
|
||||||
|
arguments: [item.sessionId, item.transaction],
|
||||||
|
),
|
||||||
|
currency: "BTC",
|
||||||
|
state: item.status,
|
||||||
|
amount: bitcoin!.formatterBitcoinAmountToString(
|
||||||
|
amount: session.amount.toInt()),
|
||||||
|
createdAt: DateFormat('HH:mm')
|
||||||
|
.format(session.inProgressSince!),
|
||||||
|
isSending: session.isSenderSession,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (item is TradeListItem) {
|
if (item is TradeListItem) {
|
||||||
final trade = item.trade;
|
final trade = item.trade;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class PayjoinTransactionRow extends StatelessWidget {
|
||||||
|
PayjoinTransactionRow({
|
||||||
|
required this.createdAt,
|
||||||
|
required this.currency,
|
||||||
|
required this.onTap,
|
||||||
|
required this.amount,
|
||||||
|
required this.state,
|
||||||
|
required this.isSending,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
final String createdAt;
|
||||||
|
final String amount;
|
||||||
|
final String currency;
|
||||||
|
final String state;
|
||||||
|
final bool isSending;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_getImage(),
|
||||||
|
SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"${isSending ? S.current.outgoing : S.current.incoming} Payjoin",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<DashboardPageTheme>()!
|
||||||
|
.textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
amount + ' ' + currency,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<DashboardPageTheme>()!
|
||||||
|
.textColor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
createdAt,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<CakeTextTheme>()!
|
||||||
|
.dateSectionRowColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
state,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.extension<CakeTextTheme>()!
|
||||||
|
.dateSectionRowColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _getImage() => ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
child: Image.asset('assets/images/payjoin.png', width: 36, height: 36));
|
||||||
|
|
||||||
|
}
|
77
lib/src/screens/payjoin_details/payjoin_details_page.dart
Normal file
77
lib/src/screens/payjoin_details/payjoin_details_page.dart
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/standard_list_card.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/standard_list_status_row.dart';
|
||||||
|
import 'package:cake_wallet/utils/show_bar.dart';
|
||||||
|
import 'package:cake_wallet/view_model/payjoin_details_view_model.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class PayjoinDetailsPage extends BasePage {
|
||||||
|
PayjoinDetailsPage({required this.payjoinDetailsViewModel});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => S.current.payjoin_details;
|
||||||
|
|
||||||
|
final PayjoinDetailsViewModel payjoinDetailsViewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget body(BuildContext context) => PayjoinDetailsPageBody(payjoinDetailsViewModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PayjoinDetailsPageBody extends StatefulWidget {
|
||||||
|
PayjoinDetailsPageBody(this.payjoinDetailsViewModel);
|
||||||
|
|
||||||
|
final PayjoinDetailsViewModel payjoinDetailsViewModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PayjoinDetailsPageBody> createState() => _PayjoinDetailsPageBodyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PayjoinDetailsPageBodyState extends State<PayjoinDetailsPageBody> {
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
widget.payjoinDetailsViewModel.listener.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SectionStandardList(
|
||||||
|
sectionCount: 1,
|
||||||
|
itemCounter: (int _) => widget.payjoinDetailsViewModel.items.length,
|
||||||
|
itemBuilder: (__, index) {
|
||||||
|
final item = widget.payjoinDetailsViewModel.items[index];
|
||||||
|
|
||||||
|
if (item is DetailsListStatusItem) {
|
||||||
|
return StandardListStatusRow(
|
||||||
|
title: item.title,
|
||||||
|
value: item.value,
|
||||||
|
status: item.status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is TradeDetailsListCardItem) {
|
||||||
|
return TradeDetailsStandardListCard(
|
||||||
|
id: item.id,
|
||||||
|
create: item.createdAt,
|
||||||
|
pair: item.pair,
|
||||||
|
currentTheme: widget.payjoinDetailsViewModel.settingsStore.currentTheme.type,
|
||||||
|
onTap: item.onTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: item.value));
|
||||||
|
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||||
|
},
|
||||||
|
child: ListRow(title: '${item.title}:', value: item.value),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import 'package:cake_wallet/entities/qr_view_data.dart';
|
import 'package:cake_wallet/entities/qr_view_data.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
|
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
@ -91,26 +93,54 @@ class QRWidget extends StatelessWidget {
|
||||||
child: Hero(
|
child: Hero(
|
||||||
tag: Key(heroTag ?? addressUri.toString()),
|
tag: Key(heroTag ?? addressUri.toString()),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1.0,
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(5),
|
padding: EdgeInsets.zero,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
border: Border(top: BorderSide.none),
|
||||||
width: 3,
|
borderRadius:
|
||||||
color: Theme.of(context)
|
BorderRadius.all(Radius.circular(5)),
|
||||||
.extension<DashboardPageTheme>()!
|
|
||||||
.textColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
width: 3,
|
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.all(3),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1.0,
|
||||||
|
child: QrImage(
|
||||||
|
data: addressUri.toString(),
|
||||||
),
|
),
|
||||||
child: QrImage(data: addressUri.toString())),
|
),
|
||||||
|
),
|
||||||
|
if (addressListViewModel
|
||||||
|
.payjoinEndpoint.isNotEmpty) ...[
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 4,
|
||||||
|
bottom: 4,
|
||||||
|
right: 4,
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/payjoin.png',
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
S.of(context).payjoin_enabled,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -179,7 +209,25 @@ class QRWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
if (addressListViewModel.payjoinEndpoint.isNotEmpty) ...[
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 12),
|
||||||
|
child: PrimaryImageButton(
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(
|
||||||
|
text: addressListViewModel.payjoinEndpoint));
|
||||||
|
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||||
|
},
|
||||||
|
image: Image.asset('assets/images/payjoin.png', width: 25,),
|
||||||
|
text: S.of(context).copy_payjoin_url,
|
||||||
|
color: Theme.of(context).cardColor,
|
||||||
|
textColor: Theme.of(context)
|
||||||
|
.extension<CakeTextTheme>()!
|
||||||
|
.buttonTextColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
|
@ -136,6 +136,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
setState(() => _setInactive(true));
|
setState(() => _setInactive(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (widget.appStore.wallet?.type == WalletType.bitcoin) {
|
||||||
|
bitcoin!.stopPayjoinSessions(widget.appStore.wallet!);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case AppLifecycleState.resumed:
|
case AppLifecycleState.resumed:
|
||||||
widget.authService.requireAuth().then((value) {
|
widget.authService.requireAuth().then((value) {
|
||||||
|
@ -145,6 +149,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (widget.appStore.wallet?.type == WalletType.bitcoin &&
|
||||||
|
widget.appStore.settingsStore.usePayjoin) {
|
||||||
|
bitcoin!.resumePayjoinSessions(widget.appStore.wallet!);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -463,7 +463,7 @@ class SendPage extends BasePage {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
text: S.of(context).send,
|
text: sendViewModel.payjoinUri != null ? S.of(context).send_payjoin : S.of(context).send,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
isLoading: sendViewModel.state is IsExecutingState ||
|
isLoading: sendViewModel.state is IsExecutingState ||
|
||||||
|
|
|
@ -188,18 +188,22 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
||||||
sendViewModel.createOpenCryptoPayTransaction(uri.toString());
|
sendViewModel.createOpenCryptoPayTransaction(uri.toString());
|
||||||
} else {
|
} else {
|
||||||
final paymentRequest = PaymentRequest.fromUri(uri);
|
final paymentRequest = PaymentRequest.fromUri(uri);
|
||||||
|
if (sendViewModel.usePayjoin) {
|
||||||
|
sendViewModel.payjoinUri = paymentRequest.pjUri;
|
||||||
|
}
|
||||||
addressController.text = paymentRequest.address;
|
addressController.text = paymentRequest.address;
|
||||||
cryptoAmountController.text = paymentRequest.amount;
|
cryptoAmountController.text = paymentRequest.amount;
|
||||||
noteController.text = paymentRequest.note;
|
noteController.text = paymentRequest.note;}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
options: [
|
options: [
|
||||||
AddressTextFieldOption.paste,
|
AddressTextFieldOption.paste,
|
||||||
AddressTextFieldOption.qrCode,
|
AddressTextFieldOption.qrCode,
|
||||||
AddressTextFieldOption.addressBook
|
AddressTextFieldOption.addressBook
|
||||||
],
|
],
|
||||||
buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
buttonColor:
|
||||||
borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
|
||||||
|
borderColor:
|
||||||
|
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
|
||||||
textStyle:
|
textStyle:
|
||||||
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
|
|
|
@ -49,6 +49,14 @@ class PrivacyPage extends BasePage {
|
||||||
_privacySettingsViewModel.setExchangeApiMode(mode),
|
_privacySettingsViewModel.setExchangeApiMode(mode),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_privacySettingsViewModel.canUsePayjoin)
|
||||||
|
SettingsSwitcherCell(
|
||||||
|
title: S.of(context).use_payjoin,
|
||||||
|
value: _privacySettingsViewModel.usePayjoin,
|
||||||
|
onValueChange: (BuildContext _, bool value) {
|
||||||
|
_privacySettingsViewModel.setUsePayjoin(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
SettingsSwitcherCell(
|
SettingsSwitcherCell(
|
||||||
title: S.current.settings_save_recipient_address,
|
title: S.current.settings_save_recipient_address,
|
||||||
value: _privacySettingsViewModel.shouldSaveRecipientAddress,
|
value: _privacySettingsViewModel.shouldSaveRecipientAddress,
|
||||||
|
|
|
@ -2,6 +2,8 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d
|
||||||
|
|
||||||
class DetailsListStatusItem extends StandartListItem {
|
class DetailsListStatusItem extends StandartListItem {
|
||||||
DetailsListStatusItem(
|
DetailsListStatusItem(
|
||||||
{required String title, required String value})
|
{required String title, required String value, this.status})
|
||||||
: super(title: title, value: value);
|
: super(title: title, value: value);
|
||||||
|
|
||||||
|
final String? status; // waiting, action required, created, fetching, finished, success
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
import 'package:cake_wallet/utils/device_info.dart';
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
|
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
|
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
||||||
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
|
import 'package:cake_wallet/utils/permission_handler.dart';
|
||||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||||
import 'package:cw_core/currency.dart';
|
import 'package:cw_core/currency.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
|
||||||
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
|
|
||||||
import 'package:cake_wallet/utils/permission_handler.dart';
|
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses }
|
enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses }
|
||||||
|
|
||||||
|
class AddressTextField<T extends Currency> extends StatelessWidget {
|
||||||
class AddressTextField<T extends Currency> extends StatelessWidget{
|
|
||||||
AddressTextField({
|
AddressTextField({
|
||||||
required this.controller,
|
required this.controller,
|
||||||
this.isActive = true,
|
this.isActive = true,
|
||||||
|
@ -234,9 +233,7 @@ class AddressTextField<T extends Currency> extends StatelessWidget{
|
||||||
if (!isCameraPermissionGranted) return;
|
if (!isCameraPermissionGranted) return;
|
||||||
final code = await presentQRScanner(context);
|
final code = await presentQRScanner(context);
|
||||||
if (code == null) return;
|
if (code == null) return;
|
||||||
if (code.isEmpty) {
|
if (code.isEmpty) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse(code);
|
final uri = Uri.parse(code);
|
||||||
|
@ -259,7 +256,8 @@ class AddressTextField<T extends Currency> extends StatelessWidget{
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _presetWalletAddressPicker(BuildContext context) async {
|
Future<void> _presetWalletAddressPicker(BuildContext context) async {
|
||||||
final address = await Navigator.of(context).pushNamed(Routes.pickerWalletAddress);
|
final address =
|
||||||
|
await Navigator.of(context).pushNamed(Routes.pickerWalletAddress);
|
||||||
|
|
||||||
if (address is String) {
|
if (address is String) {
|
||||||
controller?.text = address;
|
controller?.text = address;
|
||||||
|
@ -272,8 +270,14 @@ class AddressTextField<T extends Currency> extends StatelessWidget{
|
||||||
final address = clipboard?.text ?? '';
|
final address = clipboard?.text ?? '';
|
||||||
|
|
||||||
if (address.isNotEmpty) {
|
if (address.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(address);
|
||||||
|
controller?.text = uri.path;
|
||||||
|
onURIScanned?.call(uri);
|
||||||
|
} catch (_) {
|
||||||
controller?.text = address;
|
controller?.text = address;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onPushPasteButton?.call(context);
|
onPushPasteButton?.call(context);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||||
import 'package:cake_wallet/palette.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -7,10 +6,11 @@ import 'package:cake_wallet/themes/extensions/address_theme.dart';
|
||||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||||
|
|
||||||
class StandardListStatusRow extends StatelessWidget {
|
class StandardListStatusRow extends StatelessWidget {
|
||||||
StandardListStatusRow({required this.title, required this.value});
|
StandardListStatusRow({required this.title, required this.value, this.status});
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
final String value;
|
final String value;
|
||||||
|
final String? status; // waiting, action required, created, fetching, finished, success
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -43,7 +43,7 @@ class StandardListStatusRow extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SyncIndicatorIcon(
|
SyncIndicatorIcon(
|
||||||
boolMode: false,
|
boolMode: false,
|
||||||
value: value,
|
value: status ?? value,
|
||||||
size: 6,
|
size: 6,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|
47
lib/store/dashboard/payjoin_transactions_store.dart
Normal file
47
lib/store/dashboard/payjoin_transactions_store.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'payjoin_transactions_store.g.dart';
|
||||||
|
|
||||||
|
class PayjoinTransactionsStore = PayjoinTransactionsStoreBase
|
||||||
|
with _$PayjoinTransactionsStore;
|
||||||
|
|
||||||
|
abstract class PayjoinTransactionsStoreBase with Store {
|
||||||
|
PayjoinTransactionsStoreBase({
|
||||||
|
required this.payjoinSessionSource,
|
||||||
|
}) : transactions = <PayjoinTransactionListItem>[] {
|
||||||
|
payjoinSessionSource.watch().listen((_) => updateTransactionList());
|
||||||
|
updateTransactionList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Box<PayjoinSession> payjoinSessionSource;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
List<PayjoinTransactionListItem> transactions;
|
||||||
|
|
||||||
|
@action
|
||||||
|
Future<void> updateTransactionList() async {
|
||||||
|
final updatedTransactions = <PayjoinTransactionListItem>[];
|
||||||
|
payjoinSessionSource.toMap().forEach((dynamic key, PayjoinSession session) {
|
||||||
|
if ([
|
||||||
|
PayjoinSessionStatus.inProgress.name,
|
||||||
|
PayjoinSessionStatus.success.name,
|
||||||
|
PayjoinSessionStatus.unrecoverable.name
|
||||||
|
].contains(session.status) &&
|
||||||
|
session.inProgressSince != null) {
|
||||||
|
updatedTransactions.add(PayjoinTransactionListItem(
|
||||||
|
sessionId: key as String,
|
||||||
|
session: session,
|
||||||
|
key: ValueKey('payjoin_transaction_list_item_${key}_key'),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
transactions = updatedTransactions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -121,6 +121,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
required this.lookupsOpenAlias,
|
required this.lookupsOpenAlias,
|
||||||
required this.lookupsENS,
|
required this.lookupsENS,
|
||||||
required this.lookupsWellKnown,
|
required this.lookupsWellKnown,
|
||||||
|
required this.usePayjoin,
|
||||||
required this.customBitcoinFeeRate,
|
required this.customBitcoinFeeRate,
|
||||||
required this.silentPaymentsCardDisplay,
|
required this.silentPaymentsCardDisplay,
|
||||||
required this.silentPaymentsAlwaysScan,
|
required this.silentPaymentsAlwaysScan,
|
||||||
|
@ -483,6 +484,11 @@ abstract class SettingsStoreBase with Store {
|
||||||
(bool looksUpWellKnown) =>
|
(bool looksUpWellKnown) =>
|
||||||
_sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown));
|
_sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown));
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
(_) => usePayjoin,
|
||||||
|
(bool usePayjoin) =>
|
||||||
|
_sharedPreferences.setBool(PreferencesKey.usePayjoin, usePayjoin));
|
||||||
|
|
||||||
// secure storage keys:
|
// secure storage keys:
|
||||||
reaction(
|
reaction(
|
||||||
(_) => allowBiometricalAuthentication,
|
(_) => allowBiometricalAuthentication,
|
||||||
|
@ -802,6 +808,9 @@ abstract class SettingsStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
bool lookupsWellKnown;
|
bool lookupsWellKnown;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
bool usePayjoin;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
SyncMode currentSyncMode;
|
SyncMode currentSyncMode;
|
||||||
|
|
||||||
|
@ -1009,6 +1018,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||||
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||||
final lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true;
|
final lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true;
|
||||||
|
final usePayjoin = sharedPreferences.getBool(PreferencesKey.usePayjoin) ?? false;
|
||||||
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
|
||||||
final silentPaymentsCardDisplay =
|
final silentPaymentsCardDisplay =
|
||||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
||||||
|
@ -1311,6 +1321,7 @@ abstract class SettingsStoreBase with Store {
|
||||||
lookupsOpenAlias: lookupsOpenAlias,
|
lookupsOpenAlias: lookupsOpenAlias,
|
||||||
lookupsENS: lookupsENS,
|
lookupsENS: lookupsENS,
|
||||||
lookupsWellKnown: lookupsWellKnown,
|
lookupsWellKnown: lookupsWellKnown,
|
||||||
|
usePayjoin: usePayjoin,
|
||||||
customBitcoinFeeRate: customBitcoinFeeRate,
|
customBitcoinFeeRate: customBitcoinFeeRate,
|
||||||
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
||||||
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
|
|
||||||
class PaymentRequest {
|
class PaymentRequest {
|
||||||
PaymentRequest(this.address, this.amount, this.note, this.scheme, {this.callbackUrl, this.callbackMessage});
|
PaymentRequest(this.address, this.amount, this.note, this.scheme, this.pjUri,
|
||||||
|
{this.callbackUrl, this.callbackMessage});
|
||||||
|
|
||||||
factory PaymentRequest.fromUri(Uri? uri) {
|
factory PaymentRequest.fromUri(Uri? uri) {
|
||||||
var address = "";
|
var address = "";
|
||||||
|
@ -12,8 +12,13 @@ class PaymentRequest {
|
||||||
String? walletType;
|
String? walletType;
|
||||||
String? callbackUrl;
|
String? callbackUrl;
|
||||||
String? callbackMessage;
|
String? callbackMessage;
|
||||||
|
String? pjUri;
|
||||||
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
|
if (uri.queryParameters['pj'] != null) {
|
||||||
|
pjUri = uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
address = uri.queryParameters['address'] ?? uri.path;
|
address = uri.queryParameters['address'] ?? uri.path;
|
||||||
amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
|
amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
|
||||||
note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
|
note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
|
||||||
|
@ -42,6 +47,7 @@ class PaymentRequest {
|
||||||
amount,
|
amount,
|
||||||
note,
|
note,
|
||||||
scheme,
|
scheme,
|
||||||
|
pjUri,
|
||||||
callbackUrl: callbackUrl,
|
callbackUrl: callbackUrl,
|
||||||
callbackMessage: callbackMessage,
|
callbackMessage: callbackMessage,
|
||||||
);
|
);
|
||||||
|
@ -51,6 +57,7 @@ class PaymentRequest {
|
||||||
final String amount;
|
final String amount;
|
||||||
final String note;
|
final String note;
|
||||||
final String scheme;
|
final String scheme;
|
||||||
|
final String? pjUri;
|
||||||
final String? callbackUrl;
|
final String? callbackUrl;
|
||||||
final String? callbackMessage;
|
final String? callbackMessage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ import 'package:cake_wallet/entities/service_status.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/wownero/wownero.dart' as wow;
|
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
import 'package:cake_wallet/store/dashboard/orders_store.dart';
|
||||||
|
import 'package:cake_wallet/store/dashboard/payjoin_transactions_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
|
import 'package:cake_wallet/store/dashboard/trade_filter_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
|
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
|
||||||
|
@ -29,9 +29,12 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/filter_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart';
|
import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/payjoin_transaction_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
|
||||||
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
|
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
|
||||||
|
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||||
|
import 'package:cake_wallet/wownero/wownero.dart' as wow;
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
import 'package:cw_core/cake_hive.dart';
|
import 'package:cw_core/cake_hive.dart';
|
||||||
|
@ -70,6 +73,7 @@ abstract class DashboardViewModelBase with Store {
|
||||||
required this.yatStore,
|
required this.yatStore,
|
||||||
required this.ordersStore,
|
required this.ordersStore,
|
||||||
required this.anonpayTransactionsStore,
|
required this.anonpayTransactionsStore,
|
||||||
|
required this.payjoinTransactionsStore,
|
||||||
required this.sharedPreferences,
|
required this.sharedPreferences,
|
||||||
required this.keyService})
|
required this.keyService})
|
||||||
: hasTradeAction = true,
|
: hasTradeAction = true,
|
||||||
|
@ -408,10 +412,17 @@ abstract class DashboardViewModelBase with Store {
|
||||||
ordersStore.orders.where((item) => item.order.walletId == wallet.id).toList();
|
ordersStore.orders.where((item) => item.order.walletId == wallet.id).toList();
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
List<AnonpayTransactionListItem> get anonpayTransactons => anonpayTransactionsStore.transactions
|
List<AnonpayTransactionListItem> get anonpayTransactions =>
|
||||||
|
anonpayTransactionsStore.transactions
|
||||||
.where((item) => item.transaction.walletId == wallet.id)
|
.where((item) => item.transaction.walletId == wallet.id)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
@computed
|
||||||
|
List<PayjoinTransactionListItem> get payjoinTransactions =>
|
||||||
|
payjoinTransactionsStore.transactions
|
||||||
|
.where((item) => item.session.walletId == wallet.id)
|
||||||
|
.toList();
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
double get price => balanceViewModel.price;
|
double get price => balanceViewModel.price;
|
||||||
|
|
||||||
|
@ -423,11 +434,27 @@ abstract class DashboardViewModelBase with Store {
|
||||||
List<ActionListItem> get items {
|
List<ActionListItem> get items {
|
||||||
final _items = <ActionListItem>[];
|
final _items = <ActionListItem>[];
|
||||||
|
|
||||||
_items.addAll(
|
_items.addAll(transactionFilterStore
|
||||||
transactionFilterStore.filtered(transactions: [...transactions, ...anonpayTransactons]));
|
.filtered(transactions: [...transactions, ...anonpayTransactions]));
|
||||||
_items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
|
_items.addAll(tradeFilterStore.filtered(trades: trades, wallet: wallet));
|
||||||
_items.addAll(orders);
|
_items.addAll(orders);
|
||||||
|
|
||||||
|
if (payjoinTransactions.isNotEmpty) {
|
||||||
|
final _payjoinTransactions = payjoinTransactions;
|
||||||
|
_items.forEach((e) {
|
||||||
|
if (e is TransactionListItem &&
|
||||||
|
_payjoinTransactions
|
||||||
|
.any((t) => t.session.txId == e.transaction.id)) {
|
||||||
|
_payjoinTransactions
|
||||||
|
.firstWhere((t) => t.session.txId == e.transaction.id)
|
||||||
|
.transaction = e.transaction;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_items.addAll(_payjoinTransactions);
|
||||||
|
_items.removeWhere((e) => (e is TransactionListItem &&
|
||||||
|
_payjoinTransactions.any((t) => t.session.txId == e.transaction.id)));
|
||||||
|
}
|
||||||
|
|
||||||
return formattedItemsList(_items);
|
return formattedItemsList(_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,6 +782,8 @@ abstract class DashboardViewModelBase with Store {
|
||||||
|
|
||||||
TransactionFilterStore transactionFilterStore;
|
TransactionFilterStore transactionFilterStore;
|
||||||
|
|
||||||
|
PayjoinTransactionsStore payjoinTransactionsStore;
|
||||||
|
|
||||||
Map<String, List<FilterItem>> filterItems;
|
Map<String, List<FilterItem>> filterItems;
|
||||||
|
|
||||||
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled;
|
||||||
|
|
34
lib/view_model/dashboard/payjoin_transaction_list_item.dart
Normal file
34
lib/view_model/dashboard/payjoin_transaction_list_item.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
|
||||||
|
class PayjoinTransactionListItem extends ActionListItem {
|
||||||
|
PayjoinTransactionListItem({
|
||||||
|
required this.sessionId,
|
||||||
|
required this.session,
|
||||||
|
required super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String sessionId;
|
||||||
|
final PayjoinSession session;
|
||||||
|
TransactionInfo? transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
DateTime get date => session.inProgressSince!;
|
||||||
|
|
||||||
|
String get status {
|
||||||
|
switch (session.status) {
|
||||||
|
case 'success':
|
||||||
|
if (transaction?.isPending == true)
|
||||||
|
return S.current.payjoin_request_awaiting_tx;
|
||||||
|
return S.current.successful;
|
||||||
|
case 'inProgress':
|
||||||
|
return S.current.payjoin_request_in_progress;
|
||||||
|
case 'unrecoverable':
|
||||||
|
return S.current.error;
|
||||||
|
default:
|
||||||
|
return session.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
lib/view_model/payjoin_details_view_model.dart
Normal file
122
lib/view_model/payjoin_details_view_model.dart
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/trade_details/trade_details_list_card.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/trade_details/trade_details_status_item.dart';
|
||||||
|
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||||
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
import 'package:cw_core/utils/print_verbose.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'payjoin_details_view_model.g.dart';
|
||||||
|
|
||||||
|
class PayjoinDetailsViewModel = PayjoinDetailsViewModelBase
|
||||||
|
with _$PayjoinDetailsViewModel;
|
||||||
|
|
||||||
|
abstract class PayjoinDetailsViewModelBase with Store {
|
||||||
|
PayjoinDetailsViewModelBase(
|
||||||
|
this.payjoinSessionId,
|
||||||
|
this.transactionInfo, {
|
||||||
|
required this.payjoinSessionSource,
|
||||||
|
required this.settingsStore,
|
||||||
|
}) : items = ObservableList<StandartListItem>(),
|
||||||
|
payjoinSession = payjoinSessionSource.get(payjoinSessionId)! {
|
||||||
|
listener = payjoinSessionSource.watch().listen((e) {
|
||||||
|
if (e.key == payjoinSessionId) _updateItems();
|
||||||
|
});
|
||||||
|
_updateItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
final Box<PayjoinSession> payjoinSessionSource;
|
||||||
|
final SettingsStore settingsStore;
|
||||||
|
final String payjoinSessionId;
|
||||||
|
final TransactionInfo? transactionInfo;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
late PayjoinSession payjoinSession;
|
||||||
|
|
||||||
|
final ObservableList<StandartListItem> items;
|
||||||
|
|
||||||
|
late final StreamSubscription<BoxEvent> listener;
|
||||||
|
|
||||||
|
Timer? timer;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void _updateItems() {
|
||||||
|
final dateFormat = DateFormatter.withCurrentLocal();
|
||||||
|
items.clear();
|
||||||
|
items.addAll([
|
||||||
|
DetailsListStatusItem(
|
||||||
|
title: S.current.status,
|
||||||
|
value: _getStatusString(),
|
||||||
|
status: payjoinSession.status,
|
||||||
|
),
|
||||||
|
TradeDetailsListCardItem(
|
||||||
|
id: "${payjoinSession.isSenderSession ? S.current.outgoing : S.current.incoming} Payjoin",
|
||||||
|
createdAt:
|
||||||
|
dateFormat.format(payjoinSession.inProgressSince!).toString(),
|
||||||
|
pair:
|
||||||
|
'${bitcoin!.formatterBitcoinAmountToString(amount: payjoinSession.amount.toInt())} BTC',
|
||||||
|
onTap: (_) {},
|
||||||
|
),
|
||||||
|
if (payjoinSession.error?.isNotEmpty == true)
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.error,
|
||||||
|
value: payjoinSession.error!,
|
||||||
|
),
|
||||||
|
if (payjoinSession.txId?.isNotEmpty == true)
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_transaction_id,
|
||||||
|
value: payjoinSession.txId!,
|
||||||
|
key: ValueKey('standard_list_item_transaction_details_id_key'),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (transactionInfo != null) {
|
||||||
|
items.addAll([
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_date,
|
||||||
|
value: dateFormat.format(transactionInfo!.date),
|
||||||
|
key: ValueKey('standard_list_item_transaction_details_date_key'),
|
||||||
|
),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.confirmations,
|
||||||
|
value: transactionInfo!.confirmations.toString(),
|
||||||
|
key: ValueKey('standard_list_item_transaction_confirmations_key'),
|
||||||
|
),
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_height,
|
||||||
|
value: '${transactionInfo!.height}',
|
||||||
|
key: ValueKey('standard_list_item_transaction_details_height_key'),
|
||||||
|
),
|
||||||
|
if (transactionInfo!.feeFormatted()?.isNotEmpty ?? false)
|
||||||
|
StandartListItem(
|
||||||
|
title: S.current.transaction_details_fee,
|
||||||
|
value: transactionInfo!.feeFormatted()!,
|
||||||
|
key: ValueKey('standard_list_item_transaction_details_fee_key'),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getStatusString() {
|
||||||
|
switch (payjoinSession.status) {
|
||||||
|
case 'success':
|
||||||
|
if (transactionInfo?.isPending == true)
|
||||||
|
return S.current.payjoin_request_awaiting_tx;
|
||||||
|
return S.current.successful;
|
||||||
|
case 'inProgress':
|
||||||
|
return S.current.payjoin_request_in_progress;
|
||||||
|
case 'unrecoverable':
|
||||||
|
return S.current.error;
|
||||||
|
default:
|
||||||
|
return payjoinSession.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -619,6 +619,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
priority: priority!,
|
priority: priority!,
|
||||||
feeRate: feesViewModel.customBitcoinFeeRate,
|
feeRate: feesViewModel.customBitcoinFeeRate,
|
||||||
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
coinTypeToSpendFrom: coinTypeToSpendFrom,
|
||||||
|
payjoinUri: _settingsStore.usePayjoin ? payjoinUri : null,
|
||||||
);
|
);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return bitcoin!.createBitcoinTransactionCredentials(
|
return bitcoin!.createBitcoinTransactionCredentials(
|
||||||
|
@ -855,4 +856,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get usePayjoin => _settingsStore.usePayjoin;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
String? payjoinUri;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/exchange_api_mode.dart';
|
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
|
@ -32,20 +33,19 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
@action
|
@action
|
||||||
void setAutoGenerateSubaddresses(bool value) {
|
void setAutoGenerateSubaddresses(bool value) {
|
||||||
_wallet.isEnabledAutoGenerateSubaddress = value;
|
_wallet.isEnabledAutoGenerateSubaddress = value;
|
||||||
if (value) {
|
_settingsStore.autoGenerateSubaddressStatus = value
|
||||||
_settingsStore.autoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.enabled;
|
? AutoGenerateSubaddressStatus.enabled
|
||||||
} else {
|
: AutoGenerateSubaddressStatus.disabled;
|
||||||
_settingsStore.autoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.disabled;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isAutoGenerateSubaddressesVisible =>
|
bool get isAutoGenerateSubaddressesVisible => [
|
||||||
_wallet.type == WalletType.monero ||
|
WalletType.monero,
|
||||||
_wallet.type == WalletType.wownero ||
|
WalletType.wownero,
|
||||||
_wallet.type == WalletType.bitcoin ||
|
WalletType.bitcoin,
|
||||||
_wallet.type == WalletType.litecoin ||
|
WalletType.litecoin,
|
||||||
_wallet.type == WalletType.bitcoinCash ||
|
WalletType.bitcoinCash,
|
||||||
_wallet.type == WalletType.decred;
|
WalletType.decred
|
||||||
|
].contains(_wallet.type);
|
||||||
|
|
||||||
bool get isMoneroWallet => _wallet.type == WalletType.monero;
|
bool get isMoneroWallet => _wallet.type == WalletType.monero;
|
||||||
|
|
||||||
|
@ -100,6 +100,9 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
@computed
|
@computed
|
||||||
bool get looksUpWellKnown => _settingsStore.lookupsWellKnown;
|
bool get looksUpWellKnown => _settingsStore.lookupsWellKnown;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
bool get usePayjoin => _settingsStore.usePayjoin;
|
||||||
|
|
||||||
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
|
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
|
||||||
|
|
||||||
bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
|
bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
|
||||||
|
@ -108,6 +111,8 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
|
|
||||||
bool get canUseMempoolFeeAPI => _wallet.type == WalletType.bitcoin;
|
bool get canUseMempoolFeeAPI => _wallet.type == WalletType.bitcoin;
|
||||||
|
|
||||||
|
bool get canUsePayjoin => _wallet.type == WalletType.bitcoin;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setShouldSaveRecipientAddress(bool value) =>
|
void setShouldSaveRecipientAddress(bool value) =>
|
||||||
_settingsStore.shouldSaveRecipientAddress = value;
|
_settingsStore.shouldSaveRecipientAddress = value;
|
||||||
|
@ -170,7 +175,12 @@ abstract class PrivacySettingsViewModelBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setUseMempoolFeeAPI(bool value) {
|
void setUseMempoolFeeAPI(bool value) =>
|
||||||
_settingsStore.useMempoolFeeAPI = value;
|
_settingsStore.useMempoolFeeAPI = value;
|
||||||
|
|
||||||
|
@action
|
||||||
|
void setUsePayjoin(bool value) {
|
||||||
|
_settingsStore.usePayjoin = value;
|
||||||
|
bitcoin!.updatePayjoinState(_wallet, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:developer' as dev;
|
import 'dart:developer' as dev;
|
||||||
|
import 'dart:core';
|
||||||
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||||
|
@ -72,17 +73,21 @@ class HavenURI extends PaymentURI {
|
||||||
}
|
}
|
||||||
|
|
||||||
class BitcoinURI extends PaymentURI {
|
class BitcoinURI extends PaymentURI {
|
||||||
BitcoinURI({required super.amount, required super.address});
|
BitcoinURI({required super.amount, required super.address, this.pjUri = ''});
|
||||||
|
|
||||||
|
final String pjUri;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
var base = 'bitcoin:$address';
|
final qp = <String, String>{};
|
||||||
|
|
||||||
if (amount.isNotEmpty) {
|
if (amount.isNotEmpty) qp['amount'] = amount.replaceAll(',', '.');
|
||||||
base += '?amount=${amount.replaceAll(',', '.')}';
|
if (pjUri.isNotEmpty) {
|
||||||
|
qp['pjos'] = '0';
|
||||||
|
qp['pj'] = pjUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
return base;
|
return Uri(scheme: 'bitcoin', path: address, queryParameters: qp).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,6 +305,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
WalletAddressListItem get address =>
|
WalletAddressListItem get address =>
|
||||||
WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
|
WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false);
|
||||||
|
|
||||||
|
@computed
|
||||||
|
String get payjoinEndpoint => wallet.type == WalletType.bitcoin
|
||||||
|
? bitcoin!.getPayjoinEndpoint(wallet)
|
||||||
|
: "";
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
PaymentURI get uri {
|
PaymentURI get uri {
|
||||||
switch (wallet.type) {
|
switch (wallet.type) {
|
||||||
|
@ -308,7 +318,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||||
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:
|
||||||
return BitcoinURI(amount: amount, address: address.address);
|
return BitcoinURI(
|
||||||
|
amount: amount,
|
||||||
|
address: address.address,
|
||||||
|
pjUri: payjoinEndpoint);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return LitecoinURI(amount: amount, address: address.address);
|
return LitecoinURI(amount: amount, address: address.address);
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "نسخ",
|
"copy": "نسخ",
|
||||||
"copy_address": "نسخ العنوان",
|
"copy_address": "نسخ العنوان",
|
||||||
"copy_id": "نسخ معرف العملية",
|
"copy_id": "نسخ معرف العملية",
|
||||||
|
"copy_payjoin_url": "نسخ Payjoin url",
|
||||||
"copyWalletConnectLink": "ﺎﻨﻫ ﻪﻘﺼﻟﺍﻭ dApp ﻦﻣ WalletConnect ﻂﺑﺍﺭ ﺦﺴﻧﺍ",
|
"copyWalletConnectLink": "ﺎﻨﻫ ﻪﻘﺼﻟﺍﻭ dApp ﻦﻣ WalletConnect ﻂﺑﺍﺭ ﺦﺴﻧﺍ",
|
||||||
"corrupted_seed_notice": "تالف ملفات هذه المحفظة ولا يمكن فتحها. يرجى الاطلاع على عبارة البذور وحفظها واستعادة المحفظة.\n\nإذا كانت القيمة فارغة ، لم تتمكن البذور من استردادها بشكل صحيح.",
|
"corrupted_seed_notice": "تالف ملفات هذه المحفظة ولا يمكن فتحها. يرجى الاطلاع على عبارة البذور وحفظها واستعادة المحفظة.\n\nإذا كانت القيمة فارغة ، لم تتمكن البذور من استردادها بشكل صحيح.",
|
||||||
"countries": "بلدان",
|
"countries": "بلدان",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "كلمة المرور",
|
"password": "كلمة المرور",
|
||||||
"paste": "لصق",
|
"paste": "لصق",
|
||||||
"pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
|
"pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
|
||||||
|
"payjoin_details": "Payjoin تفاصيل",
|
||||||
|
"payjoin_enabled": "Payjoin تمكين",
|
||||||
|
"payjoin_request_awaiting_tx": "في انتظار المعاملة",
|
||||||
|
"payjoin_request_in_progress": "في تَقَدم",
|
||||||
"payment_id": "معرف الدفع:",
|
"payment_id": "معرف الدفع:",
|
||||||
"payment_was_received": "تم استلام الدفع الخاص بك.",
|
"payment_was_received": "تم استلام الدفع الخاص بك.",
|
||||||
"pending": " (في الإنتظار)",
|
"pending": " (في الإنتظار)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "أرسل من محفظة خارجية",
|
"send_from_external_wallet": "أرسل من محفظة خارجية",
|
||||||
"send_name": "الأسم",
|
"send_name": "الأسم",
|
||||||
"send_new": "جديد",
|
"send_new": "جديد",
|
||||||
|
"send_payjoin": "يرسل Payjoin",
|
||||||
"send_payment_id": "معرف عملية الدفع (اختياري)",
|
"send_payment_id": "معرف عملية الدفع (اختياري)",
|
||||||
"send_priority": "حاليًا ، تم تحديد الرسوم بأولوية ${transactionPriority}.\nيمكن تعديل أولوية المعاملة في الإعدادات",
|
"send_priority": "حاليًا ، تم تحديد الرسوم بأولوية ${transactionPriority}.\nيمكن تعديل أولوية المعاملة في الإعدادات",
|
||||||
"send_sending": "يتم الإرسال...",
|
"send_sending": "يتم الإرسال...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Копиране",
|
"copy": "Копиране",
|
||||||
"copy_address": "Copy Address",
|
"copy_address": "Copy Address",
|
||||||
"copy_id": "Копиране на ID",
|
"copy_id": "Копиране на ID",
|
||||||
|
"copy_payjoin_url": "Копиране Payjoin url",
|
||||||
"copyWalletConnectLink": "Копирайте връзката WalletConnect от dApp и я поставете тук",
|
"copyWalletConnectLink": "Копирайте връзката WalletConnect от dApp и я поставете тук",
|
||||||
"corrupted_seed_notice": "Файловете за този портфейл са повредени и не могат да бъдат отворени. Моля, прегледайте фразата за семена, запазете я и възстановете портфейла.\n\nАко стойността е празна, тогава семето не успя да бъде правилно възстановено.",
|
"corrupted_seed_notice": "Файловете за този портфейл са повредени и не могат да бъдат отворени. Моля, прегледайте фразата за семена, запазете я и възстановете портфейла.\n\nАко стойността е празна, тогава семето не успя да бъде правилно възстановено.",
|
||||||
"countries": "Държави",
|
"countries": "Държави",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Парола",
|
"password": "Парола",
|
||||||
"paste": "Поставяне",
|
"paste": "Поставяне",
|
||||||
"pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.",
|
"pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.",
|
||||||
|
"payjoin_details": "Payjoin подробности",
|
||||||
|
"payjoin_enabled": "Payjoin enabled",
|
||||||
|
"payjoin_request_awaiting_tx": "В очакване на транзакция",
|
||||||
|
"payjoin_request_in_progress": "В ход",
|
||||||
"payment_id": "Payment ID: ",
|
"payment_id": "Payment ID: ",
|
||||||
"payment_was_received": "Плащането бе получено.",
|
"payment_was_received": "Плащането бе получено.",
|
||||||
"pending": " (чакащи)",
|
"pending": " (чакащи)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Изпратете от външен портфейл",
|
"send_from_external_wallet": "Изпратете от външен портфейл",
|
||||||
"send_name": "Име",
|
"send_name": "Име",
|
||||||
"send_new": "Ново",
|
"send_new": "Ново",
|
||||||
|
"send_payjoin": "Изпратете Payjoin",
|
||||||
"send_payment_id": "Payment ID (не е задължително)",
|
"send_payment_id": "Payment ID (не е задължително)",
|
||||||
"send_priority": "В момента таксата е на ${transactionPriority} приоритетност.\nПриоритетността на транзакцията може да бъде променена в настройките",
|
"send_priority": "В момента таксата е на ${transactionPriority} приоритетност.\nПриоритетността на транзакцията може да бъде променена в настройките",
|
||||||
"send_sending": "Изпращане...",
|
"send_sending": "Изпращане...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kopírovat",
|
"copy": "Kopírovat",
|
||||||
"copy_address": "Zkopírovat adresu",
|
"copy_address": "Zkopírovat adresu",
|
||||||
"copy_id": "Kopírovat ID",
|
"copy_id": "Kopírovat ID",
|
||||||
|
"copy_payjoin_url": "Kopírovat Payjoin URL",
|
||||||
"copyWalletConnectLink": "Zkopírujte odkaz WalletConnect z dApp a vložte jej sem",
|
"copyWalletConnectLink": "Zkopírujte odkaz WalletConnect z dApp a vložte jej sem",
|
||||||
"corrupted_seed_notice": "Soubory pro tuto peněženku jsou poškozeny a nemohou být otevřeny. Podívejte se prosím na osivo, uložte ji a obnovte peněženku.\n\nPokud je hodnota prázdná, pak semeno nebylo možné správně obnovit.",
|
"corrupted_seed_notice": "Soubory pro tuto peněženku jsou poškozeny a nemohou být otevřeny. Podívejte se prosím na osivo, uložte ji a obnovte peněženku.\n\nPokud je hodnota prázdná, pak semeno nebylo možné správně obnovit.",
|
||||||
"countries": "Země",
|
"countries": "Země",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Heslo",
|
"password": "Heslo",
|
||||||
"paste": "Vložit",
|
"paste": "Vložit",
|
||||||
"pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.",
|
"pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.",
|
||||||
|
"payjoin_details": "%%TE podrobnosti",
|
||||||
|
"payjoin_enabled": "Payjoin povoleno",
|
||||||
|
"payjoin_request_awaiting_tx": "Čeká na transakci",
|
||||||
|
"payjoin_request_in_progress": "Probíhá",
|
||||||
"payment_id": "ID platby: ",
|
"payment_id": "ID platby: ",
|
||||||
"payment_was_received": "Vaše platba byla přijata.",
|
"payment_was_received": "Vaše platba byla přijata.",
|
||||||
"pending": " (čeká)",
|
"pending": " (čeká)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Odeslat z externí peněženky",
|
"send_from_external_wallet": "Odeslat z externí peněženky",
|
||||||
"send_name": "Název",
|
"send_name": "Název",
|
||||||
"send_new": "Nová",
|
"send_new": "Nová",
|
||||||
|
"send_payjoin": "Odeslat Payjoin",
|
||||||
"send_payment_id": "ID platby (nepovinné)",
|
"send_payment_id": "ID platby (nepovinné)",
|
||||||
"send_priority": "Momentálně je poplatek nastaven na prioritu: ${transactionPriority}.\nPriorita transakce může být upravena v nastavení.",
|
"send_priority": "Momentálně je poplatek nastaven na prioritu: ${transactionPriority}.\nPriorita transakce může být upravena v nastavení.",
|
||||||
"send_sending": "Odesílání...",
|
"send_sending": "Odesílání...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kopieren",
|
"copy": "Kopieren",
|
||||||
"copy_address": "Adresse kopieren",
|
"copy_address": "Adresse kopieren",
|
||||||
"copy_id": "ID kopieren",
|
"copy_id": "ID kopieren",
|
||||||
|
"copy_payjoin_url": "Payjoin URL kopieren",
|
||||||
"copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein",
|
"copyWalletConnectLink": "Kopieren Sie den WalletConnect-Link von dApp und fügen Sie ihn hier ein",
|
||||||
"corrupted_seed_notice": "Die Dateien für diese Wallet sind beschädigt und können nicht geöffnet werden. Bitte sehen Sie sich die Seeds an, speichern Sie sie und stellen Sie die Wallet wieder her.\n\nWenn der Wert leer ist, konnte der Seed nicht korrekt wiederhergestellt werden.",
|
"corrupted_seed_notice": "Die Dateien für diese Wallet sind beschädigt und können nicht geöffnet werden. Bitte sehen Sie sich die Seeds an, speichern Sie sie und stellen Sie die Wallet wieder her.\n\nWenn der Wert leer ist, konnte der Seed nicht korrekt wiederhergestellt werden.",
|
||||||
"countries": "Länder",
|
"countries": "Länder",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"paste": "Einfügen",
|
"paste": "Einfügen",
|
||||||
"pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.",
|
"pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.",
|
||||||
|
"payjoin_details": "Payjoin Details",
|
||||||
|
"payjoin_enabled": "Payjoin aktiv",
|
||||||
|
"payjoin_request_awaiting_tx": "Warten auf die Transaktion",
|
||||||
|
"payjoin_request_in_progress": "Im Gange",
|
||||||
"payment_id": "Zahlungs-ID: ",
|
"payment_id": "Zahlungs-ID: ",
|
||||||
"payment_was_received": "Ihre Zahlung ist eingegangen.",
|
"payment_was_received": "Ihre Zahlung ist eingegangen.",
|
||||||
"pending": " (ausstehend)",
|
"pending": " (ausstehend)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "Senden Sie aus der Außenschreibe",
|
"send_from_external_wallet": "Senden Sie aus der Außenschreibe",
|
||||||
"send_name": "Name",
|
"send_name": "Name",
|
||||||
"send_new": "Neu",
|
"send_new": "Neu",
|
||||||
|
"send_payjoin": "Payjoin senden",
|
||||||
"send_payment_id": "Zahlungs-ID (optional)",
|
"send_payment_id": "Zahlungs-ID (optional)",
|
||||||
"send_priority": "Derzeit ist ${transactionPriority} als Gebührenpriorität eingestellt.\nDie Transaktionspriorität kann in den Einstellungen angepasst werden",
|
"send_priority": "Derzeit ist ${transactionPriority} als Gebührenpriorität eingestellt.\nDie Transaktionspriorität kann in den Einstellungen angepasst werden",
|
||||||
"send_sending": "Senden...",
|
"send_sending": "Senden...",
|
||||||
|
@ -975,6 +981,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"copy_address": "Copy Address",
|
"copy_address": "Copy Address",
|
||||||
"copy_id": "Copy ID",
|
"copy_id": "Copy ID",
|
||||||
|
"copy_payjoin_url": "Copy Payjoin URL",
|
||||||
"copyWalletConnectLink": "Copy the WalletConnect link from dApp and paste here",
|
"copyWalletConnectLink": "Copy the WalletConnect link from dApp and paste here",
|
||||||
"corrupted_seed_notice": "The files for this wallet are corrupted and are unable to be opened. Please view the seed phrase, save it, and restore the wallet.\n\nIf the value is empty, then the seed was unable to be correctly recovered.",
|
"corrupted_seed_notice": "The files for this wallet are corrupted and are unable to be opened. Please view the seed phrase, save it, and restore the wallet.\n\nIf the value is empty, then the seed was unable to be correctly recovered.",
|
||||||
"countries": "Countries",
|
"countries": "Countries",
|
||||||
|
@ -547,6 +548,10 @@
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"paste": "Paste",
|
"paste": "Paste",
|
||||||
"pause_wallet_creation": "Ability to create Haven Wallet is currently paused.",
|
"pause_wallet_creation": "Ability to create Haven Wallet is currently paused.",
|
||||||
|
"payjoin_details": "Payjoin details",
|
||||||
|
"payjoin_enabled": "Payjoin enabled",
|
||||||
|
"payjoin_request_awaiting_tx": "Awaiting Transaction",
|
||||||
|
"payjoin_request_in_progress": "In Progress",
|
||||||
"payment_id": "Payment ID: ",
|
"payment_id": "Payment ID: ",
|
||||||
"payment_was_received": "Your payment was received.",
|
"payment_was_received": "Your payment was received.",
|
||||||
"pending": " (pending)",
|
"pending": " (pending)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "Send from External Wallet",
|
"send_from_external_wallet": "Send from External Wallet",
|
||||||
"send_name": "Name",
|
"send_name": "Name",
|
||||||
"send_new": "New",
|
"send_new": "New",
|
||||||
|
"send_payjoin": "Send Payjoin",
|
||||||
"send_payment_id": "Payment ID (optional)",
|
"send_payment_id": "Payment ID (optional)",
|
||||||
"send_priority": "Currently the fee is set at ${transactionPriority} priority.\nTransaction priority can be adjusted in the settings",
|
"send_priority": "Currently the fee is set at ${transactionPriority} priority.\nTransaction priority can be adjusted in the settings",
|
||||||
"send_sending": "Sending...",
|
"send_sending": "Sending...",
|
||||||
|
@ -974,6 +980,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Dupdo",
|
"copy": "Dupdo",
|
||||||
"copy_address": "Copiar dirección ",
|
"copy_address": "Copiar dirección ",
|
||||||
"copy_id": "Copiar ID",
|
"copy_id": "Copiar ID",
|
||||||
|
"copy_payjoin_url": "Copiar Payjoin url",
|
||||||
"copyWalletConnectLink": "Copie el enlace de WalletConnect de dApp y péguelo aquí",
|
"copyWalletConnectLink": "Copie el enlace de WalletConnect de dApp y péguelo aquí",
|
||||||
"corrupted_seed_notice": "Los archivos para esta billetera están dañados y no pueden abrirse. Vea la frase de semillas, guárdela y restaura la billetera.\n\nSi el valor está vacío, entonces la semilla no pudo recuperarse correctamente.",
|
"corrupted_seed_notice": "Los archivos para esta billetera están dañados y no pueden abrirse. Vea la frase de semillas, guárdela y restaura la billetera.\n\nSi el valor está vacío, entonces la semilla no pudo recuperarse correctamente.",
|
||||||
"countries": "Países",
|
"countries": "Países",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Contraseña",
|
"password": "Contraseña",
|
||||||
"paste": "Pegar",
|
"paste": "Pegar",
|
||||||
"pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.",
|
"pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.",
|
||||||
|
"payjoin_details": "Payjoin detalles",
|
||||||
|
"payjoin_enabled": "Payjoin activado",
|
||||||
|
"payjoin_request_awaiting_tx": "Esperando transacción",
|
||||||
|
"payjoin_request_in_progress": "En curso",
|
||||||
"payment_id": "ID de pago: ",
|
"payment_id": "ID de pago: ",
|
||||||
"payment_was_received": "Su pago fue recibido.",
|
"payment_was_received": "Su pago fue recibido.",
|
||||||
"pending": " (pendiente)",
|
"pending": " (pendiente)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "Enviar desde la billetera externa",
|
"send_from_external_wallet": "Enviar desde la billetera externa",
|
||||||
"send_name": "Nombre",
|
"send_name": "Nombre",
|
||||||
"send_new": "Nuevo",
|
"send_new": "Nuevo",
|
||||||
|
"send_payjoin": "Enviar Payjoin",
|
||||||
"send_payment_id": "ID de pago (opcional)",
|
"send_payment_id": "ID de pago (opcional)",
|
||||||
"send_priority": "Actualmente la tarifa se establece en ${transactionPriority} prioridad.\nLa prioridad de la transacción se puede ajustar en la configuración",
|
"send_priority": "Actualmente la tarifa se establece en ${transactionPriority} prioridad.\nLa prioridad de la transacción se puede ajustar en la configuración",
|
||||||
"send_sending": "Enviando...",
|
"send_sending": "Enviando...",
|
||||||
|
@ -974,6 +980,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"copy_address": "Copier l'Adresse",
|
"copy_address": "Copier l'Adresse",
|
||||||
"copy_id": "Copier l'ID",
|
"copy_id": "Copier l'ID",
|
||||||
|
"copy_payjoin_url": "Copie Payjoin URL",
|
||||||
"copyWalletConnectLink": "Copiez le lien WalletConnect depuis l'application décentralisée (dApp) et collez-le ici",
|
"copyWalletConnectLink": "Copiez le lien WalletConnect depuis l'application décentralisée (dApp) et collez-le ici",
|
||||||
"corrupted_seed_notice": "Les fichiers de ce portefeuille sont corrompus et ne peuvent pas être ouverts. Veuillez consulter la phrase de graines, sauver et restaurer le portefeuille.\n\nSi la valeur est vide, la graine n'a pas pu être correctement récupérée.",
|
"corrupted_seed_notice": "Les fichiers de ce portefeuille sont corrompus et ne peuvent pas être ouverts. Veuillez consulter la phrase de graines, sauver et restaurer le portefeuille.\n\nSi la valeur est vide, la graine n'a pas pu être correctement récupérée.",
|
||||||
"countries": "Pays",
|
"countries": "Pays",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
"paste": "Coller",
|
"paste": "Coller",
|
||||||
"pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.",
|
"pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.",
|
||||||
|
"payjoin_details": "Payjoin détails",
|
||||||
|
"payjoin_enabled": "Payjoin activé",
|
||||||
|
"payjoin_request_awaiting_tx": "En attente de transaction",
|
||||||
|
"payjoin_request_in_progress": "En cours",
|
||||||
"payment_id": "ID de Paiement : ",
|
"payment_id": "ID de Paiement : ",
|
||||||
"payment_was_received": "Votre paiement a été reçu.",
|
"payment_was_received": "Votre paiement a été reçu.",
|
||||||
"pending": " (en attente)",
|
"pending": " (en attente)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Envoyer du portefeuille externe",
|
"send_from_external_wallet": "Envoyer du portefeuille externe",
|
||||||
"send_name": "Nom",
|
"send_name": "Nom",
|
||||||
"send_new": "Nouveau",
|
"send_new": "Nouveau",
|
||||||
|
"send_payjoin": "Envoyer Payjoin",
|
||||||
"send_payment_id": "ID de paiement (optionnel)",
|
"send_payment_id": "ID de paiement (optionnel)",
|
||||||
"send_priority": "Actuellement les frais sont positionnés à la priorité ${transactionPriority}.\nLa priorité de la transaction peut être modifiée dans les réglages",
|
"send_priority": "Actuellement les frais sont positionnés à la priorité ${transactionPriority}.\nLa priorité de la transaction peut être modifiée dans les réglages",
|
||||||
"send_sending": "Envoi...",
|
"send_sending": "Envoi...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kwafi",
|
"copy": "Kwafi",
|
||||||
"copy_address": "Kwafi Adireshin",
|
"copy_address": "Kwafi Adireshin",
|
||||||
"copy_id": "Kwafi ID",
|
"copy_id": "Kwafi ID",
|
||||||
|
"copy_payjoin_url": "Kwafa Payjoin url",
|
||||||
"copyWalletConnectLink": "Kwafi hanyar haɗin WalletConnect daga dApp kuma liƙa a nan",
|
"copyWalletConnectLink": "Kwafi hanyar haɗin WalletConnect daga dApp kuma liƙa a nan",
|
||||||
"corrupted_seed_notice": "Fayilolin don wannan walat ɗin sun lalata kuma ba za a iya buɗe su ba. Da fatan za a duba kalmar iri, adana shi, da dawo da walat.\n\nIdan darajar ta kasance fanko, to sai zuriyar da ba ta iya murmurewa daidai ba.",
|
"corrupted_seed_notice": "Fayilolin don wannan walat ɗin sun lalata kuma ba za a iya buɗe su ba. Da fatan za a duba kalmar iri, adana shi, da dawo da walat.\n\nIdan darajar ta kasance fanko, to sai zuriyar da ba ta iya murmurewa daidai ba.",
|
||||||
"countries": "Kasashe",
|
"countries": "Kasashe",
|
||||||
|
@ -548,6 +549,10 @@
|
||||||
"password": "Kalmar wucewa",
|
"password": "Kalmar wucewa",
|
||||||
"paste": "Manna",
|
"paste": "Manna",
|
||||||
"pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.",
|
"pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.",
|
||||||
|
"payjoin_details": "Payjoin LIT LOCEC LOcciya",
|
||||||
|
"payjoin_enabled": "Payjoin An kunna",
|
||||||
|
"payjoin_request_awaiting_tx": "Jiran ma'amala",
|
||||||
|
"payjoin_request_in_progress": "Ana kai",
|
||||||
"payment_id": "ID na biyan kuɗi:",
|
"payment_id": "ID na biyan kuɗi:",
|
||||||
"payment_was_received": "An karɓi kuɗin ku.",
|
"payment_was_received": "An karɓi kuɗin ku.",
|
||||||
"pending": "(pending)",
|
"pending": "(pending)",
|
||||||
|
@ -737,6 +742,7 @@
|
||||||
"send_from_external_wallet": "Aika daga walat na waje",
|
"send_from_external_wallet": "Aika daga walat na waje",
|
||||||
"send_name": "Sunan",
|
"send_name": "Sunan",
|
||||||
"send_new": "Sabon",
|
"send_new": "Sabon",
|
||||||
|
"send_payjoin": "Aika Payjoin",
|
||||||
"send_payment_id": "ID na biyan kuɗi (optional)",
|
"send_payment_id": "ID na biyan kuɗi (optional)",
|
||||||
"send_priority": "Yanzu haka fee yana set a ${transactionPriority} fifiko.\nAna iya daidaita fifikon ciniki a cikin saitunan",
|
"send_priority": "Yanzu haka fee yana set a ${transactionPriority} fifiko.\nAna iya daidaita fifikon ciniki a cikin saitunan",
|
||||||
"send_sending": "Aika...",
|
"send_sending": "Aika...",
|
||||||
|
@ -975,6 +981,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 Payjoin",
|
||||||
"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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "प्रतिलिपि",
|
"copy": "प्रतिलिपि",
|
||||||
"copy_address": "पता कॉपी करें",
|
"copy_address": "पता कॉपी करें",
|
||||||
"copy_id": "प्रतिलिपि ID",
|
"copy_id": "प्रतिलिपि ID",
|
||||||
|
"copy_payjoin_url": "कॉपी Payjoin url",
|
||||||
"copyWalletConnectLink": "dApp से वॉलेटकनेक्ट लिंक को कॉपी करें और यहां पेस्ट करें",
|
"copyWalletConnectLink": "dApp से वॉलेटकनेक्ट लिंक को कॉपी करें और यहां पेस्ट करें",
|
||||||
"corrupted_seed_notice": "इस वॉलेट की फाइलें दूषित हैं और उन्हें खोलने में असमर्थ हैं। कृपया बीज वाक्यांश देखें, इसे बचाएं, और बटुए को पुनर्स्थापित करें।\n\nयदि मूल्य खाली है, तो बीज सही ढंग से पुनर्प्राप्त करने में असमर्थ था।",
|
"corrupted_seed_notice": "इस वॉलेट की फाइलें दूषित हैं और उन्हें खोलने में असमर्थ हैं। कृपया बीज वाक्यांश देखें, इसे बचाएं, और बटुए को पुनर्स्थापित करें।\n\nयदि मूल्य खाली है, तो बीज सही ढंग से पुनर्प्राप्त करने में असमर्थ था।",
|
||||||
"countries": "देशों",
|
"countries": "देशों",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "पारण शब्द",
|
"password": "पारण शब्द",
|
||||||
"paste": "पेस्ट करें",
|
"paste": "पेस्ट करें",
|
||||||
"pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
|
"pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
|
||||||
|
"payjoin_details": "Payjoin विवरण",
|
||||||
|
"payjoin_enabled": "Payjoin सक्षम",
|
||||||
|
"payjoin_request_awaiting_tx": "लेन -देन का इंतजार",
|
||||||
|
"payjoin_request_in_progress": "प्रगति पर है",
|
||||||
"payment_id": "भुगतान ID: ",
|
"payment_id": "भुगतान ID: ",
|
||||||
"Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
|
"Payment_was_received": "आपका भुगतान प्राप्त हो गया था।",
|
||||||
"payment_was_received": "आपका भुगतान प्राप्त हुआ था।",
|
"payment_was_received": "आपका भुगतान प्राप्त हुआ था।",
|
||||||
|
@ -737,6 +742,7 @@
|
||||||
"send_from_external_wallet": "बाहरी बटुए से भेजें",
|
"send_from_external_wallet": "बाहरी बटुए से भेजें",
|
||||||
"send_name": "नाम",
|
"send_name": "नाम",
|
||||||
"send_new": "नया",
|
"send_new": "नया",
|
||||||
|
"send_payjoin": "भेजना Payjoin",
|
||||||
"send_payment_id": "भुगतान ID (ऐच्छिक)",
|
"send_payment_id": "भुगतान ID (ऐच्छिक)",
|
||||||
"send_priority": "वर्तमान में शुल्क निर्धारित है ${transactionPriority} प्राथमिकता.\nलेन-देन की प्राथमिकता को सेटिंग्स में समायोजित किया जा सकता है",
|
"send_priority": "वर्तमान में शुल्क निर्धारित है ${transactionPriority} प्राथमिकता.\nलेन-देन की प्राथमिकता को सेटिंग्स में समायोजित किया जा सकता है",
|
||||||
"send_sending": "भेजना...",
|
"send_sending": "भेजना...",
|
||||||
|
@ -975,6 +981,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": "टेस्टनेट का उपयोग करें",
|
"use_testnet": "टेस्टनेट का उपयोग करें",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kopiraj",
|
"copy": "Kopiraj",
|
||||||
"copy_address": "Kopiraj adresu",
|
"copy_address": "Kopiraj adresu",
|
||||||
"copy_id": "Kopirati ID",
|
"copy_id": "Kopirati ID",
|
||||||
|
"copy_payjoin_url": "Kopirajte Payjoin url",
|
||||||
"copyWalletConnectLink": "Kopirajte vezu WalletConnect iz dApp-a i zalijepite je ovdje",
|
"copyWalletConnectLink": "Kopirajte vezu WalletConnect iz dApp-a i zalijepite je ovdje",
|
||||||
"corrupted_seed_notice": "Datoteke za ovaj novčanik su oštećene i nisu u mogućnosti otvoriti. Molimo pogledajte sjemensku frazu, spremite je i vratite novčanik.\n\nAko je vrijednost prazna, tada sjeme nije bilo u stanju ispravno oporaviti.",
|
"corrupted_seed_notice": "Datoteke za ovaj novčanik su oštećene i nisu u mogućnosti otvoriti. Molimo pogledajte sjemensku frazu, spremite je i vratite novčanik.\n\nAko je vrijednost prazna, tada sjeme nije bilo u stanju ispravno oporaviti.",
|
||||||
"countries": "Zemalja",
|
"countries": "Zemalja",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Lozinka",
|
"password": "Lozinka",
|
||||||
"paste": "Zalijepi",
|
"paste": "Zalijepi",
|
||||||
"pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.",
|
"pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.",
|
||||||
|
"payjoin_details": "Payjoin Pojedinosti",
|
||||||
|
"payjoin_enabled": "Payjoin Omogućeno",
|
||||||
|
"payjoin_request_awaiting_tx": "Čekajući transakciju",
|
||||||
|
"payjoin_request_in_progress": "U toku",
|
||||||
"payment_id": "ID plaćanja: ",
|
"payment_id": "ID plaćanja: ",
|
||||||
"payment_was_received": "Vaša uplata je primljena.",
|
"payment_was_received": "Vaša uplata je primljena.",
|
||||||
"pending": " (u tijeku)",
|
"pending": " (u tijeku)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Pošaljite iz vanjskog novčanika",
|
"send_from_external_wallet": "Pošaljite iz vanjskog novčanika",
|
||||||
"send_name": "Ime",
|
"send_name": "Ime",
|
||||||
"send_new": "Novi",
|
"send_new": "Novi",
|
||||||
|
"send_payjoin": "Pošaljite Payjoin",
|
||||||
"send_payment_id": "ID plaćanja (nije obvezno)",
|
"send_payment_id": "ID plaćanja (nije obvezno)",
|
||||||
"send_priority": "Trenutno se naknada nalazi na ${transactionPriority} mjestu prioriteta.\nPrioritet transakcije moguće je prilagoditi u postavkama",
|
"send_priority": "Trenutno se naknada nalazi na ${transactionPriority} mjestu prioriteta.\nPrioritet transakcije moguće je prilagoditi u postavkama",
|
||||||
"send_sending": "Slanje...",
|
"send_sending": "Slanje...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Պատճենել",
|
"copy": "Պատճենել",
|
||||||
"copy_address": "Պատճենել հասցեն",
|
"copy_address": "Պատճենել հասցեն",
|
||||||
"copy_id": "Պատճենել ID",
|
"copy_id": "Պատճենել ID",
|
||||||
|
"copy_payjoin_url": "Պատճենել Payjoin url",
|
||||||
"copyWalletConnectLink": "Պատճենել WalletConnect հղումը dApp-ից և տեղադրել այստեղ",
|
"copyWalletConnectLink": "Պատճենել WalletConnect հղումը dApp-ից և տեղադրել այստեղ",
|
||||||
"corrupted_seed_notice": "Այս դրամապանակի համար ֆայլերը կոռումպացված են եւ չեն կարողանում բացվել: Խնդրում ենք դիտել սերմերի արտահայտությունը, պահպանել այն եւ վերականգնել դրամապանակը:\n\nԵթե արժեքը դատարկ է, ապա սերմը չկարողացավ ճիշտ վերականգնվել:",
|
"corrupted_seed_notice": "Այս դրամապանակի համար ֆայլերը կոռումպացված են եւ չեն կարողանում բացվել: Խնդրում ենք դիտել սերմերի արտահայտությունը, պահպանել այն եւ վերականգնել դրամապանակը:\n\nԵթե արժեքը դատարկ է, ապա սերմը չկարողացավ ճիշտ վերականգնվել:",
|
||||||
"countries": "Երկրներ",
|
"countries": "Երկրներ",
|
||||||
|
@ -545,6 +546,10 @@
|
||||||
"password": "Գաղտնաբառ",
|
"password": "Գաղտնաբառ",
|
||||||
"paste": "Տեղադրել",
|
"paste": "Տեղադրել",
|
||||||
"pause_wallet_creation": "Հնարավորություն ստեղծել Haven Դրամապանակ ընթացիկ դադարեցված է",
|
"pause_wallet_creation": "Հնարավորություն ստեղծել Haven Դրամապանակ ընթացիկ դադարեցված է",
|
||||||
|
"payjoin_details": "Payjoin Մանրամասն",
|
||||||
|
"payjoin_enabled": "Payjoin միացված",
|
||||||
|
"payjoin_request_awaiting_tx": "Սպասում է գործարքին",
|
||||||
|
"payjoin_request_in_progress": "Ընթացքի մեջ",
|
||||||
"payment_id": "Վճարման հերթական համար",
|
"payment_id": "Վճարման հերթական համար",
|
||||||
"payment_was_received": "Վճարումը ստացված է",
|
"payment_was_received": "Վճարումը ստացված է",
|
||||||
"pending": " (մշակվում է)",
|
"pending": " (մշակվում է)",
|
||||||
|
@ -733,6 +738,7 @@
|
||||||
"send_from_external_wallet": "Ուղարկել արտաքին դրամապանակից",
|
"send_from_external_wallet": "Ուղարկել արտաքին դրամապանակից",
|
||||||
"send_name": "Անվանում",
|
"send_name": "Անվանում",
|
||||||
"send_new": "Նոր",
|
"send_new": "Նոր",
|
||||||
|
"send_payjoin": "Ուղարկել Payjoin",
|
||||||
"send_payment_id": "Վճարման ID (կամավոր)",
|
"send_payment_id": "Վճարման ID (կամավոր)",
|
||||||
"send_priority": "Ներկայումս վարձը սահմանված է ${transactionPriority} առաջնահերթությամբ։ Գործարքի առաջնահերթությունը կարող է կարգավորվել կարգավորումներում",
|
"send_priority": "Ներկայումս վարձը սահմանված է ${transactionPriority} առաջնահերթությամբ։ Գործարքի առաջնահերթությունը կարող է կարգավորվել կարգավորումներում",
|
||||||
"send_sending": "Ուղարկվում է...",
|
"send_sending": "Ուղարկվում է...",
|
||||||
|
@ -971,6 +977,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Salin",
|
"copy": "Salin",
|
||||||
"copy_address": "Salin Alamat",
|
"copy_address": "Salin Alamat",
|
||||||
"copy_id": "Salin ID",
|
"copy_id": "Salin ID",
|
||||||
|
"copy_payjoin_url": "Salin Payjoin url",
|
||||||
"copyWalletConnectLink": "Salin tautan WalletConnect dari dApp dan tempel di sini",
|
"copyWalletConnectLink": "Salin tautan WalletConnect dari dApp dan tempel di sini",
|
||||||
"corrupted_seed_notice": "File untuk dompet ini rusak dan tidak dapat dibuka. Silakan lihat frasa benih, simpan, dan kembalikan dompet.\n\nJika nilainya kosong, maka benih tidak dapat dipulihkan dengan benar.",
|
"corrupted_seed_notice": "File untuk dompet ini rusak dan tidak dapat dibuka. Silakan lihat frasa benih, simpan, dan kembalikan dompet.\n\nJika nilainya kosong, maka benih tidak dapat dipulihkan dengan benar.",
|
||||||
"countries": "Negara",
|
"countries": "Negara",
|
||||||
|
@ -548,6 +549,10 @@
|
||||||
"password": "Kata Sandi",
|
"password": "Kata Sandi",
|
||||||
"paste": "Tempel",
|
"paste": "Tempel",
|
||||||
"pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.",
|
"pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.",
|
||||||
|
"payjoin_details": "Payjoin detail",
|
||||||
|
"payjoin_enabled": "Payjoin diaktifkan",
|
||||||
|
"payjoin_request_awaiting_tx": "Menunggu transaksi",
|
||||||
|
"payjoin_request_in_progress": "Sedang berlangsung",
|
||||||
"payment_id": "ID Pembayaran: ",
|
"payment_id": "ID Pembayaran: ",
|
||||||
"payment_was_received": "Pembayaran Anda telah diterima.",
|
"payment_was_received": "Pembayaran Anda telah diterima.",
|
||||||
"pending": " (pending)",
|
"pending": " (pending)",
|
||||||
|
@ -738,6 +743,7 @@
|
||||||
"send_from_external_wallet": "Kirim dari dompet eksternal",
|
"send_from_external_wallet": "Kirim dari dompet eksternal",
|
||||||
"send_name": "Nama",
|
"send_name": "Nama",
|
||||||
"send_new": "Baru",
|
"send_new": "Baru",
|
||||||
|
"send_payjoin": "Mengirim Payjoin",
|
||||||
"send_payment_id": "ID Pembayaran (opsional)",
|
"send_payment_id": "ID Pembayaran (opsional)",
|
||||||
"send_priority": "Saat ini biaya diatur dengan prioritas ${transactionPriority}.\nPrioritas transaksi dapat diubah pada pengaturan",
|
"send_priority": "Saat ini biaya diatur dengan prioritas ${transactionPriority}.\nPrioritas transaksi dapat diubah pada pengaturan",
|
||||||
"send_sending": "Mengirim...",
|
"send_sending": "Mengirim...",
|
||||||
|
@ -976,6 +982,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Copia",
|
"copy": "Copia",
|
||||||
"copy_address": "Copia Indirizzo",
|
"copy_address": "Copia Indirizzo",
|
||||||
"copy_id": "Copia ID",
|
"copy_id": "Copia ID",
|
||||||
|
"copy_payjoin_url": "Copia Payjoin url",
|
||||||
"copyWalletConnectLink": "Copia il collegamento WalletConnect dalla dApp e incollalo qui",
|
"copyWalletConnectLink": "Copia il collegamento WalletConnect dalla dApp e incollalo qui",
|
||||||
"corrupted_seed_notice": "I file per questo portafoglio sono corrotti e non è possibile aprirli. Visualizza la frase del seme, salvala e ripristina il portafoglio.\n\nSe il valore è vuoto, non è stato possibile recuperare correttamente il seme.",
|
"corrupted_seed_notice": "I file per questo portafoglio sono corrotti e non è possibile aprirli. Visualizza la frase del seme, salvala e ripristina il portafoglio.\n\nSe il valore è vuoto, non è stato possibile recuperare correttamente il seme.",
|
||||||
"countries": "Paesi",
|
"countries": "Paesi",
|
||||||
|
@ -547,6 +548,10 @@
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"paste": "Incolla",
|
"paste": "Incolla",
|
||||||
"pause_wallet_creation": "La possibilità di creare Wallet Haven è attualmente sospesa.",
|
"pause_wallet_creation": "La possibilità di creare Wallet Haven è attualmente sospesa.",
|
||||||
|
"payjoin_details": "Payjoin dettagli",
|
||||||
|
"payjoin_enabled": "Payjoin abilitato",
|
||||||
|
"payjoin_request_awaiting_tx": "In attesa di transazione",
|
||||||
|
"payjoin_request_in_progress": "In corso",
|
||||||
"payment_id": "ID Pagamento: ",
|
"payment_id": "ID Pagamento: ",
|
||||||
"payment_was_received": "Il tuo pagamento è stato ricevuto.",
|
"payment_was_received": "Il tuo pagamento è stato ricevuto.",
|
||||||
"pending": " (non confermati)",
|
"pending": " (non confermati)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "Invia dal portafoglio esterno",
|
"send_from_external_wallet": "Invia dal portafoglio esterno",
|
||||||
"send_name": "Nome",
|
"send_name": "Nome",
|
||||||
"send_new": "Nuovo",
|
"send_new": "Nuovo",
|
||||||
|
"send_payjoin": "Inviare Payjoin",
|
||||||
"send_payment_id": "ID Pagamento (opzionale)",
|
"send_payment_id": "ID Pagamento (opzionale)",
|
||||||
"send_priority": "Attualmente la commissione è impostata a priorità ${transactionPriority} .\nLa priorità della transazione può essere modificata nelle impostazioni",
|
"send_priority": "Attualmente la commissione è impostata a priorità ${transactionPriority} .\nLa priorità della transazione può essere modificata nelle impostazioni",
|
||||||
"send_sending": "Invio...",
|
"send_sending": "Invio...",
|
||||||
|
@ -974,6 +980,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "コピー",
|
"copy": "コピー",
|
||||||
"copy_address": "住所をコピー",
|
"copy_address": "住所をコピー",
|
||||||
"copy_id": "IDをコピー",
|
"copy_id": "IDをコピー",
|
||||||
|
"copy_payjoin_url": "Payjoin urlをコピーします",
|
||||||
"copyWalletConnectLink": "dApp から WalletConnect リンクをコピーし、ここに貼り付けます",
|
"copyWalletConnectLink": "dApp から WalletConnect リンクをコピーし、ここに貼り付けます",
|
||||||
"corrupted_seed_notice": "このウォレットのファイルは破損しており、開くことができません。シードフレーズを表示し、保存し、財布を復元してください。\n\n値が空の場合、種子を正しく回復することができませんでした。",
|
"corrupted_seed_notice": "このウォレットのファイルは破損しており、開くことができません。シードフレーズを表示し、保存し、財布を復元してください。\n\n値が空の場合、種子を正しく回復することができませんでした。",
|
||||||
"countries": "国",
|
"countries": "国",
|
||||||
|
@ -547,6 +548,10 @@
|
||||||
"password": "パスワード",
|
"password": "パスワード",
|
||||||
"paste": "ペースト",
|
"paste": "ペースト",
|
||||||
"pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。",
|
"pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。",
|
||||||
|
"payjoin_details": "Payjoin 詳細",
|
||||||
|
"payjoin_enabled": "Payjoin enabled",
|
||||||
|
"payjoin_request_awaiting_tx": "トランザクションを待っています",
|
||||||
|
"payjoin_request_in_progress": "進行中",
|
||||||
"payment_id": "支払いID: ",
|
"payment_id": "支払いID: ",
|
||||||
"payment_was_received": "お支払いを受け取りました。",
|
"payment_was_received": "お支払いを受け取りました。",
|
||||||
"pending": " (保留中)",
|
"pending": " (保留中)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "外部ウォレットから送信します",
|
"send_from_external_wallet": "外部ウォレットから送信します",
|
||||||
"send_name": "名前",
|
"send_name": "名前",
|
||||||
"send_new": "新着",
|
"send_new": "新着",
|
||||||
|
"send_payjoin": "送信 Payjoin",
|
||||||
"send_payment_id": "支払いID (オプショナル)",
|
"send_payment_id": "支払いID (オプショナル)",
|
||||||
"send_priority": "現在、料金は ${transactionPriority} 優先度.\nトランザクションの優先度は設定で調整できます",
|
"send_priority": "現在、料金は ${transactionPriority} 優先度.\nトランザクションの優先度は設定で調整できます",
|
||||||
"send_sending": "送信...",
|
"send_sending": "送信...",
|
||||||
|
@ -974,6 +980,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": "テストネットを使用します",
|
"use_testnet": "テストネットを使用します",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "복사",
|
"copy": "복사",
|
||||||
"copy_address": "주소 복사",
|
"copy_address": "주소 복사",
|
||||||
"copy_id": "ID 복사",
|
"copy_id": "ID 복사",
|
||||||
|
"copy_payjoin_url": "Payjoin url을 복사하십시오",
|
||||||
"copyWalletConnectLink": "dApp에서 WalletConnect 링크를 복사하여 여기에 붙여넣으세요",
|
"copyWalletConnectLink": "dApp에서 WalletConnect 링크를 복사하여 여기에 붙여넣으세요",
|
||||||
"corrupted_seed_notice": "이 지갑의 파일이 손상되어 열 수 없습니다. 시드 구문을 보고 저장한 다음 지갑을 복구하세요.\n\n값이 비어 있으면 시드를 올바르게 복구할 수 없었습니다.",
|
"corrupted_seed_notice": "이 지갑의 파일이 손상되어 열 수 없습니다. 시드 구문을 보고 저장한 다음 지갑을 복구하세요.\n\n값이 비어 있으면 시드를 올바르게 복구할 수 없었습니다.",
|
||||||
"countries": "국가",
|
"countries": "국가",
|
||||||
|
@ -547,6 +548,10 @@
|
||||||
"password": "비밀번호",
|
"password": "비밀번호",
|
||||||
"paste": "붙여넣기",
|
"paste": "붙여넣기",
|
||||||
"pause_wallet_creation": "현재 Haven 지갑 생성 기능이 일시 중지되었습니다.",
|
"pause_wallet_creation": "현재 Haven 지갑 생성 기능이 일시 중지되었습니다.",
|
||||||
|
"payjoin_details": "Payjoin 세부 정보",
|
||||||
|
"payjoin_enabled": "Payjoin enabled",
|
||||||
|
"payjoin_request_awaiting_tx": "거래를 기다리고 있습니다",
|
||||||
|
"payjoin_request_in_progress": "진행 중",
|
||||||
"payment_id": "결제 ID: ",
|
"payment_id": "결제 ID: ",
|
||||||
"payment_was_received": "결제가 접수되었습니다.",
|
"payment_was_received": "결제가 접수되었습니다.",
|
||||||
"pending": " (대기 중)",
|
"pending": " (대기 중)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "외부 지갑에서 보내기",
|
"send_from_external_wallet": "외부 지갑에서 보내기",
|
||||||
"send_name": "이름",
|
"send_name": "이름",
|
||||||
"send_new": "새로 만들기",
|
"send_new": "새로 만들기",
|
||||||
|
"send_payjoin": "보내다 Payjoin",
|
||||||
"send_payment_id": "결제 ID (선택 사항)",
|
"send_payment_id": "결제 ID (선택 사항)",
|
||||||
"send_priority": "현재 수수료는 ${transactionPriority} 우선순위로 설정되어 있습니다.\n트랜잭션 우선순위는 설정에서 조정할 수 있습니다.",
|
"send_priority": "현재 수수료는 ${transactionPriority} 우선순위로 설정되어 있습니다.\n트랜잭션 우선순위는 설정에서 조정할 수 있습니다.",
|
||||||
"send_sending": "보내는 중...",
|
"send_sending": "보내는 중...",
|
||||||
|
@ -974,6 +980,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": "테스트넷 사용",
|
"use_testnet": "테스트넷 사용",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "ကော်ပီ",
|
"copy": "ကော်ပီ",
|
||||||
"copy_address": "လိပ်စာကို ကူးယူပါ။",
|
"copy_address": "လိပ်စာကို ကူးယူပါ။",
|
||||||
"copy_id": "ID ကူးယူပါ။",
|
"copy_id": "ID ကူးယူပါ။",
|
||||||
|
"copy_payjoin_url": "Payjoin URL ကိုကူးယူပါ",
|
||||||
"copyWalletConnectLink": "dApp မှ WalletConnect လင့်ခ်ကို ကူးယူပြီး ဤနေရာတွင် ကူးထည့်ပါ။",
|
"copyWalletConnectLink": "dApp မှ WalletConnect လင့်ခ်ကို ကူးယူပြီး ဤနေရာတွင် ကူးထည့်ပါ။",
|
||||||
"corrupted_seed_notice": "ဤပိုက်ဆံအိတ်အတွက်ဖိုင်များသည်အကျင့်ပျက်ခြစားမှုများနှင့်မဖွင့်နိုင်ပါ။ ကျေးဇူးပြု. မျိုးစေ့များကိုကြည့်ပါ, ၎င်းကိုသိမ်းဆည်းပါ, ပိုက်ဆံအိတ်ကိုပြန်ယူပါ။\n\nအကယ်. တန်ဖိုးသည်အချည်းနှီးဖြစ်ပါကမျိုးစေ့ကိုမှန်ကန်စွာပြန်လည်ကောင်းမွန်မရရှိနိုင်ပါ။",
|
"corrupted_seed_notice": "ဤပိုက်ဆံအိတ်အတွက်ဖိုင်များသည်အကျင့်ပျက်ခြစားမှုများနှင့်မဖွင့်နိုင်ပါ။ ကျေးဇူးပြု. မျိုးစေ့များကိုကြည့်ပါ, ၎င်းကိုသိမ်းဆည်းပါ, ပိုက်ဆံအိတ်ကိုပြန်ယူပါ။\n\nအကယ်. တန်ဖိုးသည်အချည်းနှီးဖြစ်ပါကမျိုးစေ့ကိုမှန်ကန်စွာပြန်လည်ကောင်းမွန်မရရှိနိုင်ပါ။",
|
||||||
"countries": "နိုင်ငံများ",
|
"countries": "နိုင်ငံများ",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "စကားဝှက်",
|
"password": "စကားဝှက်",
|
||||||
"paste": "ငါးပိ",
|
"paste": "ငါးပိ",
|
||||||
"pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
|
"pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
|
||||||
|
"payjoin_details": "Payjoin အသေးစိတ်အချက်အလက်များ %% အသေးစိတ်အချက်အလက်များ",
|
||||||
|
"payjoin_enabled": "Payjoin enabled",
|
||||||
|
"payjoin_request_awaiting_tx": "ငွေပေးငွေယူစောင့်ဆိုင်း",
|
||||||
|
"payjoin_request_in_progress": "ဆောင်ရွက်ဆဲဖြစ်သည်",
|
||||||
"payment_id": "ငွေပေးချေမှု ID:",
|
"payment_id": "ငွေပေးချေမှု ID:",
|
||||||
"payment_was_received": "သင့်ငွေပေးချေမှုကို လက်ခံရရှိခဲ့သည်။",
|
"payment_was_received": "သင့်ငွေပေးချေမှုကို လက်ခံရရှိခဲ့သည်။",
|
||||||
"pending": " (ဆိုင်းငံ့)",
|
"pending": " (ဆိုင်းငံ့)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "ပြင်ပပိုက်ဆံအိတ်မှပေးပို့ပါ",
|
"send_from_external_wallet": "ပြင်ပပိုက်ဆံအိတ်မှပေးပို့ပါ",
|
||||||
"send_name": "နာမည်",
|
"send_name": "နာမည်",
|
||||||
"send_new": "အသစ်",
|
"send_new": "အသစ်",
|
||||||
|
"send_payjoin": "Payjoin ကိုပို့ပါ",
|
||||||
"send_payment_id": "ငွေပေးချေမှု ID (ချန်လှပ်ထား)",
|
"send_payment_id": "ငွေပေးချေမှု ID (ချန်လှပ်ထား)",
|
||||||
"send_priority": "လောလောဆယ်အခကြေးငွေကို ${transactionPriority} ဦးစားပေးတွင် သတ်မှတ်ထားပါသည်။\nငွေပေးငွေယူဦးစားပေးကို ဆက်တင်များတွင် ချိန်ညှိနိုင်ပါသည်။",
|
"send_priority": "လောလောဆယ်အခကြေးငွေကို ${transactionPriority} ဦးစားပေးတွင် သတ်မှတ်ထားပါသည်။\nငွေပေးငွေယူဦးစားပေးကို ဆက်တင်များတွင် ချိန်ညှိနိုင်ပါသည်။",
|
||||||
"send_sending": "ပို့နေသည်...",
|
"send_sending": "ပို့နေသည်...",
|
||||||
|
@ -973,6 +979,7 @@
|
||||||
"use": "သို့ပြောင်းပါ။",
|
"use": "သို့ပြောင်းပါ။",
|
||||||
"use_card_info_three": "ဒစ်ဂျစ်တယ်ကတ်ကို အွန်လိုင်း သို့မဟုတ် ထိတွေ့မှုမဲ့ ငွေပေးချေမှုနည်းလမ်းများဖြင့် အသုံးပြုပါ။",
|
"use_card_info_three": "ဒစ်ဂျစ်တယ်ကတ်ကို အွန်လိုင်း သို့မဟုတ် ထိတွေ့မှုမဲ့ ငွေပေးချေမှုနည်းလမ်းများဖြင့် အသုံးပြုပါ။",
|
||||||
"use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
|
"use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
|
||||||
|
"use_payjoin": "Payjoin Chair ကိုအသုံးပြုပါ",
|
||||||
"use_ssl": "SSL ကိုသုံးပါ။",
|
"use_ssl": "SSL ကိုသုံးပါ။",
|
||||||
"use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။",
|
"use_suggested": "အကြံပြုထားသည်ကို အသုံးပြုပါ။",
|
||||||
"use_testnet": "testnet ကိုသုံးပါ",
|
"use_testnet": "testnet ကိုသုံးပါ",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kopiëren",
|
"copy": "Kopiëren",
|
||||||
"copy_address": "Adres kopiëren",
|
"copy_address": "Adres kopiëren",
|
||||||
"copy_id": "ID kopiëren",
|
"copy_id": "ID kopiëren",
|
||||||
|
"copy_payjoin_url": "Kopieer Payjoin url",
|
||||||
"copyWalletConnectLink": "Kopieer de WalletConnect-link van dApp en plak deze hier",
|
"copyWalletConnectLink": "Kopieer de WalletConnect-link van dApp en plak deze hier",
|
||||||
"corrupted_seed_notice": "De bestanden voor deze portemonnee zijn beschadigd en kunnen niet worden geopend. Bekijk de zaadzin, bewaar deze en herstel de portemonnee.\n\nAls de waarde leeg is, kon het zaad niet correct worden hersteld.",
|
"corrupted_seed_notice": "De bestanden voor deze portemonnee zijn beschadigd en kunnen niet worden geopend. Bekijk de zaadzin, bewaar deze en herstel de portemonnee.\n\nAls de waarde leeg is, kon het zaad niet correct worden hersteld.",
|
||||||
"countries": "Landen",
|
"countries": "Landen",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Wachtwoord",
|
"password": "Wachtwoord",
|
||||||
"paste": "Plakken",
|
"paste": "Plakken",
|
||||||
"pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.",
|
"pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.",
|
||||||
|
"payjoin_details": "Payjoin details",
|
||||||
|
"payjoin_enabled": "Payjoin ingeschakeld",
|
||||||
|
"payjoin_request_awaiting_tx": "In afwachting van transactie",
|
||||||
|
"payjoin_request_in_progress": "In uitvoering",
|
||||||
"payment_id": "Betaling ID: ",
|
"payment_id": "Betaling ID: ",
|
||||||
"payment_was_received": "Uw betaling is ontvangen.",
|
"payment_was_received": "Uw betaling is ontvangen.",
|
||||||
"pending": " (in afwachting)",
|
"pending": " (in afwachting)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Stuur vanuit een externe portemonnee",
|
"send_from_external_wallet": "Stuur vanuit een externe portemonnee",
|
||||||
"send_name": "Naam",
|
"send_name": "Naam",
|
||||||
"send_new": "Nieuw",
|
"send_new": "Nieuw",
|
||||||
|
"send_payjoin": "Versturen Payjoin",
|
||||||
"send_payment_id": "Betaling ID (facultatief)",
|
"send_payment_id": "Betaling ID (facultatief)",
|
||||||
"send_priority": "Momenteel is de vergoeding vastgesteld op ${transactionPriority} prioriteit.\nTransactieprioriteit kan worden aangepast in de instellingen",
|
"send_priority": "Momenteel is de vergoeding vastgesteld op ${transactionPriority} prioriteit.\nTransactieprioriteit kan worden aangepast in de instellingen",
|
||||||
"send_sending": "Bezig met verzenden...",
|
"send_sending": "Bezig met verzenden...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kopiuj",
|
"copy": "Kopiuj",
|
||||||
"copy_address": "Skopiuj adress",
|
"copy_address": "Skopiuj adress",
|
||||||
"copy_id": "skopiuj ID",
|
"copy_id": "skopiuj ID",
|
||||||
|
"copy_payjoin_url": "Skopiuj Payjoin url",
|
||||||
"copyWalletConnectLink": "Skopiuj link do WalletConnect z dApp i wklej tutaj",
|
"copyWalletConnectLink": "Skopiuj link do WalletConnect z dApp i wklej tutaj",
|
||||||
"corrupted_seed_notice": "Pliki dla tego portfela są uszkodzone i nie można ich otworzyć. Zobacz frazę seed, zapisz je i przywróć portfel.\n\nJeśli wartość jest pusta, frazy seed nie można było poprawnie odzyskać.",
|
"corrupted_seed_notice": "Pliki dla tego portfela są uszkodzone i nie można ich otworzyć. Zobacz frazę seed, zapisz je i przywróć portfel.\n\nJeśli wartość jest pusta, frazy seed nie można było poprawnie odzyskać.",
|
||||||
"countries": "Kraje",
|
"countries": "Kraje",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Hasło",
|
"password": "Hasło",
|
||||||
"paste": "Wklej",
|
"paste": "Wklej",
|
||||||
"pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.",
|
"pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.",
|
||||||
|
"payjoin_details": "Szczegóły Payjoin",
|
||||||
|
"payjoin_enabled": "Payjoin włączony",
|
||||||
|
"payjoin_request_awaiting_tx": "Oczekiwanie na transakcję",
|
||||||
|
"payjoin_request_in_progress": "W toku",
|
||||||
"payment_id": "ID Płatności: ",
|
"payment_id": "ID Płatności: ",
|
||||||
"payment_was_received": "Twoja płatność została otrzymana.",
|
"payment_was_received": "Twoja płatność została otrzymana.",
|
||||||
"pending": " (w oczekiwaniu)",
|
"pending": " (w oczekiwaniu)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Wyślij z portfela zewnętrznego",
|
"send_from_external_wallet": "Wyślij z portfela zewnętrznego",
|
||||||
"send_name": "Imię",
|
"send_name": "Imię",
|
||||||
"send_new": "Nowy",
|
"send_new": "Nowy",
|
||||||
|
"send_payjoin": "Wyślij Payjoin",
|
||||||
"send_payment_id": "Identyfikator płatności (opcjonalny)",
|
"send_payment_id": "Identyfikator płatności (opcjonalny)",
|
||||||
"send_priority": "Obecnie opłata ustalona jest na ${transactionPriority} priorytet.\nPriorytet transakcji można zmienić w ustawieniach",
|
"send_priority": "Obecnie opłata ustalona jest na ${transactionPriority} priorytet.\nPriorytet transakcji można zmienić w ustawieniach",
|
||||||
"send_sending": "Wysyłanie...",
|
"send_sending": "Wysyłanie...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copy_address": "Copiar endereço",
|
"copy_address": "Copiar endereço",
|
||||||
"copy_id": "Copiar ID",
|
"copy_id": "Copiar ID",
|
||||||
|
"copy_payjoin_url": "Copie Payjoin url",
|
||||||
"copyWalletConnectLink": "Copie o link WalletConnect do dApp e cole aqui",
|
"copyWalletConnectLink": "Copie o link WalletConnect do dApp e cole aqui",
|
||||||
"corrupted_seed_notice": "Os arquivos para esta carteira estão corrompidos e não podem ser abertos. Veja a frase das sementes, salve -a e restaure a carteira.\n\nSe o valor estiver vazio, a semente não pôde ser recuperada corretamente.",
|
"corrupted_seed_notice": "Os arquivos para esta carteira estão corrompidos e não podem ser abertos. Veja a frase das sementes, salve -a e restaure a carteira.\n\nSe o valor estiver vazio, a semente não pôde ser recuperada corretamente.",
|
||||||
"countries": "Países",
|
"countries": "Países",
|
||||||
|
@ -548,6 +549,10 @@
|
||||||
"password": "Senha",
|
"password": "Senha",
|
||||||
"paste": "Colar",
|
"paste": "Colar",
|
||||||
"pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.",
|
"pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.",
|
||||||
|
"payjoin_details": "Payjoin detalhes",
|
||||||
|
"payjoin_enabled": "Payjoin habilitado",
|
||||||
|
"payjoin_request_awaiting_tx": "Aguardando transação",
|
||||||
|
"payjoin_request_in_progress": "Em andamento",
|
||||||
"payment_id": "ID de pagamento: ",
|
"payment_id": "ID de pagamento: ",
|
||||||
"payment_was_received": "Seu pagamento foi recebido.",
|
"payment_was_received": "Seu pagamento foi recebido.",
|
||||||
"pending": " (pendente)",
|
"pending": " (pendente)",
|
||||||
|
@ -737,6 +742,7 @@
|
||||||
"send_from_external_wallet": "Enviar da carteira externa",
|
"send_from_external_wallet": "Enviar da carteira externa",
|
||||||
"send_name": "Nome",
|
"send_name": "Nome",
|
||||||
"send_new": "Novo",
|
"send_new": "Novo",
|
||||||
|
"send_payjoin": "Enviar Payjoin",
|
||||||
"send_payment_id": "ID de pagamento (opcional)",
|
"send_payment_id": "ID de pagamento (opcional)",
|
||||||
"send_priority": "Atualmente, a taxa está definida para a prioridade: ${transactionPriority}.\nA prioridade da transação pode ser ajustada nas configurações",
|
"send_priority": "Atualmente, a taxa está definida para a prioridade: ${transactionPriority}.\nA prioridade da transação pode ser ajustada nas configurações",
|
||||||
"send_sending": "Enviando...",
|
"send_sending": "Enviando...",
|
||||||
|
@ -975,6 +981,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Скопировать",
|
"copy": "Скопировать",
|
||||||
"copy_address": "Cкопировать адрес",
|
"copy_address": "Cкопировать адрес",
|
||||||
"copy_id": "Скопировать ID",
|
"copy_id": "Скопировать ID",
|
||||||
|
"copy_payjoin_url": "Копировать Payjoin url",
|
||||||
"copyWalletConnectLink": "Скопируйте ссылку WalletConnect из dApp и вставьте сюда.",
|
"copyWalletConnectLink": "Скопируйте ссылку WalletConnect из dApp и вставьте сюда.",
|
||||||
"corrupted_seed_notice": "Файлы для этого кошелька повреждены и не могут быть открыты. Пожалуйста, просмотрите семенную фразу, сохраните ее и восстановите кошелек.\n\nЕсли значение пустое, то семя не смог правильно восстановить.",
|
"corrupted_seed_notice": "Файлы для этого кошелька повреждены и не могут быть открыты. Пожалуйста, просмотрите семенную фразу, сохраните ее и восстановите кошелек.\n\nЕсли значение пустое, то семя не смог правильно восстановить.",
|
||||||
"countries": "Страны",
|
"countries": "Страны",
|
||||||
|
@ -547,6 +548,10 @@
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
"paste": "Вставить",
|
"paste": "Вставить",
|
||||||
"pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.",
|
"pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.",
|
||||||
|
"payjoin_details": "Payjoin подробности",
|
||||||
|
"payjoin_enabled": "Payjoin включено",
|
||||||
|
"payjoin_request_awaiting_tx": "В ожидании транзакции",
|
||||||
|
"payjoin_request_in_progress": "В ходе выполнения",
|
||||||
"payment_id": "ID платежа: ",
|
"payment_id": "ID платежа: ",
|
||||||
"payment_was_received": "Ваш платеж получен.",
|
"payment_was_received": "Ваш платеж получен.",
|
||||||
"pending": " (в ожидании)",
|
"pending": " (в ожидании)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "Отправить с внешнего кошелька",
|
"send_from_external_wallet": "Отправить с внешнего кошелька",
|
||||||
"send_name": "Имя",
|
"send_name": "Имя",
|
||||||
"send_new": "Новый",
|
"send_new": "Новый",
|
||||||
|
"send_payjoin": "Отправлять Payjoin",
|
||||||
"send_payment_id": "ID платежа (опционально)",
|
"send_payment_id": "ID платежа (опционально)",
|
||||||
"send_priority": "Комиссия установлена в зависимости от приоритета: ${transactionPriority}.\nПриоритет транзакции может быть изменён в настройках",
|
"send_priority": "Комиссия установлена в зависимости от приоритета: ${transactionPriority}.\nПриоритет транзакции может быть изменён в настройках",
|
||||||
"send_sending": "Отправка...",
|
"send_sending": "Отправка...",
|
||||||
|
@ -974,6 +980,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "คัดลอก",
|
"copy": "คัดลอก",
|
||||||
"copy_address": "คัดลอกที่อยู่",
|
"copy_address": "คัดลอกที่อยู่",
|
||||||
"copy_id": "คัดลอก ID",
|
"copy_id": "คัดลอก ID",
|
||||||
|
"copy_payjoin_url": "คัดลอก Payjoin url",
|
||||||
"copyWalletConnectLink": "คัดลอกลิงก์ WalletConnect จาก dApp แล้ววางที่นี่",
|
"copyWalletConnectLink": "คัดลอกลิงก์ WalletConnect จาก dApp แล้ววางที่นี่",
|
||||||
"corrupted_seed_notice": "ไฟล์สำหรับกระเป๋าเงินนี้เสียหายและไม่สามารถเปิดได้ โปรดดูวลีเมล็ดบันทึกและกู้คืนกระเป๋าเงิน\n\nหากค่าว่างเปล่าเมล็ดก็ไม่สามารถกู้คืนได้อย่างถูกต้อง",
|
"corrupted_seed_notice": "ไฟล์สำหรับกระเป๋าเงินนี้เสียหายและไม่สามารถเปิดได้ โปรดดูวลีเมล็ดบันทึกและกู้คืนกระเป๋าเงิน\n\nหากค่าว่างเปล่าเมล็ดก็ไม่สามารถกู้คืนได้อย่างถูกต้อง",
|
||||||
"countries": "ประเทศ",
|
"countries": "ประเทศ",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "รหัสผ่าน",
|
"password": "รหัสผ่าน",
|
||||||
"paste": "วาง",
|
"paste": "วาง",
|
||||||
"pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
|
"pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
|
||||||
|
"payjoin_details": "Payjoin รายละเอียด",
|
||||||
|
"payjoin_enabled": "Payjoin เปิดใช้งาน",
|
||||||
|
"payjoin_request_awaiting_tx": "รอธุรกรรม",
|
||||||
|
"payjoin_request_in_progress": "การดำเนินการ",
|
||||||
"payment_id": "ID การชำระเงิน: ",
|
"payment_id": "ID การชำระเงิน: ",
|
||||||
"payment_was_received": "การชำระเงินของคุณได้รับการรับทราบแล้ว",
|
"payment_was_received": "การชำระเงินของคุณได้รับการรับทราบแล้ว",
|
||||||
"pending": " (อยู่ระหว่างดำเนินการ)",
|
"pending": " (อยู่ระหว่างดำเนินการ)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "ส่งจากกระเป๋าเงินภายนอก",
|
"send_from_external_wallet": "ส่งจากกระเป๋าเงินภายนอก",
|
||||||
"send_name": "ชื่อ",
|
"send_name": "ชื่อ",
|
||||||
"send_new": "ใหม่",
|
"send_new": "ใหม่",
|
||||||
|
"send_payjoin": "ส่ง Payjoin",
|
||||||
"send_payment_id": "ID การชำระเงิน (ไม่จำเป็น)",
|
"send_payment_id": "ID การชำระเงิน (ไม่จำเป็น)",
|
||||||
"send_priority": "ในขณะนี้ค่าธรรมเนียมถูกตั้งค่าเป็นความสำคัญ ${transactionPriority} \nความสำคัญของธุรกรรมสามารถปรับได้ในการตั้งค่า",
|
"send_priority": "ในขณะนี้ค่าธรรมเนียมถูกตั้งค่าเป็นความสำคัญ ${transactionPriority} \nความสำคัญของธุรกรรมสามารถปรับได้ในการตั้งค่า",
|
||||||
"send_sending": "กำลังส่ง...",
|
"send_sending": "กำลังส่ง...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kopyahin",
|
"copy": "Kopyahin",
|
||||||
"copy_address": "Kopyahin ang Address",
|
"copy_address": "Kopyahin ang Address",
|
||||||
"copy_id": "Kopyahin ang ID",
|
"copy_id": "Kopyahin ang ID",
|
||||||
|
"copy_payjoin_url": "Kopyahin ang Payjoin url",
|
||||||
"copyWalletConnectLink": "Kopyahin ang link ng WalletConnect mula sa dApp at i-paste dito",
|
"copyWalletConnectLink": "Kopyahin ang link ng WalletConnect mula sa dApp at i-paste dito",
|
||||||
"corrupted_seed_notice": "Ang mga file para sa pitaka na ito ay nasira at hindi mabubuksan. Mangyaring tingnan ang parirala ng binhi, i -save ito, at ibalik ang pitaka.\n\nKung ang halaga ay walang laman, kung gayon ang binhi ay hindi ma -recover nang tama.",
|
"corrupted_seed_notice": "Ang mga file para sa pitaka na ito ay nasira at hindi mabubuksan. Mangyaring tingnan ang parirala ng binhi, i -save ito, at ibalik ang pitaka.\n\nKung ang halaga ay walang laman, kung gayon ang binhi ay hindi ma -recover nang tama.",
|
||||||
"countries": "Mga bansa",
|
"countries": "Mga bansa",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"paste": "I-paste",
|
"paste": "I-paste",
|
||||||
"pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.",
|
"pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.",
|
||||||
|
"payjoin_details": "Mga detalye ng Payjoin",
|
||||||
|
"payjoin_enabled": "Payjoin pinagana",
|
||||||
|
"payjoin_request_awaiting_tx": "Naghihintay ng transaksyon",
|
||||||
|
"payjoin_request_in_progress": "Sa pag -unlad",
|
||||||
"payment_id": "Payment ID: ",
|
"payment_id": "Payment ID: ",
|
||||||
"payment_was_received": "Natanggap ang iyong bayad.",
|
"payment_was_received": "Natanggap ang iyong bayad.",
|
||||||
"pending": "(hindi pa tapos)",
|
"pending": "(hindi pa tapos)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Magpadala mula sa panlabas na pitaka",
|
"send_from_external_wallet": "Magpadala mula sa panlabas na pitaka",
|
||||||
"send_name": "Pangalan",
|
"send_name": "Pangalan",
|
||||||
"send_new": "Bago",
|
"send_new": "Bago",
|
||||||
|
"send_payjoin": "Magpadala ng Payjoin",
|
||||||
"send_payment_id": "Payment ID (opsyonal)",
|
"send_payment_id": "Payment ID (opsyonal)",
|
||||||
"send_priority": "Kasalukuyang nakatakda ang fee sa ${transactionPriority} priyoridad.\n Ang priyoridad ng transaksyon ay maaaring isaayos sa mga setting",
|
"send_priority": "Kasalukuyang nakatakda ang fee sa ${transactionPriority} priyoridad.\n Ang priyoridad ng transaksyon ay maaaring isaayos sa mga setting",
|
||||||
"send_sending": "Nagpapadala...",
|
"send_sending": "Nagpapadala...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Kopyala",
|
"copy": "Kopyala",
|
||||||
"copy_address": "Adresi kopyala",
|
"copy_address": "Adresi kopyala",
|
||||||
"copy_id": "ID'yi kopyala",
|
"copy_id": "ID'yi kopyala",
|
||||||
|
"copy_payjoin_url": "Payjoin url kopyala",
|
||||||
"copyWalletConnectLink": "WalletConnect bağlantısını dApp'ten kopyalayıp buraya yapıştırın",
|
"copyWalletConnectLink": "WalletConnect bağlantısını dApp'ten kopyalayıp buraya yapıştırın",
|
||||||
"corrupted_seed_notice": "Bu cüzdanın dosyaları bozuk ve açılamıyor. Lütfen tohum ifadesini görüntüleyin, kaydedin ve cüzdanı geri yükleyin.\n\nDeğer boşsa, tohum doğru bir şekilde geri kazanılamadı.",
|
"corrupted_seed_notice": "Bu cüzdanın dosyaları bozuk ve açılamıyor. Lütfen tohum ifadesini görüntüleyin, kaydedin ve cüzdanı geri yükleyin.\n\nDeğer boşsa, tohum doğru bir şekilde geri kazanılamadı.",
|
||||||
"countries": "Ülkeler",
|
"countries": "Ülkeler",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Parola",
|
"password": "Parola",
|
||||||
"paste": "Yapıştır",
|
"paste": "Yapıştır",
|
||||||
"pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.",
|
"pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.",
|
||||||
|
"payjoin_details": "Payjoin detaylar",
|
||||||
|
"payjoin_enabled": "Payjoin etkinleştirilmiş",
|
||||||
|
"payjoin_request_awaiting_tx": "İşlem bekliyor",
|
||||||
|
"payjoin_request_in_progress": "Devam etmekte",
|
||||||
"payment_id": "Ödeme ID'si: ",
|
"payment_id": "Ödeme ID'si: ",
|
||||||
"payment_was_received": "Ödemeniz alındı.",
|
"payment_was_received": "Ödemeniz alındı.",
|
||||||
"pending": " (bekleyen)",
|
"pending": " (bekleyen)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "Harici cüzdandan gönder",
|
"send_from_external_wallet": "Harici cüzdandan gönder",
|
||||||
"send_name": "İsim",
|
"send_name": "İsim",
|
||||||
"send_new": "Yeni",
|
"send_new": "Yeni",
|
||||||
|
"send_payjoin": "Göndermek Payjoin",
|
||||||
"send_payment_id": "Ödeme ID'si (isteğe bağlı)",
|
"send_payment_id": "Ödeme ID'si (isteğe bağlı)",
|
||||||
"send_priority": "Şu anda ücret ${transactionPriority} önceliğine ayarlanmıştır.\nİşlem önceliği ayarlardan değiştirilebilir",
|
"send_priority": "Şu anda ücret ${transactionPriority} önceliğine ayarlanmıştır.\nİşlem önceliği ayarlardan değiştirilebilir",
|
||||||
"send_sending": "Gönderiliyor...",
|
"send_sending": "Gönderiliyor...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Скопіювати",
|
"copy": "Скопіювати",
|
||||||
"copy_address": "Cкопіювати адресу",
|
"copy_address": "Cкопіювати адресу",
|
||||||
"copy_id": "Скопіювати ID",
|
"copy_id": "Скопіювати ID",
|
||||||
|
"copy_payjoin_url": "Скопіюйте Payjoin url",
|
||||||
"copyWalletConnectLink": "Скопіюйте посилання WalletConnect із dApp і вставте сюди",
|
"copyWalletConnectLink": "Скопіюйте посилання WalletConnect із dApp і вставте сюди",
|
||||||
"corrupted_seed_notice": "Файли для цього гаманця пошкоджені і не можуть бути відкриті. Перегляньте насіннєву фразу, збережіть її та відновіть гаманець.\n\nЯкщо значення порожнє, то насіння не могло бути правильно відновленим.",
|
"corrupted_seed_notice": "Файли для цього гаманця пошкоджені і не можуть бути відкриті. Перегляньте насіннєву фразу, збережіть її та відновіть гаманець.\n\nЯкщо значення порожнє, то насіння не могло бути правильно відновленим.",
|
||||||
"countries": "Країни",
|
"countries": "Країни",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "Пароль",
|
"password": "Пароль",
|
||||||
"paste": "Вставити",
|
"paste": "Вставити",
|
||||||
"pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.",
|
"pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.",
|
||||||
|
"payjoin_details": "Payjoin деталей",
|
||||||
|
"payjoin_enabled": "Payjoin увімкнено",
|
||||||
|
"payjoin_request_awaiting_tx": "Чекає транзакції",
|
||||||
|
"payjoin_request_in_progress": "Триває",
|
||||||
"payment_id": "ID платежу: ",
|
"payment_id": "ID платежу: ",
|
||||||
"payment_was_received": "Ваш платіж отримано.",
|
"payment_was_received": "Ваш платіж отримано.",
|
||||||
"pending": " (в очікуванні)",
|
"pending": " (в очікуванні)",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "Надіслати із зовнішнього гаманця",
|
"send_from_external_wallet": "Надіслати із зовнішнього гаманця",
|
||||||
"send_name": "Ім'я",
|
"send_name": "Ім'я",
|
||||||
"send_new": "Новий",
|
"send_new": "Новий",
|
||||||
|
"send_payjoin": "Надіслати Payjoin",
|
||||||
"send_payment_id": "ID платежу (опційно)",
|
"send_payment_id": "ID платежу (опційно)",
|
||||||
"send_priority": "Комісія встановлена в залежності від пріоритету: ${transactionPriority}.\nПріоритет транзакції може бути змінений в налаштуваннях",
|
"send_priority": "Комісія встановлена в залежності від пріоритету: ${transactionPriority}.\nПріоритет транзакції може бути змінений в налаштуваннях",
|
||||||
"send_sending": "Відправлення...",
|
"send_sending": "Відправлення...",
|
||||||
|
@ -974,6 +980,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": "Використовуйте тестову мережу",
|
"use_testnet": "Використовуйте тестову мережу",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "کاپی",
|
"copy": "کاپی",
|
||||||
"copy_address": "ایڈریس کاپی کریں۔",
|
"copy_address": "ایڈریس کاپی کریں۔",
|
||||||
"copy_id": "کاپی ID",
|
"copy_id": "کاپی ID",
|
||||||
|
"copy_payjoin_url": "کاپی کریں Payjoin url",
|
||||||
"copyWalletConnectLink": "dApp ﮯﺳ WalletConnect ۔ﮟﯾﺮﮐ ﭧﺴﯿﭘ ﮞﺎﮩﯾ ﺭﻭﺍ ﮟﯾﺮﮐ ﯽﭘﺎﮐ ﻮﮐ ﮏﻨﻟ",
|
"copyWalletConnectLink": "dApp ﮯﺳ WalletConnect ۔ﮟﯾﺮﮐ ﭧﺴﯿﭘ ﮞﺎﮩﯾ ﺭﻭﺍ ﮟﯾﺮﮐ ﯽﭘﺎﮐ ﻮﮐ ﮏﻨﻟ",
|
||||||
"corrupted_seed_notice": "اس پرس کے لئے فائلیں خراب ہیں اور کھولنے سے قاصر ہیں۔ براہ کرم بیج کے فقرے کو دیکھیں ، اسے بچائیں ، اور بٹوے کو بحال کریں۔\n\nاگر قیمت خالی ہے ، تو بیج صحیح طور پر بازیافت کرنے سے قاصر تھا۔",
|
"corrupted_seed_notice": "اس پرس کے لئے فائلیں خراب ہیں اور کھولنے سے قاصر ہیں۔ براہ کرم بیج کے فقرے کو دیکھیں ، اسے بچائیں ، اور بٹوے کو بحال کریں۔\n\nاگر قیمت خالی ہے ، تو بیج صحیح طور پر بازیافت کرنے سے قاصر تھا۔",
|
||||||
"countries": "ممالک",
|
"countries": "ممالک",
|
||||||
|
@ -548,6 +549,10 @@
|
||||||
"password": "پاس ورڈ",
|
"password": "پاس ورڈ",
|
||||||
"paste": "چسپاں کریں۔",
|
"paste": "چسپاں کریں۔",
|
||||||
"pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ",
|
"pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ",
|
||||||
|
"payjoin_details": "Payjoin تفصیلات",
|
||||||
|
"payjoin_enabled": "Payjoin فعال",
|
||||||
|
"payjoin_request_awaiting_tx": "لین دین کے منتظر",
|
||||||
|
"payjoin_request_in_progress": "پیشرفت میں",
|
||||||
"payment_id": "ادائیگی کی شناخت:",
|
"payment_id": "ادائیگی کی شناخت:",
|
||||||
"payment_was_received": "آپ کی ادائیگی موصول ہو گئی۔",
|
"payment_was_received": "آپ کی ادائیگی موصول ہو گئی۔",
|
||||||
"pending": " (زیر التواء)",
|
"pending": " (زیر التواء)",
|
||||||
|
@ -737,6 +742,7 @@
|
||||||
"send_from_external_wallet": "بیرونی پرس سے بھیجیں",
|
"send_from_external_wallet": "بیرونی پرس سے بھیجیں",
|
||||||
"send_name": "نام",
|
"send_name": "نام",
|
||||||
"send_new": "نئی",
|
"send_new": "نئی",
|
||||||
|
"send_payjoin": "بھیجیں",
|
||||||
"send_payment_id": "ادائیگی کی شناخت (اختیاری)",
|
"send_payment_id": "ادائیگی کی شناخت (اختیاری)",
|
||||||
"send_priority": "فی الحال فیس ${transactionPriority} کی ترجیح پر سیٹ ہے۔\\nٹرانزیکشن کی ترجیح سیٹنگز میں ایڈجسٹ کی جا سکتی ہے۔",
|
"send_priority": "فی الحال فیس ${transactionPriority} کی ترجیح پر سیٹ ہے۔\\nٹرانزیکشن کی ترجیح سیٹنگز میں ایڈجسٹ کی جا سکتی ہے۔",
|
||||||
"send_sending": "بھیج رہا ہے...",
|
"send_sending": "بھیج رہا ہے...",
|
||||||
|
@ -975,6 +981,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": "ٹیسٹ نیٹ استعمال کریں",
|
"use_testnet": "ٹیسٹ نیٹ استعمال کریں",
|
||||||
|
|
|
@ -201,6 +201,7 @@
|
||||||
"copy": "Sao chép",
|
"copy": "Sao chép",
|
||||||
"copy_address": "Sao chép Địa chỉ",
|
"copy_address": "Sao chép Địa chỉ",
|
||||||
"copy_id": "Sao chép ID",
|
"copy_id": "Sao chép ID",
|
||||||
|
"copy_payjoin_url": "Sao chép Payjoin url",
|
||||||
"copyWalletConnectLink": "Sao chép liên kết WalletConnect từ dApp và dán vào đây",
|
"copyWalletConnectLink": "Sao chép liên kết WalletConnect từ dApp và dán vào đây",
|
||||||
"corrupted_seed_notice": "Các tệp cho ví này bị hỏng và không thể mở. Vui lòng xem cụm từ hạt giống, lưu nó và khôi phục ví.\n\nNếu giá trị trống, thì hạt giống không thể được phục hồi chính xác.",
|
"corrupted_seed_notice": "Các tệp cho ví này bị hỏng và không thể mở. Vui lòng xem cụm từ hạt giống, lưu nó và khôi phục ví.\n\nNếu giá trị trống, thì hạt giống không thể được phục hồi chính xác.",
|
||||||
"countries": "Quốc gia",
|
"countries": "Quốc gia",
|
||||||
|
@ -544,6 +545,10 @@
|
||||||
"password": "Mật khẩu",
|
"password": "Mật khẩu",
|
||||||
"paste": "Dán",
|
"paste": "Dán",
|
||||||
"pause_wallet_creation": "Khả năng tạo ví Haven hiện đang bị tạm dừng.",
|
"pause_wallet_creation": "Khả năng tạo ví Haven hiện đang bị tạm dừng.",
|
||||||
|
"payjoin_details": "Payjoin chi tiết",
|
||||||
|
"payjoin_enabled": "Payjoin Bật",
|
||||||
|
"payjoin_request_awaiting_tx": "Đang chờ giao dịch",
|
||||||
|
"payjoin_request_in_progress": "Trong tiến trình",
|
||||||
"payment_id": "ID thanh toán: ",
|
"payment_id": "ID thanh toán: ",
|
||||||
"payment_was_received": "Thanh toán của bạn đã được nhận.",
|
"payment_was_received": "Thanh toán của bạn đã được nhận.",
|
||||||
"pending": " (đang chờ)",
|
"pending": " (đang chờ)",
|
||||||
|
@ -732,6 +737,7 @@
|
||||||
"send_from_external_wallet": "Gửi từ ví bên ngoài",
|
"send_from_external_wallet": "Gửi từ ví bên ngoài",
|
||||||
"send_name": "Tên",
|
"send_name": "Tên",
|
||||||
"send_new": "Mới",
|
"send_new": "Mới",
|
||||||
|
"send_payjoin": "Gửi Payjoin",
|
||||||
"send_payment_id": "ID thanh toán (tùy chọn)",
|
"send_payment_id": "ID thanh toán (tùy chọn)",
|
||||||
"send_priority": "Hiện tại phí được đặt ở mức ưu tiên ${transactionPriority}.\nƯu tiên giao dịch có thể được điều chỉnh trong cài đặt",
|
"send_priority": "Hiện tại phí được đặt ở mức ưu tiên ${transactionPriority}.\nƯu tiên giao dịch có thể được điều chỉnh trong cài đặt",
|
||||||
"send_sending": "Đang gửi...",
|
"send_sending": "Đang gửi...",
|
||||||
|
@ -970,6 +976,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",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "Ṣẹ̀dà",
|
"copy": "Ṣẹ̀dà",
|
||||||
"copy_address": "Ṣẹ̀dà àdírẹ́sì",
|
"copy_address": "Ṣẹ̀dà àdírẹ́sì",
|
||||||
"copy_id": "Ṣẹ̀dà àmì ìdánimọ̀",
|
"copy_id": "Ṣẹ̀dà àmì ìdánimọ̀",
|
||||||
|
"copy_payjoin_url": "Daakọ Payjoin url",
|
||||||
"copyWalletConnectLink": "Daakọ ọna asopọ WalletConnect lati dApp ki o si lẹẹmọ nibi",
|
"copyWalletConnectLink": "Daakọ ọna asopọ WalletConnect lati dApp ki o si lẹẹmọ nibi",
|
||||||
"corrupted_seed_notice": "Awọn faili fun apamọwọ yii jẹ ibajẹ ati pe ko lagbara lati ṣii. Jọwọ wo ọrọ iseda, fipamọ rẹ, ki o mu apamọwọ naa pada.\n\nTi iye ba ṣofo, lẹhinna irugbin naa ko lagbara lati gba pada ni deede.",
|
"corrupted_seed_notice": "Awọn faili fun apamọwọ yii jẹ ibajẹ ati pe ko lagbara lati ṣii. Jọwọ wo ọrọ iseda, fipamọ rẹ, ki o mu apamọwọ naa pada.\n\nTi iye ba ṣofo, lẹhinna irugbin naa ko lagbara lati gba pada ni deede.",
|
||||||
"countries": "Awọn orilẹ-ede",
|
"countries": "Awọn orilẹ-ede",
|
||||||
|
@ -547,6 +548,10 @@
|
||||||
"password": "Ọ̀rọ̀ aṣínà",
|
"password": "Ọ̀rọ̀ aṣínà",
|
||||||
"paste": "Fikún ẹ̀dà yín",
|
"paste": "Fikún ẹ̀dà yín",
|
||||||
"pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.",
|
"pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.",
|
||||||
|
"payjoin_details": "Payjoin awọn alaye",
|
||||||
|
"payjoin_enabled": "Payjoin ṣiṣẹ",
|
||||||
|
"payjoin_request_awaiting_tx": "O duro de idunadura",
|
||||||
|
"payjoin_request_in_progress": "Ni ilọsiwaju",
|
||||||
"payment_id": "Àmì ìdánimọ̀ àránṣẹ́: ",
|
"payment_id": "Àmì ìdánimọ̀ àránṣẹ́: ",
|
||||||
"payment_was_received": "Àránṣẹ́ yín ti dé.",
|
"payment_was_received": "Àránṣẹ́ yín ti dé.",
|
||||||
"pending": " pípẹ́",
|
"pending": " pípẹ́",
|
||||||
|
@ -736,6 +741,7 @@
|
||||||
"send_from_external_wallet": "Firanṣẹ lati apamọwọ ita",
|
"send_from_external_wallet": "Firanṣẹ lati apamọwọ ita",
|
||||||
"send_name": "Orúkọ",
|
"send_name": "Orúkọ",
|
||||||
"send_new": "Títun",
|
"send_new": "Títun",
|
||||||
|
"send_payjoin": "Firanṣẹ PayjoinPayjoin",
|
||||||
"send_payment_id": "Àmì ìdánimọ̀ àránṣẹ́ (ìyàn nìyí)",
|
"send_payment_id": "Àmì ìdánimọ̀ àránṣẹ́ (ìyàn nìyí)",
|
||||||
"send_priority": "${transactionPriority} agbára ni owó àfikún lọ́wọ́lọ́wọ́.\nẸ lè pààrọ̀ iye agbára t'ẹ fikún àránṣẹ́ lórí àwọn ààtò",
|
"send_priority": "${transactionPriority} agbára ni owó àfikún lọ́wọ́lọ́wọ́.\nẸ lè pààrọ̀ iye agbára t'ẹ fikún àránṣẹ́ lórí àwọn ààtò",
|
||||||
"send_sending": "Ń Ránṣẹ́...",
|
"send_sending": "Ń Ránṣẹ́...",
|
||||||
|
@ -974,6 +980,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 Payjoin",
|
||||||
"use_ssl": "Lo SSL",
|
"use_ssl": "Lo SSL",
|
||||||
"use_suggested": "Lo àbá",
|
"use_suggested": "Lo àbá",
|
||||||
"use_testnet": "Lo tele",
|
"use_testnet": "Lo tele",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
"copy": "复制",
|
"copy": "复制",
|
||||||
"copy_address": "复制地址",
|
"copy_address": "复制地址",
|
||||||
"copy_id": "复制ID",
|
"copy_id": "复制ID",
|
||||||
|
"copy_payjoin_url": "复制Payjoin url",
|
||||||
"copyWalletConnectLink": "从 dApp 复制 WalletConnect 链接并粘贴到此处",
|
"copyWalletConnectLink": "从 dApp 复制 WalletConnect 链接并粘贴到此处",
|
||||||
"corrupted_seed_notice": "该钱包的文件被损坏,无法打开。请查看种子短语,保存并恢复钱包。\n\n如果该值为空,则种子无法正确恢复。",
|
"corrupted_seed_notice": "该钱包的文件被损坏,无法打开。请查看种子短语,保存并恢复钱包。\n\n如果该值为空,则种子无法正确恢复。",
|
||||||
"countries": "国家",
|
"countries": "国家",
|
||||||
|
@ -546,6 +547,10 @@
|
||||||
"password": "密码",
|
"password": "密码",
|
||||||
"paste": "粘贴",
|
"paste": "粘贴",
|
||||||
"pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。",
|
"pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。",
|
||||||
|
"payjoin_details": "Payjoin 细节",
|
||||||
|
"payjoin_enabled": "Payjoin启用",
|
||||||
|
"payjoin_request_awaiting_tx": "等待交易",
|
||||||
|
"payjoin_request_in_progress": "进行中",
|
||||||
"payment_id": "付款 ID: ",
|
"payment_id": "付款 ID: ",
|
||||||
"payment_was_received": "您的付款已收到。",
|
"payment_was_received": "您的付款已收到。",
|
||||||
"pending": " (待定)",
|
"pending": " (待定)",
|
||||||
|
@ -735,6 +740,7 @@
|
||||||
"send_from_external_wallet": "从外部钱包发送",
|
"send_from_external_wallet": "从外部钱包发送",
|
||||||
"send_name": "名称",
|
"send_name": "名称",
|
||||||
"send_new": "新建",
|
"send_new": "新建",
|
||||||
|
"send_payjoin": "发送 Payjoin",
|
||||||
"send_payment_id": "付款编号 (可选的)",
|
"send_payment_id": "付款编号 (可选的)",
|
||||||
"send_priority": "目前,费用设置为 ${transactionPriority} 优先.\n交易优先级可以在设置中进行调整",
|
"send_priority": "目前,费用设置为 ${transactionPriority} 优先.\n交易优先级可以在设置中进行调整",
|
||||||
"send_sending": "正在发送...",
|
"send_sending": "正在发送...",
|
||||||
|
@ -973,6 +979,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",
|
||||||
|
|
|
@ -87,6 +87,7 @@ import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||||
import 'package:cake_wallet/view_model/send/output.dart';
|
import 'package:cake_wallet/view_model/send/output.dart';
|
||||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||||
import 'package:cw_core/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
import 'package:cw_core/output_info.dart';
|
import 'package:cw_core/output_info.dart';
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_core/receive_page_option.dart';
|
import 'package:cw_core/receive_page_option.dart';
|
||||||
|
@ -118,10 +119,12 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_wallet.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_service.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_service.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
|
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
import 'package:cw_bitcoin/litecoin_wallet_service.dart';
|
import 'package:cw_bitcoin/litecoin_wallet_service.dart';
|
||||||
import 'package:cw_bitcoin/litecoin_wallet.dart';
|
import 'package:cw_bitcoin/litecoin_wallet.dart';
|
||||||
|
@ -171,7 +174,7 @@ abstract class Bitcoin {
|
||||||
int getFeeRate(Object wallet, TransactionPriority priority);
|
int getFeeRate(Object wallet, TransactionPriority priority);
|
||||||
Future<void> generateNewAddress(Object wallet, String label);
|
Future<void> generateNewAddress(Object wallet, String label);
|
||||||
Future<void> updateAddress(Object wallet,String address, String label);
|
Future<void> updateAddress(Object wallet,String address, String label);
|
||||||
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any});
|
Object createBitcoinTransactionCredentials(List<Output> outputs, {required TransactionPriority priority, int? feeRate, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, String? payjoinUri});
|
||||||
|
|
||||||
String getAddress(Object wallet);
|
String getAddress(Object wallet);
|
||||||
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet);
|
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet);
|
||||||
|
@ -189,7 +192,7 @@ abstract class Bitcoin {
|
||||||
List<Unspent> getUnspents(Object wallet, {UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any});
|
List<Unspent> getUnspents(Object wallet, {UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any});
|
||||||
Future<void> updateUnspents(Object wallet);
|
Future<void> updateUnspents(Object wallet);
|
||||||
WalletService createBitcoinWalletService(
|
WalletService createBitcoinWalletService(
|
||||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect);
|
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, Box<PayjoinSession> payjoinSessionSource, bool alwaysScan, bool isDirect);
|
||||||
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect);
|
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect);
|
||||||
TransactionPriority getBitcoinTransactionPriorityMedium();
|
TransactionPriority getBitcoinTransactionPriorityMedium();
|
||||||
TransactionPriority getBitcoinTransactionPriorityCustom();
|
TransactionPriority getBitcoinTransactionPriorityCustom();
|
||||||
|
@ -206,6 +209,7 @@ abstract class Bitcoin {
|
||||||
List<ReceivePageOption> getBitcoinReceivePageOptions();
|
List<ReceivePageOption> getBitcoinReceivePageOptions();
|
||||||
List<ReceivePageOption> getLitecoinReceivePageOptions();
|
List<ReceivePageOption> getLitecoinReceivePageOptions();
|
||||||
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option);
|
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option);
|
||||||
|
bool isPayjoinAvailable(Object wallet);
|
||||||
bool hasSelectedSilentPayments(Object wallet);
|
bool hasSelectedSilentPayments(Object wallet);
|
||||||
bool isBitcoinReceivePageOption(ReceivePageOption option);
|
bool isBitcoinReceivePageOption(ReceivePageOption option);
|
||||||
BitcoinAddressType getOptionToType(ReceivePageOption option);
|
BitcoinAddressType getOptionToType(ReceivePageOption option);
|
||||||
|
@ -240,6 +244,11 @@ abstract class Bitcoin {
|
||||||
bool getMwebEnabled(Object wallet);
|
bool getMwebEnabled(Object wallet);
|
||||||
String? getUnusedMwebAddress(Object wallet);
|
String? getUnusedMwebAddress(Object wallet);
|
||||||
String? getUnusedSegwitAddress(Object wallet);
|
String? getUnusedSegwitAddress(Object wallet);
|
||||||
|
|
||||||
|
void updatePayjoinState(Object wallet, bool state);
|
||||||
|
String getPayjoinEndpoint(Object wallet);
|
||||||
|
void resumePayjoinSessions(Object wallet);
|
||||||
|
void stopPayjoinSessions(Object wallet);
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue