mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-02-02 03:06:35 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into bitcoin-derivations
This commit is contained in:
commit
45bfcbc09a
234 changed files with 14186 additions and 1647 deletions
29
.github/workflows/pr_test_build.yml
vendored
29
.github/workflows/pr_test_build.yml
vendored
|
@ -3,6 +3,12 @@ name: PR Test Build
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
description: 'Branch name to build'
|
||||||
|
required: true
|
||||||
|
default: 'main'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
PR_test_build:
|
PR_test_build:
|
||||||
|
@ -12,6 +18,14 @@ jobs:
|
||||||
KEY_PASS: test@cake_wallet
|
KEY_PASS: test@cake_wallet
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: is pr
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: is not pr
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Free Up GitHub Actions Ubuntu Runner Disk Space
|
- name: Free Up GitHub Actions Ubuntu Runner Disk Space
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf /usr/share/dotnet
|
sudo rm -rf /usr/share/dotnet
|
||||||
|
@ -40,7 +54,7 @@ jobs:
|
||||||
cd /opt/android
|
cd /opt/android
|
||||||
-y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
-y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
cargo install cargo-ndk
|
cargo install cargo-ndk
|
||||||
git clone https://github.com/cake-tech/cake_wallet.git --branch $GITHUB_HEAD_REF
|
git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }}
|
||||||
cd cake_wallet/scripts/android/
|
cd cake_wallet/scripts/android/
|
||||||
./install_ndk.sh
|
./install_ndk.sh
|
||||||
source ./app_env.sh cakewallet
|
source ./app_env.sh cakewallet
|
||||||
|
@ -97,6 +111,7 @@ jobs:
|
||||||
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Add secrets
|
- name: Add secrets
|
||||||
|
@ -104,6 +119,7 @@ jobs:
|
||||||
cd /opt/android/cake_wallet
|
cd /opt/android/cake_wallet
|
||||||
touch lib/.secrets.g.dart
|
touch lib/.secrets.g.dart
|
||||||
touch cw_ethereum/lib/.secrets.g.dart
|
touch cw_ethereum/lib/.secrets.g.dart
|
||||||
|
touch cw_polygon/lib/.secrets.g.dart
|
||||||
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
|
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
|
||||||
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
||||||
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
|
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
@ -137,9 +153,10 @@ jobs:
|
||||||
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
||||||
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
|
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
|
||||||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
||||||
|
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
|
||||||
|
|
||||||
- name: Rename app
|
- name: Rename app
|
||||||
run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
|
run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
@ -154,7 +171,7 @@ jobs:
|
||||||
# appcenter distribute release \
|
# appcenter distribute release \
|
||||||
# --group "Testers" \
|
# --group "Testers" \
|
||||||
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
|
# --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \
|
||||||
# --release-notes ${GITHUB_HEAD_REF} \
|
# --release-notes ${{ env.BRANCH_NAME }} \
|
||||||
# --app Cake-Labs/Cake-Wallet \
|
# --app Cake-Labs/Cake-Wallet \
|
||||||
# --token ${{ secrets.APP_CENTER_TOKEN }} \
|
# --token ${{ secrets.APP_CENTER_TOKEN }} \
|
||||||
# --quiet
|
# --quiet
|
||||||
|
@ -163,7 +180,7 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
cd /opt/android/cake_wallet/build/app/outputs/apk/release
|
cd /opt/android/cake_wallet/build/app/outputs/apk/release
|
||||||
mkdir test-apk
|
mkdir test-apk
|
||||||
cp app-release.apk test-apk/$GITHUB_HEAD_REF.apk
|
cp app-release.apk test-apk/${{env.BRANCH_NAME}}.apk
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
uses: kittaakos/upload-artifact-as-is@v0
|
uses: kittaakos/upload-artifact-as-is@v0
|
||||||
|
@ -177,6 +194,6 @@ jobs:
|
||||||
token: ${{ secrets.SLACK_APP_TOKEN }}
|
token: ${{ secrets.SLACK_APP_TOKEN }}
|
||||||
path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk
|
path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk
|
||||||
channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
||||||
title: "${{github.head_ref}}.apk"
|
title: "${{ env.BRANCH_NAME }}.apk"
|
||||||
filename: ${{github.head_ref}}.apk
|
filename: ${{ env.BRANCH_NAME }}.apk
|
||||||
initial_comment: ${{ github.event.head_commit.message }}
|
initial_comment: ${{ github.event.head_commit.message }}
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -126,6 +126,7 @@ lib/haven/haven.dart
|
||||||
lib/ethereum/ethereum.dart
|
lib/ethereum/ethereum.dart
|
||||||
lib/bitcoin_cash/bitcoin_cash.dart
|
lib/bitcoin_cash/bitcoin_cash.dart
|
||||||
lib/nano/nano.dart
|
lib/nano/nano.dart
|
||||||
|
lib/polygon/polygon.dart
|
||||||
|
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||||
|
|
|
@ -62,6 +62,9 @@
|
||||||
<data android:scheme="bitcoincash" />
|
<data android:scheme="bitcoincash" />
|
||||||
<data android:scheme="bitcoincash-wallet" />
|
<data android:scheme="bitcoincash-wallet" />
|
||||||
<data android:scheme="bitcoincash_wallet" />
|
<data android:scheme="bitcoincash_wallet" />
|
||||||
|
<data android:scheme="polygon" />
|
||||||
|
<data android:scheme="polygon-wallet" />
|
||||||
|
<data android:scheme="polygon_wallet" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|
BIN
assets/images/dfx_dark.png
Normal file
BIN
assets/images/dfx_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
assets/images/dfx_light.png
Normal file
BIN
assets/images/dfx_light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
assets/images/moonpay_dark.png
Normal file
BIN
assets/images/moonpay_dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
assets/images/moonpay_light.png
Normal file
BIN
assets/images/moonpay_light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
assets/images/setup_2fa_img.png
Normal file
BIN
assets/images/setup_2fa_img.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
6
assets/polygon_node_list.yml
Normal file
6
assets/polygon_node_list.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-
|
||||||
|
uri: polygon-rpc.com
|
||||||
|
-
|
||||||
|
uri: polygon-bor.publicnode.com
|
||||||
|
-
|
||||||
|
uri: polygon.llamarpc.com
|
|
@ -1,7 +1,4 @@
|
||||||
Coin control fixes and enhancements
|
Polyseed enhancements
|
||||||
In-app Tor connection
|
New on-ramp provider DFX
|
||||||
Accessibility enhancements
|
Usability enhancements
|
||||||
Privacy settings enhancements
|
|
||||||
UI enhancements
|
|
||||||
Backup flow fixes
|
|
||||||
Bug fixes
|
Bug fixes
|
|
@ -1,7 +1,2 @@
|
||||||
Coin control fixes and enhancements
|
Support multiple address types for Bitcoin Cash
|
||||||
In-app Tor connection
|
|
||||||
Accessibility enhancements
|
|
||||||
Privacy settings enhancements
|
|
||||||
UI enhancements
|
|
||||||
Backup flow fixes
|
|
||||||
Bug fixes
|
Bug fixes
|
8717
assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt
Normal file
8717
assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt
Normal file
File diff suppressed because it is too large
Load diff
|
@ -10,4 +10,5 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build --
|
||||||
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
|
cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
|
|
@ -4,7 +4,10 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cw_core/balance.dart';
|
import 'package:cw_core/balance.dart';
|
||||||
|
|
||||||
class ElectrumBalance extends Balance {
|
class ElectrumBalance extends Balance {
|
||||||
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
|
const ElectrumBalance(
|
||||||
|
{required this.confirmed,
|
||||||
|
required this.unconfirmed,
|
||||||
|
required this.frozen})
|
||||||
: super(confirmed, unconfirmed);
|
: super(confirmed, unconfirmed);
|
||||||
|
|
||||||
static ElectrumBalance? fromJSON(String? jsonSource) {
|
static ElectrumBalance? fromJSON(String? jsonSource) {
|
||||||
|
@ -25,16 +28,19 @@ class ElectrumBalance extends Balance {
|
||||||
final int frozen;
|
final int frozen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
|
String get formattedAvailableBalance =>
|
||||||
|
bitcoinAmountToString(amount: confirmed - unconfirmed.abs() - frozen);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
|
String get formattedAdditionalBalance =>
|
||||||
|
bitcoinAmountToString(amount: unconfirmed);
|
||||||
|
|
||||||
String get formattedFrozenBalance {
|
@override
|
||||||
|
String get formattedUnAvailableBalance {
|
||||||
final frozenFormatted = bitcoinAmountToString(amount: frozen);
|
final frozenFormatted = bitcoinAmountToString(amount: frozen);
|
||||||
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJSON() =>
|
String toJSON() => json.encode(
|
||||||
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
|
{'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_core/utils/file.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:cw_core/transaction_history.dart';
|
|
||||||
import 'package:cw_bitcoin/file.dart';
|
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
|
||||||
|
|
||||||
part 'electrum_transaction_history.g.dart';
|
part 'electrum_transaction_history.g.dart';
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
||||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||||
import 'package:cw_bitcoin/file.dart';
|
|
||||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||||
import 'package:cw_bitcoin/script_hash.dart';
|
import 'package:cw_bitcoin/script_hash.dart';
|
||||||
import 'package:cw_bitcoin/utils.dart';
|
import 'package:cw_bitcoin/utils.dart';
|
||||||
|
@ -30,6 +29,7 @@ import 'package:cw_core/sync_status.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_priority.dart';
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
import 'package:cw_core/utils/file.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -82,7 +82,7 @@ abstract class ElectrumWalletBase
|
||||||
bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/0");
|
bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/0");
|
||||||
|
|
||||||
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
||||||
inputsCount * 146 + outputsCounts * 33 + 8;
|
inputsCount * 68 + outputsCounts * 34 + 10;
|
||||||
|
|
||||||
final bitcoin.HDWallet hd;
|
final bitcoin.HDWallet hd;
|
||||||
final String mnemonic;
|
final String mnemonic;
|
||||||
|
@ -725,8 +725,7 @@ abstract class ElectrumWalletBase
|
||||||
final index = address != null
|
final index = address != null
|
||||||
? walletAddresses.addresses.firstWhere((element) => element.address == address).index
|
? walletAddresses.addresses.firstWhere((element) => element.address == address).index
|
||||||
: null;
|
: null;
|
||||||
return index == null
|
final HD = index == null ? hd : hd.derive(index);
|
||||||
? base64Encode(hd.sign(message))
|
return base64Encode(HD.signMessage(message));
|
||||||
: base64Encode(hd.derive(index).sign(message));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||||
import 'package:cw_bitcoin/file.dart';
|
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/utils/file.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class ElectrumWallletSnapshot {
|
class ElectrumWallletSnapshot {
|
||||||
|
|
|
@ -79,11 +79,11 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: cake-update-v3
|
ref: cake-update-v4
|
||||||
resolved-ref: df9204144011ed9419eff7d9ef3143102a40252d
|
resolved-ref: e19ffb7e7977278a75b27e0479b3c6f4034223b3
|
||||||
url: "https://github.com/cake-tech/bitcoin_flutter.git"
|
url: "https://github.com/cake-tech/bitcoin_flutter.git"
|
||||||
source: git
|
source: git
|
||||||
version: "2.0.2"
|
version: "2.1.0"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -244,7 +244,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.4"
|
version: "2.2.4"
|
||||||
encrypt:
|
encrypt:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: encrypt
|
name: encrypt
|
||||||
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
|
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
|
||||||
|
@ -698,15 +698,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
tor:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: main
|
|
||||||
resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5"
|
|
||||||
url: "https://github.com/cake-tech/tor.git"
|
|
||||||
source: git
|
|
||||||
version: "0.0.1"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -22,7 +22,7 @@ dependencies:
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
url: https://github.com/cake-tech/bitcoin_flutter.git
|
||||||
ref: cake-update-v3
|
ref: cake-update-v4
|
||||||
bitbox:
|
bitbox:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||||
|
@ -30,7 +30,6 @@ dependencies:
|
||||||
rxdart: ^0.27.5
|
rxdart: ^0.27.5
|
||||||
unorm_dart: ^0.2.0
|
unorm_dart: ^0.2.0
|
||||||
cryptography: ^2.0.5
|
cryptography: ^2.0.5
|
||||||
encrypt: ^5.0.1
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||||
|
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||||
|
@ -210,9 +211,28 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
txb.addInput(input.hash, input.vout);
|
txb.addInput(input.hash, input.vout);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String bchPrefix = "bitcoincash:";
|
||||||
|
|
||||||
outputs.forEach((item) {
|
outputs.forEach((item) {
|
||||||
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
|
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
|
||||||
final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
|
String outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
|
||||||
|
|
||||||
|
if (!outputAddress.startsWith(bchPrefix)) {
|
||||||
|
outputAddress = "$bchPrefix$outputAddress";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isP2sh = outputAddress.startsWith("p", bchPrefix.length);
|
||||||
|
|
||||||
|
if (isP2sh) {
|
||||||
|
final p2sh = P2shAddress.fromAddress(
|
||||||
|
address: outputAddress,
|
||||||
|
network: BitcoinCashNetwork.mainnet,
|
||||||
|
);
|
||||||
|
|
||||||
|
txb.addOutput(Uint8List.fromList(p2sh.toScriptPubKey().toBytes()), outputAmount!);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
txb.addOutput(outputAddress, outputAmount!);
|
txb.addOutput(outputAddress, outputAmount!);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -302,10 +322,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
||||||
final index = address != null
|
final index = address != null
|
||||||
? walletAddresses.addresses
|
? walletAddresses.addresses
|
||||||
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
||||||
.index
|
.index : null;
|
||||||
: null;
|
final HD = index == null ? hd : hd.derive(index);
|
||||||
return index == null
|
return base64Encode(HD.signMessage(message));
|
||||||
? base64Encode(hd.sign(message))
|
|
||||||
: base64Encode(hd.derive(index).sign(message));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
/Users/blazebrain/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/
|
|
|
@ -1 +0,0 @@
|
||||||
/Users/blazebrain/.pub-cache/git/tor-09ba92cb11d4e3cacf97256e57863b805f79f2e5/
|
|
|
@ -1,11 +0,0 @@
|
||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
//
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
|
||||||
|
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
//
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
|
||||||
#define GENERATED_PLUGIN_REGISTRANT_
|
|
||||||
|
|
||||||
#include <flutter_linux/flutter_linux.h>
|
|
||||||
|
|
||||||
// Registers Flutter plugins.
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry);
|
|
||||||
|
|
||||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
|
|
@ -1,24 +0,0 @@
|
||||||
#
|
|
||||||
# Generated file, do not edit.
|
|
||||||
#
|
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
|
||||||
)
|
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
|
||||||
tor
|
|
||||||
)
|
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
|
||||||
|
|
||||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
|
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
|
||||||
endforeach(plugin)
|
|
||||||
|
|
||||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin})
|
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
|
||||||
endforeach(ffi_plugin)
|
|
|
@ -1,12 +0,0 @@
|
||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
//
|
|
||||||
|
|
||||||
import FlutterMacOS
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
import path_provider_foundation
|
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
// This is a generated file; do not edit or check into version control.
|
|
||||||
FLUTTER_ROOT=C:\Users\borod\flutter
|
|
||||||
FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash
|
|
||||||
COCOAPODS_PARALLEL_CODE_SIGN=true
|
|
||||||
FLUTTER_BUILD_DIR=build
|
|
||||||
FLUTTER_BUILD_NAME=0.0.1
|
|
||||||
FLUTTER_BUILD_NUMBER=0.0.1
|
|
||||||
DART_OBFUSCATION=false
|
|
||||||
TRACK_WIDGET_CREATION=true
|
|
||||||
TREE_SHAKE_ICONS=false
|
|
||||||
PACKAGE_CONFIG=.dart_tool/package_config.json
|
|
|
@ -1,12 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# This is a generated file; do not edit or check into version control.
|
|
||||||
export "FLUTTER_ROOT=C:\Users\borod\flutter"
|
|
||||||
export "FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash"
|
|
||||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
|
||||||
export "FLUTTER_BUILD_DIR=build"
|
|
||||||
export "FLUTTER_BUILD_NAME=0.0.1"
|
|
||||||
export "FLUTTER_BUILD_NUMBER=0.0.1"
|
|
||||||
export "DART_OBFUSCATION=false"
|
|
||||||
export "TRACK_WIDGET_CREATION=true"
|
|
||||||
export "TREE_SHAKE_ICONS=false"
|
|
||||||
export "PACKAGE_CONFIG=.dart_tool/package_config.json"
|
|
|
@ -24,11 +24,12 @@ dependencies:
|
||||||
bitcoin_flutter:
|
bitcoin_flutter:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitcoin_flutter.git
|
url: https://github.com/cake-tech/bitcoin_flutter.git
|
||||||
ref: cake-update-v3
|
ref: cake-update-v4
|
||||||
bitbox:
|
bitbox:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||||
ref: master
|
ref: master
|
||||||
|
bitcoin_base: ^3.0.1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
//
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
|
||||||
|
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
//
|
|
||||||
// Generated file. Do not edit.
|
|
||||||
//
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
|
|
||||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
|
||||||
#define GENERATED_PLUGIN_REGISTRANT_
|
|
||||||
|
|
||||||
#include <flutter/plugin_registry.h>
|
|
||||||
|
|
||||||
// Registers Flutter plugins.
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry);
|
|
||||||
|
|
||||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
|
|
@ -1,23 +0,0 @@
|
||||||
#
|
|
||||||
# Generated file, do not edit.
|
|
||||||
#
|
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
|
||||||
)
|
|
||||||
|
|
||||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
|
||||||
)
|
|
||||||
|
|
||||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
|
||||||
|
|
||||||
foreach(plugin ${FLUTTER_PLUGIN_LIST})
|
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
|
|
||||||
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
|
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
|
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
|
|
||||||
endforeach(plugin)
|
|
||||||
|
|
||||||
foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
|
|
||||||
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
|
|
||||||
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
|
|
||||||
endforeach(ffi_plugin)
|
|
|
@ -93,6 +93,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
CryptoCurrency.dydx,
|
CryptoCurrency.dydx,
|
||||||
CryptoCurrency.steth,
|
CryptoCurrency.steth,
|
||||||
CryptoCurrency.banano,
|
CryptoCurrency.banano,
|
||||||
|
CryptoCurrency.usdtPoly,
|
||||||
|
CryptoCurrency.usdcEPoly,
|
||||||
];
|
];
|
||||||
|
|
||||||
static const havenCurrencies = [
|
static const havenCurrencies = [
|
||||||
|
@ -202,6 +204,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png', decimals: 18);
|
static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png', decimals: 18);
|
||||||
static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png', decimals: 18);
|
static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png', decimals: 18);
|
||||||
static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29);
|
static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29);
|
||||||
|
static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||||
|
static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||||
|
|
||||||
|
|
||||||
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||||
|
@ -241,7 +245,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
}
|
}
|
||||||
|
|
||||||
static CryptoCurrency fromFullName(String name) {
|
static CryptoCurrency fromFullName(String name) {
|
||||||
if (CryptoCurrency._fullNameCurrencyMap[name.toLowerCase()] == null) {
|
if (CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()] == null) {
|
||||||
final s = 'Unexpected token: $name for CryptoCurrency fromFullName';
|
final s = 'Unexpected token: $name for CryptoCurrency fromFullName';
|
||||||
throw ArgumentError.value(name, 'Fullname', s);
|
throw ArgumentError.value(name, 'Fullname', s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
|
||||||
return CryptoCurrency.nano;
|
return CryptoCurrency.nano;
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return CryptoCurrency.banano;
|
return CryptoCurrency.banano;
|
||||||
|
case WalletType.polygon:
|
||||||
|
return CryptoCurrency.maticpoly;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
|
||||||
bool _enabled;
|
bool _enabled;
|
||||||
@HiveField(5)
|
@HiveField(5)
|
||||||
final String? iconPath;
|
final String? iconPath;
|
||||||
|
@HiveField(6)
|
||||||
|
final String? tag;
|
||||||
|
|
||||||
bool get enabled => _enabled;
|
bool get enabled => _enabled;
|
||||||
|
|
||||||
|
@ -30,37 +32,41 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
|
||||||
required this.decimal,
|
required this.decimal,
|
||||||
bool enabled = true,
|
bool enabled = true,
|
||||||
this.iconPath,
|
this.iconPath,
|
||||||
|
this.tag,
|
||||||
}) : _enabled = enabled,
|
}) : _enabled = enabled,
|
||||||
super(
|
super(
|
||||||
name: symbol.toLowerCase(),
|
name: symbol.toLowerCase(),
|
||||||
title: symbol.toUpperCase(),
|
title: symbol.toUpperCase(),
|
||||||
fullName: name,
|
fullName: name,
|
||||||
tag: "ETH",
|
tag: tag,
|
||||||
iconPath: iconPath,
|
iconPath: iconPath,
|
||||||
decimals: decimal
|
decimals: decimal);
|
||||||
);
|
|
||||||
|
|
||||||
Erc20Token.copyWith(Erc20Token other, String? icon)
|
Erc20Token.copyWith(Erc20Token other, String? icon, String? tag)
|
||||||
: this.name = other.name,
|
: this.name = other.name,
|
||||||
this.symbol = other.symbol,
|
this.symbol = other.symbol,
|
||||||
this.contractAddress = other.contractAddress,
|
this.contractAddress = other.contractAddress,
|
||||||
this.decimal = other.decimal,
|
this.decimal = other.decimal,
|
||||||
this._enabled = other.enabled,
|
this._enabled = other.enabled,
|
||||||
|
this.tag = tag,
|
||||||
this.iconPath = icon,
|
this.iconPath = icon,
|
||||||
super(
|
super(
|
||||||
name: other.name,
|
name: other.name,
|
||||||
title: other.symbol.toUpperCase(),
|
title: other.symbol.toUpperCase(),
|
||||||
fullName: other.name,
|
fullName: other.name,
|
||||||
tag: "ETH",
|
tag: tag,
|
||||||
iconPath: icon,
|
iconPath: icon,
|
||||||
decimals: other.decimal
|
decimals: other.decimal,
|
||||||
);
|
);
|
||||||
|
|
||||||
static const typeId = ERC20_TOKEN_TYPE_ID;
|
static const typeId = ERC20_TOKEN_TYPE_ID;
|
||||||
static const boxName = 'Erc20Tokens';
|
static const boxName = 'Erc20Tokens';
|
||||||
|
static const ethereumBoxName = 'EthereumErc20Tokens';
|
||||||
|
static const polygonBoxName = 'PolygonErc20Tokens';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(other) => (other is Erc20Token && other.contractAddress == contractAddress) ||
|
bool operator ==(other) =>
|
||||||
|
(other is Erc20Token && other.contractAddress == contractAddress) ||
|
||||||
(other is CryptoCurrency && other.title == title);
|
(other is CryptoCurrency && other.title == title);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -118,7 +118,10 @@ final dates = {
|
||||||
"2023-6": 2898234,
|
"2023-6": 2898234,
|
||||||
"2023-7": 2919771,
|
"2023-7": 2919771,
|
||||||
"2023-8": 2942045,
|
"2023-8": 2942045,
|
||||||
"2023-9": 2964280
|
"2023-9": 2964280,
|
||||||
|
"2023-10": 2985937,
|
||||||
|
"2023-11": 3008178,
|
||||||
|
"2023-12": 3029759
|
||||||
};
|
};
|
||||||
|
|
||||||
int getMoneroHeigthByDate({required DateTime date}) {
|
int getMoneroHeigthByDate({required DateTime date}) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:hive/hive.dart';
|
||||||
import 'package:cw_core/hive_type_ids.dart';
|
import 'package:cw_core/hive_type_ids.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:http/io_client.dart' as ioc;
|
import 'package:http/io_client.dart' as ioc;
|
||||||
import 'package:tor/tor.dart';
|
// import 'package:tor/tor.dart';
|
||||||
|
|
||||||
part 'node.g.dart';
|
part 'node.g.dart';
|
||||||
|
|
||||||
|
@ -88,6 +88,8 @@ class Node extends HiveObject with Keyable {
|
||||||
} else {
|
} else {
|
||||||
return Uri.http(uriRaw, '');
|
return Uri.http(uriRaw, '');
|
||||||
}
|
}
|
||||||
|
case WalletType.polygon:
|
||||||
|
return Uri.https(uriRaw, '');
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||||
}
|
}
|
||||||
|
@ -146,6 +148,8 @@ class Node extends HiveObject with Keyable {
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return requestNanoNode();
|
return requestNanoNode();
|
||||||
|
case WalletType.polygon:
|
||||||
|
return requestElectrumServer();
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -210,14 +214,17 @@ class Node extends HiveObject with Keyable {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> requestNodeWithProxy() async {
|
Future<bool> requestNodeWithProxy() async {
|
||||||
if (!isValidProxyAddress && !Tor.instance.enabled) {
|
if (!isValidProxyAddress /* && !Tor.instance.enabled*/) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? proxy = socksProxyAddress;
|
String? proxy = socksProxyAddress;
|
||||||
|
|
||||||
if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) {
|
// if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) {
|
||||||
proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}";
|
// proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}";
|
||||||
|
// }
|
||||||
|
if (proxy == null) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
final proxyAddress = proxy!.split(':')[0];
|
final proxyAddress = proxy!.split(':')[0];
|
||||||
final proxyPort = int.parse(proxy.split(':')[1]);
|
final proxyPort = int.parse(proxy.split(':')[1]);
|
||||||
|
|
|
@ -2,17 +2,8 @@ import 'dart:io';
|
||||||
import 'package:cw_core/key.dart';
|
import 'package:cw_core/key.dart';
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||||
|
|
||||||
Future<void> write(
|
Future<void> write({required String path, required String password, required String data}) async =>
|
||||||
{required String path,
|
writeData(path: path, password: password, data: data);
|
||||||
required String password,
|
|
||||||
required String data}) async {
|
|
||||||
final keys = extractKeys(password);
|
|
||||||
final key = encrypt.Key.fromBase64(keys.first);
|
|
||||||
final iv = encrypt.IV.fromBase64(keys.last);
|
|
||||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
|
||||||
final f = File(path);
|
|
||||||
f.writeAsStringSync(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> writeData(
|
Future<void> writeData(
|
||||||
{required String path,
|
{required String path,
|
|
@ -13,6 +13,7 @@ const walletTypes = [
|
||||||
WalletType.bitcoinCash,
|
WalletType.bitcoinCash,
|
||||||
WalletType.nano,
|
WalletType.nano,
|
||||||
WalletType.banano,
|
WalletType.banano,
|
||||||
|
WalletType.polygon,
|
||||||
];
|
];
|
||||||
|
|
||||||
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
|
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
|
||||||
|
@ -44,6 +45,8 @@ enum WalletType {
|
||||||
@HiveField(8)
|
@HiveField(8)
|
||||||
bitcoinCash,
|
bitcoinCash,
|
||||||
|
|
||||||
|
@HiveField(9)
|
||||||
|
polygon
|
||||||
}
|
}
|
||||||
|
|
||||||
int serializeToInt(WalletType type) {
|
int serializeToInt(WalletType type) {
|
||||||
|
@ -64,6 +67,8 @@ int serializeToInt(WalletType type) {
|
||||||
return 6;
|
return 6;
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
return 7;
|
return 7;
|
||||||
|
case WalletType.polygon:
|
||||||
|
return 8;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -87,6 +92,8 @@ WalletType deserializeFromInt(int raw) {
|
||||||
return WalletType.banano;
|
return WalletType.banano;
|
||||||
case 7:
|
case 7:
|
||||||
return WalletType.bitcoinCash;
|
return WalletType.bitcoinCash;
|
||||||
|
case 8:
|
||||||
|
return WalletType.polygon;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
||||||
}
|
}
|
||||||
|
@ -110,6 +117,8 @@ String walletTypeToString(WalletType type) {
|
||||||
return 'Nano';
|
return 'Nano';
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return 'Banano';
|
return 'Banano';
|
||||||
|
case WalletType.polygon:
|
||||||
|
return 'Polygon';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -133,6 +142,8 @@ String walletTypeToDisplayName(WalletType type) {
|
||||||
return 'Nano (XNO)';
|
return 'Nano (XNO)';
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return 'Banano (BAN)';
|
return 'Banano (BAN)';
|
||||||
|
case WalletType.polygon:
|
||||||
|
return 'Polygon (MATIC)';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -156,7 +167,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
|
||||||
return CryptoCurrency.nano;
|
return CryptoCurrency.nano;
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return CryptoCurrency.banano;
|
return CryptoCurrency.banano;
|
||||||
|
case WalletType.polygon:
|
||||||
|
return CryptoCurrency.maticpoly;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
throw Exception(
|
||||||
|
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -616,15 +616,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
tor:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: main
|
|
||||||
resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5"
|
|
||||||
url: "https://github.com/cake-tech/tor.git"
|
|
||||||
source: git
|
|
||||||
version: "0.0.1"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -20,10 +20,10 @@ dependencies:
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
encrypt: ^5.0.1
|
encrypt: ^5.0.1
|
||||||
socks5_proxy: ^1.0.4
|
socks5_proxy: ^1.0.4
|
||||||
tor:
|
# tor:
|
||||||
git:
|
# git:
|
||||||
url: https://github.com/cake-tech/tor.git
|
# url: https://github.com/cake-tech/tor.git
|
||||||
ref: main
|
# ref: main
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -300,10 +300,6 @@ class DefaultErc20Tokens {
|
||||||
.iconPath;
|
.iconPath;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
if (iconPath != null) {
|
return Erc20Token.copyWith(token, iconPath, 'ETH');
|
||||||
return Erc20Token.copyWith(token, iconPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
|
@ -15,12 +15,12 @@ import 'package:cw_ethereum/ethereum_transaction_priority.dart';
|
||||||
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
|
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
class EthereumClient {
|
class EthereumClient {
|
||||||
final _httpClient = Client();
|
final httpClient = Client();
|
||||||
Web3Client? _client;
|
Web3Client? _client;
|
||||||
|
|
||||||
bool connect(Node node) {
|
bool connect(Node node) {
|
||||||
try {
|
try {
|
||||||
_client = Web3Client(node.uri.toString(), _httpClient);
|
_client = Web3Client(node.uri.toString(), httpClient);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -74,29 +74,34 @@ class EthereumClient {
|
||||||
required int exponent,
|
required int exponent,
|
||||||
String? contractAddress,
|
String? contractAddress,
|
||||||
}) async {
|
}) async {
|
||||||
assert(currency == CryptoCurrency.eth || contractAddress != null);
|
assert(currency == CryptoCurrency.eth ||
|
||||||
|
currency == CryptoCurrency.maticpoly ||
|
||||||
|
contractAddress != null);
|
||||||
|
|
||||||
bool _isEthereum = currency == CryptoCurrency.eth;
|
bool _isEVMCompatibleChain =
|
||||||
|
currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
|
||||||
|
|
||||||
final price = _client!.getGasPrice();
|
final price = _client!.getGasPrice();
|
||||||
|
|
||||||
final Transaction transaction = Transaction(
|
final Transaction transaction = createTransaction(
|
||||||
from: privateKey.address,
|
from: privateKey.address,
|
||||||
to: EthereumAddress.fromHex(toAddress),
|
to: EthereumAddress.fromHex(toAddress),
|
||||||
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
||||||
value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
amount: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
||||||
);
|
);
|
||||||
|
|
||||||
final signedTransaction = await _client!.signTransaction(privateKey, transaction);
|
final signedTransaction =
|
||||||
|
await _client!.signTransaction(privateKey, transaction, chainId: chainId);
|
||||||
|
|
||||||
final Function _sendTransaction;
|
final Function _sendTransaction;
|
||||||
|
|
||||||
if (_isEthereum) {
|
if (_isEVMCompatibleChain) {
|
||||||
_sendTransaction = () async => await sendTransaction(signedTransaction);
|
_sendTransaction = () async => await sendTransaction(signedTransaction);
|
||||||
} else {
|
} else {
|
||||||
final erc20 = ERC20(
|
final erc20 = ERC20(
|
||||||
client: _client!,
|
client: _client!,
|
||||||
address: EthereumAddress.fromHex(contractAddress!),
|
address: EthereumAddress.fromHex(contractAddress!),
|
||||||
|
chainId: chainId,
|
||||||
);
|
);
|
||||||
|
|
||||||
_sendTransaction = () async {
|
_sendTransaction = () async {
|
||||||
|
@ -118,8 +123,27 @@ class EthereumClient {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get chainId => 1;
|
||||||
|
|
||||||
|
Transaction createTransaction({
|
||||||
|
required EthereumAddress from,
|
||||||
|
required EthereumAddress to,
|
||||||
|
required EtherAmount amount,
|
||||||
|
EtherAmount? maxPriorityFeePerGas,
|
||||||
|
}) {
|
||||||
|
return Transaction(
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||||
|
value: amount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> sendTransaction(Uint8List signedTransaction) async =>
|
Future<String> sendTransaction(Uint8List signedTransaction) async =>
|
||||||
await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction));
|
await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
|
||||||
|
|
||||||
|
Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) =>
|
||||||
|
prependTransactionType(0x02, signedTransaction);
|
||||||
|
|
||||||
Future getTransactionDetails(String transactionHash) async {
|
Future getTransactionDetails(String transactionHash) async {
|
||||||
// Wait for the transaction receipt to become available
|
// Wait for the transaction receipt to become available
|
||||||
|
@ -198,7 +222,7 @@ I/flutter ( 4474): Gas Used: 53000
|
||||||
Future<List<EthereumTransactionModel>> fetchTransactions(String address,
|
Future<List<EthereumTransactionModel>> fetchTransactions(String address,
|
||||||
{String? contractAddress}) async {
|
{String? contractAddress}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", {
|
final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", {
|
||||||
"module": "account",
|
"module": "account",
|
||||||
"action": contractAddress != null ? "tokentx" : "txlist",
|
"action": contractAddress != null ? "tokentx" : "txlist",
|
||||||
if (contractAddress != null) "contractaddress": contractAddress,
|
if (contractAddress != null) "contractaddress": contractAddress,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:cw_core/format_amount.dart';
|
import 'package:cw_core/format_amount.dart';
|
||||||
import 'package:cw_core/transaction_direction.dart';
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
import 'package:cw_core/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
@ -34,8 +36,10 @@ class EthereumTransactionInfo extends TransactionInfo {
|
||||||
final String? to;
|
final String? to;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String amountFormatted() =>
|
String amountFormatted() {
|
||||||
'${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol';
|
final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString());
|
||||||
|
return '${amount.substring(0, min(10, amount.length))} $tokenSymbol';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String fiatAmount() => _fiatAmount ?? '';
|
String fiatAmount() => _fiatAmount ?? '';
|
||||||
|
@ -44,7 +48,10 @@ class EthereumTransactionInfo extends TransactionInfo {
|
||||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH';
|
String feeFormatted() {
|
||||||
|
final amount = (ethFee / BigInt.from(10).pow(18)).toString();
|
||||||
|
return '${amount.substring(0, min(10, amount.length))} ETH';
|
||||||
|
}
|
||||||
|
|
||||||
factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
|
factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||||
return EthereumTransactionInfo(
|
return EthereumTransactionInfo(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Model used for in parsing transactions fetched using etherscan
|
||||||
class EthereumTransactionModel {
|
class EthereumTransactionModel {
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
final String hash;
|
final String hash;
|
||||||
|
|
|
@ -14,7 +14,7 @@ import 'package:cw_core/transaction_priority.dart';
|
||||||
import 'package:cw_core/wallet_addresses.dart';
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cw_ethereum/default_erc20_tokens.dart';
|
import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
|
||||||
import 'package:cw_ethereum/erc20_balance.dart';
|
import 'package:cw_ethereum/erc20_balance.dart';
|
||||||
import 'package:cw_ethereum/ethereum_client.dart';
|
import 'package:cw_ethereum/ethereum_client.dart';
|
||||||
import 'package:cw_ethereum/ethereum_exceptions.dart';
|
import 'package:cw_ethereum/ethereum_exceptions.dart';
|
||||||
|
@ -75,6 +75,8 @@ abstract class EthereumWalletBase
|
||||||
|
|
||||||
late final Box<Erc20Token> erc20TokensBox;
|
late final Box<Erc20Token> erc20TokensBox;
|
||||||
|
|
||||||
|
late final Box<Erc20Token> ethereumErc20TokensBox;
|
||||||
|
|
||||||
late final EthPrivateKey _ethPrivateKey;
|
late final EthPrivateKey _ethPrivateKey;
|
||||||
|
|
||||||
EthPrivateKey get ethPrivateKey => _ethPrivateKey;
|
EthPrivateKey get ethPrivateKey => _ethPrivateKey;
|
||||||
|
@ -102,7 +104,8 @@ abstract class EthereumWalletBase
|
||||||
Completer<SharedPreferences> _sharedPrefs = Completer();
|
Completer<SharedPreferences> _sharedPrefs = Completer();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName);
|
await movePreviousErc20BoxConfigsToNewBox();
|
||||||
|
|
||||||
await walletAddresses.init();
|
await walletAddresses.init();
|
||||||
await transactionHistory.init();
|
await transactionHistory.init();
|
||||||
_ethPrivateKey = await getPrivateKey(
|
_ethPrivateKey = await getPrivateKey(
|
||||||
|
@ -114,6 +117,33 @@ abstract class EthereumWalletBase
|
||||||
await save();
|
await save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Majorly for backward compatibility for previous configs that have been set.
|
||||||
|
Future<void> movePreviousErc20BoxConfigsToNewBox() async {
|
||||||
|
// Opens a box specific to this wallet
|
||||||
|
ethereumErc20TokensBox = await CakeHive.openBox<Erc20Token>(
|
||||||
|
"${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}");
|
||||||
|
|
||||||
|
//Open the previous token configs box
|
||||||
|
erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName);
|
||||||
|
|
||||||
|
// Check if it's empty, if it is, we stop the flow and return.
|
||||||
|
if (erc20TokensBox.isEmpty) {
|
||||||
|
// If it's empty, but the new wallet specific box is also empty,
|
||||||
|
// we load the initial tokens to the new box.
|
||||||
|
if (ethereumErc20TokensBox.isEmpty) addInitialTokens();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final allValues = erc20TokensBox.values.toList();
|
||||||
|
|
||||||
|
// Clear and delete the old token box
|
||||||
|
await erc20TokensBox.clear();
|
||||||
|
await erc20TokensBox.deleteFromDisk();
|
||||||
|
|
||||||
|
// Add all the previous tokens with configs to the new box
|
||||||
|
ethereumErc20TokensBox.addAll(allValues);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||||
try {
|
try {
|
||||||
|
@ -378,7 +408,7 @@ abstract class EthereumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchErc20Balances() async {
|
Future<void> _fetchErc20Balances() async {
|
||||||
for (var token in erc20TokensBox.values) {
|
for (var token in ethereumErc20TokensBox.values) {
|
||||||
try {
|
try {
|
||||||
if (token.enabled) {
|
if (token.enabled) {
|
||||||
balance[token] = await _client.fetchERC20Balances(
|
balance[token] = await _client.fetchERC20Balances(
|
||||||
|
@ -413,7 +443,7 @@ abstract class EthereumWalletBase
|
||||||
|
|
||||||
Future<void>? updateBalance() async => await _updateBalance();
|
Future<void>? updateBalance() async => await _updateBalance();
|
||||||
|
|
||||||
List<Erc20Token> get erc20Currencies => erc20TokensBox.values.toList();
|
List<Erc20Token> get erc20Currencies => ethereumErc20TokensBox.values.toList();
|
||||||
|
|
||||||
Future<void> addErc20Token(Erc20Token token) async {
|
Future<void> addErc20Token(Erc20Token token) async {
|
||||||
String? iconPath;
|
String? iconPath;
|
||||||
|
@ -429,10 +459,11 @@ abstract class EthereumWalletBase
|
||||||
contractAddress: token.contractAddress,
|
contractAddress: token.contractAddress,
|
||||||
decimal: token.decimal,
|
decimal: token.decimal,
|
||||||
enabled: token.enabled,
|
enabled: token.enabled,
|
||||||
|
tag: token.tag ?? "ETH",
|
||||||
iconPath: iconPath,
|
iconPath: iconPath,
|
||||||
);
|
);
|
||||||
|
|
||||||
await erc20TokensBox.put(_token.contractAddress, _token);
|
await ethereumErc20TokensBox.put(_token.contractAddress, _token);
|
||||||
|
|
||||||
if (_token.enabled) {
|
if (_token.enabled) {
|
||||||
balance[_token] = await _client.fetchERC20Balances(
|
balance[_token] = await _client.fetchERC20Balances(
|
||||||
|
@ -462,7 +493,7 @@ abstract class EthereumWalletBase
|
||||||
void addInitialTokens() {
|
void addInitialTokens() {
|
||||||
final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens;
|
final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens;
|
||||||
|
|
||||||
initialErc20Tokens.forEach((token) => erc20TokensBox.put(token.contractAddress, token));
|
initialErc20Tokens.forEach((token) => ethereumErc20TokensBox.put(token.contractAddress, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -492,7 +523,7 @@ abstract class EthereumWalletBase
|
||||||
_transactionsUpdateTimer!.cancel();
|
_transactionsUpdateTimer!.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
_transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) {
|
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) {
|
||||||
_updateTransactions();
|
_updateTransactions();
|
||||||
_updateBalance();
|
_updateBalance();
|
||||||
});
|
});
|
||||||
|
@ -508,7 +539,7 @@ abstract class EthereumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String signMessage(String message, {String? address = null}) =>
|
String signMessage(String message, {String? address}) =>
|
||||||
bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
|
bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
|
||||||
|
|
||||||
Web3Client? getWeb3Client() => _client.getWeb3Client();
|
Web3Client? getWeb3Client() => _client.getWeb3Client();
|
||||||
|
|
|
@ -20,12 +20,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
|
Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
|
||||||
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
final strength = (credentials.seedPhraseLength == 12)
|
|
||||||
? 128
|
|
||||||
: (credentials.seedPhraseLength == 24)
|
|
||||||
? 256
|
|
||||||
: 128;
|
|
||||||
|
|
||||||
final mnemonic = bip39.generateMnemonic(strength: strength);
|
final mnemonic = bip39.generateMnemonic(strength: strength);
|
||||||
final wallet = EthereumWallet(
|
final wallet = EthereumWallet(
|
||||||
|
@ -67,8 +62,8 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
|
||||||
@override
|
@override
|
||||||
Future<void> remove(String wallet) async {
|
Future<void> remove(String wallet) async {
|
||||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||||
final walletInfo = walletInfoSource.values.firstWhereOrNull(
|
final walletInfo = walletInfoSource.values
|
||||||
(info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
await walletInfoSource.delete(walletInfo.key);
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,8 @@ class PendingEthereumTransaction with PendingTransaction {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get amountFormatted {
|
String get amountFormatted {
|
||||||
final _amount = BigInt.parse(amount) / BigInt.from(pow(10, exponent));
|
final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
|
||||||
return _amount.toStringAsFixed(min(15, _amount.toString().length));
|
return _amount.substring(0, min(10, _amount.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -30,8 +30,8 @@ class PendingEthereumTransaction with PendingTransaction {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get feeFormatted {
|
String get feeFormatted {
|
||||||
final _fee = fee / BigInt.from(pow(10, 18));
|
final _fee = (fee / BigInt.from(pow(10, 18))).toString();
|
||||||
return _fee.toStringAsFixed(min(15, _fee.toString().length));
|
return _fee.substring(0, min(10, _fee.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -17,7 +17,6 @@ dependencies:
|
||||||
mobx: ^2.0.7+4
|
mobx: ^2.0.7+4
|
||||||
bip39: ^1.0.6
|
bip39: ^1.0.6
|
||||||
bip32: ^2.0.0
|
bip32: ^2.0.0
|
||||||
ed25519_hd_key: ^2.2.0
|
|
||||||
hex: ^0.2.0
|
hex: ^0.2.0
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.0.15
|
||||||
|
|
|
@ -623,15 +623,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
tor:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: main
|
|
||||||
resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5"
|
|
||||||
url: "https://github.com/cake-tech/tor.git"
|
|
||||||
source: git
|
|
||||||
version: "0.0.1"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -153,6 +153,22 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
hashlib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hashlib
|
||||||
|
sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
hashlib_codecs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hashlib_codecs
|
||||||
|
sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -308,12 +324,11 @@ packages:
|
||||||
polyseed:
|
polyseed:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
path: "."
|
name: polyseed
|
||||||
ref: HEAD
|
sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f"
|
||||||
resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7"
|
url: "https://pub.dev"
|
||||||
url: "https://github.com/cake-tech/polyseed_dart.git"
|
source: hosted
|
||||||
source: git
|
version: "0.0.2"
|
||||||
version: "0.0.1"
|
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -383,15 +398,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.5.1"
|
||||||
tor:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: main
|
|
||||||
resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5"
|
|
||||||
url: "https://github.com/cake-tech/tor.git"
|
|
||||||
source: git
|
|
||||||
version: "0.0.1"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -385,6 +385,9 @@ extern "C"
|
||||||
(uint64_t)restoreHeight,
|
(uint64_t)restoreHeight,
|
||||||
std::string(spendKey));
|
std::string(spendKey));
|
||||||
|
|
||||||
|
// Cache Raw to support Polyseed
|
||||||
|
wallet->setCacheAttribute("cakewallet.seed", std::string(seed));
|
||||||
|
|
||||||
int status;
|
int status;
|
||||||
std::string errorString;
|
std::string errorString;
|
||||||
|
|
||||||
|
@ -396,9 +399,6 @@ extern "C"
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache Raw to support Polyseed
|
|
||||||
wallet->setCacheAttribute("cakewallet.seed", std::string(seed));
|
|
||||||
|
|
||||||
change_current_wallet(wallet);
|
change_current_wallet(wallet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -926,6 +926,8 @@ extern "C"
|
||||||
return m_wallet->trustedDaemon();
|
return m_wallet->trustedDaemon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Coin Control //
|
||||||
|
|
||||||
CoinsInfoRow* coin(int index)
|
CoinsInfoRow* coin(int index)
|
||||||
{
|
{
|
||||||
if (index >= 0 && index < m_coins_info.size()) {
|
if (index >= 0 && index < m_coins_info.size()) {
|
||||||
|
@ -1020,6 +1022,13 @@ extern "C"
|
||||||
m_coins->thaw(index);
|
m_coins->thaw(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign Messages //
|
||||||
|
|
||||||
|
char *sign_message(char *message, char *address = "")
|
||||||
|
{
|
||||||
|
return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -32,6 +32,7 @@ void store(char *path);
|
||||||
|
|
||||||
void set_trusted_daemon(bool arg);
|
void set_trusted_daemon(bool arg);
|
||||||
bool trusted_daemon();
|
bool trusted_daemon();
|
||||||
|
char *sign_message(char *message, char *address);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,3 +149,5 @@ typedef coin = Pointer<CoinsInfoRow> Function(Int32 index);
|
||||||
typedef freeze_coin = Void Function(Int32 index);
|
typedef freeze_coin = Void Function(Int32 index);
|
||||||
|
|
||||||
typedef thaw_coin = Void Function(Int32 index);
|
typedef thaw_coin = Void Function(Int32 index);
|
||||||
|
|
||||||
|
typedef sign_message = Pointer<Utf8> Function(Pointer<Utf8> message, Pointer<Utf8> address);
|
||||||
|
|
|
@ -149,3 +149,5 @@ typedef GetCoin = Pointer<CoinsInfoRow> Function(int);
|
||||||
typedef FreezeCoin = void Function(int);
|
typedef FreezeCoin = void Function(int);
|
||||||
|
|
||||||
typedef ThawCoin = void Function(int);
|
typedef ThawCoin = void Function(int);
|
||||||
|
|
||||||
|
typedef SignMessage = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>);
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:cw_monero/api/types.dart';
|
||||||
import 'package:cw_monero/api/monero_api.dart';
|
import 'package:cw_monero/api/monero_api.dart';
|
||||||
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
|
import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
int _boolToInt(bool value) => value ? 1 : 0;
|
int _boolToInt(bool value) => value ? 1 : 0;
|
||||||
|
|
||||||
|
@ -128,6 +127,10 @@ final trustedDaemonNative = moneroApi
|
||||||
.lookup<NativeFunction<trusted_daemon>>('trusted_daemon')
|
.lookup<NativeFunction<trusted_daemon>>('trusted_daemon')
|
||||||
.asFunction<TrustedDaemon>();
|
.asFunction<TrustedDaemon>();
|
||||||
|
|
||||||
|
final signMessageNative = moneroApi
|
||||||
|
.lookup<NativeFunction<sign_message>>('sign_message')
|
||||||
|
.asFunction<SignMessage>();
|
||||||
|
|
||||||
int getSyncingHeight() => getSyncingHeightNative();
|
int getSyncingHeight() => getSyncingHeightNative();
|
||||||
|
|
||||||
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
|
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
|
||||||
|
@ -296,7 +299,7 @@ class SyncListener {
|
||||||
|
|
||||||
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
|
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
|
||||||
|
|
||||||
if (_lastKnownBlockHeight == syncHeight || syncHeight == null) {
|
if (_lastKnownBlockHeight == syncHeight) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +314,7 @@ class SyncListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
|
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
|
||||||
onNewBlock?.call(syncHeight, left, ptc);
|
onNewBlock.call(syncHeight, left, ptc);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,3 +386,14 @@ String getSubaddressLabel(int accountIndex, int addressIndex) {
|
||||||
Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted));
|
Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted));
|
||||||
|
|
||||||
Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
|
Future<bool> trustedDaemon() async => trustedDaemonNative() != 0;
|
||||||
|
|
||||||
|
String signMessage(String message, {String address = ""}) {
|
||||||
|
final messagePointer = message.toNativeUtf8();
|
||||||
|
final addressPointer = address.toNativeUtf8();
|
||||||
|
|
||||||
|
final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer));
|
||||||
|
calloc.free(messagePointer);
|
||||||
|
calloc.free(addressPointer);
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cw_monero/api/convert_utf8_to_string.dart';
|
import 'package:cw_monero/api/convert_utf8_to_string.dart';
|
||||||
import 'package:cw_monero/api/signatures.dart';
|
|
||||||
import 'package:cw_monero/api/types.dart';
|
|
||||||
import 'package:cw_monero/api/monero_api.dart';
|
|
||||||
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
|
|
||||||
import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
|
import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
|
||||||
|
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
|
||||||
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
|
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
|
||||||
import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
|
import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
|
||||||
|
import 'package:cw_monero/api/monero_api.dart';
|
||||||
|
import 'package:cw_monero/api/signatures.dart';
|
||||||
|
import 'package:cw_monero/api/types.dart';
|
||||||
|
import 'package:cw_monero/api/wallet.dart';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
final createWalletNative = moneroApi
|
final createWalletNative = moneroApi
|
||||||
.lookup<NativeFunction<create_wallet>>('create_wallet')
|
.lookup<NativeFunction<create_wallet>>('create_wallet')
|
||||||
|
@ -175,6 +177,8 @@ void restoreWalletFromSpendKeySync(
|
||||||
calloc.free(languagePointer);
|
calloc.free(languagePointer);
|
||||||
calloc.free(spendKeyPointer);
|
calloc.free(spendKeyPointer);
|
||||||
|
|
||||||
|
storeSync();
|
||||||
|
|
||||||
if (!isWalletRestored) {
|
if (!isWalletRestored) {
|
||||||
throw WalletRestoreFromKeysException(
|
throw WalletRestoreFromKeysException(
|
||||||
message: convertUTF8ToString(pointer: errorMessagePointer));
|
message: convertUTF8ToString(pointer: errorMessagePointer));
|
||||||
|
|
|
@ -651,4 +651,10 @@ abstract class MoneroWalletBase
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
|
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String signMessage(String message, {String? address}) {
|
||||||
|
final useAddress = address ?? "";
|
||||||
|
return monero_wallet.signMessage(message, address: useAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
|
||||||
import 'package:cw_monero/monero_wallet.dart';
|
import 'package:cw_monero/monero_wallet.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:polyseed/polyseed.dart';
|
import 'package:polyseed/polyseed.dart';
|
||||||
import 'package:polyseed/src/utils/key_utils.dart';
|
|
||||||
|
|
||||||
class MoneroNewWalletCredentials extends WalletCredentials {
|
class MoneroNewWalletCredentials extends WalletCredentials {
|
||||||
MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password})
|
MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password})
|
||||||
|
@ -77,8 +76,12 @@ class MoneroWalletService extends WalletService<
|
||||||
final polyseed = Polyseed.create();
|
final polyseed = Polyseed.create();
|
||||||
final lang = PolyseedLang.getByEnglishName(credentials.language);
|
final lang = PolyseedLang.getByEnglishName(credentials.language);
|
||||||
|
|
||||||
|
final heightOverride =
|
||||||
|
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
|
||||||
|
|
||||||
return _restoreFromPolyseed(
|
return _restoreFromPolyseed(
|
||||||
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
|
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
|
||||||
|
overrideHeight: heightOverride);
|
||||||
}
|
}
|
||||||
|
|
||||||
await monero_wallet_manager.createWallet(
|
await monero_wallet_manager.createWallet(
|
||||||
|
@ -268,18 +271,23 @@ class MoneroWalletService extends WalletService<
|
||||||
|
|
||||||
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
|
Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
|
||||||
WalletInfo walletInfo, PolyseedLang lang,
|
WalletInfo walletInfo, PolyseedLang lang,
|
||||||
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO}) async {
|
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
|
||||||
final height = getMoneroHeigthByDate(
|
final height = overrideHeight ?? getMoneroHeigthByDate(
|
||||||
date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
|
date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
|
||||||
final spendKey = keyToHexString(polyseed.generateKey(coin, 32));
|
final spendKey = polyseed.generateKey(coin, 32).toHexString();
|
||||||
|
final seed = polyseed.encode(lang, coin);
|
||||||
|
|
||||||
|
walletInfo.isRecovery = true;
|
||||||
|
walletInfo.restoreHeight = height;
|
||||||
|
|
||||||
await monero_wallet_manager.restoreFromSpendKey(
|
await monero_wallet_manager.restoreFromSpendKey(
|
||||||
path: path,
|
path: path,
|
||||||
password: password,
|
password: password,
|
||||||
seed: polyseed.encode(lang, coin),
|
seed: seed,
|
||||||
language: lang.nameEnglish,
|
language: lang.nameEnglish,
|
||||||
restoreHeight: height,
|
restoreHeight: height,
|
||||||
spendKey: spendKey);
|
spendKey: spendKey);
|
||||||
|
|
||||||
final wallet = MoneroWallet(
|
final wallet = MoneroWallet(
|
||||||
walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
await wallet.init();
|
await wallet.init();
|
||||||
|
|
|
@ -234,7 +234,6 @@ extern "C"
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUnlocked(bool unlocked);
|
void setUnlocked(bool unlocked);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Monero::Coins *m_coins;
|
Monero::Coins *m_coins;
|
||||||
|
@ -375,6 +374,35 @@ extern "C"
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error)
|
||||||
|
{
|
||||||
|
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
|
||||||
|
Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey(
|
||||||
|
std::string(path),
|
||||||
|
std::string(password),
|
||||||
|
std::string(language),
|
||||||
|
_networkType,
|
||||||
|
(uint64_t)restoreHeight,
|
||||||
|
std::string(spendKey));
|
||||||
|
|
||||||
|
// Cache Raw to support Polyseed
|
||||||
|
wallet->setCacheAttribute("cakewallet.seed", std::string(seed));
|
||||||
|
|
||||||
|
int status;
|
||||||
|
std::string errorString;
|
||||||
|
|
||||||
|
wallet->statusWithErrorString(status, errorString);
|
||||||
|
|
||||||
|
if (status != Monero::Wallet::Status_Ok || !errorString.empty())
|
||||||
|
{
|
||||||
|
error = strdup(errorString.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
change_current_wallet(wallet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool load_wallet(char *path, char *password, int32_t nettype)
|
bool load_wallet(char *path, char *password, int32_t nettype)
|
||||||
{
|
{
|
||||||
nice(19);
|
nice(19);
|
||||||
|
@ -439,6 +467,11 @@ extern "C"
|
||||||
|
|
||||||
const char *seed()
|
const char *seed()
|
||||||
{
|
{
|
||||||
|
std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed");
|
||||||
|
if (!_rawSeed.empty())
|
||||||
|
{
|
||||||
|
return strdup(_rawSeed.c_str());
|
||||||
|
}
|
||||||
return strdup(get_current_wallet()->seed().c_str());
|
return strdup(get_current_wallet()->seed().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -842,6 +875,12 @@ extern "C"
|
||||||
return m_transaction_history->count();
|
return m_transaction_history->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TransactionInfoRow* get_transaction(char * txId)
|
||||||
|
{
|
||||||
|
Monero::TransactionInfo *row = m_transaction_history->transaction(std::string(txId));
|
||||||
|
return new TransactionInfoRow(row);
|
||||||
|
}
|
||||||
|
|
||||||
int LedgerExchange(
|
int LedgerExchange(
|
||||||
unsigned char *command,
|
unsigned char *command,
|
||||||
unsigned int cmd_len,
|
unsigned int cmd_len,
|
||||||
|
@ -971,6 +1010,22 @@ extern "C"
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void freeze_coin(int index)
|
||||||
|
{
|
||||||
|
m_coins->setFrozen(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void thaw_coin(int index)
|
||||||
|
{
|
||||||
|
m_coins->thaw(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign Messages //
|
||||||
|
|
||||||
|
char *sign_message(char *message, char *address = "")
|
||||||
|
{
|
||||||
|
return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ void store(char *path);
|
||||||
|
|
||||||
void set_trusted_daemon(bool arg);
|
void set_trusted_daemon(bool arg);
|
||||||
bool trusted_daemon();
|
bool trusted_daemon();
|
||||||
|
char *sign_message(char *message, char *address);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,6 +266,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
hashlib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hashlib
|
||||||
|
sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
hashlib_codecs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hashlib_codecs
|
||||||
|
sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
hive:
|
hive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -485,12 +501,11 @@ packages:
|
||||||
polyseed:
|
polyseed:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
name: polyseed
|
||||||
ref: HEAD
|
sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f"
|
||||||
resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7"
|
url: "https://pub.dev"
|
||||||
url: "https://github.com/cake-tech/polyseed_dart.git"
|
source: hosted
|
||||||
source: git
|
version: "0.0.2"
|
||||||
version: "0.0.1"
|
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -632,15 +647,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
tor:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: main
|
|
||||||
resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5"
|
|
||||||
url: "https://github.com/cake-tech/tor.git"
|
|
||||||
source: git
|
|
||||||
version: "0.0.1"
|
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -19,9 +19,7 @@ dependencies:
|
||||||
flutter_mobx: ^2.0.6+1
|
flutter_mobx: ^2.0.6+1
|
||||||
intl: ^0.18.0
|
intl: ^0.18.0
|
||||||
encrypt: ^5.0.1
|
encrypt: ^5.0.1
|
||||||
polyseed:
|
polyseed: ^0.0.2
|
||||||
git:
|
|
||||||
url: https://github.com/cake-tech/polyseed_dart.git
|
|
||||||
cw_core:
|
cw_core:
|
||||||
path: ../cw_core
|
path: ../cw_core
|
||||||
|
|
||||||
|
|
30
cw_polygon/.gitignore
vendored
Normal file
30
cw_polygon/.gitignore
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
/pubspec.lock
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
10
cw_polygon/.metadata
Normal file
10
cw_polygon/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: package
|
3
cw_polygon/CHANGELOG.md
Normal file
3
cw_polygon/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
* TODO: Describe initial release.
|
1
cw_polygon/LICENSE
Normal file
1
cw_polygon/LICENSE
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TODO: Add your license here.
|
39
cw_polygon/README.md
Normal file
39
cw_polygon/README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<!--
|
||||||
|
This README describes the package. If you publish this package to pub.dev,
|
||||||
|
this README's contents appear on the landing page for your package.
|
||||||
|
|
||||||
|
For information about how to write a good package README, see the guide for
|
||||||
|
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
||||||
|
|
||||||
|
For general information about developing packages, see the Dart guide for
|
||||||
|
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
||||||
|
and the Flutter guide for
|
||||||
|
[developing packages and plugins](https://flutter.dev/developing-packages).
|
||||||
|
-->
|
||||||
|
|
||||||
|
TODO: Put a short description of the package here that helps potential users
|
||||||
|
know whether this package might be useful for them.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
TODO: List prerequisites and provide or point to information on how to
|
||||||
|
start using the package.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
TODO: Include short and useful examples for package users. Add longer examples
|
||||||
|
to `/example` folder.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
const like = 'sample';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
TODO: Tell users more about the package: where to find more information, how to
|
||||||
|
contribute to the package, how to file issues, what response they can expect
|
||||||
|
from the package authors, and more.
|
4
cw_polygon/analysis_options.yaml
Normal file
4
cw_polygon/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
7
cw_polygon/lib/cw_polygon.dart
Normal file
7
cw_polygon/lib/cw_polygon.dart
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
library cw_polygon;
|
||||||
|
|
||||||
|
/// A Calculator.
|
||||||
|
class Calculator {
|
||||||
|
/// Returns [value] plus 1.
|
||||||
|
int addOne(int value) => value + 1;
|
||||||
|
}
|
82
cw_polygon/lib/default_polygon_erc20_tokens.dart
Normal file
82
cw_polygon/lib/default_polygon_erc20_tokens.dart
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
|
||||||
|
class DefaultPolygonErc20Tokens {
|
||||||
|
final List<Erc20Token> _defaultTokens = [
|
||||||
|
Erc20Token(
|
||||||
|
name: "Wrapped Ether",
|
||||||
|
symbol: "WETH",
|
||||||
|
contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Tether USD (PoS)",
|
||||||
|
symbol: "USDT",
|
||||||
|
contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
||||||
|
decimal: 6,
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "USD Coin",
|
||||||
|
symbol: "USDC",
|
||||||
|
contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
||||||
|
decimal: 6,
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "USD Coin (POS)",
|
||||||
|
symbol: "USDC.e",
|
||||||
|
contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
|
||||||
|
decimal: 6,
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Avalanche Token",
|
||||||
|
symbol: "AVAX",
|
||||||
|
contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Wrapped BTC (PoS)",
|
||||||
|
symbol: "WBTC",
|
||||||
|
contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
|
||||||
|
decimal: 8,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Dai (PoS)",
|
||||||
|
symbol: "DAI",
|
||||||
|
contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: true,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "SHIBA INU (PoS)",
|
||||||
|
symbol: "SHIB",
|
||||||
|
contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
Erc20Token(
|
||||||
|
name: "Uniswap (PoS)",
|
||||||
|
symbol: "UNI",
|
||||||
|
contractAddress: "0xb33EaAd8d922B1083446DC23f610c2567fB5180f",
|
||||||
|
decimal: 18,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
List<Erc20Token> get initialPolygonErc20Tokens => _defaultTokens.map((token) {
|
||||||
|
String? iconPath;
|
||||||
|
try {
|
||||||
|
iconPath = CryptoCurrency.all
|
||||||
|
.firstWhere((element) =>
|
||||||
|
element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase())
|
||||||
|
.iconPath;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return Erc20Token.copyWith(token, iconPath, 'POLY');
|
||||||
|
}).toList();
|
||||||
|
}
|
19
cw_polygon/lib/pending_polygon_transaction.dart
Normal file
19
cw_polygon/lib/pending_polygon_transaction.dart
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:cw_ethereum/pending_ethereum_transaction.dart';
|
||||||
|
|
||||||
|
class PendingPolygonTransaction extends PendingEthereumTransaction {
|
||||||
|
PendingPolygonTransaction({
|
||||||
|
required Function sendTransaction,
|
||||||
|
required Uint8List signedTransaction,
|
||||||
|
required BigInt fee,
|
||||||
|
required String amount,
|
||||||
|
required int exponent,
|
||||||
|
}) : super(
|
||||||
|
amount: amount,
|
||||||
|
sendTransaction: sendTransaction,
|
||||||
|
signedTransaction: signedTransaction,
|
||||||
|
fee: fee,
|
||||||
|
exponent: exponent,
|
||||||
|
);
|
||||||
|
}
|
55
cw_polygon/lib/polygon_client.dart
Normal file
55
cw_polygon/lib/polygon_client.dart
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cw_ethereum/ethereum_client.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_model.dart';
|
||||||
|
import 'package:cw_ethereum/.secrets.g.dart' as secrets;
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:web3dart/web3dart.dart';
|
||||||
|
|
||||||
|
class PolygonClient extends EthereumClient {
|
||||||
|
@override
|
||||||
|
Transaction createTransaction({
|
||||||
|
required EthereumAddress from,
|
||||||
|
required EthereumAddress to,
|
||||||
|
required EtherAmount amount,
|
||||||
|
EtherAmount? maxPriorityFeePerGas,
|
||||||
|
}) {
|
||||||
|
return Transaction(
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
value: amount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get chainId => 137;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<PolygonTransactionModel>> fetchTransactions(String address,
|
||||||
|
{String? contractAddress}) async {
|
||||||
|
try {
|
||||||
|
final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", {
|
||||||
|
"module": "account",
|
||||||
|
"action": contractAddress != null ? "tokentx" : "txlist",
|
||||||
|
if (contractAddress != null) "contractaddress": contractAddress,
|
||||||
|
"address": address,
|
||||||
|
"apikey": secrets.polygonScanApiKey,
|
||||||
|
}));
|
||||||
|
|
||||||
|
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
|
||||||
|
return (jsonResponse['result'] as List)
|
||||||
|
.map((e) => PolygonTransactionModel.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
} catch (e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
cw_polygon/lib/polygon_exceptions.dart
Normal file
6
cw_polygon/lib/polygon_exceptions.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_exceptions.dart';
|
||||||
|
|
||||||
|
class PolygonTransactionCreationException extends EthereumTransactionCreationException {
|
||||||
|
PolygonTransactionCreationException(CryptoCurrency currency) : super(currency);
|
||||||
|
}
|
25
cw_polygon/lib/polygon_formatter.dart
Normal file
25
cw_polygon/lib/polygon_formatter.dart
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
const polygonAmountLength = 12;
|
||||||
|
const polygonAmountDivider = 1000000000000;
|
||||||
|
final polygonAmountFormat = NumberFormat()
|
||||||
|
..maximumFractionDigits = polygonAmountLength
|
||||||
|
..minimumFractionDigits = 1;
|
||||||
|
|
||||||
|
class PolygonFormatter {
|
||||||
|
static int parsePolygonAmount(String amount) {
|
||||||
|
try {
|
||||||
|
return (double.parse(amount) * polygonAmountDivider).round();
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static double parsePolygonAmountToDouble(int amount) {
|
||||||
|
try {
|
||||||
|
return amount / polygonAmountDivider;
|
||||||
|
} catch (_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
cw_polygon/lib/polygon_mnemonics_exception.dart
Normal file
5
cw_polygon/lib/polygon_mnemonics_exception.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class PolygonMnemonicIsIncorrectException implements Exception {
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||||
|
}
|
18
cw_polygon/lib/polygon_transaction_credentials.dart
Normal file
18
cw_polygon/lib/polygon_transaction_credentials.dart
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/output_info.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_priority.dart';
|
||||||
|
|
||||||
|
class PolygonTransactionCredentials extends EthereumTransactionCredentials {
|
||||||
|
PolygonTransactionCredentials(
|
||||||
|
List<OutputInfo> outputs, {
|
||||||
|
required PolygonTransactionPriority? priority,
|
||||||
|
required CryptoCurrency currency,
|
||||||
|
final int? feeRate,
|
||||||
|
}) : super(
|
||||||
|
outputs,
|
||||||
|
currency: currency,
|
||||||
|
priority: priority,
|
||||||
|
feeRate: feeRate,
|
||||||
|
);
|
||||||
|
}
|
77
cw_polygon/lib/polygon_transaction_history.dart
Normal file
77
cw_polygon/lib/polygon_transaction_history.dart
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:core';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_ethereum/file.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_info.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
|
||||||
|
part 'polygon_transaction_history.g.dart';
|
||||||
|
|
||||||
|
const transactionsHistoryFileName = 'polygon_transactions.json';
|
||||||
|
|
||||||
|
class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory;
|
||||||
|
|
||||||
|
abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase<PolygonTransactionInfo>
|
||||||
|
with Store {
|
||||||
|
PolygonTransactionHistoryBase({required this.walletInfo, required String password})
|
||||||
|
: _password = password {
|
||||||
|
transactions = ObservableMap<String, PolygonTransactionInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
final WalletInfo walletInfo;
|
||||||
|
String _password;
|
||||||
|
|
||||||
|
Future<void> init() async => await _load();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
try {
|
||||||
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
|
final data = json.encode({'transactions': transactions});
|
||||||
|
await writeData(path: path, password: _password, data: data);
|
||||||
|
} catch (e, s) {
|
||||||
|
print('Error while saving polygon transaction history: ${e.toString()}');
|
||||||
|
print(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addMany(Map<String, PolygonTransactionInfo> transactions) =>
|
||||||
|
this.transactions.addAll(transactions);
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> _read() async {
|
||||||
|
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
final path = '$dirPath/$transactionsHistoryFileName';
|
||||||
|
final content = await read(path: path, password: _password);
|
||||||
|
if (content.isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return json.decode(content) as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _load() async {
|
||||||
|
try {
|
||||||
|
final content = await _read();
|
||||||
|
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
|
txs.entries.forEach((entry) {
|
||||||
|
final val = entry.value;
|
||||||
|
|
||||||
|
if (val is Map<String, dynamic>) {
|
||||||
|
final tx = PolygonTransactionInfo.fromJson(val);
|
||||||
|
_update(tx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||||
|
}
|
49
cw_polygon/lib/polygon_transaction_info.dart
Normal file
49
cw_polygon/lib/polygon_transaction_info.dart
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_info.dart';
|
||||||
|
|
||||||
|
class PolygonTransactionInfo extends EthereumTransactionInfo {
|
||||||
|
PolygonTransactionInfo({
|
||||||
|
required String id,
|
||||||
|
required int height,
|
||||||
|
required BigInt ethAmount,
|
||||||
|
int exponent = 18,
|
||||||
|
required TransactionDirection direction,
|
||||||
|
required DateTime date,
|
||||||
|
required bool isPending,
|
||||||
|
required BigInt ethFee,
|
||||||
|
required int confirmations,
|
||||||
|
String tokenSymbol = "MATIC",
|
||||||
|
required String? to,
|
||||||
|
}) : super(
|
||||||
|
confirmations: confirmations,
|
||||||
|
id: id,
|
||||||
|
height: height,
|
||||||
|
ethAmount: ethAmount,
|
||||||
|
exponent: exponent,
|
||||||
|
direction: direction,
|
||||||
|
date: date,
|
||||||
|
isPending: isPending,
|
||||||
|
ethFee: ethFee,
|
||||||
|
to: to,
|
||||||
|
tokenSymbol: tokenSymbol,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory PolygonTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||||
|
return PolygonTransactionInfo(
|
||||||
|
id: data['id'] as String,
|
||||||
|
height: data['height'] as int,
|
||||||
|
ethAmount: BigInt.parse(data['amount']),
|
||||||
|
exponent: data['exponent'] as int,
|
||||||
|
ethFee: BigInt.parse(data['fee']),
|
||||||
|
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||||
|
isPending: data['isPending'] as bool,
|
||||||
|
confirmations: data['confirmations'] as int,
|
||||||
|
tokenSymbol: data['tokenSymbol'] as String,
|
||||||
|
to: data['to'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC';
|
||||||
|
}
|
49
cw_polygon/lib/polygon_transaction_model.dart
Normal file
49
cw_polygon/lib/polygon_transaction_model.dart
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_model.dart';
|
||||||
|
|
||||||
|
class PolygonTransactionModel extends EthereumTransactionModel {
|
||||||
|
PolygonTransactionModel({
|
||||||
|
required DateTime date,
|
||||||
|
required String hash,
|
||||||
|
required String from,
|
||||||
|
required String to,
|
||||||
|
required BigInt amount,
|
||||||
|
required int gasUsed,
|
||||||
|
required BigInt gasPrice,
|
||||||
|
required String contractAddress,
|
||||||
|
required int confirmations,
|
||||||
|
required int blockNumber,
|
||||||
|
required String? tokenSymbol,
|
||||||
|
required int? tokenDecimal,
|
||||||
|
required bool isError,
|
||||||
|
}) : super(
|
||||||
|
amount: amount,
|
||||||
|
date: date,
|
||||||
|
hash: hash,
|
||||||
|
from: from,
|
||||||
|
to: to,
|
||||||
|
gasPrice: gasPrice,
|
||||||
|
gasUsed: gasUsed,
|
||||||
|
confirmations: confirmations,
|
||||||
|
contractAddress: contractAddress,
|
||||||
|
blockNumber: blockNumber,
|
||||||
|
tokenDecimal: tokenDecimal,
|
||||||
|
tokenSymbol: tokenSymbol,
|
||||||
|
isError: isError,
|
||||||
|
);
|
||||||
|
|
||||||
|
factory PolygonTransactionModel.fromJson(Map<String, dynamic> json) => PolygonTransactionModel(
|
||||||
|
date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
|
||||||
|
hash: json["hash"],
|
||||||
|
from: json["from"],
|
||||||
|
to: json["to"],
|
||||||
|
amount: BigInt.parse(json["value"]),
|
||||||
|
gasUsed: int.parse(json["gasUsed"]),
|
||||||
|
gasPrice: BigInt.parse(json["gasPrice"]),
|
||||||
|
contractAddress: json["contractAddress"],
|
||||||
|
confirmations: int.parse(json["confirmations"]),
|
||||||
|
blockNumber: int.parse(json["blockNumber"]),
|
||||||
|
tokenSymbol: json["tokenSymbol"] ?? "MATIC",
|
||||||
|
tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
|
||||||
|
isError: json["isError"] == "1",
|
||||||
|
);
|
||||||
|
}
|
51
cw_polygon/lib/polygon_transaction_priority.dart
Normal file
51
cw_polygon/lib/polygon_transaction_priority.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import 'package:cw_ethereum/ethereum_transaction_priority.dart';
|
||||||
|
|
||||||
|
class PolygonTransactionPriority extends EthereumTransactionPriority {
|
||||||
|
const PolygonTransactionPriority({required String title, required int raw, required int tip})
|
||||||
|
: super(title: title, raw: raw, tip: tip);
|
||||||
|
|
||||||
|
static const List<PolygonTransactionPriority> all = [fast, medium, slow];
|
||||||
|
static const PolygonTransactionPriority slow =
|
||||||
|
PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1);
|
||||||
|
static const PolygonTransactionPriority medium =
|
||||||
|
PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2);
|
||||||
|
static const PolygonTransactionPriority fast =
|
||||||
|
PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4);
|
||||||
|
|
||||||
|
static PolygonTransactionPriority deserialize({required int raw}) {
|
||||||
|
switch (raw) {
|
||||||
|
case 0:
|
||||||
|
return slow;
|
||||||
|
case 1:
|
||||||
|
return medium;
|
||||||
|
case 2:
|
||||||
|
return fast;
|
||||||
|
default:
|
||||||
|
throw Exception('Unexpected token: $raw for PolygonTransactionPriority deserialize');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get units => 'gas';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
var label = '';
|
||||||
|
|
||||||
|
switch (this) {
|
||||||
|
case PolygonTransactionPriority.slow:
|
||||||
|
label = 'Slow';
|
||||||
|
break;
|
||||||
|
case PolygonTransactionPriority.medium:
|
||||||
|
label = 'Medium';
|
||||||
|
break;
|
||||||
|
case PolygonTransactionPriority.fast:
|
||||||
|
label = 'Fast';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
521
cw_polygon/lib/polygon_wallet.dart
Normal file
521
cw_polygon/lib/polygon_wallet.dart
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/cake_hive.dart';
|
||||||
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_ethereum/erc20_balance.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_formatter.dart';
|
||||||
|
import 'package:cw_ethereum/file.dart';
|
||||||
|
import 'package:cw_core/erc20_token.dart';
|
||||||
|
import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
|
||||||
|
import 'package:cw_polygon/polygon_client.dart';
|
||||||
|
import 'package:cw_polygon/polygon_exceptions.dart';
|
||||||
|
import 'package:cw_polygon/polygon_formatter.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_credentials.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_history.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_info.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_model.dart';
|
||||||
|
import 'package:cw_polygon/polygon_transaction_priority.dart';
|
||||||
|
import 'package:cw_polygon/polygon_wallet_addresses.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:web3dart/crypto.dart';
|
||||||
|
import 'package:web3dart/web3dart.dart';
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:bip32/bip32.dart' as bip32;
|
||||||
|
|
||||||
|
part 'polygon_wallet.g.dart';
|
||||||
|
|
||||||
|
class PolygonWallet = PolygonWalletBase with _$PolygonWallet;
|
||||||
|
|
||||||
|
abstract class PolygonWalletBase
|
||||||
|
extends WalletBase<ERC20Balance, PolygonTransactionHistory, PolygonTransactionInfo> with Store {
|
||||||
|
PolygonWalletBase({
|
||||||
|
required WalletInfo walletInfo,
|
||||||
|
String? mnemonic,
|
||||||
|
String? privateKey,
|
||||||
|
required String password,
|
||||||
|
ERC20Balance? initialBalance,
|
||||||
|
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||||
|
_password = password,
|
||||||
|
_mnemonic = mnemonic,
|
||||||
|
_hexPrivateKey = privateKey,
|
||||||
|
_isTransactionUpdating = false,
|
||||||
|
_client = PolygonClient(),
|
||||||
|
walletAddresses = PolygonWalletAddresses(walletInfo),
|
||||||
|
balance = ObservableMap<CryptoCurrency, ERC20Balance>.of(
|
||||||
|
{CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero)}),
|
||||||
|
super(walletInfo) {
|
||||||
|
this.walletInfo = walletInfo;
|
||||||
|
transactionHistory = PolygonTransactionHistory(walletInfo: walletInfo, password: password);
|
||||||
|
|
||||||
|
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
|
||||||
|
CakeHive.registerAdapter(Erc20TokenAdapter());
|
||||||
|
}
|
||||||
|
|
||||||
|
_sharedPrefs.complete(SharedPreferences.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
final String? _mnemonic;
|
||||||
|
final String? _hexPrivateKey;
|
||||||
|
final String _password;
|
||||||
|
|
||||||
|
late final Box<Erc20Token> polygonErc20TokensBox;
|
||||||
|
|
||||||
|
late final EthPrivateKey _polygonPrivateKey;
|
||||||
|
|
||||||
|
late final PolygonClient _client;
|
||||||
|
|
||||||
|
EthPrivateKey get polygonPrivateKey => _polygonPrivateKey;
|
||||||
|
|
||||||
|
int? _gasPrice;
|
||||||
|
int? _estimatedGas;
|
||||||
|
bool _isTransactionUpdating;
|
||||||
|
|
||||||
|
// TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
|
||||||
|
Timer? _transactionsUpdateTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletAddresses walletAddresses;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
late ObservableMap<CryptoCurrency, ERC20Balance> balance;
|
||||||
|
|
||||||
|
final Completer<SharedPreferences> _sharedPrefs = Completer();
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(
|
||||||
|
"${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.polygonBoxName}");
|
||||||
|
await walletAddresses.init();
|
||||||
|
await transactionHistory.init();
|
||||||
|
_polygonPrivateKey = await getPrivateKey(
|
||||||
|
mnemonic: _mnemonic,
|
||||||
|
privateKey: _hexPrivateKey,
|
||||||
|
password: _password,
|
||||||
|
);
|
||||||
|
walletAddresses.address = _polygonPrivateKey.address.toString();
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||||
|
try {
|
||||||
|
if (priority is PolygonTransactionPriority) {
|
||||||
|
final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
|
||||||
|
return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> changePassword(String password) {
|
||||||
|
throw UnimplementedError("changePassword");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() {
|
||||||
|
_client.stop();
|
||||||
|
_transactionsUpdateTimer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> connectToNode({required Node node}) async {
|
||||||
|
try {
|
||||||
|
syncStatus = ConnectingSyncStatus();
|
||||||
|
|
||||||
|
final isConnected = _client.connect(node);
|
||||||
|
|
||||||
|
if (!isConnected) {
|
||||||
|
throw Exception("Polygon Node connection failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
_client.setListeners(_polygonPrivateKey.address, _onNewTransaction);
|
||||||
|
|
||||||
|
_setTransactionUpdateTimer();
|
||||||
|
|
||||||
|
syncStatus = ConnectedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
final credentials0 = credentials as PolygonTransactionCredentials;
|
||||||
|
final outputs = credentials0.outputs;
|
||||||
|
final hasMultiDestination = outputs.length > 1;
|
||||||
|
|
||||||
|
final CryptoCurrency transactionCurrency =
|
||||||
|
balance.keys.firstWhere((element) => element.title == credentials0.currency.title);
|
||||||
|
|
||||||
|
final erc20Balance = balance[transactionCurrency]!;
|
||||||
|
BigInt totalAmount = BigInt.zero;
|
||||||
|
int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
|
||||||
|
num amountToPolygonMultiplier = pow(10, exponent);
|
||||||
|
|
||||||
|
// so far this can not be made with Polygon as Polygon does not support multiple recipients
|
||||||
|
if (hasMultiDestination) {
|
||||||
|
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||||
|
throw PolygonTransactionCreationException(transactionCurrency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble(
|
||||||
|
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
|
||||||
|
totalAmount = BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
|
||||||
|
|
||||||
|
if (erc20Balance.balance < totalAmount) {
|
||||||
|
throw PolygonTransactionCreationException(transactionCurrency);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final output = outputs.first;
|
||||||
|
// since the fees are taken from Ethereum
|
||||||
|
// then no need to subtract the fees from the amount if send all
|
||||||
|
final BigInt allAmount;
|
||||||
|
if (transactionCurrency is Erc20Token) {
|
||||||
|
allAmount = erc20Balance.balance;
|
||||||
|
} else {
|
||||||
|
allAmount =
|
||||||
|
erc20Balance.balance - BigInt.from(calculateEstimatedFee(credentials0.priority!, null));
|
||||||
|
}
|
||||||
|
final totalOriginalAmount =
|
||||||
|
EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0);
|
||||||
|
totalAmount =
|
||||||
|
output.sendAll ? allAmount : BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
|
||||||
|
|
||||||
|
if (erc20Balance.balance < totalAmount) {
|
||||||
|
throw PolygonTransactionCreationException(transactionCurrency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final pendingPolygonTransaction = await _client.signTransaction(
|
||||||
|
privateKey: _polygonPrivateKey,
|
||||||
|
toAddress: credentials0.outputs.first.isParsedAddress
|
||||||
|
? credentials0.outputs.first.extractedAddress!
|
||||||
|
: credentials0.outputs.first.address,
|
||||||
|
amount: totalAmount.toString(),
|
||||||
|
gas: _estimatedGas!,
|
||||||
|
priority: credentials0.priority!,
|
||||||
|
currency: transactionCurrency,
|
||||||
|
exponent: exponent,
|
||||||
|
contractAddress:
|
||||||
|
transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return pendingPolygonTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateTransactions() async {
|
||||||
|
try {
|
||||||
|
if (_isTransactionUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true;
|
||||||
|
if (!isPolygonScanEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTransactionUpdating = true;
|
||||||
|
final transactions = await fetchTransactions();
|
||||||
|
transactionHistory.addMany(transactions);
|
||||||
|
await transactionHistory.save();
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
} catch (_) {
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, PolygonTransactionInfo>> fetchTransactions() async {
|
||||||
|
final address = _polygonPrivateKey.address.hex;
|
||||||
|
final transactions = await _client.fetchTransactions(address);
|
||||||
|
|
||||||
|
final List<Future<List<PolygonTransactionModel>>> polygonErc20TokensTransactions = [];
|
||||||
|
|
||||||
|
for (var token in balance.keys) {
|
||||||
|
if (token is Erc20Token) {
|
||||||
|
polygonErc20TokensTransactions.add(
|
||||||
|
_client.fetchTransactions(
|
||||||
|
address,
|
||||||
|
contractAddress: token.contractAddress,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final tokensTransaction = await Future.wait(polygonErc20TokensTransactions);
|
||||||
|
transactions.addAll(tokensTransaction.expand((element) => element));
|
||||||
|
|
||||||
|
final Map<String, PolygonTransactionInfo> result = {};
|
||||||
|
|
||||||
|
for (var transactionModel in transactions) {
|
||||||
|
if (transactionModel.isError) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result[transactionModel.hash] = PolygonTransactionInfo(
|
||||||
|
id: transactionModel.hash,
|
||||||
|
height: transactionModel.blockNumber,
|
||||||
|
ethAmount: transactionModel.amount,
|
||||||
|
direction: transactionModel.from == address
|
||||||
|
? TransactionDirection.outgoing
|
||||||
|
: TransactionDirection.incoming,
|
||||||
|
isPending: false,
|
||||||
|
date: transactionModel.date,
|
||||||
|
confirmations: transactionModel.confirmations,
|
||||||
|
ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
|
||||||
|
exponent: transactionModel.tokenDecimal ?? 18,
|
||||||
|
tokenSymbol: transactionModel.tokenSymbol ?? "MATIC",
|
||||||
|
to: transactionModel.to,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object get keys => throw UnimplementedError("keys");
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rescan({required int height}) {
|
||||||
|
throw UnimplementedError("rescan");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
await walletAddresses.updateAddressesInBox();
|
||||||
|
final path = await makePath();
|
||||||
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
await transactionHistory.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get seed => _mnemonic;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get privateKey => HEX.encode(_polygonPrivateKey.privateKey);
|
||||||
|
|
||||||
|
@action
|
||||||
|
@override
|
||||||
|
Future<void> startSync() async {
|
||||||
|
try {
|
||||||
|
syncStatus = AttemptingSyncStatus();
|
||||||
|
await _updateBalance();
|
||||||
|
await _updateTransactions();
|
||||||
|
_gasPrice = await _client.getGasUnitPrice();
|
||||||
|
_estimatedGas = await _client.getEstimatedGas();
|
||||||
|
|
||||||
|
Timer.periodic(
|
||||||
|
const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
|
||||||
|
Timer.periodic(const Duration(seconds: 10),
|
||||||
|
(timer) async => _estimatedGas = await _client.getEstimatedGas());
|
||||||
|
|
||||||
|
syncStatus = SyncedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||||
|
|
||||||
|
String toJSON() => json.encode({
|
||||||
|
'mnemonic': _mnemonic,
|
||||||
|
'private_key': privateKey,
|
||||||
|
'balance': balance[currency]!.toJSON(),
|
||||||
|
});
|
||||||
|
|
||||||
|
static Future<PolygonWallet> open({
|
||||||
|
required String name,
|
||||||
|
required String password,
|
||||||
|
required WalletInfo walletInfo,
|
||||||
|
}) async {
|
||||||
|
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||||
|
final jsonSource = await read(path: path, password: password);
|
||||||
|
final data = json.decode(jsonSource) as Map;
|
||||||
|
final mnemonic = data['mnemonic'] as String?;
|
||||||
|
final privateKey = data['private_key'] as String?;
|
||||||
|
final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
|
||||||
|
|
||||||
|
return PolygonWallet(
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
password: password,
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
privateKey: privateKey,
|
||||||
|
initialBalance: balance,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updateBalance() async {
|
||||||
|
balance[currency] = await _fetchMaticBalance();
|
||||||
|
|
||||||
|
await _fetchErc20Balances();
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ERC20Balance> _fetchMaticBalance() async {
|
||||||
|
final balance = await _client.getBalance(_polygonPrivateKey.address);
|
||||||
|
return ERC20Balance(balance.getInWei);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchErc20Balances() async {
|
||||||
|
for (var token in polygonErc20TokensBox.values) {
|
||||||
|
try {
|
||||||
|
if (token.enabled) {
|
||||||
|
balance[token] = await _client.fetchERC20Balances(
|
||||||
|
_polygonPrivateKey.address,
|
||||||
|
token.contractAddress,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
balance.remove(token);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<EthPrivateKey> getPrivateKey(
|
||||||
|
{String? mnemonic, String? privateKey, required String password}) async {
|
||||||
|
assert(mnemonic != null || privateKey != null);
|
||||||
|
|
||||||
|
if (privateKey != null) {
|
||||||
|
return EthPrivateKey.fromHex(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
final seed = bip39.mnemonicToSeed(mnemonic!);
|
||||||
|
|
||||||
|
final root = bip32.BIP32.fromSeed(seed);
|
||||||
|
|
||||||
|
const hdPathPolygon = "m/44'/60'/0'/0";
|
||||||
|
const index = 0;
|
||||||
|
final addressAtIndex = root.derivePath("$hdPathPolygon/$index");
|
||||||
|
|
||||||
|
return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void>? updateBalance() async => await _updateBalance();
|
||||||
|
|
||||||
|
List<Erc20Token> get erc20Currencies => polygonErc20TokensBox.values.toList();
|
||||||
|
|
||||||
|
Future<void> addErc20Token(Erc20Token token) async {
|
||||||
|
String? iconPath;
|
||||||
|
try {
|
||||||
|
iconPath = CryptoCurrency.all
|
||||||
|
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||||
|
.iconPath;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
final token0 = Erc20Token(
|
||||||
|
name: token.name,
|
||||||
|
symbol: token.symbol,
|
||||||
|
contractAddress: token.contractAddress,
|
||||||
|
decimal: token.decimal,
|
||||||
|
enabled: token.enabled,
|
||||||
|
tag: token.tag ?? "POLY",
|
||||||
|
iconPath: iconPath,
|
||||||
|
);
|
||||||
|
|
||||||
|
await polygonErc20TokensBox.put(token0.contractAddress, token0);
|
||||||
|
|
||||||
|
if (token0.enabled) {
|
||||||
|
balance[token0] = await _client.fetchERC20Balances(
|
||||||
|
_polygonPrivateKey.address,
|
||||||
|
token0.contractAddress,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
balance.remove(token0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteErc20Token(Erc20Token token) async {
|
||||||
|
await token.delete();
|
||||||
|
|
||||||
|
balance.remove(token);
|
||||||
|
_updateBalance();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
|
||||||
|
await _client.getErc20Token(contractAddress);
|
||||||
|
|
||||||
|
void _onNewTransaction() {
|
||||||
|
_updateBalance();
|
||||||
|
_updateTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addInitialTokens() {
|
||||||
|
final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
|
||||||
|
|
||||||
|
for (var token in initialErc20Tokens) {
|
||||||
|
polygonErc20TokensBox.put(token.contractAddress, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
|
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||||
|
final currentWalletFile = File(currentWalletPath);
|
||||||
|
|
||||||
|
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||||
|
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
|
||||||
|
|
||||||
|
// Copies current wallet files into new wallet name's dir and files
|
||||||
|
if (currentWalletFile.existsSync()) {
|
||||||
|
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||||
|
await currentWalletFile.copy(newWalletPath);
|
||||||
|
}
|
||||||
|
if (currentTransactionsFile.existsSync()) {
|
||||||
|
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||||
|
await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old name's dir and files
|
||||||
|
await Directory(currentDirPath).delete(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setTransactionUpdateTimer() {
|
||||||
|
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||||
|
_transactionsUpdateTimer!.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) {
|
||||||
|
_updateTransactions();
|
||||||
|
_updateBalance();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePolygonScanUsageState(bool isEnabled) {
|
||||||
|
if (isEnabled) {
|
||||||
|
_updateTransactions();
|
||||||
|
_setTransactionUpdateTimer();
|
||||||
|
} else {
|
||||||
|
_transactionsUpdateTimer?.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String signMessage(String message, {String? address}) =>
|
||||||
|
bytesToHex(_polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
|
||||||
|
|
||||||
|
Web3Client? getWeb3Client() => _client.getWeb3Client();
|
||||||
|
}
|
5
cw_polygon/lib/polygon_wallet_addresses.dart
Normal file
5
cw_polygon/lib/polygon_wallet_addresses.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
|
||||||
|
|
||||||
|
class PolygonWalletAddresses extends EthereumWalletAddresses {
|
||||||
|
PolygonWalletAddresses(super.walletInfo);
|
||||||
|
}
|
28
cw_polygon/lib/polygon_wallet_creation_credentials.dart
Normal file
28
cw_polygon/lib/polygon_wallet_creation_credentials.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
class PolygonNewWalletCredentials extends WalletCredentials {
|
||||||
|
PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||||
|
: super(name: name, walletInfo: walletInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
|
PolygonRestoreWalletFromSeedCredentials(
|
||||||
|
{required String name,
|
||||||
|
required String password,
|
||||||
|
required this.mnemonic,
|
||||||
|
WalletInfo? walletInfo})
|
||||||
|
: super(name: name, password: password, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
final String mnemonic;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PolygonRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||||
|
PolygonRestoreWalletFromPrivateKey(
|
||||||
|
{required String name,
|
||||||
|
required String password,
|
||||||
|
required this.privateKey,
|
||||||
|
WalletInfo? walletInfo})
|
||||||
|
: super(name: name, password: password, walletInfo: walletInfo);
|
||||||
|
|
||||||
|
final String privateKey;
|
||||||
|
}
|
119
cw_polygon/lib/polygon_wallet_service.dart
Normal file
119
cw_polygon/lib/polygon_wallet_service.dart
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:cw_ethereum/ethereum_mnemonics.dart';
|
||||||
|
import 'package:cw_polygon/polygon_wallet.dart';
|
||||||
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'polygon_wallet_creation_credentials.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
|
||||||
|
PolygonRestoreWalletFromSeedCredentials, PolygonRestoreWalletFromPrivateKey> {
|
||||||
|
PolygonWalletService(this.walletInfoSource);
|
||||||
|
|
||||||
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PolygonWallet> create(PolygonNewWalletCredentials credentials) async {
|
||||||
|
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||||
|
|
||||||
|
final mnemonic = bip39.generateMnemonic(strength: strength);
|
||||||
|
final wallet = PolygonWallet(
|
||||||
|
walletInfo: credentials.walletInfo!,
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
password: credentials.password!,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
wallet.addInitialTokens();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.polygon;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isWalletExit(String name) async =>
|
||||||
|
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PolygonWallet> openWallet(String name, String password) async {
|
||||||
|
final walletInfo =
|
||||||
|
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||||
|
final wallet = await PolygonWalletBase.open(
|
||||||
|
name: name,
|
||||||
|
password: password,
|
||||||
|
walletInfo: walletInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> remove(String wallet) async {
|
||||||
|
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||||
|
final walletInfo = walletInfoSource.values
|
||||||
|
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||||
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PolygonWallet> restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async {
|
||||||
|
final wallet = PolygonWallet(
|
||||||
|
password: credentials.password!,
|
||||||
|
privateKey: credentials.privateKey,
|
||||||
|
walletInfo: credentials.walletInfo!,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
wallet.addInitialTokens();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PolygonWallet> restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async {
|
||||||
|
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||||
|
throw EthereumMnemonicIsIncorrectException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final wallet = PolygonWallet(
|
||||||
|
password: credentials.password!,
|
||||||
|
mnemonic: credentials.mnemonic,
|
||||||
|
walletInfo: credentials.walletInfo!,
|
||||||
|
);
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
wallet.addInitialTokens();
|
||||||
|
await wallet.save();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rename(String currentName, String password, String newName) async {
|
||||||
|
final currentWalletInfo = walletInfoSource.values
|
||||||
|
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||||
|
final currentWallet = await PolygonWalletBase.open(
|
||||||
|
password: password, name: currentName, walletInfo: currentWalletInfo);
|
||||||
|
|
||||||
|
await currentWallet.renameWalletFiles(newName);
|
||||||
|
|
||||||
|
final newWalletInfo = currentWalletInfo;
|
||||||
|
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||||
|
newWalletInfo.name = newName;
|
||||||
|
|
||||||
|
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||||
|
}
|
||||||
|
}
|
73
cw_polygon/pubspec.yaml
Normal file
73
cw_polygon/pubspec.yaml
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
name: cw_polygon
|
||||||
|
description: A new Flutter package project.
|
||||||
|
version: 0.0.1
|
||||||
|
publish_to: none
|
||||||
|
author: Cake Wallet
|
||||||
|
homepage: https://cakewallet.com
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.0.6 <4.0.0'
|
||||||
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
cw_core:
|
||||||
|
path: ../cw_core
|
||||||
|
cw_ethereum:
|
||||||
|
path: ../cw_ethereum
|
||||||
|
mobx: ^2.0.7+4
|
||||||
|
intl: ^0.18.0
|
||||||
|
bip39: ^1.0.6
|
||||||
|
hive: ^2.2.3
|
||||||
|
collection: ^1.17.1
|
||||||
|
web3dart: ^2.7.1
|
||||||
|
bip32: ^2.0.0
|
||||||
|
hex: ^0.2.0
|
||||||
|
shared_preferences: ^2.0.15
|
||||||
|
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_lints: ^2.0.0
|
||||||
|
build_runner: ^2.1.11
|
||||||
|
mobx_codegen: ^2.0.7
|
||||||
|
hive_generator: ^1.1.3
|
||||||
|
|
||||||
|
# For information on the generic Dart part of this file, see the
|
||||||
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
||||||
|
# The following section is specific to Flutter packages.
|
||||||
|
flutter:
|
||||||
|
|
||||||
|
# To add assets to your package, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
#
|
||||||
|
# For details regarding assets in packages, see
|
||||||
|
# https://flutter.dev/assets-and-images/#from-packages
|
||||||
|
#
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|
|
||||||
|
# To add custom fonts to your package, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts in packages, see
|
||||||
|
# https://flutter.dev/custom-fonts/#from-packages
|
12
cw_polygon/test/cw_polygon_test.dart
Normal file
12
cw_polygon/test/cw_polygon_test.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:cw_polygon/cw_polygon.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('adds one to input values', () {
|
||||||
|
final calculator = Calculator();
|
||||||
|
expect(calculator.addOne(2), 3);
|
||||||
|
expect(calculator.addOne(-7), -6);
|
||||||
|
expect(calculator.addOne(0), 1);
|
||||||
|
});
|
||||||
|
}
|
|
@ -145,8 +145,6 @@ PODS:
|
||||||
- SwiftProtobuf (1.22.0)
|
- SwiftProtobuf (1.22.0)
|
||||||
- SwiftyGif (5.4.4)
|
- SwiftyGif (5.4.4)
|
||||||
- Toast (4.0.0)
|
- Toast (4.0.0)
|
||||||
- tor (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- uni_links (0.0.1):
|
- uni_links (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- UnstoppableDomainsResolution (4.0.0):
|
- UnstoppableDomainsResolution (4.0.0):
|
||||||
|
@ -184,7 +182,6 @@ DEPENDENCIES:
|
||||||
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
|
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- tor (from `.symlinks/plugins/tor/ios`)
|
|
||||||
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||||
- UnstoppableDomainsResolution (~> 4.0.0)
|
- UnstoppableDomainsResolution (~> 4.0.0)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
@ -253,8 +250,6 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
tor:
|
|
||||||
:path: ".symlinks/plugins/tor/ios"
|
|
||||||
uni_links:
|
uni_links:
|
||||||
:path: ".symlinks/plugins/uni_links/ios"
|
:path: ".symlinks/plugins/uni_links/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
|
@ -299,7 +294,6 @@ SPEC CHECKSUMS:
|
||||||
SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989
|
SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989
|
||||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
tor: 662a9f5b980b5c86decb8ba611de9bcd4c8286eb
|
|
||||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||||
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
|
UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841
|
||||||
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
|
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
|
||||||
|
|
|
@ -160,6 +160,26 @@
|
||||||
<string>bitcoincash-wallet</string>
|
<string>bitcoincash-wallet</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>polygon</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>polygon</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>polygon-wallet</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>polygon-wallet</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
|
|
@ -2,11 +2,11 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||||
|
|
||||||
class BuyException implements Exception {
|
class BuyException implements Exception {
|
||||||
BuyException({required this.description, required this.text});
|
BuyException({required this.title, required this.content});
|
||||||
|
|
||||||
final BuyProviderDescription description;
|
final String title;
|
||||||
final String text;
|
final String content;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '${description.title}: $text';
|
String toString() => '$title: $content';
|
||||||
}
|
}
|
|
@ -1,27 +1,33 @@
|
||||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
import 'package:cw_core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
abstract class BuyProvider {
|
abstract class BuyProvider {
|
||||||
BuyProvider({required this.wallet, required this.isTestEnvironment});
|
BuyProvider({
|
||||||
|
required this.wallet,
|
||||||
|
required this.isTestEnvironment,
|
||||||
|
});
|
||||||
|
|
||||||
final WalletBase wallet;
|
final WalletBase wallet;
|
||||||
final bool isTestEnvironment;
|
final bool isTestEnvironment;
|
||||||
|
|
||||||
String get title;
|
String get title;
|
||||||
BuyProviderDescription get description;
|
|
||||||
String get trackUrl;
|
|
||||||
|
|
||||||
WalletType get walletType => wallet.type;
|
String get providerDescription;
|
||||||
String get walletAddress => wallet.walletAddresses.address;
|
|
||||||
String get walletId => wallet.id;
|
String get lightIcon;
|
||||||
|
|
||||||
|
String get darkIcon;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => title;
|
String toString() => title;
|
||||||
|
|
||||||
Future<String> requestUrl(String amount, String sourceCurrency);
|
Future<void> launchProvider(BuildContext context, bool? isBuyAction);
|
||||||
Future<Order> findOrderById(String id);
|
|
||||||
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency);
|
Future<String> requestUrl(String amount, String sourceCurrency) => throw UnimplementedError();
|
||||||
|
|
||||||
|
Future<Order> findOrderById(String id) => throw UnimplementedError();
|
||||||
|
|
||||||
|
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError();
|
||||||
}
|
}
|
201
lib/buy/dfx/dfx_buy_provider.dart
Normal file
201
lib/buy/dfx/dfx_buy_provider.dart
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/routes.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:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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})
|
||||||
|
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||||
|
|
||||||
|
static const _baseUrl = 'api.dfx.swiss';
|
||||||
|
static const _authPath = '/v1/auth/signMessage';
|
||||||
|
static const _signUpPath = '/v1/auth/signUp';
|
||||||
|
static const _signInPath = '/v1/auth/signIn';
|
||||||
|
static const walletName = 'CakeWallet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'DFX Connect';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get providerDescription => S.current.dfx_option_description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lightIcon => 'assets/images/dfx_light.png';
|
||||||
|
|
||||||
|
@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';
|
||||||
|
default:
|
||||||
|
throw Exception("WalletType is not available for DFX ${wallet.type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get blockchain {
|
||||||
|
switch (wallet.type) {
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
case WalletType.litecoin:
|
||||||
|
return 'Bitcoin';
|
||||||
|
case WalletType.monero:
|
||||||
|
return 'Monero';
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return 'Ethereum';
|
||||||
|
default:
|
||||||
|
throw Exception("WalletType is not available for DFX ${wallet.type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getSignMessage() async {
|
||||||
|
final walletAddress = wallet.walletAddresses.address;
|
||||||
|
final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress});
|
||||||
|
|
||||||
|
var response = await http.get(uri, headers: {'accept': 'application/json'});
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final responseBody = jsonDecode(response.body);
|
||||||
|
return responseBody['message'] as String;
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to get sign message. Status: ${response.statusCode} ${response.body}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> signUp() async {
|
||||||
|
final signMessage = getSignature(await getSignMessage());
|
||||||
|
final walletAddress = wallet.walletAddresses.address;
|
||||||
|
|
||||||
|
final requestBody = jsonEncode({
|
||||||
|
'wallet': walletName,
|
||||||
|
'address': walletAddress,
|
||||||
|
'signature': signMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final uri = Uri.https(_baseUrl, _signUpPath);
|
||||||
|
var response = await http.post(uri,
|
||||||
|
headers: {'Content-Type': 'application/json'}, body: requestBody);
|
||||||
|
|
||||||
|
if (response.statusCode == 201) {
|
||||||
|
final responseBody = jsonDecode(response.body);
|
||||||
|
return responseBody['accessToken'] as String;
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
final responseBody = jsonDecode(response.body);
|
||||||
|
final message = responseBody['message'] ?? 'Service unavailable in your country';
|
||||||
|
throw Exception(message);
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to sign up. Status: ${response.statusCode} ${response.body}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> signIn() async {
|
||||||
|
final signMessage = getSignature(await getSignMessage());
|
||||||
|
final walletAddress = wallet.walletAddresses.address;
|
||||||
|
|
||||||
|
final requestBody = jsonEncode({
|
||||||
|
'address': walletAddress,
|
||||||
|
'signature': signMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
final uri = Uri.https(_baseUrl, _signInPath);
|
||||||
|
var response = await http.post(uri,
|
||||||
|
headers: {'Content-Type': 'application/json'}, body: requestBody);
|
||||||
|
|
||||||
|
if (response.statusCode == 201) {
|
||||||
|
final responseBody = jsonDecode(response.body);
|
||||||
|
return responseBody['accessToken'] as String;
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
final responseBody = jsonDecode(response.body);
|
||||||
|
final message = responseBody['message'] ?? 'Service unavailable in your country';
|
||||||
|
throw Exception(message);
|
||||||
|
} else {
|
||||||
|
throw Exception(
|
||||||
|
'Failed to sign in. Status: ${response.statusCode} ${response.body}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSignature(String message) {
|
||||||
|
switch (wallet.type) {
|
||||||
|
case WalletType.ethereum:
|
||||||
|
return wallet.signMessage(message);
|
||||||
|
case WalletType.monero:
|
||||||
|
case WalletType.litecoin:
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
case WalletType.bitcoinCash:
|
||||||
|
return wallet.signMessage(message,
|
||||||
|
address: wallet.walletAddresses.address);
|
||||||
|
default:
|
||||||
|
throw Exception("WalletType is not available for DFX ${wallet.type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||||
|
try {
|
||||||
|
final assetOut = this.assetOut;
|
||||||
|
final blockchain = this.blockchain;
|
||||||
|
final actionType = isBuyAction == true ? '/buy' : '/sell';
|
||||||
|
|
||||||
|
String accessToken;
|
||||||
|
|
||||||
|
try {
|
||||||
|
accessToken = await signUp();
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (e.toString().contains('409')) {
|
||||||
|
accessToken = await signIn();
|
||||||
|
} else {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final uri = Uri.https('services.dfx.swiss', actionType, {
|
||||||
|
'session': accessToken,
|
||||||
|
'lang': 'en',
|
||||||
|
'asset-out': assetOut,
|
||||||
|
'blockchain': blockchain,
|
||||||
|
'asset-in': 'EUR',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
if (DeviceInfo.instance.isMobile) {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushNamed(Routes.webViewPage, arguments: ["DFX Connect", uri]);
|
||||||
|
} else {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Could not launch URL');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
await showPopUp<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertWithOneAction(
|
||||||
|
alertTitle: "DFX Connect",
|
||||||
|
alertContent: S.of(context).buy_provider_unavailable + ': $e',
|
||||||
|
buttonText: S.of(context).ok,
|
||||||
|
buttonAction: () => Navigator.of(context).pop());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,14 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cake_wallet/palette.dart';
|
import 'package:cake_wallet/palette.dart';
|
||||||
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/themes/theme_base.dart';
|
import 'package:cake_wallet/themes/theme_base.dart';
|
||||||
|
import 'package:cake_wallet/utils/device_info.dart';
|
||||||
import 'package:crypto/crypto.dart';
|
import 'package:crypto/crypto.dart';
|
||||||
import 'package:cake_wallet/buy/buy_exception.dart';
|
import 'package:cake_wallet/buy/buy_exception.dart';
|
||||||
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||||
|
@ -14,33 +19,55 @@ import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
import 'package:cw_core/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class MoonPaySellProvider {
|
class MoonPaySellProvider extends BuyProvider {
|
||||||
MoonPaySellProvider({this.isTest = false})
|
MoonPaySellProvider({
|
||||||
: baseUrl = isTest ? _baseTestUrl : _baseProductUrl;
|
required SettingsStore settingsStore,
|
||||||
|
required WalletBase wallet,
|
||||||
|
bool isTestEnvironment = false,
|
||||||
|
}) : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl,
|
||||||
|
this._settingsStore = settingsStore,
|
||||||
|
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||||
|
|
||||||
|
final SettingsStore _settingsStore;
|
||||||
|
|
||||||
static const _baseTestUrl = 'sell-sandbox.moonpay.com';
|
static const _baseTestUrl = 'sell-sandbox.moonpay.com';
|
||||||
static const _baseProductUrl = 'sell.moonpay.com';
|
static const _baseProductUrl = 'sell.moonpay.com';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get providerDescription =>
|
||||||
|
'MoonPay offers a fast and simple way to buy and sell cryptocurrencies';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'MoonPay';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lightIcon => 'assets/images/moonpay_light.png';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get darkIcon => 'assets/images/moonpay_dark.png';
|
||||||
|
|
||||||
static String themeToMoonPayTheme(ThemeBase theme) {
|
static String themeToMoonPayTheme(ThemeBase theme) {
|
||||||
switch (theme.type) {
|
switch (theme.type) {
|
||||||
case ThemeType.bright:
|
case ThemeType.bright:
|
||||||
return 'light';
|
|
||||||
case ThemeType.light:
|
case ThemeType.light:
|
||||||
return 'light';
|
return 'light';
|
||||||
case ThemeType.dark:
|
case ThemeType.dark:
|
||||||
return 'dark';
|
return 'dark';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static String get _apiKey => secrets.moonPayApiKey;
|
static String get _apiKey => secrets.moonPayApiKey;
|
||||||
|
|
||||||
static String get _secretKey => secrets.moonPaySecretKey;
|
static String get _secretKey => secrets.moonPaySecretKey;
|
||||||
final bool isTest;
|
|
||||||
final String baseUrl;
|
final String baseUrl;
|
||||||
|
|
||||||
Future<Uri> requestUrl(
|
Future<Uri> requestMoonPayUrl({
|
||||||
{required CryptoCurrency currency,
|
required CryptoCurrency currency,
|
||||||
required String refundWalletAddress,
|
required String refundWalletAddress,
|
||||||
required SettingsStore settingsStore}) async {
|
required SettingsStore settingsStore,
|
||||||
|
}) async {
|
||||||
final customParams = {
|
final customParams = {
|
||||||
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
|
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
|
||||||
'language': settingsStore.languageCode,
|
'language': settingsStore.languageCode,
|
||||||
|
@ -50,18 +77,22 @@ class MoonPaySellProvider {
|
||||||
};
|
};
|
||||||
|
|
||||||
final originalUri = Uri.https(
|
final originalUri = Uri.https(
|
||||||
baseUrl, '', <String, dynamic>{
|
baseUrl,
|
||||||
|
'',
|
||||||
|
<String, dynamic>{
|
||||||
'apiKey': _apiKey,
|
'apiKey': _apiKey,
|
||||||
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
|
'defaultBaseCurrencyCode': currency.toString().toLowerCase(),
|
||||||
'refundWalletAddress': refundWalletAddress
|
'refundWalletAddress': refundWalletAddress,
|
||||||
}..addAll(customParams));
|
}..addAll(customParams),
|
||||||
|
);
|
||||||
|
|
||||||
final messageBytes = utf8.encode('?${originalUri.query}');
|
final messageBytes = utf8.encode('?${originalUri.query}');
|
||||||
final key = utf8.encode(_secretKey);
|
final key = utf8.encode(_secretKey);
|
||||||
final hmac = Hmac(sha256, key);
|
final hmac = Hmac(sha256, key);
|
||||||
final digest = hmac.convert(messageBytes);
|
final digest = hmac.convert(messageBytes);
|
||||||
final signature = base64.encode(digest.bytes);
|
final signature = base64.encode(digest.bytes);
|
||||||
|
|
||||||
if (isTest) {
|
if (isTestEnvironment) {
|
||||||
return originalUri;
|
return originalUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +101,39 @@ class MoonPaySellProvider {
|
||||||
final signedUri = originalUri.replace(queryParameters: query);
|
final signedUri = originalUri.replace(queryParameters: query);
|
||||||
return signedUri;
|
return signedUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||||
|
try {
|
||||||
|
final uri = await requestMoonPayUrl(
|
||||||
|
currency: wallet.currency,
|
||||||
|
refundWalletAddress: wallet.walletAddresses.address,
|
||||||
|
settingsStore: _settingsStore,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
if (DeviceInfo.instance.isMobile) {
|
||||||
|
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
|
||||||
|
} else {
|
||||||
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Exception('Could not launch URL');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
await showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertWithOneAction(
|
||||||
|
alertTitle: 'MoonPay',
|
||||||
|
alertContent: 'The MoonPay service is currently unavailable: $e',
|
||||||
|
buttonText: S.of(context).ok,
|
||||||
|
buttonAction: () => Navigator.of(context).pop(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoonPayBuyProvider extends BuyProvider {
|
class MoonPayBuyProvider extends BuyProvider {
|
||||||
|
@ -91,28 +155,40 @@ class MoonPayBuyProvider extends BuyProvider {
|
||||||
String get title => 'MoonPay';
|
String get title => 'MoonPay';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BuyProviderDescription get description => BuyProviderDescription.moonPay;
|
String get providerDescription =>
|
||||||
|
'MoonPay offers a fast and simple way to buy and sell cryptocurrencies';
|
||||||
String get currencyCode =>
|
|
||||||
walletTypeToCryptoCurrency(walletType).title.toLowerCase();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
String get lightIcon => 'assets/images/moonpay_light.png';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get darkIcon => 'assets/images/moonpay_dark.png';
|
||||||
|
|
||||||
|
String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
|
||||||
|
|
||||||
String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
|
String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
|
||||||
|
|
||||||
String baseUrl;
|
String baseUrl;
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> requestUrl(String amount, String sourceCurrency) async {
|
Future<String> requestUrl(String amount, String sourceCurrency) async {
|
||||||
final enabledPaymentMethods =
|
final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
|
||||||
'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
|
|
||||||
'%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment';
|
'%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment';
|
||||||
|
|
||||||
final suffix = '?apiKey=' + _apiKey + '¤cyCode=' +
|
final suffix = '?apiKey=' +
|
||||||
currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods +
|
_apiKey +
|
||||||
'&walletAddress=' + walletAddress +
|
'¤cyCode=' +
|
||||||
'&baseCurrencyCode=' + sourceCurrency.toLowerCase() +
|
currencyCode +
|
||||||
'&baseCurrencyAmount=' + amount + '&lockAmount=true' +
|
'&enabledPaymentMethods=' +
|
||||||
'&showAllCurrencies=false' + '&showWalletAddressForm=false';
|
enabledPaymentMethods +
|
||||||
|
'&walletAddress=' +
|
||||||
|
wallet.walletAddresses.address +
|
||||||
|
'&baseCurrencyCode=' +
|
||||||
|
sourceCurrency.toLowerCase() +
|
||||||
|
'&baseCurrencyAmount=' +
|
||||||
|
amount +
|
||||||
|
'&lockAmount=true' +
|
||||||
|
'&showAllCurrencies=false' +
|
||||||
|
'&showWalletAddressForm=false';
|
||||||
|
|
||||||
final originalUrl = baseUrl + suffix;
|
final originalUrl = baseUrl + suffix;
|
||||||
|
|
||||||
|
@ -121,25 +197,27 @@ class MoonPayBuyProvider extends BuyProvider {
|
||||||
final hmac = Hmac(sha256, key);
|
final hmac = Hmac(sha256, key);
|
||||||
final digest = hmac.convert(messageBytes);
|
final digest = hmac.convert(messageBytes);
|
||||||
final signature = base64.encode(digest.bytes);
|
final signature = base64.encode(digest.bytes);
|
||||||
final urlWithSignature = originalUrl +
|
final urlWithSignature = originalUrl + '&signature=${Uri.encodeComponent(signature)}';
|
||||||
'&signature=${Uri.encodeComponent(signature)}';
|
|
||||||
|
|
||||||
return isTestEnvironment ? originalUrl : urlWithSignature;
|
return isTestEnvironment ? originalUrl : urlWithSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
|
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
|
||||||
final url = _apiUrl + _currenciesSuffix + '/$currencyCode' +
|
final url = _apiUrl +
|
||||||
_quoteSuffix + '/?apiKey=' + _apiKey +
|
_currenciesSuffix +
|
||||||
'&baseCurrencyAmount=' + amount +
|
'/$currencyCode' +
|
||||||
'&baseCurrencyCode=' + sourceCurrency.toLowerCase();
|
_quoteSuffix +
|
||||||
|
'/?apiKey=' +
|
||||||
|
_apiKey +
|
||||||
|
'&baseCurrencyAmount=' +
|
||||||
|
amount +
|
||||||
|
'&baseCurrencyCode=' +
|
||||||
|
sourceCurrency.toLowerCase();
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
final response = await get(uri);
|
final response = await get(uri);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw BuyException(
|
throw BuyException(title: providerDescription, content: 'Quote is not found!');
|
||||||
description: description,
|
|
||||||
text: 'Quote is not found!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
@ -148,22 +226,16 @@ class MoonPayBuyProvider extends BuyProvider {
|
||||||
final minSourceAmount = responseJSON['baseCurrency']['minAmount'] as int;
|
final minSourceAmount = responseJSON['baseCurrency']['minAmount'] as int;
|
||||||
|
|
||||||
return BuyAmount(
|
return BuyAmount(
|
||||||
sourceAmount: sourceAmount,
|
sourceAmount: sourceAmount, destAmount: destAmount, minAmount: minSourceAmount);
|
||||||
destAmount: destAmount,
|
|
||||||
minAmount: minSourceAmount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Order> findOrderById(String id) async {
|
Future<Order> findOrderById(String id) async {
|
||||||
final url = _apiUrl + _transactionsSuffix + '/$id' +
|
final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey;
|
||||||
'?apiKey=' + _apiKey;
|
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
final response = await get(uri);
|
final response = await get(uri);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw BuyException(
|
throw BuyException(title: providerDescription, content: 'Transaction $id is not found!');
|
||||||
description: description,
|
|
||||||
text: 'Transaction $id is not found!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
@ -175,14 +247,13 @@ class MoonPayBuyProvider extends BuyProvider {
|
||||||
|
|
||||||
return Order(
|
return Order(
|
||||||
id: id,
|
id: id,
|
||||||
provider: description,
|
provider: BuyProviderDescription.moonPay,
|
||||||
transferId: id,
|
transferId: id,
|
||||||
state: state,
|
state: state,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
amount: amount.toString(),
|
amount: amount.toString(),
|
||||||
receiveAddress: walletAddress,
|
receiveAddress: wallet.walletAddresses.address,
|
||||||
walletId: walletId
|
walletId: wallet.id);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> onEnabled() async {
|
static Future<bool> onEnabled() async {
|
||||||
|
@ -201,4 +272,8 @@ class MoonPayBuyProvider extends BuyProvider {
|
||||||
|
|
||||||
return isBuyEnable;
|
return isBuyEnable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> launchProvider(BuildContext context, bool? isBuyAction) =>
|
||||||
|
throw UnimplementedError();
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
|
@ -9,20 +10,31 @@ import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class OnRamperBuyProvider {
|
class OnRamperBuyProvider extends BuyProvider {
|
||||||
OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet})
|
OnRamperBuyProvider(this._settingsStore,
|
||||||
: this._settingsStore = settingsStore,
|
{required WalletBase wallet, bool isTestEnvironment = false})
|
||||||
this._wallet = wallet;
|
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||||
|
|
||||||
final SettingsStore _settingsStore;
|
|
||||||
final WalletBase _wallet;
|
|
||||||
|
|
||||||
static const _baseUrl = 'buy.onramper.com';
|
static const _baseUrl = 'buy.onramper.com';
|
||||||
|
|
||||||
|
final SettingsStore _settingsStore;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Onramper';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get providerDescription => S.current.onramper_option_description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lightIcon => 'assets/images/onramper_light.png';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get darkIcon => 'assets/images/onramper_dark.png';
|
||||||
|
|
||||||
String get _apiKey => secrets.onramperApiKey;
|
String get _apiKey => secrets.onramperApiKey;
|
||||||
|
|
||||||
String get _normalizeCryptoCurrency {
|
String get _normalizeCryptoCurrency {
|
||||||
switch (_wallet.currency) {
|
switch (wallet.currency) {
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
return "LTC_LITECOIN";
|
return "LTC_LITECOIN";
|
||||||
case CryptoCurrency.xmr:
|
case CryptoCurrency.xmr:
|
||||||
|
@ -32,7 +44,7 @@ class OnRamperBuyProvider {
|
||||||
case CryptoCurrency.nano:
|
case CryptoCurrency.nano:
|
||||||
return "XNO_NANO";
|
return "XNO_NANO";
|
||||||
default:
|
default:
|
||||||
return _wallet.currency.title;
|
return wallet.currency.title;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +52,7 @@ class OnRamperBuyProvider {
|
||||||
return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
|
return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
Uri requestUrl(BuildContext context) {
|
Uri requestOnramperUrl(BuildContext context) {
|
||||||
String primaryColor,
|
String primaryColor,
|
||||||
secondaryColor,
|
secondaryColor,
|
||||||
primaryTextColor,
|
primaryTextColor,
|
||||||
|
@ -50,9 +62,10 @@ class OnRamperBuyProvider {
|
||||||
|
|
||||||
primaryColor = getColorStr(Theme.of(context).primaryColor);
|
primaryColor = getColorStr(Theme.of(context).primaryColor);
|
||||||
secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
|
secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
|
||||||
primaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
primaryTextColor =
|
||||||
secondaryTextColor =
|
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
||||||
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
|
secondaryTextColor = getColorStr(
|
||||||
|
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
|
||||||
containerColor = getColorStr(Theme.of(context).colorScheme.background);
|
containerColor = getColorStr(Theme.of(context).colorScheme.background);
|
||||||
cardColor = getColorStr(Theme.of(context).cardColor);
|
cardColor = getColorStr(Theme.of(context).cardColor);
|
||||||
|
|
||||||
|
@ -60,12 +73,13 @@ class OnRamperBuyProvider {
|
||||||
cardColor = getColorStr(Colors.white);
|
cardColor = getColorStr(Colors.white);
|
||||||
}
|
}
|
||||||
|
|
||||||
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
|
final networkName =
|
||||||
|
wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
|
||||||
|
|
||||||
return Uri.https(_baseUrl, '', <String, dynamic>{
|
return Uri.https(_baseUrl, '', <String, dynamic>{
|
||||||
'apiKey': _apiKey,
|
'apiKey': _apiKey,
|
||||||
'defaultCrypto': _normalizeCryptoCurrency,
|
'defaultCrypto': _normalizeCryptoCurrency,
|
||||||
'networkWallets': '${networkName}:${_wallet.walletAddresses.address}',
|
'networkWallets': '${networkName}:${wallet.walletAddresses.address}',
|
||||||
'supportSell': "false",
|
'supportSell': "false",
|
||||||
'supportSwap': "false",
|
'supportSwap': "false",
|
||||||
'primaryColor': primaryColor,
|
'primaryColor': primaryColor,
|
||||||
|
@ -77,10 +91,11 @@ class OnRamperBuyProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> launchProvider(BuildContext context) async {
|
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||||
final uri = requestUrl(context);
|
final uri = requestOnramperUrl(context);
|
||||||
if (DeviceInfo.instance.isMobile) {
|
if (DeviceInfo.instance.isMobile) {
|
||||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]);
|
Navigator.of(context)
|
||||||
|
.pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]);
|
||||||
} else {
|
} else {
|
||||||
await launchUrl(uri);
|
await launchUrl(uri);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
|
@ -10,40 +11,44 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class RobinhoodBuyProvider {
|
class RobinhoodBuyProvider extends BuyProvider {
|
||||||
RobinhoodBuyProvider({required WalletBase wallet}) : this._wallet = wallet;
|
RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
|
||||||
|
: super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||||
final WalletBase _wallet;
|
|
||||||
|
|
||||||
static const _baseUrl = 'applink.robinhood.com';
|
static const _baseUrl = 'applink.robinhood.com';
|
||||||
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
|
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get title => 'Robinhood Connect';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get providerDescription => S.current.robinhood_option_description;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get lightIcon => 'assets/images/robinhood_light.png';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get darkIcon => 'assets/images/robinhood_dark.png';
|
||||||
|
|
||||||
String get _applicationId => secrets.robinhoodApplicationId;
|
String get _applicationId => secrets.robinhoodApplicationId;
|
||||||
|
|
||||||
String get _apiSecret => secrets.robinhoodCIdApiSecret;
|
String get _apiSecret => secrets.robinhoodCIdApiSecret;
|
||||||
|
|
||||||
bool get isAvailable => [
|
|
||||||
WalletType.bitcoin,
|
|
||||||
WalletType.bitcoinCash,
|
|
||||||
WalletType.litecoin,
|
|
||||||
WalletType.ethereum
|
|
||||||
].contains(_wallet.type);
|
|
||||||
|
|
||||||
String getSignature(String message) {
|
String getSignature(String message) {
|
||||||
switch (_wallet.type) {
|
switch (wallet.type) {
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
return _wallet.signMessage(message);
|
return wallet.signMessage(message);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
return _wallet.signMessage(message, address: _wallet.walletAddresses.address);
|
return wallet.signMessage(message, address: wallet.walletAddresses.address);
|
||||||
default:
|
default:
|
||||||
throw Exception("WalletType is not available for Robinhood ${_wallet.type}");
|
throw Exception("WalletType is not available for Robinhood ${wallet.type}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getConnectId() async {
|
Future<String> getConnectId() async {
|
||||||
final walletAddress = _wallet.walletAddresses.address;
|
final walletAddress = wallet.walletAddresses.address;
|
||||||
final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10;
|
final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10;
|
||||||
final message = "$_apiSecret:${valid_until}";
|
final message = "$_apiSecret:${valid_until}";
|
||||||
|
|
||||||
|
@ -64,22 +69,22 @@ class RobinhoodBuyProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uri> requestUrl() async {
|
Future<Uri> requestProviderUrl() async {
|
||||||
final connectId = await getConnectId();
|
final connectId = await getConnectId();
|
||||||
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_");
|
final networkName = wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_");
|
||||||
|
|
||||||
return Uri.https(_baseUrl, '/u/connect', <String, dynamic>{
|
return Uri.https(_baseUrl, '/u/connect', <String, dynamic>{
|
||||||
'applicationId': _applicationId,
|
'applicationId': _applicationId,
|
||||||
'connectId': connectId,
|
'connectId': connectId,
|
||||||
'walletAddress': _wallet.walletAddresses.address,
|
'walletAddress': wallet.walletAddresses.address,
|
||||||
'userIdentifier': _wallet.walletAddresses.address,
|
'userIdentifier': wallet.walletAddresses.address,
|
||||||
'supportedNetworks': networkName
|
'supportedNetworks': networkName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> launchProvider(BuildContext context) async {
|
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||||
try {
|
try {
|
||||||
final uri = await requestUrl();
|
final uri = await requestProviderUrl();
|
||||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
await showPopUp<void>(
|
await showPopUp<void>(
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cake_wallet/buy/buy_exception.dart';
|
import 'package:cake_wallet/buy/buy_exception.dart';
|
||||||
|
import 'package:flutter/src/widgets/framework.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||||
|
@ -12,9 +13,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
class WyreBuyProvider extends BuyProvider {
|
class WyreBuyProvider extends BuyProvider {
|
||||||
WyreBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
|
WyreBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
|
||||||
: baseApiUrl = isTestEnvironment
|
: baseApiUrl = isTestEnvironment ? _baseTestApiUrl : _baseProductApiUrl,
|
||||||
? _baseTestApiUrl
|
|
||||||
: _baseProductApiUrl,
|
|
||||||
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||||
|
|
||||||
static const _baseTestApiUrl = 'https://api.testwyre.com';
|
static const _baseTestApiUrl = 'https://api.testwyre.com';
|
||||||
|
@ -35,26 +34,27 @@ class WyreBuyProvider extends BuyProvider {
|
||||||
String get title => 'Wyre';
|
String get title => 'Wyre';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BuyProviderDescription get description => BuyProviderDescription.wyre;
|
String get providerDescription => '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get trackUrl => isTestEnvironment
|
String get lightIcon => 'assets/images/robinhood_light.png';
|
||||||
? _trackTestUrl
|
|
||||||
: _trackProductUrl;
|
@override
|
||||||
|
String get darkIcon => 'assets/images/robinhood_dark.png';
|
||||||
|
|
||||||
|
String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl;
|
||||||
|
|
||||||
String baseApiUrl;
|
String baseApiUrl;
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String> requestUrl(String amount, String sourceCurrency) async {
|
Future<String> requestUrl(String amount, String sourceCurrency) async {
|
||||||
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
final timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
final url = baseApiUrl + _ordersSuffix + _reserveSuffix +
|
final url = baseApiUrl + _ordersSuffix + _reserveSuffix + _timeStampSuffix + timestamp;
|
||||||
_timeStampSuffix + timestamp;
|
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
final body = {
|
final body = {
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
'sourceCurrency': sourceCurrency,
|
'sourceCurrency': sourceCurrency,
|
||||||
'destCurrency': walletTypeToCryptoCurrency(walletType).title,
|
'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
|
||||||
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress,
|
'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
|
||||||
'referrerAccountId': _accountId,
|
'referrerAccountId': _accountId,
|
||||||
'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest']
|
'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest']
|
||||||
};
|
};
|
||||||
|
@ -67,9 +67,7 @@ class WyreBuyProvider extends BuyProvider {
|
||||||
body: json.encode(body));
|
body: json.encode(body));
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw BuyException(
|
throw BuyException(title: providerDescription, content: 'Url $url is not found!');
|
||||||
description: description,
|
|
||||||
text: 'Url $url is not found!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
@ -77,14 +75,13 @@ class WyreBuyProvider extends BuyProvider {
|
||||||
return urlFromResponse;
|
return urlFromResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
|
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
|
||||||
final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix;
|
final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix;
|
||||||
final body = {
|
final body = {
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
'sourceCurrency': sourceCurrency,
|
'sourceCurrency': sourceCurrency,
|
||||||
'destCurrency': walletTypeToCryptoCurrency(walletType).title,
|
'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
|
||||||
'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress,
|
'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
|
||||||
'accountId': _accountId,
|
'accountId': _accountId,
|
||||||
'country': _countryCode
|
'country': _countryCode
|
||||||
};
|
};
|
||||||
|
@ -98,9 +95,7 @@ class WyreBuyProvider extends BuyProvider {
|
||||||
body: json.encode(body));
|
body: json.encode(body));
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
throw BuyException(
|
throw BuyException(title: providerDescription, content: 'Quote is not found!');
|
||||||
description: description,
|
|
||||||
text: 'Quote is not found!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||||
|
@ -108,58 +103,55 @@ class WyreBuyProvider extends BuyProvider {
|
||||||
final destAmount = responseJSON['destAmount'] as double;
|
final destAmount = responseJSON['destAmount'] as double;
|
||||||
final achAmount = responseJSON['sourceAmountWithoutFees'] as double;
|
final achAmount = responseJSON['sourceAmountWithoutFees'] as double;
|
||||||
|
|
||||||
return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount);
|
return BuyAmount(
|
||||||
|
sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Order> findOrderById(String id) async {
|
Future<Order> findOrderById(String id) async {
|
||||||
final orderUrl = baseApiUrl + _ordersSuffix + '/$id';
|
final orderUrl = baseApiUrl + _ordersSuffix + '/$id';
|
||||||
final orderUri = Uri.parse(orderUrl);
|
final orderUri = Uri.parse(orderUrl);
|
||||||
final orderResponse = await get(orderUri);
|
final orderResponse = await get(orderUri);
|
||||||
|
|
||||||
if (orderResponse.statusCode != 200) {
|
if (orderResponse.statusCode != 200) {
|
||||||
throw BuyException(
|
throw BuyException(title: providerDescription, content: 'Order $id is not found!');
|
||||||
description: description,
|
|
||||||
text: 'Order $id is not found!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final orderResponseJSON =
|
final orderResponseJSON = json.decode(orderResponse.body) as Map<String, dynamic>;
|
||||||
json.decode(orderResponse.body) as Map<String, dynamic>;
|
|
||||||
final transferId = orderResponseJSON['transferId'] as String;
|
final transferId = orderResponseJSON['transferId'] as String;
|
||||||
final from = orderResponseJSON['sourceCurrency'] as String;
|
final from = orderResponseJSON['sourceCurrency'] as String;
|
||||||
final to = orderResponseJSON['destCurrency'] as String;
|
final to = orderResponseJSON['destCurrency'] as String;
|
||||||
final status = orderResponseJSON['status'] as String;
|
final status = orderResponseJSON['status'] as String;
|
||||||
final state = TradeState.deserialize(raw: status.toLowerCase());
|
final state = TradeState.deserialize(raw: status.toLowerCase());
|
||||||
final createdAtRaw = orderResponseJSON['createdAt'] as int;
|
final createdAtRaw = orderResponseJSON['createdAt'] as int;
|
||||||
final createdAt =
|
final createdAt = DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal();
|
||||||
DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal();
|
|
||||||
|
|
||||||
final transferUrl =
|
final transferUrl = baseApiUrl + _transferSuffix + transferId + _trackSuffix;
|
||||||
baseApiUrl + _transferSuffix + transferId + _trackSuffix;
|
|
||||||
final transferUri = Uri.parse(transferUrl);
|
final transferUri = Uri.parse(transferUrl);
|
||||||
final transferResponse = await get(transferUri);
|
final transferResponse = await get(transferUri);
|
||||||
|
|
||||||
if (transferResponse.statusCode != 200) {
|
if (transferResponse.statusCode != 200) {
|
||||||
throw BuyException(
|
throw BuyException(title: providerDescription, content: 'Transfer $transferId is not found!');
|
||||||
description: description,
|
|
||||||
text: 'Transfer $transferId is not found!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final transferResponseJSON =
|
final transferResponseJSON = json.decode(transferResponse.body) as Map<String, dynamic>;
|
||||||
json.decode(transferResponse.body) as Map<String, dynamic>;
|
|
||||||
final amount = transferResponseJSON['destAmount'] as double;
|
final amount = transferResponseJSON['destAmount'] as double;
|
||||||
|
|
||||||
return Order(
|
return Order(
|
||||||
id: id,
|
id: id,
|
||||||
provider: description,
|
provider: BuyProviderDescription.wyre,
|
||||||
transferId: transferId,
|
transferId: transferId,
|
||||||
from: from,
|
from: from,
|
||||||
to: to,
|
to: to,
|
||||||
state: state,
|
state: state,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
amount: amount.toString(),
|
amount: amount.toString(),
|
||||||
receiveAddress: walletAddress,
|
receiveAddress: wallet.walletAddresses.address,
|
||||||
walletId: walletId
|
walletId: wallet.id);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> launchProvider(BuildContext context, bool? isBuyAction) {
|
||||||
|
// TODO: implement launchProvider
|
||||||
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -32,6 +32,8 @@ class AddressValidator extends TextValidator {
|
||||||
return '[0-9a-zA-Z_]';
|
return '[0-9a-zA-Z_]';
|
||||||
case CryptoCurrency.usdc:
|
case CryptoCurrency.usdc:
|
||||||
case CryptoCurrency.usdcpoly:
|
case CryptoCurrency.usdcpoly:
|
||||||
|
case CryptoCurrency.usdtPoly:
|
||||||
|
case CryptoCurrency.usdcEPoly:
|
||||||
case CryptoCurrency.ape:
|
case CryptoCurrency.ape:
|
||||||
case CryptoCurrency.avaxc:
|
case CryptoCurrency.avaxc:
|
||||||
case CryptoCurrency.eth:
|
case CryptoCurrency.eth:
|
||||||
|
@ -90,7 +92,7 @@ class AddressValidator extends TextValidator {
|
||||||
case CryptoCurrency.eos:
|
case CryptoCurrency.eos:
|
||||||
return '[0-9a-zA-Z]';
|
return '[0-9a-zA-Z]';
|
||||||
case CryptoCurrency.bch:
|
case CryptoCurrency.bch:
|
||||||
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q[0-9a-zA-Z]{42}\$|^bitcoincash:q[0-9a-zA-Z]{41}\$|^bitcoincash:q[0-9a-zA-Z]{42}\$';
|
return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$';
|
||||||
case CryptoCurrency.bnb:
|
case CryptoCurrency.bnb:
|
||||||
return '[0-9a-zA-Z]';
|
return '[0-9a-zA-Z]';
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
|
@ -141,6 +143,8 @@ class AddressValidator extends TextValidator {
|
||||||
return [42];
|
return [42];
|
||||||
case CryptoCurrency.eth:
|
case CryptoCurrency.eth:
|
||||||
case CryptoCurrency.usdcpoly:
|
case CryptoCurrency.usdcpoly:
|
||||||
|
case CryptoCurrency.usdtPoly:
|
||||||
|
case CryptoCurrency.usdcEPoly:
|
||||||
case CryptoCurrency.mana:
|
case CryptoCurrency.mana:
|
||||||
case CryptoCurrency.matic:
|
case CryptoCurrency.matic:
|
||||||
case CryptoCurrency.maticpoly:
|
case CryptoCurrency.maticpoly:
|
||||||
|
@ -271,6 +275,8 @@ class AddressValidator extends TextValidator {
|
||||||
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
|
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
|
||||||
case CryptoCurrency.eth:
|
case CryptoCurrency.eth:
|
||||||
return '0x[0-9a-zA-Z]{42}';
|
return '0x[0-9a-zA-Z]{42}';
|
||||||
|
case CryptoCurrency.maticpoly:
|
||||||
|
return '0x[0-9a-zA-Z]{42}';
|
||||||
case CryptoCurrency.nano:
|
case CryptoCurrency.nano:
|
||||||
return 'nano_[0-9a-zA-Z]{60}';
|
return 'nano_[0-9a-zA-Z]{60}';
|
||||||
case CryptoCurrency.banano:
|
case CryptoCurrency.banano:
|
||||||
|
|
|
@ -16,7 +16,7 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
|
||||||
|
|
||||||
final Map<String, String> queryParams = {
|
final Map<String, String> queryParams = {
|
||||||
'interval_count': '1',
|
'interval_count': '1',
|
||||||
'base': crypto,
|
'base': crypto.split(".").first,
|
||||||
'quote': fiat,
|
'quote': fiat,
|
||||||
'key': secrets.fiatApiKey,
|
'key': secrets.fiatApiKey,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||||
import 'package:cake_wallet/haven/haven.dart';
|
import 'package:cake_wallet/haven/haven.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
||||||
|
import 'package:cake_wallet/polygon/polygon.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/nano/nano.dart';
|
import 'package:cake_wallet/nano/nano.dart';
|
||||||
|
@ -34,6 +35,8 @@ class SeedValidator extends Validator<MnemonicItem> {
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
return nano!.getNanoWordList(language);
|
return nano!.getNanoWordList(language);
|
||||||
|
case WalletType.polygon:
|
||||||
|
return polygon!.getPolygonWordList(language);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
|
import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
|
||||||
|
@ -14,7 +15,6 @@ import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget
|
||||||
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart';
|
||||||
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
|
||||||
import 'package:convert/convert.dart';
|
import 'package:convert/convert.dart';
|
||||||
import 'package:cw_core/wallet_type.dart';
|
|
||||||
import 'package:eth_sig_util/eth_sig_util.dart';
|
import 'package:eth_sig_util/eth_sig_util.dart';
|
||||||
import 'package:eth_sig_util/util/utils.dart';
|
import 'package:eth_sig_util/util/utils.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
@ -46,13 +46,12 @@ class EvmChainServiceImpl implements ChainService {
|
||||||
required this.wcKeyService,
|
required this.wcKeyService,
|
||||||
required this.bottomSheetService,
|
required this.bottomSheetService,
|
||||||
required this.wallet,
|
required this.wallet,
|
||||||
Web3Client? ethClient,
|
Web3Client? web3Client,
|
||||||
}) : ethClient = ethClient ??
|
}) : ethClient = web3Client ??
|
||||||
Web3Client(
|
Web3Client(
|
||||||
appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(),
|
appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
|
||||||
http.Client(),
|
http.Client(),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
for (final String event in getEvents()) {
|
for (final String event in getEvents()) {
|
||||||
wallet.registerEventEmitter(chainId: getChainId(), event: event);
|
wallet.registerEventEmitter(chainId: getChainId(), event: event);
|
||||||
}
|
}
|
||||||
|
@ -138,7 +137,8 @@ class EvmChainServiceImpl implements ChainService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load the private key
|
// Load the private key
|
||||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
final List<ChainKeyModel> keys = wcKeyService
|
||||||
|
.getKeysForChain(appStore.wallet!);
|
||||||
|
|
||||||
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||||
|
|
||||||
|
@ -176,13 +176,15 @@ class EvmChainServiceImpl implements ChainService {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load the private key
|
// Load the private key
|
||||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
final List<ChainKeyModel> keys = wcKeyService
|
||||||
|
.getKeysForChain(appStore.wallet!);
|
||||||
|
|
||||||
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||||
|
|
||||||
final String signature = hex.encode(
|
final String signature = hex.encode(
|
||||||
credentials.signPersonalMessageToUint8List(
|
credentials.signPersonalMessageToUint8List(
|
||||||
Uint8List.fromList(utf8.encode(message)),
|
Uint8List.fromList(utf8.encode(message)),
|
||||||
|
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
log(signature);
|
log(signature);
|
||||||
|
@ -212,7 +214,8 @@ class EvmChainServiceImpl implements ChainService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the private key
|
// Load the private key
|
||||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
final List<ChainKeyModel> keys = wcKeyService
|
||||||
|
.getKeysForChain(appStore.wallet!);
|
||||||
|
|
||||||
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey);
|
||||||
|
|
||||||
|
@ -232,7 +235,11 @@ class EvmChainServiceImpl implements ChainService {
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = await ethClient.sendTransaction(credentials, transaction);
|
final result = await ethClient.sendTransaction(
|
||||||
|
credentials,
|
||||||
|
transaction,
|
||||||
|
chainId: getChainIdBasedOnWalletType(appStore.wallet!.type),
|
||||||
|
);
|
||||||
|
|
||||||
log('Result: $result');
|
log('Result: $result');
|
||||||
|
|
||||||
|
@ -267,7 +274,8 @@ class EvmChainServiceImpl implements ChainService {
|
||||||
return authError;
|
return authError;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<ChainKeyModel> keys = wcKeyService.getKeysForChain(getChainId());
|
final List<ChainKeyModel> keys = wcKeyService
|
||||||
|
.getKeysForChain(appStore.wallet!);
|
||||||
|
|
||||||
return EthSigUtil.signTypedData(
|
return EthSigUtil.signTypedData(
|
||||||
privateKey: keys[0].privateKey,
|
privateKey: keys[0].privateKey,
|
||||||
|
@ -277,10 +285,12 @@ class EvmChainServiceImpl implements ChainService {
|
||||||
}
|
}
|
||||||
|
|
||||||
String _convertToReadable(Map<String, dynamic> data) {
|
String _convertToReadable(Map<String, dynamic> data) {
|
||||||
|
final tokenName = getTokenNameBasedOnWalletType(appStore.wallet!.type);
|
||||||
String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString();
|
String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString();
|
||||||
String value = data['value'] != null
|
String value = data['value'] != null
|
||||||
? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() + ' ETH'
|
? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() +
|
||||||
: '0 ETH';
|
' $tokenName'
|
||||||
|
: '0 $tokenName';
|
||||||
String from = data['from'] as String;
|
String from = data['from'] as String;
|
||||||
String to = data['to'] as String;
|
String to = data['to'] as String;
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue