diff --git a/.github/workflows/cache_dependencies.yml b/.github/workflows/cache_dependencies.yml
index c0042bf5c..ce098b7f1 100644
--- a/.github/workflows/cache_dependencies.yml
+++ b/.github/workflows/cache_dependencies.yml
@@ -62,10 +62,22 @@ jobs:
/opt/android/cake_wallet/cw_haven/android/.cxx
/opt/android/cake_wallet/scripts/monero_c/release
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
-
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
name: Generate Externals
run: |
cd /opt/android/cake_wallet/scripts/android/
source ./app_env.sh cakewallet
./build_monero_all.sh
+
+ - name: Cache Keystore
+ id: cache-keystore
+ uses: actions/cache@v3
+ with:
+ path: /opt/android/cake_wallet/android/app/key.jks
+ key: $STORE_PASS
+
+ - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
+ name: Generate KeyStore
+ run: |
+ cd /opt/android/cake_wallet/android/app
+ keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS
diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index fde22a74c..720d2c3ce 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -116,6 +116,14 @@ jobs:
cd /opt/android/cake_wallet/scripts/android/
./build_mwebd.sh --dont-install
+# - name: Cache Keystore
+# id: cache-keystore
+# uses: actions/cache@v3
+# with:
+# path: /opt/android/cake_wallet/android/app/key.jks
+# key: $STORE_PASS
+#
+# - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
- name: Generate KeyStore
run: |
cd /opt/android/cake_wallet/android/app
@@ -193,6 +201,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
+ echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
@@ -202,6 +212,36 @@ jobs:
run: |
echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
+ # Step 3: Download previous build number
+ - name: Download previous build number
+ id: download-build-number
+ run: |
+ # Download the artifact if it exists
+ if [[ ! -f build_number.txt ]]; then
+ echo "1" > build_number.txt
+ fi
+
+ # Step 4: Read and Increment Build Number
+ - name: Increment Build Number
+ id: increment-build-number
+ run: |
+ # Read current build number from file
+ BUILD_NUMBER=$(cat build_number.txt)
+ BUILD_NUMBER=$((BUILD_NUMBER + 1))
+ echo "New build number: $BUILD_NUMBER"
+
+ # Save the incremented build number
+ echo "$BUILD_NUMBER" > build_number.txt
+
+ # Export the build number to use in later steps
+ echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV
+
+ # Step 5: Update pubspec.yaml with new build number
+ - name: Update build number
+ run: |
+ cd /opt/android/cake_wallet
+ sed -i "s/^version: .*/version: 1.0.$BUILD_NUMBER/" pubspec.yaml
+
- name: Build
run: |
cd /opt/android/cake_wallet
@@ -232,6 +272,13 @@ jobs:
with:
path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/
+ # Re-upload updated build number for the next run
+ - name: Upload updated build number
+ uses: actions/upload-artifact@v3
+ with:
+ name: build_number
+ path: build_number.txt
+
- name: Send Test APK
continue-on-error: true
uses: adrey/slack-file-upload-action@1.0.5
@@ -242,3 +289,4 @@ jobs:
title: "${{ env.BRANCH_NAME }}.apk"
filename: ${{ env.BRANCH_NAME }}.apk
initial_comment: ${{ github.event.head_commit.message }}
+
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 5ea0cb377..f3ce1045d 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -175,6 +175,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
+ echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
+ echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 2f5427531..5005a8bab 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -92,3 +92,8 @@ dependencies {
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
+configurations {
+ implementation.exclude module:'proto-google-common-protos'
+ implementation.exclude module:'protolite-well-known-types'
+ implementation.exclude module:'protobuf-javalite'
+}
\ No newline at end of file
diff --git a/android/gradle.properties b/android/gradle.properties
index 38c8d4544..66dd09454 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,4 +1,4 @@
-org.gradle.jvmargs=-Xmx1536M
+org.gradle.jvmargs=-Xmx3072M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
diff --git a/assets/images/apple_pay_logo.png b/assets/images/apple_pay_logo.png
new file mode 100644
index 000000000..346007e3b
Binary files /dev/null and b/assets/images/apple_pay_logo.png differ
diff --git a/assets/images/apple_pay_round_dark.svg b/assets/images/apple_pay_round_dark.svg
new file mode 100644
index 000000000..82443bfb4
--- /dev/null
+++ b/assets/images/apple_pay_round_dark.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/assets/images/apple_pay_round_light.svg b/assets/images/apple_pay_round_light.svg
new file mode 100644
index 000000000..2beb1248f
--- /dev/null
+++ b/assets/images/apple_pay_round_light.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/assets/images/bank.png b/assets/images/bank.png
new file mode 100644
index 000000000..9dc68147a
Binary files /dev/null and b/assets/images/bank.png differ
diff --git a/assets/images/bank_dark.svg b/assets/images/bank_dark.svg
new file mode 100644
index 000000000..670120796
--- /dev/null
+++ b/assets/images/bank_dark.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/assets/images/bank_light.svg b/assets/images/bank_light.svg
new file mode 100644
index 000000000..804716289
--- /dev/null
+++ b/assets/images/bank_light.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/assets/images/buy_sell.png b/assets/images/buy_sell.png
new file mode 100644
index 000000000..0fbffe56f
Binary files /dev/null and b/assets/images/buy_sell.png differ
diff --git a/assets/images/card.svg b/assets/images/card.svg
new file mode 100644
index 000000000..95530cdc9
--- /dev/null
+++ b/assets/images/card.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/card_dark.svg b/assets/images/card_dark.svg
new file mode 100644
index 000000000..2e5bcf986
--- /dev/null
+++ b/assets/images/card_dark.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/assets/images/dollar_coin.svg b/assets/images/dollar_coin.svg
new file mode 100644
index 000000000..22218f332
--- /dev/null
+++ b/assets/images/dollar_coin.svg
@@ -0,0 +1,12 @@
+
+
+
\ No newline at end of file
diff --git a/assets/images/google_pay_icon.png b/assets/images/google_pay_icon.png
new file mode 100644
index 000000000..a3ca38311
Binary files /dev/null and b/assets/images/google_pay_icon.png differ
diff --git a/assets/images/meld_logo.svg b/assets/images/meld_logo.svg
new file mode 100644
index 000000000..1d9211d64
--- /dev/null
+++ b/assets/images/meld_logo.svg
@@ -0,0 +1,30 @@
+
diff --git a/assets/images/revolut.png b/assets/images/revolut.png
new file mode 100644
index 000000000..bbe342592
Binary files /dev/null and b/assets/images/revolut.png differ
diff --git a/assets/images/skrill.svg b/assets/images/skrill.svg
new file mode 100644
index 000000000..b264b57eb
--- /dev/null
+++ b/assets/images/skrill.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/assets/images/usd_round_dark.svg b/assets/images/usd_round_dark.svg
new file mode 100644
index 000000000..f329dd617
--- /dev/null
+++ b/assets/images/usd_round_dark.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/assets/images/usd_round_light.svg b/assets/images/usd_round_light.svg
new file mode 100644
index 000000000..f5965c597
--- /dev/null
+++ b/assets/images/usd_round_light.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/assets/images/wallet_new.png b/assets/images/wallet_new.png
new file mode 100644
index 000000000..47c43bfca
Binary files /dev/null and b/assets/images/wallet_new.png differ
diff --git a/assets/node_list.yml b/assets/node_list.yml
index d04a9a2e8..6191129b3 100644
--- a/assets/node_list.yml
+++ b/assets/node_list.yml
@@ -1,6 +1,7 @@
-
uri: xmr-node.cakewallet.com:18081
is_default: true
+ trusted: true
-
uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081
is_default: false
diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart
index 7988a4479..6245aa787 100644
--- a/cw_bitcoin/lib/electrum_wallet.dart
+++ b/cw_bitcoin/lib/electrum_wallet.dart
@@ -11,7 +11,6 @@ import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
-import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
@@ -597,8 +596,8 @@ abstract class ElectrumWalletBase
UtxoDetails _createUTXOS({
required bool sendAll,
- required int credentialsAmount,
required bool paysToSilentPayment,
+ int credentialsAmount = 0,
int? inputsCount,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) {
@@ -732,13 +731,11 @@ abstract class ElectrumWalletBase
List outputs,
int feeRate, {
String? memo,
- int credentialsAmount = 0,
bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async {
final utxoDetails = _createUTXOS(
sendAll: true,
- credentialsAmount: credentialsAmount,
paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
@@ -764,23 +761,11 @@ abstract class ElectrumWalletBase
throw BitcoinTransactionWrongBalanceException(amount: utxoDetails.allInputsAmount + fee);
}
- if (amount <= 0) {
- throw BitcoinTransactionWrongBalanceException();
- }
-
// Attempting to send less than the dust limit
if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException();
}
- if (credentialsAmount > 0) {
- final amountLeftForFee = amount - credentialsAmount;
- if (amountLeftForFee > 0 && _isBelowDust(amountLeftForFee)) {
- amount -= amountLeftForFee;
- fee += amountLeftForFee;
- }
- }
-
if (outputs.length == 1) {
outputs[0] = BitcoinOutput(address: outputs.last.address, value: BigInt.from(amount));
}
@@ -810,6 +795,11 @@ abstract class ElectrumWalletBase
bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async {
+ // Attempting to send less than the dust limit
+ if (_isBelowDust(credentialsAmount)) {
+ throw BitcoinTransactionNoDustException();
+ }
+
final utxoDetails = _createUTXOS(
sendAll: false,
credentialsAmount: credentialsAmount,
@@ -894,7 +884,43 @@ abstract class ElectrumWalletBase
final lastOutput = updatedOutputs.last;
final amountLeftForChange = amountLeftForChangeAndFee - fee;
- if (!_isBelowDust(amountLeftForChange)) {
+ if (_isBelowDust(amountLeftForChange)) {
+ // If has change that is lower than dust, will end up with tx rejected by network rules
+ // so remove the change amount
+ updatedOutputs.removeLast();
+ outputs.removeLast();
+
+ if (amountLeftForChange < 0) {
+ if (!spendingAllCoins) {
+ return estimateTxForAmount(
+ credentialsAmount,
+ outputs,
+ updatedOutputs,
+ feeRate,
+ inputsCount: utxoDetails.utxos.length + 1,
+ memo: memo,
+ useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
+ hasSilentPayment: hasSilentPayment,
+ coinTypeToSpendFrom: coinTypeToSpendFrom,
+ );
+ } else {
+ throw BitcoinTransactionWrongBalanceException();
+ }
+ }
+
+ return EstimatedTxResult(
+ utxos: utxoDetails.utxos,
+ inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
+ publicKeys: utxoDetails.publicKeys,
+ fee: fee,
+ amount: amount,
+ hasChange: false,
+ isSendAll: spendingAllCoins,
+ memo: memo,
+ spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
+ spendsSilentPayment: utxoDetails.spendsSilentPayment,
+ );
+ } else {
// Here, lastOutput already is change, return the amount left without the fee to the user's address.
updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
address: lastOutput.address,
@@ -908,88 +934,20 @@ abstract class ElectrumWalletBase
isSilentPayment: lastOutput.isSilentPayment,
isChange: true,
);
- } else {
- // If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
- updatedOutputs.removeLast();
- outputs.removeLast();
- // Still has inputs to spend before failing
- if (!spendingAllCoins) {
- return estimateTxForAmount(
- credentialsAmount,
- outputs,
- updatedOutputs,
- feeRate,
- inputsCount: utxoDetails.utxos.length + 1,
- memo: memo,
- hasSilentPayment: hasSilentPayment,
- useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
- coinTypeToSpendFrom: coinTypeToSpendFrom,
- );
- }
-
- final estimatedSendAll = await estimateSendAllTx(
- updatedOutputs,
- feeRate,
+ return EstimatedTxResult(
+ utxos: utxoDetails.utxos,
+ inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
+ publicKeys: utxoDetails.publicKeys,
+ fee: fee,
+ amount: amount,
+ hasChange: true,
+ isSendAll: spendingAllCoins,
memo: memo,
- coinTypeToSpendFrom: coinTypeToSpendFrom,
- );
-
- if (estimatedSendAll.amount == credentialsAmount) {
- return estimatedSendAll;
- }
-
- // Estimate to user how much is needed to send to cover the fee
- final maxAmountWithReturningChange = utxoDetails.allInputsAmount - _dustAmount - fee - 1;
- throw BitcoinTransactionNoDustOnChangeException(
- bitcoinAmountToString(amount: maxAmountWithReturningChange),
- bitcoinAmountToString(amount: estimatedSendAll.amount),
+ spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
+ spendsSilentPayment: utxoDetails.spendsSilentPayment,
);
}
-
- // Attempting to send less than the dust limit
- if (_isBelowDust(amount)) {
- throw BitcoinTransactionNoDustException();
- }
-
- final totalAmount = amount + fee;
-
- if (totalAmount > (balance[currency]!.confirmed + balance[currency]!.secondConfirmed)) {
- throw BitcoinTransactionWrongBalanceException();
- }
-
- if (totalAmount > utxoDetails.allInputsAmount) {
- if (spendingAllCoins) {
- throw BitcoinTransactionWrongBalanceException();
- } else {
- updatedOutputs.removeLast();
- outputs.removeLast();
- return estimateTxForAmount(
- credentialsAmount,
- outputs,
- updatedOutputs,
- feeRate,
- inputsCount: utxoDetails.utxos.length + 1,
- memo: memo,
- useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
- hasSilentPayment: hasSilentPayment,
- coinTypeToSpendFrom: coinTypeToSpendFrom,
- );
- }
- }
-
- return EstimatedTxResult(
- utxos: utxoDetails.utxos,
- inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
- publicKeys: utxoDetails.publicKeys,
- fee: fee,
- amount: amount,
- hasChange: true,
- isSendAll: false,
- memo: memo,
- spendsUnconfirmedTX: utxoDetails.spendsUnconfirmedTX,
- spendsSilentPayment: utxoDetails.spendsSilentPayment,
- );
}
Future calcFee({
@@ -1080,15 +1038,20 @@ abstract class ElectrumWalletBase
: feeRate(transactionCredentials.priority!);
EstimatedTxResult estimatedTx;
- final updatedOutputs =
- outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList();
+ final updatedOutputs = outputs
+ .map((e) => BitcoinOutput(
+ address: e.address,
+ value: e.value,
+ isSilentPayment: e.isSilentPayment,
+ isChange: e.isChange,
+ ))
+ .toList();
if (sendAll) {
estimatedTx = await estimateSendAllTx(
updatedOutputs,
feeRateInt,
memo: memo,
- credentialsAmount: credentialsAmount,
hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
index 140965655..411c7de16 100644
--- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart
+++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart
@@ -153,4 +153,9 @@ class PendingBitcoinTransaction with PendingTransaction {
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
outputAddresses: outputAddresses,
fee: fee);
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart
index e1fa9d6e0..05483ce54 100644
--- a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart
+++ b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart
@@ -85,4 +85,8 @@ class PendingBitcoinCashTransaction with PendingTransaction {
fee: fee,
isReplaced: false,
);
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart
index 45e11e281..bd1c224a3 100644
--- a/cw_core/lib/currency_for_wallet_type.dart
+++ b/cw_core/lib/currency_for_wallet_type.dart
@@ -38,3 +38,34 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
}
}
+
+WalletType? walletTypeForCurrency(CryptoCurrency currency) {
+ switch (currency) {
+ case CryptoCurrency.btc:
+ return WalletType.bitcoin;
+ case CryptoCurrency.xmr:
+ return WalletType.monero;
+ case CryptoCurrency.ltc:
+ return WalletType.litecoin;
+ case CryptoCurrency.xhv:
+ return WalletType.haven;
+ case CryptoCurrency.eth:
+ return WalletType.ethereum;
+ case CryptoCurrency.bch:
+ return WalletType.bitcoinCash;
+ case CryptoCurrency.nano:
+ return WalletType.nano;
+ case CryptoCurrency.banano:
+ return WalletType.banano;
+ case CryptoCurrency.maticpoly:
+ return WalletType.polygon;
+ case CryptoCurrency.sol:
+ return WalletType.solana;
+ case CryptoCurrency.trx:
+ return WalletType.tron;
+ case CryptoCurrency.wow:
+ return WalletType.wownero;
+ default:
+ return null;
+ }
+}
diff --git a/cw_core/lib/monero_wallet_keys.dart b/cw_core/lib/monero_wallet_keys.dart
index 1435002a8..fe3784986 100644
--- a/cw_core/lib/monero_wallet_keys.dart
+++ b/cw_core/lib/monero_wallet_keys.dart
@@ -1,10 +1,12 @@
class MoneroWalletKeys {
const MoneroWalletKeys(
- {required this.privateSpendKey,
+ {required this.primaryAddress,
+ required this.privateSpendKey,
required this.privateViewKey,
required this.publicSpendKey,
required this.publicViewKey});
+ final String primaryAddress;
final String publicViewKey;
final String privateViewKey;
final String publicSpendKey;
diff --git a/cw_core/lib/pending_transaction.dart b/cw_core/lib/pending_transaction.dart
index 0a6103a5f..78eba68a3 100644
--- a/cw_core/lib/pending_transaction.dart
+++ b/cw_core/lib/pending_transaction.dart
@@ -14,5 +14,8 @@ mixin PendingTransaction {
int? get outputCount => null;
PendingChange? change;
+ bool shouldCommitUR() => false;
+
Future commit();
+ Future commitUR();
}
diff --git a/cw_evm/lib/pending_evm_chain_transaction.dart b/cw_evm/lib/pending_evm_chain_transaction.dart
index 0b367da68..6861b41f8 100644
--- a/cw_evm/lib/pending_evm_chain_transaction.dart
+++ b/cw_evm/lib/pending_evm_chain_transaction.dart
@@ -47,4 +47,9 @@ class PendingEVMChainTransaction with PendingTransaction {
return '0x${Hex.HEX.encode(txid)}';
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart
index c6b6c6ef7..3ff5f2438 100644
--- a/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart
+++ b/cw_haven/lib/api/exceptions/wallet_restore_from_keys_exception.dart
@@ -2,4 +2,9 @@ class WalletRestoreFromKeysException implements Exception {
WalletRestoreFromKeysException({required this.message});
final String message;
+
+ @override
+ String toString() {
+ return message;
+ }
}
\ No newline at end of file
diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart
index 06a838100..e2e598bbe 100644
--- a/cw_haven/lib/haven_wallet.dart
+++ b/cw_haven/lib/haven_wallet.dart
@@ -73,6 +73,7 @@ abstract class HavenWalletBase
@override
MoneroWalletKeys get keys => MoneroWalletKeys(
+ primaryAddress: haven_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: haven_wallet.getSecretSpendKey(),
privateViewKey: haven_wallet.getSecretViewKey(),
publicSpendKey: haven_wallet.getPublicSpendKey(),
diff --git a/cw_haven/lib/pending_haven_transaction.dart b/cw_haven/lib/pending_haven_transaction.dart
index 1d95de08c..dbf075044 100644
--- a/cw_haven/lib/pending_haven_transaction.dart
+++ b/cw_haven/lib/pending_haven_transaction.dart
@@ -48,4 +48,9 @@ class PendingHavenTransaction with PendingTransaction {
rethrow;
}
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart
index 7cb95a507..e3bb25c97 100644
--- a/cw_monero/lib/api/account_list.dart
+++ b/cw_monero/lib/api/account_list.dart
@@ -2,6 +2,7 @@ import 'package:cw_monero/api/wallet.dart';
import 'package:monero/monero.dart' as monero;
monero.wallet? wptr = null;
+bool get isViewOnly => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;
int _wlptrForW = 0;
monero.WalletListener? _wlptr = null;
diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart
index a308b682e..7748a16af 100644
--- a/cw_monero/lib/api/transaction_history.dart
+++ b/cw_monero/lib/api/transaction_history.dart
@@ -13,7 +13,13 @@ import 'package:mutex/mutex.dart';
String getTxKey(String txId) {
- return monero.Wallet_getTxKey(wptr!, txid: txId);
+ final txKey = monero.Wallet_getTxKey(wptr!, txid: txId);
+ final status = monero.Wallet_status(wptr!);
+ if (status != 0) {
+ final error = monero.Wallet_errorString(wptr!);
+ return txId+"_"+error;
+ }
+ return txKey;
}
final txHistoryMutex = Mutex();
monero.TransactionHistory? txhistory;
@@ -178,12 +184,13 @@ PendingTransactionDescription createTransactionMultDestSync(
);
}
-void commitTransactionFromPointerAddress({required int address}) =>
- commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address));
+String? commitTransactionFromPointerAddress({required int address, required bool useUR}) =>
+ commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);
-void commitTransaction({required monero.PendingTransaction transactionPointer}) {
-
- final txCommit = monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
+String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
+ final txCommit = useUR
+ ? monero.PendingTransaction_commitUR(transactionPointer, 120)
+ : monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
final String? error = (() {
final status = monero.PendingTransaction_status(transactionPointer.cast());
@@ -196,6 +203,11 @@ void commitTransaction({required monero.PendingTransaction transactionPointer})
if (error != null) {
throw CreationTransactionException(message: error);
}
+ if (useUR) {
+ return txCommit as String?;
+ } else {
+ return null;
+ }
}
Future _createTransactionSync(Map args) async {
diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart
index dca7586fb..61fd6edf0 100644
--- a/cw_monero/lib/api/wallet_manager.dart
+++ b/cw_monero/lib/api/wallet_manager.dart
@@ -425,3 +425,5 @@ Future restoreFromSpendKey(
});
bool isWalletExist({required String path}) => _isWalletExist(path);
+
+bool isViewOnlyBySpendKey() => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0;
\ No newline at end of file
diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart
index 0ae2202ba..bc8c3c94d 100644
--- a/cw_monero/lib/monero_wallet.dart
+++ b/cw_monero/lib/monero_wallet.dart
@@ -19,6 +19,7 @@ import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
+import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/coins_info.dart';
import 'package:cw_monero/api/monero_output.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
@@ -121,6 +122,7 @@ abstract class MoneroWalletBase extends WalletBase MoneroWalletKeys(
+ primaryAddress: monero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: monero_wallet.getSecretSpendKey(),
privateViewKey: monero_wallet.getSecretViewKey(),
publicSpendKey: monero_wallet.getPublicSpendKey(),
@@ -230,6 +232,36 @@ abstract class MoneroWalletBase extends WalletBase submitTransactionUR(String ur) async {
+ final retStatus = monero.Wallet_submitTransactionUR(wptr!, ur);
+ final status = monero.Wallet_status(wptr!);
+ if (status != 0) {
+ final err = monero.Wallet_errorString(wptr!);
+ throw MoneroTransactionCreationException("unable to broadcast signed transaction: $err");
+ }
+ return retStatus;
+ }
+
+ bool importKeyImagesUR(String ur) {
+ final retStatus = monero.Wallet_importKeyImagesUR(wptr!, ur);
+ final status = monero.Wallet_status(wptr!);
+ if (status != 0) {
+ final err = monero.Wallet_errorString(wptr!);
+ throw Exception("unable to import key images: $err");
+ }
+ return retStatus;
+ }
+
+ String exportOutputsUR(bool all) {
+ final str = monero.Wallet_exportOutputsUR(wptr!, all: all);
+ final status = monero.Wallet_status(wptr!);
+ if (status != 0) {
+ final err = monero.Wallet_errorString(wptr!);
+ throw MoneroTransactionCreationException("unable to export UR: $err");
+ }
+ return str;
+ }
+
@override
Future createTransaction(Object credentials) async {
final _credentials = credentials as MoneroTransactionCreationCredentials;
diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart
index 2a75fc26b..eb714eeb3 100644
--- a/cw_monero/lib/pending_monero_transaction.dart
+++ b/cw_monero/lib/pending_monero_transaction.dart
@@ -1,3 +1,4 @@
+import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/transaction_history.dart'
as monero_transaction_history;
@@ -35,11 +36,32 @@ class PendingMoneroTransaction with PendingTransaction {
String get feeFormatted => AmountConverter.amountIntToString(
CryptoCurrency.xmr, pendingTransactionDescription.fee);
+ bool shouldCommitUR() => isViewOnly;
+
@override
Future commit() async {
try {
monero_transaction_history.commitTransactionFromPointerAddress(
- address: pendingTransactionDescription.pointerAddress);
+ address: pendingTransactionDescription.pointerAddress,
+ useUR: false);
+ } catch (e) {
+ final message = e.toString();
+
+ if (message.contains('Reason: double spend')) {
+ throw DoubleSpendException();
+ }
+
+ rethrow;
+ }
+ }
+
+ @override
+ Future commitUR() async {
+ try {
+ final ret = monero_transaction_history.commitTransactionFromPointerAddress(
+ address: pendingTransactionDescription.pointerAddress,
+ useUR: true);
+ return ret;
} catch (e) {
final message = e.toString();
diff --git a/cw_nano/lib/pending_nano_transaction.dart b/cw_nano/lib/pending_nano_transaction.dart
index a027100fd..51a4ef6c1 100644
--- a/cw_nano/lib/pending_nano_transaction.dart
+++ b/cw_nano/lib/pending_nano_transaction.dart
@@ -37,4 +37,9 @@ class PendingNanoTransaction with PendingTransaction {
await nanoClient.processBlock(block, "send");
}
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_shared_external/pubspec.lock b/cw_shared_external/pubspec.lock
new file mode 100644
index 000000000..203ecd5c3
--- /dev/null
+++ b/cw_shared_external/pubspec.lock
@@ -0,0 +1,189 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.11.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.18.0"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "10.0.4"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.1"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.16+1"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.8.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.12.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.0"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.0"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
+ url: "https://pub.dev"
+ source: hosted
+ version: "14.2.1"
+sdks:
+ dart: ">=3.3.0 <4.0.0"
+ flutter: ">=3.18.0-18.0.pre.54"
diff --git a/cw_solana/lib/pending_solana_transaction.dart b/cw_solana/lib/pending_solana_transaction.dart
index 38347ed13..e01446000 100644
--- a/cw_solana/lib/pending_solana_transaction.dart
+++ b/cw_solana/lib/pending_solana_transaction.dart
@@ -40,4 +40,9 @@ class PendingSolanaTransaction with PendingTransaction {
@override
String get id => '';
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_tron/lib/pending_tron_transaction.dart b/cw_tron/lib/pending_tron_transaction.dart
index b6d064b31..2420f083b 100644
--- a/cw_tron/lib/pending_tron_transaction.dart
+++ b/cw_tron/lib/pending_tron_transaction.dart
@@ -30,4 +30,9 @@ class PendingTronTransaction with PendingTransaction {
@override
String get id => '';
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart
index ad576faa2..6c461ee4c 100644
--- a/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart
+++ b/cw_wownero/lib/api/exceptions/wallet_restore_from_keys_exception.dart
@@ -3,5 +3,6 @@ class WalletRestoreFromKeysException implements Exception {
final String message;
+ @override
String toString() => message;
}
\ No newline at end of file
diff --git a/cw_wownero/lib/pending_wownero_transaction.dart b/cw_wownero/lib/pending_wownero_transaction.dart
index 1fc1805eb..967f63756 100644
--- a/cw_wownero/lib/pending_wownero_transaction.dart
+++ b/cw_wownero/lib/pending_wownero_transaction.dart
@@ -50,4 +50,9 @@ class PendingWowneroTransaction with PendingTransaction {
rethrow;
}
}
+
+ @override
+ Future commitUR() {
+ throw UnimplementedError();
+ }
}
diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart
index 331957d67..5927d6434 100644
--- a/cw_wownero/lib/wownero_wallet.dart
+++ b/cw_wownero/lib/wownero_wallet.dart
@@ -120,6 +120,7 @@ abstract class WowneroWalletBase
@override
MoneroWalletKeys get keys => MoneroWalletKeys(
+ primaryAddress: wownero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
privateSpendKey: wownero_wallet.getSecretSpendKey(),
privateViewKey: wownero_wallet.getSecretViewKey(),
publicSpendKey: wownero_wallet.getPublicSpendKey(),
diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart
index 1a37e09b3..8e79e16b8 100644
--- a/lib/buy/buy_provider.dart
+++ b/lib/buy/buy_provider.dart
@@ -1,6 +1,10 @@
import 'package:cake_wallet/buy/buy_amount.dart';
+import 'package:cake_wallet/buy/buy_quote.dart';
import 'package:cake_wallet/buy/order.dart';
+import 'package:cake_wallet/buy/payment_method.dart';
+import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:flutter/material.dart';
@@ -23,14 +27,38 @@ abstract class BuyProvider {
String get darkIcon;
+ bool get isAggregator;
+
@override
String toString() => title;
- Future launchProvider(BuildContext context, bool? isBuyAction);
+ Future? launchProvider(
+ {required BuildContext context,
+ required Quote quote,
+ required double amount,
+ required bool isBuyAction,
+ required String cryptoCurrencyAddress,
+ String? countryCode}) =>
+ null;
Future requestUrl(String amount, String sourceCurrency) => throw UnimplementedError();
Future findOrderById(String id) => throw UnimplementedError();
- Future calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError();
+ Future calculateAmount(String amount, String sourceCurrency) =>
+ throw UnimplementedError();
+
+ Future> getAvailablePaymentTypes(
+ String fiatCurrency, String cryptoCurrency, bool isBuyAction) async =>
+ [];
+
+ Future?> fetchQuote(
+ {required CryptoCurrency cryptoCurrency,
+ required FiatCurrency fiatCurrency,
+ required double amount,
+ required bool isBuyAction,
+ required String walletAddress,
+ PaymentType? paymentType,
+ String? countryCode}) async =>
+ null;
}
diff --git a/lib/buy/buy_quote.dart b/lib/buy/buy_quote.dart
new file mode 100644
index 000000000..72ab7bd7d
--- /dev/null
+++ b/lib/buy/buy_quote.dart
@@ -0,0 +1,302 @@
+import 'package:cake_wallet/buy/buy_provider.dart';
+import 'package:cake_wallet/buy/payment_method.dart';
+import 'package:cake_wallet/core/selectable_option.dart';
+import 'package:cake_wallet/entities/fiat_currency.dart';
+import 'package:cake_wallet/entities/provider_types.dart';
+import 'package:cake_wallet/exchange/limits.dart';
+import 'package:cw_core/crypto_currency.dart';
+
+enum ProviderRecommendation { bestRate, lowKyc, successRate }
+
+extension RecommendationTitle on ProviderRecommendation {
+ String get title {
+ switch (this) {
+ case ProviderRecommendation.bestRate:
+ return 'BEST RATE';
+ case ProviderRecommendation.lowKyc:
+ return 'LOW KYC';
+ case ProviderRecommendation.successRate:
+ return 'SUCCESS RATE';
+ }
+ }
+}
+
+ProviderRecommendation? getRecommendationFromString(String title) {
+ switch (title) {
+ case 'BEST RATE':
+ return ProviderRecommendation.bestRate;
+ case 'LowKyc':
+ return ProviderRecommendation.lowKyc;
+ case 'SuccessRate':
+ return ProviderRecommendation.successRate;
+ default:
+ return null;
+ }
+}
+
+class Quote extends SelectableOption {
+ Quote({
+ required this.rate,
+ required this.feeAmount,
+ required this.networkFee,
+ required this.transactionFee,
+ required this.payout,
+ required this.provider,
+ required this.paymentType,
+ required this.recommendations,
+ this.isBuyAction = true,
+ this.quoteId,
+ this.rampId,
+ this.rampName,
+ this.rampIconPath,
+ this.limits,
+ }) : super(title: provider.isAggregator ? rampName ?? '' : provider.title);
+
+ final double rate;
+ final double feeAmount;
+ final double networkFee;
+ final double transactionFee;
+ final double payout;
+ final PaymentType paymentType;
+ final BuyProvider provider;
+ final String? quoteId;
+ final List recommendations;
+ String? rampId;
+ String? rampName;
+ String? rampIconPath;
+ bool _isSelected = false;
+ bool _isBestRate = false;
+ bool isBuyAction;
+ Limits? limits;
+
+ late FiatCurrency _fiatCurrency;
+ late CryptoCurrency _cryptoCurrency;
+
+
+ bool get isSelected => _isSelected;
+ bool get isBestRate => _isBestRate;
+ FiatCurrency get fiatCurrency => _fiatCurrency;
+ CryptoCurrency get cryptoCurrency => _cryptoCurrency;
+
+ @override
+ bool get isOptionSelected => this._isSelected;
+
+ @override
+ String get lightIconPath =>
+ provider.isAggregator ? rampIconPath ?? provider.lightIcon : provider.lightIcon;
+
+ @override
+ String get darkIconPath =>
+ provider.isAggregator ? rampIconPath ?? provider.darkIcon : provider.darkIcon;
+
+ @override
+ List get badges => recommendations.map((e) => e.title).toList();
+
+ @override
+ String get topLeftSubTitle =>
+ this.rate > 0 ? '1 $cryptoName = ${rate.toStringAsFixed(2)} $fiatName' : '';
+
+ @override
+ String get bottomLeftSubTitle {
+ if (limits != null) {
+ final min = limits!.min;
+ final max = limits!.max;
+ return 'min: ${min} ${fiatCurrency.toString()} | max: ${max == double.infinity ? '' : '${max} ${fiatCurrency.toString()}'}';
+ }
+ return '';
+ }
+
+ String get fiatName => isBuyAction ? fiatCurrency.toString() : cryptoCurrency.toString();
+
+ String get cryptoName => isBuyAction ? cryptoCurrency.toString() : fiatCurrency.toString();
+
+ @override
+ String? get topRightSubTitle => '';
+
+ @override
+ String get topRightSubTitleLightIconPath => provider.isAggregator ? provider.lightIcon : '';
+
+ @override
+ String get topRightSubTitleDarkIconPath => provider.isAggregator ? provider.darkIcon : '';
+
+ String get quoteTitle => '${provider.title} - ${paymentType.name}';
+
+ String get formatedFee => '$feeAmount ${isBuyAction ? fiatCurrency : cryptoCurrency}';
+
+ set setIsSelected(bool isSelected) => _isSelected = isSelected;
+ set setIsBestRate(bool isBestRate) => _isBestRate = isBestRate;
+ set setFiatCurrency(FiatCurrency fiatCurrency) => _fiatCurrency = fiatCurrency;
+ set setCryptoCurrency(CryptoCurrency cryptoCurrency) => _cryptoCurrency = cryptoCurrency;
+ set setLimits(Limits limits) => this.limits = limits;
+
+ factory Quote.fromOnramperJson(Map json, bool isBuyAction,
+ Map metaData, PaymentType paymentType) {
+ final rate = _toDouble(json['rate']) ?? 0.0;
+ final networkFee = _toDouble(json['networkFee']) ?? 0.0;
+ final transactionFee = _toDouble(json['transactionFee']) ?? 0.0;
+ final feeAmount = double.parse((networkFee + transactionFee).toStringAsFixed(2));
+
+ final rampId = json['ramp'] as String? ?? '';
+ final rampData = metaData[rampId] ?? {};
+ final rampName = rampData['displayName'] as String? ?? '';
+ final rampIconPath = rampData['svg'] as String? ?? '';
+
+ final recommendations = json['recommendations'] != null
+ ? List.from(json['recommendations'] as List)
+ : [];
+
+ final enumRecommendations = recommendations
+ .map((e) => getRecommendationFromString(e))
+ .whereType()
+ .toList();
+
+ final availablePaymentMethods = json['availablePaymentMethods'] as List? ?? [];
+ double minLimit = 0.0;
+ double maxLimit = double.infinity;
+
+ for (var paymentMethod in availablePaymentMethods) {
+ if (paymentMethod is Map) {
+ final details = paymentMethod['details'] as Map?;
+
+ if (details != null) {
+ final limits = details['limits'] as Map?;
+
+ if (limits != null && limits.isNotEmpty) {
+ final firstLimitEntry = limits.values.first as Map?;
+ if (firstLimitEntry != null) {
+ minLimit = _toDouble(firstLimitEntry['min'])?.roundToDouble() ?? 0.0;
+ maxLimit = _toDouble(firstLimitEntry['max'])?.roundToDouble() ?? double.infinity;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return Quote(
+ rate: rate,
+ feeAmount: feeAmount,
+ networkFee: networkFee,
+ transactionFee: transactionFee,
+ payout: json['payout'] as double? ?? 0.0,
+ rampId: rampId,
+ rampName: rampName,
+ rampIconPath: rampIconPath,
+ paymentType: paymentType,
+ quoteId: json['quoteId'] as String? ?? '',
+ recommendations: enumRecommendations,
+ provider: ProvidersHelper.getProviderByType(ProviderType.onramper)!,
+ isBuyAction: isBuyAction,
+ limits: Limits(min: minLimit, max: maxLimit),
+ );
+ }
+
+ factory Quote.fromMoonPayJson(
+ Map json, bool isBuyAction, PaymentType paymentType) {
+ final rate = isBuyAction
+ ? json['quoteCurrencyPrice'] as double? ?? 0.0
+ : json['baseCurrencyPrice'] as double? ?? 0.0;
+ final fee = _toDouble(json['feeAmount']) ?? 0.0;
+ final networkFee = _toDouble(json['networkFeeAmount']) ?? 0.0;
+ final transactionFee = _toDouble(json['extraFeeAmount']) ?? 0.0;
+ final feeAmount = double.parse((fee + networkFee + transactionFee).toStringAsFixed(2));
+
+ final baseCurrency = json['baseCurrency'] as Map?;
+
+ double minLimit = 0.0;
+ double maxLimit = double.infinity;
+
+ if (baseCurrency != null) {
+ minLimit = _toDouble(baseCurrency['minAmount']) ?? minLimit;
+ maxLimit = _toDouble(baseCurrency['maxAmount']) ?? maxLimit;
+ }
+
+ return Quote(
+ rate: rate,
+ feeAmount: feeAmount,
+ networkFee: networkFee,
+ transactionFee: transactionFee,
+ payout: _toDouble(json['quoteCurrencyAmount']) ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [],
+ quoteId: json['signature'] as String? ?? '',
+ provider: ProvidersHelper.getProviderByType(ProviderType.moonpay)!,
+ isBuyAction: isBuyAction,
+ limits: Limits(min: minLimit, max: maxLimit),
+ );
+ }
+
+ factory Quote.fromDFXJson(
+ Map json,
+ bool isBuyAction,
+ PaymentType paymentType,
+ ) {
+ final rate = _toDouble(json['exchangeRate']) ?? 0.0;
+ final fees = json['fees'] as Map;
+
+ final minVolume = _toDouble(json['minVolume']) ?? 0.0;
+ final maxVolume = _toDouble(json['maxVolume']) ?? double.infinity;
+
+ return Quote(
+ rate: isBuyAction ? rate : 1 / rate,
+ feeAmount: _toDouble(json['feeAmount']) ?? 0.0,
+ networkFee: _toDouble(fees['network']) ?? 0.0,
+ transactionFee: _toDouble(fees['rate']) ?? 0.0,
+ payout: _toDouble(json['payout']) ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [ProviderRecommendation.lowKyc],
+ provider: ProvidersHelper.getProviderByType(ProviderType.dfx)!,
+ isBuyAction: isBuyAction,
+ limits: Limits(min: minVolume, max: maxVolume),
+ );
+ }
+
+ factory Quote.fromRobinhoodJson(
+ Map json, bool isBuyAction, PaymentType paymentType) {
+ final networkFee = json['networkFee'] as Map;
+ final processingFee = json['processingFee'] as Map;
+ final networkFeeAmount = _toDouble(networkFee['fiatAmount']) ?? 0.0;
+ final transactionFeeAmount = _toDouble(processingFee['fiatAmount']) ?? 0.0;
+ final feeAmount = double.parse((networkFeeAmount + transactionFeeAmount).toStringAsFixed(2));
+
+ return Quote(
+ rate: _toDouble(json['price']) ?? 0.0,
+ feeAmount: feeAmount,
+ networkFee: _toDouble(networkFee['fiatAmount']) ?? 0.0,
+ transactionFee: _toDouble(processingFee['fiatAmount']) ?? 0.0,
+ payout: _toDouble(json['cryptoAmount']) ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [],
+ provider: ProvidersHelper.getProviderByType(ProviderType.robinhood)!,
+ isBuyAction: isBuyAction,
+ limits: Limits(min: 0.0, max: double.infinity),
+ );
+ }
+
+ factory Quote.fromMeldJson(Map json, bool isBuyAction, PaymentType paymentType) {
+ final quotes = json['quotes'][0] as Map;
+ return Quote(
+ rate: quotes['exchangeRate'] as double? ?? 0.0,
+ feeAmount: quotes['totalFee'] as double? ?? 0.0,
+ networkFee: quotes['networkFee'] as double? ?? 0.0,
+ transactionFee: quotes['transactionFee'] as double? ?? 0.0,
+ payout: quotes['payout'] as double? ?? 0.0,
+ paymentType: paymentType,
+ recommendations: [],
+ provider: ProvidersHelper.getProviderByType(ProviderType.meld)!,
+ isBuyAction: isBuyAction,
+ limits: Limits(min: 0.0, max: double.infinity),
+ );
+ }
+
+ static double? _toDouble(dynamic value) {
+ if (value is int) {
+ return value.toDouble();
+ } else if (value is double) {
+ return value;
+ } else if (value is String) {
+ return double.tryParse(value);
+ }
+ return null;
+ }
+}
diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart
index b3ed72498..c1ed762b1 100644
--- a/lib/buy/dfx/dfx_buy_provider.dart
+++ b/lib/buy/dfx/dfx_buy_provider.dart
@@ -1,13 +1,17 @@
import 'dart:convert';
+import 'dart:developer';
import 'package:cake_wallet/buy/buy_provider.dart';
+import 'package:cake_wallet/buy/buy_quote.dart';
+import 'package:cake_wallet/buy/payment_method.dart';
+import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
-import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
+import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -15,10 +19,12 @@ import 'package:http/http.dart' as http;
import 'package:url_launcher/url_launcher.dart';
class DFXBuyProvider extends BuyProvider {
- DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
+ DFXBuyProvider(
+ {required WalletBase wallet, bool isTestEnvironment = false, LedgerViewModel? ledgerVM})
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: ledgerVM);
static const _baseUrl = 'api.dfx.swiss';
+
// static const _signMessagePath = '/v1/auth/signMessage';
static const _authPath = '/v1/auth';
static const walletName = 'CakeWallet';
@@ -35,24 +41,8 @@ class DFXBuyProvider extends BuyProvider {
@override
String get darkIcon => 'assets/images/dfx_dark.png';
- String get assetOut {
- switch (wallet.type) {
- case WalletType.bitcoin:
- return 'BTC';
- case WalletType.bitcoinCash:
- return 'BCH';
- case WalletType.litecoin:
- return 'LTC';
- case WalletType.monero:
- return 'XMR';
- case WalletType.ethereum:
- return 'ETH';
- case WalletType.polygon:
- return 'MATIC';
- default:
- throw Exception("WalletType is not available for DFX ${wallet.type}");
- }
- }
+ @override
+ bool get isAggregator => false;
String get blockchain {
switch (wallet.type) {
@@ -60,21 +50,13 @@ class DFXBuyProvider extends BuyProvider {
case WalletType.bitcoinCash:
case WalletType.litecoin:
return 'Bitcoin';
- case WalletType.monero:
- return 'Monero';
- case WalletType.ethereum:
- return 'Ethereum';
- case WalletType.polygon:
- return 'Polygon';
default:
- throw Exception("WalletType is not available for DFX ${wallet.type}");
+ return walletTypeToString(wallet.type);
}
}
- String get walletAddress =>
- wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address;
- Future getSignMessage() async =>
+ Future getSignMessage(String walletAddress) async =>
"By_signing_this_message,_you_confirm_that_you_are_the_sole_owner_of_the_provided_Blockchain_address._Your_ID:_$walletAddress";
// // Lets keep this just in case, but we can avoid this API Call
@@ -92,8 +74,9 @@ class DFXBuyProvider extends BuyProvider {
// }
// }
- Future auth() async {
- final signMessage = await getSignature(await getSignMessage());
+ Future auth(String walletAddress) async {
+ final signMessage = await getSignature(
+ await getSignMessage(walletAddress), walletAddress);
final requestBody = jsonEncode({
'wallet': walletName,
@@ -120,7 +103,7 @@ class DFXBuyProvider extends BuyProvider {
}
}
- Future getSignature(String message) async {
+ Future getSignature(String message, String walletAddress) async {
switch (wallet.type) {
case WalletType.ethereum:
case WalletType.polygon:
@@ -135,8 +118,178 @@ class DFXBuyProvider extends BuyProvider {
}
}
+ Future