Compare commits

...

35 commits
v5.0.0 ... main

Author SHA1 Message Date
Omar Hatem
c2cca1ff37
v5.1.0 Release Candidate (#2332)
* v5.1.0 Release Candidate

* update app versions [skip ci]

* separate secrets for Monero.com [skip ci]

* fix: amount getting wiped when pasting address

* update Trocador Monero api key [skip ci]

* move fiat api key to headers [skip ci]

* latest Release candidate
2025-06-27 22:20:18 +03:00
David Adegoke
5aeb6b7522
Deuro Savings Error Handling (#2340)
* feat(deuro): Enhance gas fee handling and error management for Deuro Savings Transactions.

This change:
- Introduces DeuroGasFeeException to handle insufficient ETH for gas fees.
- Adds check for ETH balance before savings transactions to prevent failures due to insufficient funds.
- Updates savings transaction methods to include error handling.
- Adds UI feedback for transaction failures in DEuroSavingsPage.

* Fix conflicts

* Update cw_ethereum/lib/deuro/deuro_savings.dart

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
2025-06-27 17:53:46 +03:00
Konstantin Ullrich
b3c20a5818
Generic fixes for payjoin (#2342)
* feat: enhance Payjoin transaction details with block explorer link

* feat: enhance Payjoin transaction details with block explorer link

* fix: handle connectivity errors in Payjoin session operations
2025-06-27 16:38:09 +03:00
Serhii
d0827dd39e
add USDT Binance Smart Chain (BSC) support (#2341) 2025-06-27 15:47:34 +03:00
cyan
900304d405
fix(cw_monero): keys nullcheck handling (#2338)
add default values to monero wallet keys functions in order to prevent null check crashes
2025-06-27 15:06:56 +03:00
cyan
73588071ba
feat(tor): add Tor endpoints for fiat-api and service-api (#2339) 2025-06-27 15:05:54 +03:00
Serhii
1a5601f755
bugfix-unspent-coins-back-button (#2335)
* Update unspent_coins_list_page.dart

* minor fix

* close the page after the update is done
2025-06-26 19:02:05 +03:00
Konstantin Ullrich
04c86c567f
fix: use cake wallet frontend code, remove unused imports (#2337) 2025-06-26 16:43:16 +03:00
David Adegoke
65bb917bfb
CW-1094-WalletConnect-Issues (#2318)
* feat(walletconnect): Minor update to WalletConnect tile UI to fix expanded image issue

* feat(walletconnect): Minor update to WalletConnect tile UI to fix expanded image issue

* feat(walletconnect): Enhance WalletConnect EVM chain service.

This change:
- Improves signTypedDataV4 method handing and data parsing in extractPermitData.
- Adjusts UI for One Click Auth requests

* feat(walletconnect): Add redirect to PairingMetadata in WalletKit setup

* fix(walletconnect): Ensure session null checks before handling redirects in EvmChainService

* fix(walletconnect): Add null safety checks for permitData properties in EvmChainService

* refactor(walletconnect): Update WCPairingItemWidget layout and improve error handling for image loading

* fix(walletconnect): Handle break in connection flow triggered by global exception handler when SVGParser fails on invalid SvgData and triggers FlutterError.

* refactor(solana): Remove redundant session request responses and simplify error handling in SolanaChainService

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2025-06-24 05:48:27 +03:00
David Adegoke
af89603b81
CW-1103:Token Validation Issues (#2327)
* feat(token_validation): Improve flow for adding new tokens across wallets

This change:
- Implements check to see if a token is already added, preventing duplicates
- Triggers dialog warning if its a duplicate token
- Takes EVM Chains contract adddress case-insensitivity when making checks for potential scams.

* refactor(token_validation): Modify token management flow

This change:
- Removes duplicate token check during token addition in EVMChainWalletBase.
- Introduces a flag to indicate if a token is being edited
- Adjusts token addition validation to bypass checks when editing an existing token.

* Update lib/src/screens/dashboard/edit_token_page.dart

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2025-06-24 05:47:21 +03:00
David Adegoke
4434ad7363
CW-1086: Polygon Issues (#2329)
* fix(polygon): Polygon wallets not showing in address book when sending tokens

* feat(polygon_issues): Enhance gas price handling in EVM Chains

This change:
- Updates gas price calculation to account for minimum priority fee in Polygon
- Adjusts gas price handling to use maxFeePerGas when gasPrice is not provided.
- Fixes issue with send all for Polygon
2025-06-24 05:41:43 +03:00
cyan
5082dc20f3
CW-519 Enable built-in Tor (#1950)
* tor wip

* Enable tor on iOS

* Prevent app lag when node is exceptionally slow (usually over tor)

* fix: logic in daemonBlockchainHeight refresh
fix: storing tor state

* Pin ledger_flutter_plus dependency to fix builds

* bump arti version

* wip

* add single httpclient

* route everything I was able to catch trough the built-in tor node

* Enable proxy for http.Client [run tests]

* add tor proxy support to cw_evm, cw_tron and cw_polygon [run tests]

* remove log pollution, cleanup [skip slack]

* fix tests not working in latest main [skip slack] [run tests]

* remove cw_wownero import

* fix build issues

* migrate all remaining calls to use ProxyWrapper
add a CI action to enforce using ProxyWrapper instead of http/http.dart to prevent leaks

* fix tor background sync (will work on test builds after #2142 is merged and this PR is rebased on top)

* wip [skip ci]

* relicense to GPLv3 add socks5 license, build fixes

* use ProxyWrapper instead of http in robinhood

* Revert "relicense to GPLv3"

* feat(cw_bitcoin): support socks proxy and CakeTor

* fix(tor): migrate OCP and EVM over to ProxyWrapper()

* chore: cleanup
fix: show tor loading screen when app is starting

* fix: tor switch properly dismisses fullscreen loading dialog
fix: connectToNode after tor startup on app start

* fix(tor): status check for xmr/wow/zano

* fix(tor): onramper request fix

* fix(api): ServicesResponse is now being cached and doesn't fetch data everytime DashboardViewModel is being rebuilt
fix(tor): do not fallback to clearnet when tor failed.
fix(tor): do not leak connections during app startup
chore: refactor bootstrap() function to be separated into bootstrapOffline and bootstrapOnline
fix(cw_bitcoin): migrate payjoin to use ProxyWrapper

* [skip ci] remove print

* address comments from review

* fix: derusting tor implementation

Instead of rust-based Arti I've moved back to the
OG C++ tor implementation.
This fixed all issues we had with Tor.
- onion services now work
- all requests are going through without random errors
- we don't have to navigate a maze of multiple forks of multiple packages
- fully working `torrc` config file (probably will be needed for Tari).
- logging for Tor client
- and so on.

feat: network logging tab
feat: use built-in proxy on Tails - this should resolve all issues for Tails users (needs testing though)

* fix conflicts with main
bump https to fix build issue
relax store() call

* fix(cw_wownero): tor connection
fix(tor): connection issues

* fix(cw_evm): add missing chainId
fix(cw_core): solana rpc fix

* feat: mark tor as experimental
fix: drop anonpay onion authority
fix: drop fiatapi onion authority
fix: drop trocador onion authority
fix: disable networkimage when tor is enabled
fix: handle cakepay errors gracefully

* fix re-formatting [skip ci]

* changes from review

* Delete android/.kotlin/sessions/kotlin-compiler-2468481326039681181.salive

* fix missing imports

* Update pubspec_base.yaml

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-06-20 22:56:18 +03:00
Konstantin Ullrich
18c2ba9366
CW-1090-ledger-issues (#2314)
* build: bump ledger_flutter_plus dependencies

* fix: handle connection errors occurring during connecting to a ledger device

* fix: enhance ledger error handling and improve wallet connection reliability

* fix: enhance ledger error handling and improve wallet connection reliability

* feat: add localized strings for "Try again" and update Ledger error handling on auth

* fix: handle rethrow behavior in onAuthenticationStateChange

* fix: improve Ledger error handling and refine error code interpretation

* fix: refine Ledger error code interpretation and enhance error handling [skip-ci]

* add missing ledger error codes

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2025-06-19 20:00:16 +03:00
Konstantin Ullrich
4b137bc968
CW-1091-payjoin-error-handeling (#2317)
* feat: stop polling payjoin on switch wallet

* refactor: improve Payjoin session handling and cleanup unused methods

- Replaced `initReceiver` with `getUnusedReceiver` to reuse existing Payjoin sessions.
- Streamlined session initialization by removing `spawnNewReceiver`.
- Adjusted wallet sync reactions to resume Payjoin sessions when necessary.

* fix: Receiver.fromJson correctly handle parameter format in Payjoin manager

* fix: try reloading unspents if unspents are empty; No Unpsents available are now recoverable errors

* fix: ensure transaction details display only if transactionInfo is available and adjust payjoin success status handling

* fix: adjust payjoin success status handling for pending transactions

* fix: add error handling for Payjoin initialization and receiver creation [skip-ci]

* fix: add unrecoverable error handling for Payjoin sender sessions
2025-06-19 19:55:41 +03:00
OmarHatem
21d5c51cc9 remove unused file [skip ci] 2025-06-19 05:38:58 +03:00
Konstantin Ullrich
edaf485993
CW-1069-implement-deuro-dapp-support (#2304)
* feat: started dEuro Savings integration

* fix: merge conflict regarding theming

* feat: Add dEuro Savings Screen

* feat: Change DEuro Savings UI

* feat: Complete DEuro Savings integration with UI enhancements and transaction support

* style: remove forgotten print statements

* feat: localize dEuro subtitle

* feat: add approval flow and priority handling to DEuro Savings integration

- Introduced approval flow for DEuro Savings to enable token approvals.
- Added priority handling for deposit and withdrawal operations.
- Updated UI to support approval state and interactions.
- Localized new strings for multiple languages.
- Enhanced transaction handling with separate approval and commit actions.

* feat: add support for ERC20 token approval transactions

- Introduced `signApprovalTransaction` and `createApprovalTransaction` methods.
- Added handling for infinite approvals.
- Implemented encoding for approval transaction data.
- Enhanced transaction creation flow with approval-specific functionality.

* Update UI

* feat: enhance DEuro Savings logic and UI with computed property and fix gradient background

* feat: localize transaction confirmation content for DEuro Savings

* feat: enable interest collection for DEuro Savings with localized support

* fix reformatting [skip ci]

---------

Co-authored-by: tuxsudo <tuxsudo@tux.pizza>
Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
2025-06-19 05:37:41 +03:00
Konstantin Ullrich
150becb679
CW-1073 Implement Monero wallet definition URI scheme (#2323)
* feat: add optional parameter to customize address extraction pattern

* refactor: add parameter to control address extraction surrounding whitespace validation

* fix: ensure proper handling of unmounted context in address extraction logic

* test: add comprehensive unit tests for AddressResolver and AddressValidator classes
2025-06-18 17:20:03 +03:00
David Adegoke
c6cb48096d
refactor: Update CakeImageWidget to allow nullable BoxFit (#2326) 2025-06-17 23:24:18 +03:00
JoeGruffins
65402ba1eb
dcr: Always fetch the current dir path. (#2242)
* dcr: Always fetch the current dir path.

On ios devices the path will change between updates breaking decred.
Never save the path and always check to ensure it is up to date.
Previous wallets were also not creating a directory in the correct
place. Move those when found.

* Update cw_decred/lib/wallet_service.dart

* dcr: Update libwallet dep.

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2025-06-17 01:37:49 +03:00
cyan
17d99e5451
feat(ui): add app logo to qr code (#2072)
fix(ui): use high instead of low error correction for QR codes
2025-06-17 01:32:11 +03:00
Konstantin Ullrich
85d3e727e2
CW-1092-restoring-from-backup-doesnt-maintain-hardware-wallets (#2319)
* feat: add hardware wallet verification during backup restoration

* style: improve readability of verifyHardwareWallets in backup_service_v3.dart
2025-06-17 01:31:49 +03:00
Serhii
4fb2fc47ad
fix: extra ID for Trocador swap (#2307) 2025-06-16 17:52:15 +03:00
cyan
a96b493b60
CW 1080: fix(cw_monero): call store() directly after commiting tx (#2312)
* fix(cw_monero): call store() directly after commiting tx to make sure that tx key is written to cache
also, store it in TransactionDescription hive box

* Update lib/view_model/send/send_view_model.dart

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2025-06-16 17:49:43 +03:00
David Adegoke
fe0c9ecc0e
CW-1084: Solana Issues (#2305)
* fix(solana-issues): Fix missing solana transaction history entries

* fix(solana-issues): Fixes issues relating to Solana Transaction History

This change:
- Modifies transaction parsing logic to handle more scenarios and better parse Solana transaction data.
- Adds partial filter for spam transactions

* fix(solana-issues): Enhance transaction parsing for Associated Token Account (ATA) programs

This change:
- Adds logic to differentiate between create account and token transfer transactions for the ATA program.
- Introduces a check to skip transactions that only create accounts without associated token transfers.

* fix(solana-issues): Improve transaction update logic and enhance error handling

This change:
- Updates the transaction update callback to only trigger when new valid transactions are present.
- Enhances error handling for insufficient funds by distinguishing between errors for sender and receiver.
2025-06-14 04:18:46 +03:00
Luis Miguel
e5d0194f11
Improving spanish translation (#2313)
Thanks for contibuting
2025-06-12 00:48:10 +03:00
Konstantin Ullrich
8457a45c2a
update-payjoin (#2281)
* feat: upgrade to flutter_payjoin 0.23.0

* fix: dependency discrepancy freezed_annotation

* feat(cw_bitcoin): use latest payjoin deps

* ci: update rust toolchain

* Update pubspec.yaml

* trial: downgrade flutter payjoin ffi

* some checks

* trial 2

* [skip ci] use correct image for CI

* fix: bump flutter sdk to 3.29.0

* partial revert

---------

Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
2025-06-11 19:44:06 +03:00
David Adegoke
b0edf1fe75
fix(desktop-icon): Desktop Icons Fix (#2303)
This change:
- Adds svg icons for history and notification
- Updates the sidebar and services widget to use new images
- Fixes services widget icon looking very tiny on desktop
2025-06-04 20:15:36 +03:00
David Adegoke
1d6e594e04
CW-959: Swap Status on Transaction Screen (#2299)
* feat(swap-status-monitor): add real-time swap status monitoring and UI updates

- Introduce SwapManager for automatic tracking of active-wallet swaps.
- Automatically queues new or updated trades from the Hive box.
- Periodically fetch and persist swap statuses via the corresponding trade provider.
- Implement start(wallet, providers), stop(), and dispose() for lifecycle control.
- Apply user's ExchangeApiMode(disabled, tor-only, enabled) when fetching updates.
- Remove swaps from the watchlist on any final state (completed, expired, failed).
- Dispose SwapManager in AppState.dispose() to cancel polling and the Hive subscription.

* refactor(swap-status): replace SwapManager with TradeMonitor for improved trade monitoring.

This change improves the flow by simplifying the trade monitoring logic.

- Removes SwapManager class and replace with TradeMonitor implementation
- Update di and Appstate to register and dispose TradeMonitor
- Modify DashboardViewModel to use TradeMonitor instead of SwapManager

* fix: Modify trade monitoring logic to ensure trade timers are properly disposed when wallet switching occurs

* fix(swap-status): Fix receive amount for exchanges showing as .00 because of null values

* feat(swap-status): Enhance Trade Monitoring

This change:
- Adds a privacy settings option to disable automatic exchange status updates.
- Prevents trade monitoring when privacy settings option is enabled.
- Disables trade monitoring when the app is in background, we only want to run these checks in foreground.
- Refactors the trade monitoring logic to remove unneccessary checks and use of resources.

* feat(swap-status): Enhance Trade Monitoring

This change:
- Adds a privacy settings option to disable automatic exchange status updates.
- Prevents trade monitoring when privacy settings option is enabled.
- Disables trade monitoring when the app is in background, we only want to run these checks in foreground.
- Refactors the trade monitoring logic to remove unneccessary checks and use of resources.

* fix(swap-staus): Prevent unneccessary calls

* feat(swap-status): Prevent api request calls as long as last update time is less than specified interval
2025-06-04 18:24:56 +03:00
OmarHatem
1134915920 fix cakepay terms and conditions link [skip ci] 2025-06-03 03:22:04 +03:00
OmarHatem
418fdb62d3 v5.0.1 [skip ci] 2025-05-29 18:09:39 +03:00
Omar Hatem
d332377a2b
Revert "FIX (#2283)" (#2298)
This reverts commit 7b8ddf9685.
2025-05-29 16:54:22 +03:00
David Adegoke
b77c22b0df
Update Theme Setup (#2300)
* refactor(theme-setup-update): Move didChangePlatformBrightness lifecycle method to Root widget instead of AppState. We already have an active WidgetsBindingObserver in Root widget.

* fix(theme): Add workaround for flutter iOS didChangePlatformBrightness issue causing unneeded rebuilds.
2025-05-29 16:46:43 +03:00
OmarHatem
b79fb6af1f minor UI fix [skip ci] 2025-05-29 05:59:10 +03:00
OmarHatem
1fa1181c82 minor UI fix [skip ci] 2025-05-29 05:41:31 +03:00
OmarHatem
9a426ee5d4 minor UI fix [skip ci] 2025-05-29 05:04:20 +03:00
272 changed files with 7808 additions and 2553 deletions

View file

@ -55,7 +55,7 @@ jobs:
- name: Flutter action - name: Flutter action
uses: subosito/flutter-action@v1 uses: subosito/flutter-action@v1
with: with:
flutter-version: "3.27.4" flutter-version: "3.27.0"
channel: stable channel: stable
- name: Install package dependencies - name: Install package dependencies
@ -153,8 +153,8 @@ jobs:
echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart
echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart
echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart echo "const changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart
@ -168,6 +168,7 @@ jobs:
echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart
echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorMoneroApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
@ -178,7 +179,8 @@ jobs:
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const exolixMoneroApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const exchangeHelperApiKey = '${{ 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

21
.github/workflows/no_http_imports.yaml vendored Normal file
View file

@ -0,0 +1,21 @@
name: No http imports
on: [pull_request]
jobs:
PR_test_build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Check for http package usage
if: github.event_name == 'pull_request'
run: |
GIT_GREP_OUT="$(git grep package:http | (grep .dart: || test $? = 1) | (grep -v proxy_wrapper.dart || test $? = 1) | (grep -v very_insecure_http_do_not_use || test $? = 1) || true)"
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT"
echo "There are .dart files which use http imports"
echo "Using http package breaks proxy integration"
echo "Please use ProxyWrapper.getHttpClient() from package:cw_core/utils/proxy_wrapper.dart"
exit 1

View file

@ -15,5 +15,5 @@ jobs:
[[ "x$GIT_GREP_OUT" == "x" ]] && exit 0 [[ "x$GIT_GREP_OUT" == "x" ]] && exit 0
echo "$GIT_GREP_OUT" echo "$GIT_GREP_OUT"
echo "There are .dart files which use print() statements" echo "There are .dart files which use print() statements"
echo "Please use printV from package: cw_core/utils/print_verbose.dart" echo "Please use printV from package:cw_core/utils/print_verbose.dart"
exit 1 exit 1

View file

@ -9,7 +9,7 @@ jobs:
PR_test_build: PR_test_build:
runs-on: linux-amd64 runs-on: linux-amd64
container: container:
image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1 image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
env: env:
STORE_PASS: test@cake_wallet STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet KEY_PASS: test@cake_wallet
@ -98,8 +98,8 @@ jobs:
else else
echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
fi fi
echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart echo "const changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart
@ -113,6 +113,7 @@ jobs:
echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart
echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorMoneroApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
@ -124,7 +125,8 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const exolixMoneroApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const exchangeHelperApiKey = '${{ 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
@ -253,6 +255,11 @@ jobs:
- name: Build generated code - name: Build generated code
run: | run: |
flutter --version
flutter clean
rm -rf .dart_tool
rm pubspec.lock
flutter pub get
./model_generator.sh async ./model_generator.sh async
- name: Generate key properties - name: Generate key properties

View file

@ -9,7 +9,7 @@ jobs:
PR_test_build: PR_test_build:
runs-on: linux-amd64 runs-on: linux-amd64
container: container:
image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1 image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
env: env:
STORE_PASS: test@cake_wallet STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet KEY_PASS: test@cake_wallet
@ -91,8 +91,8 @@ jobs:
else else
echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
fi fi
echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart echo "const changeNowCakeWalletApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart echo "const changeNowMoneroApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart
echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart
@ -106,6 +106,7 @@ jobs:
echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart
echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorMoneroApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
@ -117,7 +118,8 @@ jobs:
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const exolixCakeWalletApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const exolixMoneroApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const exchangeHelperApiKey = '${{ 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

View file

@ -1,4 +1,4 @@
# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1 # docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
# Heavily inspired by cirrusci images # Heavily inspired by cirrusci images
# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile # https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile
@ -15,11 +15,11 @@ LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet
ENV GOLANG_VERSION=1.24.1 ENV GOLANG_VERSION=1.24.1
# Pin Flutter version to latest known-working version # Pin Flutter version to latest known-working version
ENV FLUTTER_VERSION=3.27.4 ENV FLUTTER_VERSION=3.27.0
# Pin Android Studio, platform, and build tools versions to latest known-working version # Pin Android Studio, platform, and build tools versions to latest known-working version
# Comes from https://developer.android.com/studio/#command-tools # Comes from https://developer.android.com/studio/#command-tools
ENV ANDROID_SDK_TOOLS_VERSION=11076708 ENV ANDROID_SDK_TOOLS_VERSION=13114758
# Comes from https://developer.android.com/studio/releases/build-tools # Comes from https://developer.android.com/studio/releases/build-tools
ENV ANDROID_PLATFORM_VERSION=35 ENV ANDROID_PLATFORM_VERSION=35
ENV ANDROID_BUILD_TOOLS_VERSION=34.0.0 ENV ANDROID_BUILD_TOOLS_VERSION=34.0.0
@ -164,9 +164,12 @@ RUN (addgroup kvm || true) && \
ENV PATH=${HOME}/.cargo/bin:${PATH} ENV PATH=${HOME}/.cargo/bin:${PATH}
RUN curl https://sh.rustup.rs -sSf | bash -s -- -y && \ RUN curl https://sh.rustup.rs -sSf | bash -s -- -y && \
cargo install cargo-ndk && \ cargo install cargo-ndk && \
for toolchain in stable nightly; \
do \
for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu; \ for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu aarch64-unknown-linux-gnu; \
do \ do \
rustup target add --toolchain stable $target; \ rustup target add --toolchain $toolchain $target; \
done \
done done
# Download and install Flutter # Download and install Flutter
@ -175,8 +178,11 @@ ENV FLUTTER_HOME=${HOME}/sdks/flutter/${FLUTTER_VERSION}
ENV FLUTTER_ROOT=$FLUTTER_HOME ENV FLUTTER_ROOT=$FLUTTER_HOME
ENV PATH=${PATH}:${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin ENV PATH=${PATH}:${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin
RUN git clone --depth 1 --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git ${FLUTTER_HOME} \ RUN git clone --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git ${FLUTTER_HOME} && \
&& yes | flutter doctor --android-licenses \ cd ${FLUTTER_HOME} && \
git fetch -a
RUN yes | flutter doctor --android-licenses \
&& flutter doctor \ && flutter doctor \
&& chown -R root:root ${FLUTTER_HOME} && chown -R root:root ${FLUTTER_HOME}

10
assets/images/history.svg Normal file
View file

@ -0,0 +1,10 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_67_21)">
<path d="M70.4443 8.33334C101.173 8.33336 126 33.1598 126 63.889C126 94.6181 101.173 119.445 70.4443 119.445C53.0832 119.445 37.4581 111.458 27.3887 98.9583L37.2842 89.0628C44.7495 98.9587 56.9027 105.556 70.4443 105.556C93.361 105.556 112.111 86.8056 112.111 63.889C112.111 40.9723 93.361 22.222 70.4443 22.222C56.9027 22.222 44.7495 28.8193 37.2842 38.7152C33.1175 44.0971 30.3405 50.521 29.2988 57.639C28.9516 59.7223 28.7773 61.8057 28.7773 63.889H42.667L21.833 84.722L1 63.889H14.8887C14.8887 61.9793 14.8891 60.2431 15.2363 58.3333C15.7572 52.7778 17.146 47.5692 19.0557 42.5345C27.3891 22.5694 47.3542 8.33334 70.4443 8.33334ZM75.6523 35.4163V60.9378L93.708 78.9925L83.8125 88.889L61.7637 66.6663V35.4163H75.6523Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_67_21">
<rect width="128" height="128" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 984 B

3
assets/images/notif.svg Normal file
View file

@ -0,0 +1,3 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M77.9219 103.359C77.9219 111.953 71.0469 119 62.4531 119C53.8594 119 46.8125 111.953 46.8125 103.359C46.8125 102.672 46.8125 102.156 46.9844 101.641H9V87.8906H21.0312V51.9688C21.0312 28.25 40.2812 9 64 9C87.7188 9 106.969 28.25 106.969 51.9688V87.8906H119V101.641H77.9219V103.359ZM64 22.75C47.8438 22.75 34.7812 35.8125 34.7812 51.9688V87.8906H93.2188V51.9688C93.2188 35.8125 80.1562 22.75 64 22.75Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 532 B

BIN
assets/images/qr-cake.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 150.5 91.1" enable-background="new 0 0 150.5 91.1" xml:space="preserve">
<g>
<g>
<g id="layer3_22_" transform="translate(-92,-63.999774)">
<g id="layer5_22_">
<g id="path2554_38_">
<path fill="#68B044" d="M176.9,70.8l-2.4,9.6c3.4-6.8,8.9-11.9,15.2-16.4c-4.6,5.3-8.8,10.6-11.3,16
c4.3-6.1,10.1-9.4,16.6-11.7c-8.7,7.7-15.6,16.1-20.8,24.4l-4.2-1.8C170.7,84.2,173.2,77.4,176.9,70.8L176.9,70.8z"/>
</g>
<g id="path2534_13_">
<path fill="#F5F8DE" d="M165.8,89.1l7.9,3.3c0,2-0.2,8.2,1.1,10c13.2,17,11,51.2-2.7,52c-20.8,0-28.8-14.1-28.8-27.1
c0-11.9,14.2-19.7,22.7-26.7C168.3,98.7,167.9,94.5,165.8,89.1L165.8,89.1z"/>
</g>
<g id="path2536_23_">
<path fill="#7E4798" d="M173.7,92.3l2.9,1.5c-0.3,1.9,0.1,6.1,2,7.1c8.4,5.2,16.2,10.8,19.3,16.5c11,19.9-7.7,38.4-24,36.6
c8.8-6.5,11.4-19.9,8.1-34.6c-1.3-5.7-3.4-10.9-7.1-16.8C173.3,99.9,173.9,96.3,173.7,92.3L173.7,92.3z"/>
</g>
</g>
<g id="layer4_22_">
<g id="path2540_23_">
<path fill="#010101" d="M170.5,101.8c-0.6,3.1-1.3,8.7-4,10.8c-1.1,0.8-2.3,1.6-3.5,2.4c-4.9,3.3-9.7,6.4-11.9,14.3
c-0.5,1.7-0.1,3.5,0.3,5.2c1.2,4.9,4.6,10.1,7.3,13.2c0,0.1,0.5,0.5,0.5,0.6c2.2,2.6,2.9,3.4,11.3,5.3l-0.2,0.9
c-5.1-1.3-9.2-2.6-11.9-5.6c0-0.1-0.5-0.5-0.5-0.5c-2.8-3.2-6.3-8.6-7.5-13.7c-0.5-2-0.9-3.6-0.3-5.7
c2.3-8.2,7.3-11.5,12.3-14.9c1.1-0.7,2.5-1.4,3.6-2.3C168.3,110.3,169.4,105.6,170.5,101.8L170.5,101.8z"/>
</g>
<g id="path2542_23_">
<path fill="#010101" d="M172.7,114.8c0.1,3.5-0.3,5.3,0.6,7.8c0.5,1.5,2.4,3.5,2.9,5.5c0.7,2.6,1.5,5.5,1.5,7.3
c0,2-0.1,5.8-1,9.8c-0.7,3.3-2.2,6.2-4.8,7.8c-2.7-0.5-5.8-1.5-7.6-3.1c-3.6-3.1-6.7-8.3-7.1-12.8c-0.3-3.7,3.1-9.2,7.9-11.9
c4-2.4,5-5,5.9-9.4c-1.2,3.8-2.4,6.9-6.3,9c-5.7,3-8.6,7.9-8.3,12.7c0.4,6.1,2.8,10.2,7.6,13.5c2,1.4,5.8,2.9,8.2,3.3v-0.3
c1.8-0.3,4.1-3.3,5.3-7.2c1-3.6,1.4-8.1,1.3-11c-0.1-1.7-0.8-5.3-2.2-8.6c-0.7-1.8-1.9-3.6-2.6-4.9
C173.1,120.8,173.1,118,172.7,114.8z"/>
</g>
<g id="path2544_23_">
<path fill="#010101" d="M172.1,128.5c0.1,2.4,1,5.4,1.4,8.5c0.3,2.3,0.2,4.6,0.1,6.6c-0.1,2.3-0.8,6.5-1.9,8.6
c-1-0.5-1.4-1-2.1-1.8c-0.8-1.1-1.4-2.3-1.9-3.6c-0.4-1-0.9-2.2-1.1-3.5c-0.3-2-0.2-5.2,2.1-8.4c1.8-2.6,2.2-2.8,2.8-5.7
c-0.8,2.6-1.4,2.9-3.3,5.1c-2.1,2.4-2.4,6-2.4,8.9c0,1.2,0.5,2.6,1,3.8c0.5,1.3,1,2.7,1.7,3.7c1.1,1.6,2.5,2.6,3.2,2.7
c0,0,0,0,0,0c0,0,0,0,0,0v-0.1c1.3-1.5,2.1-2.9,2.4-4.4c0.3-1.8,0.4-3.5,0.6-5.6c0.2-1.8,0.1-4.1-0.4-6.5
C173.7,133.8,172.6,130.7,172.1,128.5L172.1,128.5z"/>
</g>
<g id="path2550_23_">
<path fill="#010101" d="M172.5,99c0.1,3.5,0.3,10,1.3,12.6c0.3,0.9,2.8,4.7,4.5,9.4c1.2,3.2,1.5,6.2,1.7,7.1
c0.8,3.8-0.2,10.3-1.5,16.4c-0.7,3.3-3,7.4-5.6,9l-0.5,0.9c1.5-0.1,5.1-3.6,6.4-8.1c2.2-7.5,3-11,2-19.4
c-0.1-0.8-0.5-3.6-1.8-6.5c-1.9-4.5-4.6-8.8-4.9-9.7C173.4,109.3,172.6,103.1,172.5,99L172.5,99z"/>
</g>
<g id="path2552_23_">
<path fill="#010101" d="M173.7,92.6c-0.2,3.6-0.2,6.4,0.4,9.1c0.7,2.9,4.5,7.1,6.1,11.9c3,9.2,2.2,21.2,0.1,30.5
c-0.8,3.3-4.6,8.1-8.5,9.6l2.8,0.7c1.5-0.1,5.5-3.8,7.1-8c2.5-6.7,3-14.6,2-23c-0.1-0.8-1.4-8-2.7-11
c-1.8-4.5-4.7-7.7-5.7-10.5c-0.8-2.1-1.1-7.7-0.6-8.8L173.7,92.6z"/>
</g>
</g>
</g>
<g id="path2528_18_">
<path fill="#7E4798" d="M3.8,8.8h54.1c2,0,3.8,1.7,3.8,3.8v16c0,2.1-1.8,3.8-3.8,3.8H46.8c-2.5,0-3.6,1.4-3.6,3v52.3
c0,1.8-1.4,3.1-3.1,3.1H21.7c-1.7,0-3-1.3-3-3.1V34.9c0-1.6-1.5-2.6-2.6-2.6H3.8c-2.1,0-3.8-1.7-3.8-3.8v-16
C0,10.5,1.7,8.8,3.8,8.8z"/>
</g>
<g id="path2532_18_">
<path fill="#7E4798" d="M142.6,30h4.8c1.8,0,3.2,1.4,3.2,3.1v17.1c0,2.2,0.1,3.1-2.6,3.1c-5.3,0-7.7,2.8-7.7,5.9v28.9
c0,1.3-1.3,2.5-2.8,2.5h-17.2c-1.5,0-2.8-1.1-2.8-2.5V55.2c0-0.6,0-1.4,0.1-1.9c0.9-12.2,10.5-21.9,22.6-23.2
C140.5,30.1,141.9,30,142.6,30L142.6,30z"/>
</g>
</g>
<g>
<path fill="#010101" d="M101.7,46.3c-2.9-2.6-6.5-4.8-10.3-6.9c-1.7-0.9-6.9-5-5.1-10.8l-13.1-5.4l-0.9,0.7
c4.4,7.9,2.1,12.1-0.1,13.5c-4.4,3-10.8,6.8-13.9,10.1c-6.1,6.3-7.9,12.3-7.3,20.1c0.6,10.1,7.9,18.5,17.8,21.8
c4.3,1.4,8.3,1.6,12.7,1.6c7.1,0,14.5-1.9,19.8-6.3c5.7-4.7,9-11.8,9-19.1C110.3,58.3,107.2,51.3,101.7,46.3z M99.8,83.2
c-4.9,4-13.7,6.8-18.4,6.6c-5.2-0.3-10.3-1.1-14.8-3.3c-7.9-3.8-13.1-12.1-13.5-18.8C52.4,54,59,50.1,65.1,45.1
c3.4-2.8,8.2-4.2,10.9-9.2c0.5-1.1,0.8-3.5,0.2-6c-0.3-0.9-1.5-3.9-2-4.6l9.8,4.3c-1.2,4.5,2.5,9.2,5.5,10.9
c3,1.7,7.7,4.9,10.6,7.5c5.1,4.5,7.7,10.9,7.7,17.6C107.8,72.3,105,78.9,99.8,83.2z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

View file

@ -1,3 +1,4 @@
New themes and UI/UX improvements Add built-in Tor support (experimental)
Ledger flow enhancements Ledger improvements
UI/UX improvements
Bug fixes Bug fixes

View file

@ -1,5 +1,9 @@
New themes and UI/UX improvements Add built-in Tor support (experimental)
Silent Payments scanning fix Add dEuro investments
Payjoin enhancements Solana fixes/enhancements
Ledger flow enhancements Polygon fixes/enhancements
WalletConnect improvements
Ledger improvements
Payjoin improvements
UI/UX improvements
Bug fixes Bug fixes

View file

@ -17,16 +17,21 @@ BitcoinBaseAddress addressFromScript(Script script,
switch (addressType) { switch (addressType) {
case P2pkhAddressType.p2pkh: case P2pkhAddressType.p2pkh:
return P2pkhAddress.fromScriptPubkey(script: script); return P2pkhAddress.fromScriptPubkey(
script: script, network: BitcoinNetwork.mainnet);
case P2shAddressType.p2pkhInP2sh: case P2shAddressType.p2pkhInP2sh:
case P2shAddressType.p2pkInP2sh: case P2shAddressType.p2pkInP2sh:
return P2shAddress.fromScriptPubkey(script: script); return P2shAddress.fromScriptPubkey(
case SegwitAddressType.p2wpkh: script: script, network: BitcoinNetwork.mainnet);
return P2wpkhAddress.fromScriptPubkey(script: script); case SegwitAddresType.p2wpkh:
case SegwitAddressType.p2wsh: return P2wpkhAddress.fromScriptPubkey(
return P2wshAddress.fromScriptPubkey(script: script); script: script, network: BitcoinNetwork.mainnet);
case SegwitAddressType.p2tr: case SegwitAddresType.p2wsh:
return P2trAddress.fromScriptPubkey(script: script); return P2wshAddress.fromScriptPubkey(
script: script, network: BitcoinNetwork.mainnet);
case SegwitAddresType.p2tr:
return P2trAddress.fromScriptPubkey(
script: script, network: BitcoinNetwork.mainnet);
} }
throw ArgumentError("Invalid script"); throw ArgumentError("Invalid script");

View file

@ -82,7 +82,7 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
type: decoded['type'] != null && decoded['type'] != '' type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values ? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String) .firstWhere((type) => type.toString() == decoded['type'] as String)
: SegwitAddressType.p2wpkh, : SegwitAddresType.p2wpkh,
scriptHash: decoded['scriptHash'] as String?, scriptHash: decoded['scriptHash'] as String?,
network: network, network: network,
); );

View file

@ -36,9 +36,9 @@ class BitcoinReceivePageOption implements ReceivePageOption {
BitcoinAddressType toType() { BitcoinAddressType toType() {
switch (this) { switch (this) {
case BitcoinReceivePageOption.p2tr: case BitcoinReceivePageOption.p2tr:
return SegwitAddressType.p2tr; return SegwitAddresType.p2tr;
case BitcoinReceivePageOption.p2wsh: case BitcoinReceivePageOption.p2wsh:
return SegwitAddressType.p2wsh; return SegwitAddresType.p2wsh;
case BitcoinReceivePageOption.p2pkh: case BitcoinReceivePageOption.p2pkh:
return P2pkhAddressType.p2pkh; return P2pkhAddressType.p2pkh;
case BitcoinReceivePageOption.p2sh: case BitcoinReceivePageOption.p2sh:
@ -46,20 +46,20 @@ class BitcoinReceivePageOption implements ReceivePageOption {
case BitcoinReceivePageOption.silent_payments: case BitcoinReceivePageOption.silent_payments:
return SilentPaymentsAddresType.p2sp; return SilentPaymentsAddresType.p2sp;
case BitcoinReceivePageOption.mweb: case BitcoinReceivePageOption.mweb:
return SegwitAddressType.mweb; return SegwitAddresType.mweb;
case BitcoinReceivePageOption.p2wpkh: case BitcoinReceivePageOption.p2wpkh:
default: default:
return SegwitAddressType.p2wpkh; return SegwitAddresType.p2wpkh;
} }
} }
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) { factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
switch (type) { switch (type) {
case SegwitAddressType.p2tr: case SegwitAddresType.p2tr:
return BitcoinReceivePageOption.p2tr; return BitcoinReceivePageOption.p2tr;
case SegwitAddressType.p2wsh: case SegwitAddresType.p2wsh:
return BitcoinReceivePageOption.p2wsh; return BitcoinReceivePageOption.p2wsh;
case SegwitAddressType.mweb: case SegwitAddresType.mweb:
return BitcoinReceivePageOption.mweb; return BitcoinReceivePageOption.mweb;
case P2pkhAddressType.p2pkh: case P2pkhAddressType.p2pkh:
return BitcoinReceivePageOption.p2pkh; return BitcoinReceivePageOption.p2pkh;
@ -67,7 +67,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
return BitcoinReceivePageOption.p2sh; return BitcoinReceivePageOption.p2sh;
case SilentPaymentsAddresType.p2sp: case SilentPaymentsAddresType.p2sp:
return BitcoinReceivePageOption.silent_payments; return BitcoinReceivePageOption.silent_payments;
case SegwitAddressType.p2wpkh: case SegwitAddresType.p2wpkh:
default: default:
return BitcoinReceivePageOption.p2wpkh; return BitcoinReceivePageOption.p2wpkh;
} }

View file

@ -73,8 +73,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
encryptionFileUtils: encryptionFileUtils, encryptionFileUtils: encryptionFileUtils,
currency: currency: networkParam == BitcoinNetwork.testnet
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, ? CryptoCurrency.tbtc
: CryptoCurrency.btc,
alwaysScan: alwaysScan, alwaysScan: alwaysScan,
) { ) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
@ -93,12 +94,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
mainHd: hd, mainHd: hd,
sideHd: accountHD.childKey(Bip32KeyIndex(1)), sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: networkParam ?? network, network: networkParam ?? network,
masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, masterHd:
seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
isHardwareWallet: walletInfo.isHardwareWallet, isHardwareWallet: walletInfo.isHardwareWallet,
payjoinManager: payjoinManager); payjoinManager: payjoinManager);
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress =
this.isEnabledAutoGenerateSubaddress;
}); });
} }
@ -133,7 +136,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
break; break;
case DerivationType.electrum: case DerivationType.electrum:
default: default:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); seedBytes =
await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break; break;
} }
@ -206,8 +210,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo ??= DerivationInfo(); walletInfo.derivationInfo ??= DerivationInfo();
// set the default if not present: // set the default if not present:
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; walletInfo.derivationInfo!.derivationPath ??=
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; snp?.derivationPath ?? electrum_path;
walletInfo.derivationInfo!.derivationType ??=
snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null; Uint8List? seedBytes = null;
final mnemonic = keysData.mnemonic; final mnemonic = keysData.mnemonic;
@ -216,7 +222,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
if (mnemonic != null) { if (mnemonic != null) {
switch (walletInfo.derivationInfo!.derivationType) { switch (walletInfo.derivationInfo!.derivationType) {
case DerivationType.electrum: case DerivationType.electrum:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? ""); seedBytes =
await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break; break;
case DerivationType.bip39: case DerivationType.bip39:
default: default:
@ -259,10 +266,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
derivationPath: walletInfo.derivationInfo!.derivationPath!); derivationPath: walletInfo.derivationInfo!.derivationPath!);
} }
@override
Future<void> close({bool shouldCleanup = false}) async {
payjoinManager.cleanupSessions();
super.close(shouldCleanup: shouldCleanup);
}
late final PayjoinManager payjoinManager; late final PayjoinManager payjoinManager;
bool get isPayjoinAvailable => unspentCoinsInfo.values bool get isPayjoinAvailable => unspentCoinsInfo.values
.where((element) => element.walletId == id && element.isSending && !element.isFrozen) .where((element) =>
element.walletId == id && element.isSending && !element.isFrozen)
.isNotEmpty; .isNotEmpty;
Future<PsbtV2> buildPsbt({ Future<PsbtV2> buildPsbt({
@ -279,8 +293,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
}) async { }) async {
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[]; final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
for (final utxo in utxos) { for (final utxo in utxos) {
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); final rawTx =
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath =
publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress( psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo, utxo: utxo.utxo,
@ -292,7 +308,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
)); ));
} }
return PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF) return PSBTTransactionBuild(
inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF)
.psbt; .psbt;
} }
@ -331,7 +348,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future<PendingTransaction> createTransaction(Object credentials) async { Future<PendingTransaction> createTransaction(Object credentials) async {
credentials = credentials as BitcoinTransactionCredentials; credentials = credentials as BitcoinTransactionCredentials;
final tx = (await super.createTransaction(credentials)) as PendingBitcoinTransaction; final tx = (await super.createTransaction(credentials))
as PendingBitcoinTransaction;
final payjoinUri = credentials.payjoinUri; final payjoinUri = credentials.payjoinUri;
if (payjoinUri == null) return tx; if (payjoinUri == null) return tx;
@ -354,12 +372,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
publicKeys: tx.publicKeys!, publicKeys: tx.publicKeys!,
masterFingerprint: Uint8List(0)); masterFingerprint: Uint8List(0));
final originalPsbt = final originalPsbt = await signPsbt(
await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys()); base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
tx.commitOverride = () async { tx.commitOverride = () async {
final sender = final sender = await payjoinManager.initSender(
await payjoinManager.initSender(payjoinUri, originalPsbt, int.parse(tx.feeRate)); payjoinUri, originalPsbt, int.parse(tx.feeRate));
payjoinManager.spawnNewSender( payjoinManager.spawnNewSender(
sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount));
}; };
@ -375,7 +393,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future<void> commitPsbt(String finalizedPsbt) { Future<void> commitPsbt(String finalizedPsbt) {
final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt)); final psbt = PsbtV2()..deserializeV0(base64.decode(finalizedPsbt));
final btcTx = BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract())); final btcTx =
BtcTransaction.fromRaw(BytesUtils.toHexString(psbt.extract()));
return PendingBitcoinTransaction( return PendingBitcoinTransaction(
btcTx, btcTx,
@ -389,11 +408,12 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
).commit(); ).commit();
} }
Future<String> signPsbt(String preProcessedPsbt, List<UtxoWithPrivateKey> utxos) async { Future<String> signPsbt(
String preProcessedPsbt, List<UtxoWithPrivateKey> utxos) async {
final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt)); final psbt = PsbtV2()..deserializeV0(base64Decode(preProcessedPsbt));
await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) { await psbt.signWithUTXO(utxos, (txDigest, utxo, key, sighash) {
return utxo.utxo.isP2tr return utxo.utxo.isP2tr()
? key.signTapRoot( ? key.signTapRoot(
txDigest, txDigest,
sighash: sighash, sighash: sighash,
@ -414,15 +434,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future<String> signMessage(String message, {String? address = null}) async { Future<String> signMessage(String message, {String? address = null}) async {
if (walletInfo.isHardwareWallet) { if (walletInfo.isHardwareWallet) {
final addressEntry = address != null final addressEntry = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address) ? walletAddresses.allAddresses
.firstWhere((element) => element.address == address)
: null; : null;
final index = addressEntry?.index ?? 0; final index = addressEntry?.index ?? 0;
final isChange = addressEntry?.isHidden == true ? 1 : 0; final isChange = addressEntry?.isHidden == true ? 1 : 0;
final accountPath = walletInfo.derivationInfo?.derivationPath; final accountPath = walletInfo.derivationInfo?.derivationPath;
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; final derivationPath =
accountPath != null ? "$accountPath/$isChange/$index" : null;
final signature = await _bitcoinLedgerApp! final signature = await _bitcoinLedgerApp!.signMessage(
.signMessage(message: ascii.encode(message), signDerivationPath: derivationPath); message: ascii.encode(message), signDerivationPath: derivationPath);
return base64Encode(signature); return base64Encode(signature);
} }

View file

@ -31,12 +31,10 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
final PayjoinManager payjoinManager; final PayjoinManager payjoinManager;
@observable
payjoin.Receiver? currentPayjoinReceiver; payjoin.Receiver? currentPayjoinReceiver;
@computed @observable
String? get payjoinEndpoint => String? payjoinEndpoint = null;
currentPayjoinReceiver?.pjUriBuilder().build().pjEndpoint();
@override @override
String getAddress( String getAddress(
@ -47,10 +45,10 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
if (addressType == P2pkhAddressType.p2pkh) if (addressType == P2pkhAddressType.p2pkh)
return generateP2PKHAddress(hd: hd, index: index, network: network); return generateP2PKHAddress(hd: hd, index: index, network: network);
if (addressType == SegwitAddressType.p2tr) if (addressType == SegwitAddresType.p2tr)
return generateP2TRAddress(hd: hd, index: index, network: network); return generateP2TRAddress(hd: hd, index: index, network: network);
if (addressType == SegwitAddressType.p2wsh) if (addressType == SegwitAddresType.p2wsh)
return generateP2WSHAddress(hd: hd, index: index, network: network); return generateP2WSHAddress(hd: hd, index: index, network: network);
if (addressType == P2shAddressType.p2wpkhInP2sh) if (addressType == P2shAddressType.p2wpkhInP2sh)
@ -59,16 +57,32 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
return generateP2WPKHAddress(hd: hd, index: index, network: network); return generateP2WPKHAddress(hd: hd, index: index, network: network);
} }
@action
Future<void> initPayjoin() async { Future<void> initPayjoin() async {
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress); try {
await payjoinManager.initPayjoin();
currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress);
payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
payjoinManager.resumeSessions(); payjoinManager.resumeSessions();
} catch (e) {
printV(e);
// Ignore Connectivity errors
if (!e.toString().contains("error sending request for url")) rethrow;
}
} }
@action
Future<void> newPayjoinReceiver() async { Future<void> newPayjoinReceiver() async {
currentPayjoinReceiver = await payjoinManager.initReceiver(primaryAddress); try {
currentPayjoinReceiver = await payjoinManager.getUnusedReceiver(primaryAddress);
payjoinEndpoint = (await currentPayjoinReceiver?.pjUri())?.pjEndpoint();
printV("Initializing new Payjoin Receiver"); payjoinManager.spawnReceiver(receiver: currentPayjoinReceiver!);
payjoinManager.spawnNewReceiver(receiver: currentPayjoinReceiver!); } catch (e) {
printV(e);
// Ignore Connectivity errors
if (!e.toString().contains("error sending request for url")) rethrow;
}
} }
} }

View file

@ -5,6 +5,8 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
@ -42,7 +44,7 @@ class ElectrumClient {
static const aliveTimerDuration = Duration(seconds: 4); static const aliveTimerDuration = Duration(seconds: 4);
bool get isConnected => _isConnected; bool get isConnected => _isConnected;
Socket? socket; ProxySocket? socket;
void Function(ConnectionStatus)? onConnectionStatusChange; void Function(ConnectionStatus)? onConnectionStatusChange;
int _id; int _id;
final Map<String, SocketTask> _tasks; final Map<String, SocketTask> _tasks;
@ -72,18 +74,11 @@ class ElectrumClient {
} catch (_) {} } catch (_) {}
socket = null; socket = null;
final ssl = !(useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum")));
try { try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { socket = await ProxyWrapper().getSocksSocket(ssl, host, port, connectionTimeout: connectionTimeout);
socket = await Socket.connect(host, port, timeout: connectionTimeout);
} else {
socket = await SecureSocket.connect(
host,
port,
timeout: connectionTimeout,
onBadCertificate: (_) => true,
);
}
} catch (e) { } catch (e) {
printV("connect: $e");
if (e is HandshakeException) { if (e is HandshakeException) {
useSSL = !(useSSL ?? false); useSSL = !(useSSL ?? false);
} }
@ -105,7 +100,6 @@ class ElectrumClient {
// use ping to determine actual connection status since we could've just not timed out yet: // use ping to determine actual connection status since we could've just not timed out yet:
// _setConnectionStatus(ConnectionStatus.connected); // _setConnectionStatus(ConnectionStatus.connected);
socket!.listen( socket!.listen(
(Uint8List event) { (Uint8List event) {
try { try {

View file

@ -4,7 +4,9 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart';
@ -17,7 +19,7 @@ import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart'; import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/electrum.dart' as electrum; import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_history.dart';
@ -48,7 +50,6 @@ import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart'; import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart'; import 'package:sp_scanner/sp_scanner.dart';
import 'package:hex/hex.dart'; import 'package:hex/hex.dart';
import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart'; part 'electrum_wallet.g.dart';
@ -68,7 +69,7 @@ abstract class ElectrumWalletBase
Uint8List? seedBytes, Uint8List? seedBytes,
this.passphrase, this.passphrase,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
electrum.ElectrumClient? electrumClient, ElectrumClient? electrumClient,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
CryptoCurrency? currency, CryptoCurrency? currency,
this.alwaysScan, this.alwaysScan,
@ -95,7 +96,7 @@ abstract class ElectrumWalletBase
this.isTestnet = !network.isMainnet, this.isTestnet = !network.isMainnet,
this._mnemonic = mnemonic, this._mnemonic = mnemonic,
super(walletInfo) { super(walletInfo) {
this.electrumClient = electrumClient ?? electrum.ElectrumClient(); this.electrumClient = electrumClient ?? ElectrumClient();
this.walletInfo = walletInfo; this.walletInfo = walletInfo;
transactionHistory = ElectrumTransactionHistory( transactionHistory = ElectrumTransactionHistory(
walletInfo: walletInfo, walletInfo: walletInfo,
@ -166,7 +167,7 @@ abstract class ElectrumWalletBase
@observable @observable
bool isEnabledAutoGenerateSubaddress; bool isEnabledAutoGenerateSubaddress;
late electrum.ElectrumClient electrumClient; late ElectrumClient electrumClient;
Box<UnspentCoinsInfo> unspentCoinsInfo; Box<UnspentCoinsInfo> unspentCoinsInfo;
@override @override
@ -181,7 +182,7 @@ abstract class ElectrumWalletBase
SyncStatus syncStatus; SyncStatus syncStatus;
Set<String> get addressesSet => walletAddresses.allAddresses Set<String> get addressesSet => walletAddresses.allAddresses
.where((element) => element.type != SegwitAddressType.mweb) .where((element) => element.type != SegwitAddresType.mweb)
.map((addr) => addr.address) .map((addr) => addr.address)
.toSet(); .toSet();
@ -332,14 +333,14 @@ abstract class ElectrumWalletBase
final receivePort = ReceivePort(); final receivePort = ReceivePort();
_isolate = Isolate.spawn( _isolate = Isolate.spawn(
_handleScanSilentPayments, startRefresh,
ScanData( ScanData(
sendPort: receivePort.sendPort, sendPort: receivePort.sendPort,
silentAddress: walletAddresses.silentAddress!, silentAddress: walletAddresses.silentAddress!,
network: network, network: network,
height: height, height: height,
chainTip: chainTip, chainTip: chainTip,
electrumClient: electrum.ElectrumClient(), electrumClient: ElectrumClient(),
transactionHistoryIds: transactionHistory.transactions.keys.toList(), transactionHistoryIds: transactionHistory.transactions.keys.toList(),
node: (await getNodeSupportsSilentPayments()) == true node: (await getNodeSupportsSilentPayments()) == true
? ScanNode(node!.uri, node!.useSSL) ? ScanNode(node!.uri, node!.useSSL)
@ -438,6 +439,7 @@ abstract class ElectrumWalletBase
BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)), BigintUtils.fromBytes(BytesUtils.fromHexString(unspent.silentPaymentLabel!)),
) )
: silentAddress.B_spend, : silentAddress.B_spend,
network: network,
); );
final addressRecord = walletAddresses.silentAddresses final addressRecord = walletAddresses.silentAddresses
@ -491,10 +493,9 @@ abstract class ElectrumWalletBase
Future<void> updateFeeRates() async { Future<void> updateFeeRates() async {
if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) { if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) {
try { try {
final response = await http final response = await ProxyWrapper()
.get(Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended")) .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/fees/recommended"))
.timeout(Duration(seconds: 5)); .timeout(Duration(seconds: 15));
final result = json.decode(response.body) as Map<String, dynamic>; final result = json.decode(response.body) as Map<String, dynamic>;
final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0; final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0; int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
@ -562,7 +563,7 @@ abstract class ElectrumWalletBase
node!.save(); node!.save();
return node!.supportsSilentPayments!; return node!.supportsSilentPayments!;
} }
} on electrum.RequestFailedTimeoutException catch (_) { } on RequestFailedTimeoutException catch (_) {
node!.supportsSilentPayments = false; node!.supportsSilentPayments = false;
node!.save(); node!.save();
return node!.supportsSilentPayments!; return node!.supportsSilentPayments!;
@ -623,9 +624,9 @@ abstract class ElectrumWalletBase
switch (coinTypeToSpendFrom) { switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb: case UnspentCoinType.mweb:
return utx.bitcoinAddressRecord.type == SegwitAddressType.mweb; return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb: case UnspentCoinType.nonMweb:
return utx.bitcoinAddressRecord.type != SegwitAddressType.mweb; return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb;
case UnspentCoinType.any: case UnspentCoinType.any:
return true; return true;
} }
@ -633,7 +634,7 @@ abstract class ElectrumWalletBase
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
// sort the unconfirmed coins so that mweb coins are last: // sort the unconfirmed coins so that mweb coins are last:
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddressType.mweb ? 1 : -1); availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1);
for (int i = 0; i < availableInputs.length; i++) { for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i]; final utx = availableInputs[i];
@ -641,7 +642,7 @@ abstract class ElectrumWalletBase
if (paysToSilentPayment) { if (paysToSilentPayment) {
// Check inputs for shared secret derivation // Check inputs for shared secret derivation
if (utx.bitcoinAddressRecord.type == SegwitAddressType.p2wsh) { if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) {
throw BitcoinTransactionSilentPaymentsNotSupported(); throw BitcoinTransactionSilentPaymentsNotSupported();
} }
} }
@ -676,7 +677,7 @@ abstract class ElectrumWalletBase
if (privkey != null) { if (privkey != null) {
inputPrivKeyInfos.add(ECPrivateInfo( inputPrivKeyInfos.add(ECPrivateInfo(
privkey, privkey,
address.type == SegwitAddressType.p2tr, address.type == SegwitAddresType.p2tr,
tweak: !isSilentPayment, tweak: !isSilentPayment,
)); ));
@ -1162,7 +1163,7 @@ abstract class ElectrumWalletBase
throw Exception(error); throw Exception(error);
} }
if (utxo.utxo.isP2tr) { if (utxo.utxo.isP2tr()) {
hasTaprootInputs = true; hasTaprootInputs = true;
return key.privkey.signTapRoot( return key.privkey.signTapRoot(
txDigest, txDigest,
@ -1229,7 +1230,7 @@ abstract class ElectrumWalletBase
'change_address_index': walletAddresses.currentChangeAddressIndexByType, 'change_address_index': walletAddresses.currentChangeAddressIndexByType,
'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(), 'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(),
'address_page_type': walletInfo.addressPageType == null 'address_page_type': walletInfo.addressPageType == null
? SegwitAddressType.p2wpkh.toString() ? SegwitAddresType.p2wpkh.toString()
: walletInfo.addressPageType.toString(), : walletInfo.addressPageType.toString(),
'balance': balance[currency]?.toJSON(), 'balance': balance[currency]?.toJSON(),
'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index, 'derivationTypeIndex': walletInfo.derivationInfo?.derivationType?.index,
@ -1369,7 +1370,7 @@ abstract class ElectrumWalletBase
List<BitcoinUnspent> updatedUnspentCoins = []; List<BitcoinUnspent> updatedUnspentCoins = [];
final previousUnspentCoins = List<BitcoinUnspent>.from(unspentCoins.where((utxo) => final previousUnspentCoins = List<BitcoinUnspent>.from(unspentCoins.where((utxo) =>
utxo.bitcoinAddressRecord.type != SegwitAddressType.mweb && utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb &&
utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)); utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord));
if (hasSilentPaymentsScanning) { if (hasSilentPaymentsScanning) {
@ -1383,13 +1384,13 @@ abstract class ElectrumWalletBase
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating // Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses walletAddresses.allAddresses
.where((element) => element.type != SegwitAddressType.mweb) .where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) { .forEach((addr) {
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0; if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
}); });
final addressFutures = walletAddresses.allAddresses final addressFutures = walletAddresses.allAddresses
.where((element) => element.type != SegwitAddressType.mweb) .where((element) => element.type != SegwitAddresType.mweb)
.map((address) => fetchUnspent(address)) .map((address) => fetchUnspent(address))
.toList(); .toList();
@ -1830,7 +1831,7 @@ abstract class ElectrumWalletBase
throw Exception("Cannot find private key"); throw Exception("Cannot find private key");
} }
if (utxo.utxo.isP2tr) { if (utxo.utxo.isP2tr()) {
return key.signTapRoot(txDigest, sighash: sighash); return key.signTapRoot(txDigest, sighash: sighash);
} else { } else {
return key.signInput(txDigest, sigHash: sighash); return key.signInput(txDigest, sigHash: sighash);
@ -1876,20 +1877,17 @@ abstract class ElectrumWalletBase
if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) { if (height != null && height > 0 && await checkIfMempoolAPIIsEnabled()) {
try { try {
final blockHash = await http.get( final blockHash = await ProxyWrapper()
Uri.parse( .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block-height/$height"))
"https://mempool.cakewallet.com/api/v1/block-height/$height", .timeout(Duration(seconds: 15));
),
);
if (blockHash.statusCode == 200 && if (blockHash.statusCode == 200 &&
blockHash.body.isNotEmpty && blockHash.body.isNotEmpty &&
jsonDecode(blockHash.body) != null) { jsonDecode(blockHash.body) != null) {
final blockResponse = await http.get( final blockResponse = await ProxyWrapper()
Uri.parse( .get(clearnetUri: Uri.parse("https://mempool.cakewallet.com/api/v1/block/${blockHash}"))
"https://mempool.cakewallet.com/api/v1/block/${blockHash.body}", .timeout(Duration(seconds: 15));
),
);
if (blockResponse.statusCode == 200 && if (blockResponse.statusCode == 200 &&
blockResponse.body.isNotEmpty && blockResponse.body.isNotEmpty &&
jsonDecode(blockResponse.body)['timestamp'] != null) { jsonDecode(blockResponse.body)['timestamp'] != null) {
@ -1980,7 +1978,7 @@ abstract class ElectrumWalletBase
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) { } else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES await Future.wait(LITECOIN_ADDRESS_TYPES
.where((type) => type != SegwitAddressType.mweb) .where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} }
@ -2169,7 +2167,7 @@ abstract class ElectrumWalletBase
final unsubscribedScriptHashes = walletAddresses.allAddresses.where( final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) => (address) =>
!_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)) && !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)) &&
address.type != SegwitAddressType.mweb, address.type != SegwitAddresType.mweb,
); );
await Future.wait(unsubscribedScriptHashes.map((address) async { await Future.wait(unsubscribedScriptHashes.map((address) async {
@ -2392,9 +2390,9 @@ abstract class ElectrumWalletBase
derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1); derivationPath.substring(0, derivationPath.lastIndexOf("'") + 1);
@action @action
void _onConnectionStatusChange(electrum.ConnectionStatus status) { void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) { switch (status) {
case electrum.ConnectionStatus.connected: case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus || if (syncStatus is NotConnectedSyncStatus ||
syncStatus is LostConnectionSyncStatus || syncStatus is LostConnectionSyncStatus ||
syncStatus is ConnectingSyncStatus) { syncStatus is ConnectingSyncStatus) {
@ -2402,19 +2400,19 @@ abstract class ElectrumWalletBase
} }
break; break;
case electrum.ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
if (syncStatus is! NotConnectedSyncStatus && if (syncStatus is! NotConnectedSyncStatus &&
syncStatus is! ConnectingSyncStatus && syncStatus is! ConnectingSyncStatus &&
syncStatus is! SyncronizingSyncStatus) { syncStatus is! SyncronizingSyncStatus) {
syncStatus = NotConnectedSyncStatus(); syncStatus = NotConnectedSyncStatus();
} }
break; break;
case electrum.ConnectionStatus.failed: case ConnectionStatus.failed:
if (syncStatus is! LostConnectionSyncStatus) { if (syncStatus is! LostConnectionSyncStatus) {
syncStatus = LostConnectionSyncStatus(); syncStatus = LostConnectionSyncStatus();
} }
break; break;
case electrum.ConnectionStatus.connecting: case ConnectionStatus.connecting:
if (syncStatus is! ConnectingSyncStatus) { if (syncStatus is! ConnectingSyncStatus) {
syncStatus = ConnectingSyncStatus(); syncStatus = ConnectingSyncStatus();
} }
@ -2526,7 +2524,7 @@ class ScanData {
final ScanNode? node; final ScanNode? node;
final BasedUtxoNetwork network; final BasedUtxoNetwork network;
final int chainTip; final int chainTip;
final electrum.ElectrumClient electrumClient; final ElectrumClient electrumClient;
final List<String> transactionHistoryIds; final List<String> transactionHistoryIds;
final Map<String, String> labels; final Map<String, String> labels;
final List<int> labelIndexes; final List<int> labelIndexes;
@ -2570,234 +2568,6 @@ class SyncResponse {
SyncResponse(this.height, this.syncStatus); SyncResponse(this.height, this.syncStatus);
} }
Future<void> _handleScanSilentPayments(ScanData scanData) async {
try {
// if (scanData.shouldSwitchNodes) {
var scanningClient = await ElectrumProvider.connect(
ElectrumTCPService.connect(
Uri.parse("tcp://electrs.cakewallet.com:50001"),
),
);
// }
int syncHeight = scanData.height;
int initialSyncHeight = syncHeight;
final receiver = Receiver(
scanData.silentAddress.b_scan.toHex(),
scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes,
);
int getCountToScanPerRequest(int syncHeight) {
if (scanData.isSingleScan) {
return 1;
}
final amountLeft = scanData.chainTip - syncHeight + 1;
return amountLeft;
}
// Initial status UI update, send how many blocks in total to scan
scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
final req = ElectrumTweaksSubscribe(
height: syncHeight,
count: getCountToScanPerRequest(syncHeight),
historicalMode: false,
);
var _scanningStream = await scanningClient.subscribe(req);
void listenFn(Map<String, dynamic> event, ElectrumTweaksSubscribe req) {
final response = req.onResponse(event);
if (response == null || _scanningStream == null) {
return;
}
// is success or error msg
final noData = response.message != null;
if (noData) {
if (scanData.isSingleScan) {
return;
}
// re-subscribe to continue receiving messages, starting from the next unscanned height
final nextHeight = syncHeight + 1;
if (nextHeight <= scanData.chainTip) {
final nextStream = scanningClient.subscribe(
ElectrumTweaksSubscribe(
height: nextHeight,
count: getCountToScanPerRequest(nextHeight),
historicalMode: false,
),
);
if (nextStream != null) {
nextStream.listen((event) => listenFn(event, req));
} else {
scanData.sendPort.send(
SyncResponse(scanData.height, LostConnectionSyncStatus()),
);
}
}
return;
}
final tweakHeight = response.block;
if (initialSyncHeight < tweakHeight) initialSyncHeight = tweakHeight;
// Continuous status UI update, send how many blocks left to scan
final syncingStatus = scanData.isSingleScan
? SyncingSyncStatus(1, 0)
: SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, tweakHeight);
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
try {
final blockTweaks = response.blockTweaks;
for (final txid in blockTweaks.keys) {
final tweakData = blockTweaks[txid];
final outputPubkeys = tweakData!.outputPubkeys;
final tweak = tweakData.tweak;
try {
final addToWallet = {};
// receivers.forEach((receiver) {
// NOTE: scanOutputs, from sp_scanner package, called from rust here
final scanResult = scanOutputs([outputPubkeys.keys.toList()], tweak, receiver);
if (scanResult.isEmpty) {
continue;
}
if (addToWallet[receiver.BSpend] == null) {
addToWallet[receiver.BSpend] = scanResult;
} else {
addToWallet[receiver.BSpend].addAll(scanResult);
}
// });
if (addToWallet.isEmpty) {
// no results tx, continue to next tx
continue;
}
// initial placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s) on the following loop
final txInfo = ElectrumTransactionInfo(
WalletType.bitcoin,
id: txid,
height: tweakHeight,
amount: 0,
fee: 0,
direction: TransactionDirection.incoming,
isReplaced: false,
date: DateTime.fromMillisecondsSinceEpoch(
DateTime.now().millisecondsSinceEpoch * 1000,
),
confirmations: scanData.chainTip - tweakHeight + 1,
isReceivedSilentPayment: true,
isPending: false,
unspents: [],
);
List<BitcoinUnspent> unspents = [];
addToWallet.forEach((BSpend, scanResultPerLabel) {
scanResultPerLabel.forEach((label, scanOutput) {
final labelValue = label == "None" ? null : label.toString();
(scanOutput as Map<String, dynamic>).forEach((outputPubkey, tweak) {
final t_k = tweak as String;
final receivingOutputAddress = ECPublic.fromHex(outputPubkey)
.toTaprootAddress(tweak: false)
.toAddress(scanData.network);
final matchingOutput = outputPubkeys[outputPubkey]!;
final amount = matchingOutput.amount;
final pos = matchingOutput.vout;
// final matchingSPWallet = scanData.silentPaymentsWallets.firstWhere(
// (receiver) => receiver.B_spend.toHex() == BSpend.toString(),
// );
// final labelIndex = labelValue != null ? scanData.labels[label] : 0;
// final balance = ElectrumBalance();
// balance.confirmed = amount;
final receivedAddressRecord = BitcoinSilentPaymentAddressRecord(
receivingOutputAddress,
index: 0,
isHidden: false,
isUsed: true,
network: scanData.network,
silentPaymentTweak: t_k,
type: SegwitAddressType.p2tr,
txCount: 1,
balance: amount,
);
final unspent = BitcoinSilentPaymentsUnspent(
receivedAddressRecord,
txid,
amount,
pos,
silentPaymentTweak: t_k,
silentPaymentLabel: labelValue,
);
unspents.add(unspent);
txInfo.unspents!.add(unspent);
txInfo.amount += unspent.value;
});
});
});
scanData.sendPort.send({txInfo.id: txInfo});
} catch (e, stacktrace) {
printV(stacktrace);
printV(e.toString());
}
}
} catch (e, stacktrace) {
printV(stacktrace);
printV(e.toString());
}
syncHeight = tweakHeight;
if ((tweakHeight >= scanData.chainTip) || scanData.isSingleScan) {
if (tweakHeight >= scanData.chainTip)
scanData.sendPort.send(
SyncResponse(syncHeight, SyncedTipSyncStatus(scanData.chainTip)),
);
if (scanData.isSingleScan) {
scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
}
_scanningStream?.close();
_scanningStream = null;
return;
}
}
_scanningStream?.listen((event) => listenFn(event, req));
} catch (e) {
printV("Error in _handleScanSilentPayments: $e");
scanData.sendPort.send(SyncResponse(scanData.height, LostConnectionSyncStatus()));
}
}
Future<void> startRefresh(ScanData scanData) async { Future<void> startRefresh(ScanData scanData) async {
int syncHeight = scanData.height; int syncHeight = scanData.height;
int initialSyncHeight = syncHeight; int initialSyncHeight = syncHeight;
@ -2810,7 +2580,7 @@ Future<void> startRefresh(ScanData scanData) async {
useSSL: scanData.node?.useSSL ?? false, useSSL: scanData.node?.useSSL ?? false,
); );
int getCountToScanPerRequest(int syncHeight) { int getCountPerRequest(int syncHeight) {
if (scanData.isSingleScan) { if (scanData.isSingleScan) {
return 1; return 1;
} }
@ -2825,10 +2595,11 @@ Future<void> startRefresh(ScanData scanData) async {
scanData.silentAddress.B_spend.toHex(), scanData.silentAddress.B_spend.toHex(),
scanData.network == BitcoinNetwork.testnet, scanData.network == BitcoinNetwork.testnet,
scanData.labelIndexes, scanData.labelIndexes,
scanData.labelIndexes.length,
); );
// Initial status UI update, send how many blocks in total to scan // Initial status UI update, send how many blocks in total to scan
final initialCount = getCountToScanPerRequest(syncHeight); final initialCount = getCountPerRequest(syncHeight);
scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight))); scanData.sendPort.send(SyncResponse(syncHeight, StartingScanSyncStatus(syncHeight)));
tweaksSubscription = await electrumClient.tweaksSubscribe( tweaksSubscription = await electrumClient.tweaksSubscribe(
@ -2839,24 +2610,22 @@ Future<void> startRefresh(ScanData scanData) async {
Future<void> listenFn(t) async { Future<void> listenFn(t) async {
final tweaks = t as Map<String, dynamic>; final tweaks = t as Map<String, dynamic>;
final msg = tweaks["message"]; final msg = tweaks["message"];
// success or error msg
// is success or error msg
final noData = msg != null; final noData = msg != null;
if (noData) { if (noData) {
if (scanData.isSingleScan) {
return;
}
// re-subscribe to continue receiving messages, starting from the next unscanned height // re-subscribe to continue receiving messages, starting from the next unscanned height
final nextHeight = syncHeight + 1; final nextHeight = syncHeight + 1;
final nextCount = getCountPerRequest(nextHeight);
if (nextHeight <= scanData.chainTip) { if (nextCount > 0) {
final nextStream = electrumClient.tweaksSubscribe( tweaksSubscription?.close();
final nextTweaksSubscription = electrumClient.tweaksSubscribe(
height: nextHeight, height: nextHeight,
count: getCountToScanPerRequest(nextHeight), count: nextCount,
); );
nextStream?.listen(listenFn); nextTweaksSubscription?.listen(listenFn);
} }
return; return;
@ -2938,7 +2707,7 @@ Future<void> startRefresh(ScanData scanData) async {
isUsed: true, isUsed: true,
network: scanData.network, network: scanData.network,
silentPaymentTweak: t_k, silentPaymentTweak: t_k,
type: SegwitAddressType.p2tr, type: SegwitAddresType.p2tr,
txCount: 1, txCount: 1,
balance: amount!, balance: amount!,
); );
@ -3031,15 +2800,15 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
} else if (type is P2shAddress) { } else if (type is P2shAddress) {
return P2shAddressType.p2wpkhInP2sh; return P2shAddressType.p2wpkhInP2sh;
} else if (type is P2wshAddress) { } else if (type is P2wshAddress) {
return SegwitAddressType.p2wsh; return SegwitAddresType.p2wsh;
} else if (type is P2trAddress) { } else if (type is P2trAddress) {
return SegwitAddressType.p2tr; return SegwitAddresType.p2tr;
} else if (type is MwebAddress) { } else if (type is MwebAddress) {
return SegwitAddressType.mweb; return SegwitAddresType.mweb;
} else if (type is SilentPaymentsAddresType) { } else if (type is SilentPaymentsAddresType) {
return SilentPaymentsAddresType.p2sp; return SilentPaymentsAddresType.p2sp;
} else { } else {
return SegwitAddressType.p2wpkh; return SegwitAddresType.p2wpkh;
} }
} }

View file

@ -17,16 +17,16 @@ part 'electrum_wallet_addresses.g.dart';
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses; class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [ const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
SegwitAddressType.p2wpkh, SegwitAddresType.p2wpkh,
P2pkhAddressType.p2pkh, P2pkhAddressType.p2pkh,
SegwitAddressType.p2tr, SegwitAddresType.p2tr,
SegwitAddressType.p2wsh, SegwitAddresType.p2wsh,
P2shAddressType.p2wpkhInP2sh, P2shAddressType.p2wpkhInP2sh,
]; ];
const List<BitcoinAddressType> LITECOIN_ADDRESS_TYPES = [ const List<BitcoinAddressType> LITECOIN_ADDRESS_TYPES = [
SegwitAddressType.p2wpkh, SegwitAddresType.p2wpkh,
SegwitAddressType.mweb, SegwitAddresType.mweb,
]; ];
const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [ const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [
@ -62,7 +62,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
_addressPageType = initialAddressPageType ?? _addressPageType = initialAddressPageType ??
(walletInfo.addressPageType != null (walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!) ? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddressType.p2wpkh), : SegwitAddresType.p2wpkh),
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of( silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
(initialSilentAddresses ?? []).toSet()), (initialSilentAddresses ?? []).toSet()),
currentSilentAddressIndex = initialSilentAddressIndex, currentSilentAddressIndex = initialSilentAddressIndex,
@ -71,12 +71,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
super(walletInfo) { super(walletInfo) {
if (masterHd != null) { if (masterHd != null) {
silentAddress = SilentPaymentOwner.fromPrivateKeys( silentAddress = SilentPaymentOwner.fromPrivateKeys(
b_scan: ECPrivate.fromHex( b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privateKey.toHex()),
masterHd.derivePath("m/352'/1'/0'/1'/0").privateKey.toHex(), b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privateKey.toHex()),
), network: network,
b_spend: ECPrivate.fromHex(
masterHd.derivePath("m/352'/1'/0'/0'/0").privateKey.toHex(),
),
); );
if (silentAddresses.length == 0) { if (silentAddresses.length == 0) {
@ -147,13 +144,12 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return silentAddress.toString(); return silentAddress.toString();
} }
final typeMatchingAddresses = final typeMatchingAddresses = _addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList();
_addresses.where((addr) => !addr.isHidden && _isAddressPageTypeMatch(addr)).toList(); final typeMatchingReceiveAddresses = typeMatchingAddresses.where((addr) => !addr.isUsed).toList();
final typeMatchingReceiveAddresses =
typeMatchingAddresses.where((addr) => !addr.isUsed).toList();
if (!isEnabledAutoGenerateSubaddress) { if (!isEnabledAutoGenerateSubaddress) {
if (previousAddressRecord != null && previousAddressRecord!.type == addressPageType) { if (previousAddressRecord != null &&
previousAddressRecord!.type == addressPageType) {
return previousAddressRecord!.address; return previousAddressRecord!.address;
} }
@ -253,17 +249,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
if (walletInfo.type == WalletType.bitcoinCash) { if (walletInfo.type == WalletType.bitcoinCash) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) { } else if (walletInfo.type == WalletType.litecoin) {
await _generateInitialAddresses(type: SegwitAddressType.p2wpkh); await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
await _generateInitialAddresses(type: SegwitAddressType.mweb); await _generateInitialAddresses(type: SegwitAddresType.mweb);
} }
} else if (walletInfo.type == WalletType.bitcoin) { } else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses(); await _generateInitialAddresses();
if (!isHardwareWallet) { if (!isHardwareWallet) {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddressType.p2tr); await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddressType.p2wsh); await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
} }
} }
@ -327,7 +323,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) { BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) { if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) {
final currentSilentAddressIndex = silentAddresses final currentSilentAddressIndex = silentAddresses
.where((addressRecord) => addressRecord.type != SegwitAddressType.p2tr) .where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr)
.length - .length -
1; 1;
@ -385,7 +381,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void addBitcoinAddressTypes() { void addBitcoinAddressTypes() {
final lastP2wpkh = _addresses final lastP2wpkh = _addresses
.where((addressRecord) => .where((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2wpkh)) _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList() .toList()
.last; .last;
if (lastP2wpkh.address != address) { if (lastP2wpkh.address != address) {
@ -411,7 +407,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
final lastP2tr = _addresses.firstWhere( final lastP2tr = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2tr)); (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
if (lastP2tr.address != address) { if (lastP2tr.address != address) {
addressesMap[lastP2tr.address] = 'P2TR'; addressesMap[lastP2tr.address] = 'P2TR';
} else { } else {
@ -419,7 +415,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
final lastP2wsh = _addresses.firstWhere( final lastP2wsh = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2wsh)); (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
if (lastP2wsh.address != address) { if (lastP2wsh.address != address) {
addressesMap[lastP2wsh.address] = 'P2WSH'; addressesMap[lastP2wsh.address] = 'P2WSH';
} else { } else {
@ -444,7 +440,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void addLitecoinAddressTypes() { void addLitecoinAddressTypes() {
final lastP2wpkh = _addresses final lastP2wpkh = _addresses
.where((addressRecord) => .where((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.p2wpkh)) _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList() .toList()
.last; .last;
if (lastP2wpkh.address != address) { if (lastP2wpkh.address != address) {
@ -454,7 +450,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
final lastMweb = _addresses.firstWhere( final lastMweb = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddressType.mweb)); (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
if (lastMweb.address != address) { if (lastMweb.address != address) {
addressesMap[lastMweb.address] = 'MWEB'; addressesMap[lastMweb.address] = 'MWEB';
} else { } else {
@ -564,14 +560,14 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
addressRecord.isHidden && addressRecord.isHidden &&
!addressRecord.isUsed && !addressRecord.isUsed &&
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type // TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
(walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddressType.p2wpkh)); (walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
changeAddresses.addAll(newAddresses); changeAddresses.addAll(newAddresses);
} }
@action @action
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden, Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
Future<String?> Function(BitcoinAddressRecord) getAddressHistory, Future<String?> Function(BitcoinAddressRecord) getAddressHistory,
{BitcoinAddressType type = SegwitAddressType.p2wpkh}) async { {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
final newAddresses = await _createNewAddresses(gap, final newAddresses = await _createNewAddresses(gap,
startIndex: addressList.length, isHidden: isHidden, type: type); startIndex: addressList.length, isHidden: isHidden, type: type);
addAddresses(newAddresses); addAddresses(newAddresses);
@ -585,7 +581,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
Future<void> _generateInitialAddresses( Future<void> _generateInitialAddresses(
{BitcoinAddressType type = SegwitAddressType.p2wpkh}) async { {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
var countOfReceiveAddresses = 0; var countOfReceiveAddresses = 0;
var countOfHiddenAddresses = 0; var countOfHiddenAddresses = 0;
@ -662,7 +658,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void _validateAddresses() { void _validateAddresses() {
_addresses.forEach((element) async { _addresses.forEach((element) async {
if (element.type == SegwitAddressType.mweb) { if (element.type == SegwitAddresType.mweb) {
// this would add a ton of startup lag for mweb addresses since we have 1000 of them // this would add a ton of startup lag for mweb addresses since we have 1000 of them
return; return;
} }

View file

@ -87,8 +87,8 @@ class ElectrumWalletSnapshot {
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ?? final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
var regularAddressIndexByType = {SegwitAddressType.p2wpkh.toString(): 0}; var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndexByType = {SegwitAddressType.p2wpkh.toString(): 0}; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var silentAddressIndex = 0; var silentAddressIndex = 0;
final derivationType = DerivationType final derivationType = DerivationType
@ -97,10 +97,10 @@ class ElectrumWalletSnapshot {
try { try {
regularAddressIndexByType = { regularAddressIndexByType = {
SegwitAddressType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0') SegwitAddresType.p2wpkh.toString(): int.parse(data['account_index'] as String? ?? '0')
}; };
changeAddressIndexByType = { changeAddressIndexByType = {
SegwitAddressType.p2wpkh.toString(): SegwitAddresType.p2wpkh.toString():
int.parse(data['change_address_index'] as String? ?? '0') int.parse(data['change_address_index'] as String? ?? '0')
}; };
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0'); silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');

View file

@ -16,6 +16,7 @@ import 'package:fixnum/fixnum.dart';
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:blockchain_utils/signer/ecdsa_signing_key.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
@ -970,9 +971,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
List<ECPrivateInfo>? inputPrivKeyInfos, List<ECPrivateInfo>? inputPrivKeyInfos,
List<Outpoint>? vinOutpoints, List<Outpoint>? vinOutpoints,
}) async { }) async {
bool spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddressType.mweb); bool spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddresType.mweb);
bool paysToMweb = outputs bool paysToMweb = outputs
.any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddressType.mweb); .any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddresType.mweb);
bool isRegular = !spendsMweb && !paysToMweb; bool isRegular = !spendsMweb && !paysToMweb;
bool isMweb = spendsMweb || paysToMweb; bool isMweb = spendsMweb || paysToMweb;
@ -1063,8 +1064,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.isMweb = mwebEnabled; tx.isMweb = mwebEnabled;
if (!mwebEnabled) { if (!mwebEnabled) {
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses) tx.changeAddressOverride =
.getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb)) (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(coinTypeToSpendFrom: UnspentCoinType.nonMweb))
.address; .address;
return tx; return tx;
} }
@ -1107,7 +1108,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// check if mweb inputs are used: // check if mweb inputs are used:
for (final utxo in tx.utxos) { for (final utxo in tx.utxos) {
if (utxo.utxo.scriptType == SegwitAddressType.mweb) { if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
hasMwebInput = true; hasMwebInput = true;
} }
} }
@ -1118,9 +1119,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool isRegular = !hasMwebInput && !hasMwebOutput; bool isRegular = !hasMwebInput && !hasMwebOutput;
bool shouldNotUseMwebChange = isPegIn || isRegular || !hasMwebInput; bool shouldNotUseMwebChange = isPegIn || isRegular || !hasMwebInput;
tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses) tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress( .getChangeAddress(coinTypeToSpendFrom: shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
coinTypeToSpendFrom:
shouldNotUseMwebChange ? UnspentCoinType.nonMweb : UnspentCoinType.any))
.address; .address;
if (isRegular) { if (isRegular) {
tx.isMweb = false; tx.isMweb = false;

View file

@ -106,7 +106,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
.map((e) => BitcoinAddressRecord( .map((e) => BitcoinAddressRecord(
e.value, e.value,
index: e.key, index: e.key,
type: SegwitAddressType.mweb, type: SegwitAddresType.mweb,
network: network, network: network,
)) ))
.toList(); .toList();
@ -128,7 +128,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required Bip32Slip10Secp256k1 hd, required Bip32Slip10Secp256k1 hd,
BitcoinAddressType? addressType, BitcoinAddressType? addressType,
}) { }) {
if (addressType == SegwitAddressType.mweb) { if (addressType == SegwitAddresType.mweb) {
return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1]; return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1];
} }
return generateP2WPKHAddress(hd: hd, index: index, network: network); return generateP2WPKHAddress(hd: hd, index: index, network: network);
@ -140,7 +140,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required Bip32Slip10Secp256k1 hd, required Bip32Slip10Secp256k1 hd,
BitcoinAddressType? addressType, BitcoinAddressType? addressType,
}) async { }) async {
if (addressType == SegwitAddressType.mweb) { if (addressType == SegwitAddresType.mweb) {
await ensureMwebAddressUpToIndexExists(index); await ensureMwebAddressUpToIndexExists(index);
} }
return getAddress(index: index, hd: hd, addressType: addressType); return getAddress(index: index, hd: hd, addressType: addressType);
@ -195,7 +195,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
return BitcoinAddressRecord( return BitcoinAddressRecord(
mwebAddrs[0], mwebAddrs[0],
index: 0, index: 0,
type: SegwitAddressType.mweb, type: SegwitAddresType.mweb,
network: network, network: network,
); );
} }
@ -207,7 +207,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
String get addressForExchange { String get addressForExchange {
// don't use mweb addresses for exchange refund address: // don't use mweb addresses for exchange refund address:
final addresses = receiveAddresses final addresses = receiveAddresses
.where((element) => element.type == SegwitAddressType.p2wpkh && !element.isUsed); .where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed);
return addresses.first.address; return addresses.first.address;
} }
} }

View file

@ -6,6 +6,7 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/payjoin/payjoin_persister.dart';
import 'package:cw_bitcoin/payjoin/payjoin_receive_worker.dart'; import 'package:cw_bitcoin/payjoin/payjoin_receive_worker.dart';
import 'package:cw_bitcoin/payjoin/payjoin_send_worker.dart'; import 'package:cw_bitcoin/payjoin/payjoin_send_worker.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
@ -16,6 +17,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:payjoin_flutter/common.dart'; import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/receive.dart'; import 'package:payjoin_flutter/receive.dart';
import 'package:payjoin_flutter/send.dart'; import 'package:payjoin_flutter/send.dart';
import 'package:payjoin_flutter/src/config.dart' as pj_config;
import 'package:payjoin_flutter/uri.dart' as PayjoinUri; import 'package:payjoin_flutter/uri.dart' as PayjoinUri;
class PayjoinManager { class PayjoinManager {
@ -31,11 +33,13 @@ class PayjoinManager {
'https://ohttp.cakewallet.com', 'https://ohttp.cakewallet.com',
]; ];
static Future<PayjoinUri.Url> randomOhttpRelayUrl() => static String randomOhttpRelayUrl() =>
PayjoinUri.Url.fromStr(ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)]); ohttpRelayUrls[Random.secure().nextInt(ohttpRelayUrls.length)];
static const payjoinDirectoryUrl = 'https://payjo.in'; static const payjoinDirectoryUrl = 'https://payjo.in';
Future<void> initPayjoin() => pj_config.PConfig.initializeApp();
Future<void> resumeSessions() async { Future<void> resumeSessions() async {
final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id); final allSessions = _payjoinStorage.readAllOpenSessions(_wallet.id);
@ -43,13 +47,13 @@ class PayjoinManager {
if (session.isSenderSession) { if (session.isSenderSession) {
printV("Resuming Payjoin Sender Session ${session.pjUri!}"); printV("Resuming Payjoin Sender Session ${session.pjUri!}");
return _spawnSender( return _spawnSender(
sender: Sender.fromJson(session.sender!), sender: Sender.fromJson(json: session.sender!),
pjUri: session.pjUri!, pjUri: session.pjUri!,
); );
} }
final receiver = Receiver.fromJson(session.receiver!); final receiver = Receiver.fromJson(json: session.receiver!);
printV("Resuming Payjoin Receiver Session ${receiver.id()}"); printV("Resuming Payjoin Receiver Session ${receiver.id()}");
return _spawnReceiver(receiver: receiver); return spawnReceiver(receiver: receiver);
}); });
printV("Resumed ${spawnedSessions.length} Payjoin Sessions"); printV("Resumed ${spawnedSessions.length} Payjoin Sessions");
@ -59,13 +63,19 @@ class PayjoinManager {
Future<Sender> initSender( Future<Sender> initSender(
String pjUriString, String originalPsbt, int networkFeesSatPerVb) async { String pjUriString, String originalPsbt, int networkFeesSatPerVb) async {
try { try {
final pjUri = (await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported(); final pjUri =
(await PayjoinUri.Uri.fromStr(pjUriString)).checkPjSupported();
final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250); final minFeeRateSatPerKwu = BigInt.from(networkFeesSatPerVb * 250);
final senderBuilder = await SenderBuilder.fromPsbtAndUri( final senderBuilder = await SenderBuilder.fromPsbtAndUri(
psbtBase64: originalPsbt, psbtBase64: originalPsbt,
pjUri: pjUri, pjUri: pjUri,
); );
return senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu); final persister = PayjoinSenderPersister.impl();
final newSender =
await senderBuilder.buildRecommended(minFeeRate: minFeeRateSatPerKwu);
final senderToken = await newSender.persist(persister: persister);
return Sender.load(token: senderToken, persister: persister);
} catch (e) { } catch (e) {
throw Exception('Error initializing Payjoin Sender: $e'); throw Exception('Error initializing Payjoin Sender: $e');
} }
@ -78,7 +88,8 @@ class PayjoinManager {
bool isTestnet = false, bool isTestnet = false,
}) async { }) async {
final pjUri = Uri.parse(pjUrl).queryParameters['pj']!; final pjUri = Uri.parse(pjUrl).queryParameters['pj']!;
await _payjoinStorage.insertSenderSession(sender, pjUri, _wallet.id, amount); await _payjoinStorage.insertSenderSession(
sender, pjUri, _wallet.id, amount);
return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri); return _spawnSender(isTestnet: isTestnet, sender: sender, pjUri: pjUri);
} }
@ -110,15 +121,13 @@ class PayjoinManager {
} }
} catch (e) { } catch (e) {
_cleanupSession(pjUri); _cleanupSession(pjUri);
printV(e); await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, e.toString());
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri); completer.complete();
completer.completeError(e);
} }
} else if (message is PayjoinSessionError) { } else if (message is PayjoinSessionError) {
_cleanupSession(pjUri); _cleanupSession(pjUri);
if (message is UnrecoverableError) { if (message is UnrecoverableError) {
printV(message.message); await _payjoinStorage.markSenderSessionUnrecoverable(pjUri, message.message);
await _payjoinStorage.markSenderSessionUnrecoverable(pjUri);
completer.complete(); completer.complete();
} else if (message is RecoverableError) { } else if (message is RecoverableError) {
completer.complete(); completer.complete();
@ -138,40 +147,41 @@ class PayjoinManager {
return completer.future; return completer.future;
} }
Future<Receiver> initReceiver(String address, [bool isTestnet = false]) async { Future<Receiver> getUnusedReceiver(String address,
try { [bool isTestnet = false]) async {
final payjoinDirectory = await PayjoinUri.Url.fromStr(payjoinDirectoryUrl); final session = _payjoinStorage.getUnusedActiveReceiverSession(_wallet.id);
if (session != null) {
await PayjoinUri.Url.fromStr(payjoinDirectoryUrl);
return Receiver.fromJson(json: session.receiver!);
}
return initReceiver(address);
}
Future<Receiver> initReceiver(String address, [bool isTestnet = false]) async {
final ohttpKeys = await PayjoinUri.fetchOhttpKeys( final ohttpKeys = await PayjoinUri.fetchOhttpKeys(
ohttpRelay: await randomOhttpRelayUrl(), ohttpRelay: await randomOhttpRelayUrl(),
payjoinDirectory: payjoinDirectory, payjoinDirectory: payjoinDirectoryUrl,
); );
final receiver = await Receiver.create( final newReceiver = await NewReceiver.create(
address: address, address: address,
network: isTestnet ? Network.testnet : Network.bitcoin, network: isTestnet ? Network.testnet : Network.bitcoin,
directory: payjoinDirectory, directory: payjoinDirectoryUrl,
ohttpKeys: ohttpKeys, ohttpKeys: ohttpKeys,
ohttpRelay: await randomOhttpRelayUrl(),
); );
final persister = PayjoinReceiverPersister.impl();
final receiverToken = await newReceiver.persist(persister: persister);
final receiver = await Receiver.load(persister: persister, token: receiverToken);
await _payjoinStorage.insertReceiverSession(receiver, _wallet.id); await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
return receiver; return receiver;
} catch (e) {
throw Exception('Error initializing Payjoin Receiver: $e');
}
} }
Future<void> spawnNewReceiver({ Future<void> spawnReceiver({
required Receiver receiver,
bool isTestnet = false,
}) async {
await _payjoinStorage.insertReceiverSession(receiver, _wallet.id);
return _spawnReceiver(isTestnet: isTestnet, receiver: receiver);
}
Future<void> _spawnReceiver({
required Receiver receiver, required Receiver receiver,
bool isTestnet = false, bool isTestnet = false,
}) async { }) async {
@ -191,11 +201,13 @@ class PayjoinManager {
rawAmount = getOutputAmountFromTx(tx, _wallet); rawAmount = getOutputAmountFromTx(tx, _wallet);
break; break;
case PayjoinReceiverRequestTypes.checkIsOwned: case PayjoinReceiverRequestTypes.checkIsOwned:
(_wallet.walletAddresses as BitcoinWalletAddresses).newPayjoinReceiver(); (_wallet.walletAddresses as BitcoinWalletAddresses)
.newPayjoinReceiver();
_payjoinStorage.markReceiverSessionInProgress(receiver.id()); _payjoinStorage.markReceiverSessionInProgress(receiver.id());
final inputScript = message['input_script'] as Uint8List; final inputScript = message['input_script'] as Uint8List;
final isOwned = _wallet.isMine(Script.fromRaw(bytes: inputScript)); final isOwned =
_wallet.isMine(Script.fromRaw(byteData: inputScript));
mainToIsolateSendPort?.send({ mainToIsolateSendPort?.send({
'requestId': message['requestId'], 'requestId': message['requestId'],
'result': isOwned, 'result': isOwned,
@ -204,7 +216,8 @@ class PayjoinManager {
case PayjoinReceiverRequestTypes.checkIsReceiverOutput: case PayjoinReceiverRequestTypes.checkIsReceiverOutput:
final outputScript = message['output_script'] as Uint8List; final outputScript = message['output_script'] as Uint8List;
final isReceiverOutput = _wallet.isMine(Script.fromRaw(bytes: outputScript)); final isReceiverOutput =
_wallet.isMine(Script.fromRaw(byteData: outputScript));
mainToIsolateSendPort?.send({ mainToIsolateSendPort?.send({
'requestId': message['requestId'], 'requestId': message['requestId'],
'result': isReceiverOutput, 'result': isReceiverOutput,
@ -213,6 +226,10 @@ class PayjoinManager {
case PayjoinReceiverRequestTypes.getCandidateInputs: case PayjoinReceiverRequestTypes.getCandidateInputs:
utxos = _wallet.getUtxoWithPrivateKeys(); utxos = _wallet.getUtxoWithPrivateKeys();
if (utxos.isEmpty) {
await _wallet.updateAllUnspents();
utxos = _wallet.getUtxoWithPrivateKeys();
}
mainToIsolateSendPort?.send({ mainToIsolateSendPort?.send({
'requestId': message['requestId'], 'requestId': message['requestId'],
'result': utxos, 'result': utxos,
@ -237,13 +254,15 @@ class PayjoinManager {
} }
} catch (e) { } catch (e) {
_cleanupSession(receiver.id()); _cleanupSession(receiver.id());
await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id(), e.toString()); await _payjoinStorage.markReceiverSessionUnrecoverable(
receiver.id(), e.toString());
completer.completeError(e); completer.completeError(e);
} }
} else if (message is PayjoinSessionError) { } else if (message is PayjoinSessionError) {
_cleanupSession(receiver.id()); _cleanupSession(receiver.id());
if (message is UnrecoverableError) { if (message is UnrecoverableError) {
await _payjoinStorage.markReceiverSessionUnrecoverable(receiver.id(), message.message); await _payjoinStorage.markReceiverSessionUnrecoverable(
receiver.id(), message.message);
completer.complete(); completer.complete();
} else if (message is RecoverableError) { } else if (message is RecoverableError) {
completer.complete(); completer.complete();

View file

@ -0,0 +1,66 @@
import 'package:payjoin_flutter/src/generated/api/receive.dart';
import 'package:payjoin_flutter/src/generated/api/send.dart';
class PayjoinSenderPersister implements DartSenderPersister {
static DartSenderPersister impl() {
final impl = PayjoinSenderPersister();
return DartSenderPersister(
save: (sender) => impl.save(sender: sender),
load: (token) => impl.load(token: token),
);
}
final Map<String, FfiSender> _store = {};
Future<SenderToken> save({required FfiSender sender}) async {
final token = sender.key();
_store[token.toBytes().toString()] = sender;
return token;
}
Future<FfiSender> load({required SenderToken token}) async {
final sender = _store[token.toBytes().toString()];
if (sender == null) {
throw Exception('Sender not found for the provided token.');
}
return sender;
}
@override
void dispose() => _store.clear();
@override
bool get isDisposed => _store.isEmpty;
}
class PayjoinReceiverPersister implements DartReceiverPersister {
static DartReceiverPersister impl() {
final impl = PayjoinReceiverPersister();
return DartReceiverPersister(
save: (receiver) => impl.save(receiver: receiver),
load: (token) => impl.load(token: token),
);
}
final Map<String, FfiReceiver> _store = {};
Future<ReceiverToken> save({required FfiReceiver receiver}) async {
final token = receiver.key();
_store[token.toBytes().toString()] = receiver;
return token;
}
Future<FfiReceiver> load({required ReceiverToken token}) async {
final receiver = _store[token.toBytes().toString()];
if (receiver == null) {
throw Exception('Receiver not found for the provided token.');
}
return receiver;
}
@override
void dispose() => _store.clear();
@override
bool get isDisposed => _store.isEmpty;
}

View file

@ -4,14 +4,16 @@ import 'dart:isolate';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/payjoin/manager.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
import 'package:cw_bitcoin/psbt/signer.dart'; import 'package:cw_bitcoin/psbt/signer.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:http/http.dart' as http; import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:payjoin_flutter/bitcoin_ffi.dart'; import 'package:payjoin_flutter/bitcoin_ffi.dart';
import 'package:payjoin_flutter/common.dart'; import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/receive.dart'; import 'package:payjoin_flutter/receive.dart';
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
import 'package:http/http.dart' as very_insecure_http_do_not_use; // for errors
enum PayjoinReceiverRequestTypes { enum PayjoinReceiverRequestTypes {
processOriginalTx, processOriginalTx,
@ -27,7 +29,7 @@ class PayjoinReceiverWorker {
final pendingRequests = <String, Completer<dynamic>>{}; final pendingRequests = <String, Completer<dynamic>>{};
PayjoinReceiverWorker._(this.sendPort); PayjoinReceiverWorker._(this.sendPort);
static final client = ProxyWrapper().getHttpIOClient();
static Future<void> run(List<Object> args) async { static Future<void> run(List<Object> args) async {
await pj.core.init(); await pj.core.init();
@ -41,11 +43,10 @@ class PayjoinReceiverWorker {
receivePort.listen(worker.handleMessage); receivePort.listen(worker.handleMessage);
try { try {
final httpClient = http.Client(); final receiver = Receiver.fromJson(json: receiverJson);
final receiver = Receiver.fromJson(receiverJson);
final uncheckedProposal = final uncheckedProposal =
await worker.receiveUncheckedProposal(httpClient, receiver); await worker.receiveUncheckedProposal(receiver);
final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast(); final originalTx = await uncheckedProposal.extractTxToScheduleBroadcast();
sendPort.send({ sendPort.send({
@ -56,14 +57,14 @@ class PayjoinReceiverWorker {
final payjoinProposal = await worker.processPayjoinProposal( final payjoinProposal = await worker.processPayjoinProposal(
uncheckedProposal, uncheckedProposal,
); );
final psbt = await worker.sendFinalProposal(httpClient, payjoinProposal); final psbt = await worker.sendFinalProposal(payjoinProposal);
sendPort.send({ sendPort.send({
'type': PayjoinReceiverRequestTypes.proposalSent, 'type': PayjoinReceiverRequestTypes.proposalSent,
'psbt': psbt, 'psbt': psbt,
}); });
} catch (e) { } catch (e) {
if (e is HttpException || if (e is HttpException ||
(e is http.ClientException && (e is very_insecure_http_do_not_use.ClientException &&
e.message.contains("Software caused connection abort"))) { e.message.contains("Software caused connection abort"))) {
sendPort.send(PayjoinSessionError.recoverable(e.toString())); sendPort.send(PayjoinSessionError.recoverable(e.toString()));
} else { } else {
@ -97,15 +98,16 @@ class PayjoinReceiverWorker {
return completer.future; return completer.future;
} }
Future<UncheckedProposal> receiveUncheckedProposal( Future<UncheckedProposal> receiveUncheckedProposal(Receiver session) async {
http.Client httpClient, Receiver session) async {
while (true) { while (true) {
printV("Polling for Proposal (${session.id()})"); printV("Polling for Proposal (${session.id()})");
final extractReq = await session.extractReq(); final extractReq = await session.extractReq(
ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
);
final request = extractReq.$1; final request = extractReq.$1;
final url = Uri.parse(request.url.asString()); final url = Uri.parse(request.url.asString());
final httpRequest = await httpClient.post(url, final httpRequest = await client.post(url,
headers: {'Content-Type': request.contentType}, body: request.body); headers: {'Content-Type': request.contentType}, body: request.body);
final proposal = await session.processRes( final proposal = await session.processRes(
@ -114,13 +116,14 @@ class PayjoinReceiverWorker {
} }
} }
Future<String> sendFinalProposal( Future<String> sendFinalProposal(PayjoinProposal finalProposal) async {
http.Client httpClient, PayjoinProposal finalProposal) async { final req = await finalProposal.extractReq(
final req = await finalProposal.extractV2Req(); ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
);
final proposalReq = req.$1; final proposalReq = req.$1;
final proposalCtx = req.$2; final proposalCtx = req.$2;
final request = await httpClient.post( final request = await client.post(
Uri.parse(proposalReq.url.asString()), Uri.parse(proposalReq.url.asString()),
headers: {"Content-Type": proposalReq.contentType}, headers: {"Content-Type": proposalReq.contentType},
body: proposalReq.body, body: proposalReq.body,
@ -171,7 +174,7 @@ class PayjoinReceiverWorker {
final listUnspent = final listUnspent =
await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs); await _sendRequest(PayjoinReceiverRequestTypes.getCandidateInputs);
final unspent = listUnspent as List<UtxoWithPrivateKey>; final unspent = listUnspent as List<UtxoWithPrivateKey>;
if (unspent.isEmpty) throw Exception('No unspent outputs available'); if (unspent.isEmpty) throw RecoverableError('No unspent outputs available');
final selectedUtxo = await _inputPairFromUtxo(unspent[0]); final selectedUtxo = await _inputPairFromUtxo(unspent[0]);
final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]); final pj6 = await pj5.contributeInputs(replacementInputs: [selectedUtxo]);
@ -214,6 +217,6 @@ class PayjoinReceiverWorker {
sequence: 0, sequence: 0,
); );
return InputPair.newInstance(txin, psbtin); return InputPair.newInstance(txin: txin, psbtin: psbtin);
} }
} }

View file

@ -5,10 +5,12 @@ import 'dart:isolate';
import 'package:cw_bitcoin/payjoin/manager.dart'; import 'package:cw_bitcoin/payjoin/manager.dart';
import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart'; import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:http/http.dart' as http; import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:payjoin_flutter/common.dart'; import 'package:payjoin_flutter/common.dart';
import 'package:payjoin_flutter/send.dart'; import 'package:payjoin_flutter/send.dart';
import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj; import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
import 'package:payjoin_flutter/src/generated/api/send/error.dart' as pj_error;
import 'package:payjoin_flutter/uri.dart' as pj_uri;
enum PayjoinSenderRequestTypes { enum PayjoinSenderRequestTypes {
requestPosted, requestPosted,
@ -29,7 +31,7 @@ class PayjoinSenderWorker {
final senderJson = args[1] as String; final senderJson = args[1] as String;
final pjUrl = args[2] as String; final pjUrl = args[2] as String;
final sender = Sender.fromJson(senderJson); final sender = Sender.fromJson(json: senderJson);
final worker = PayjoinSenderWorker._(sendPort, pjUrl); final worker = PayjoinSenderWorker._(sendPort, pjUrl);
try { try {
@ -42,19 +44,17 @@ class PayjoinSenderWorker {
sendPort.send(e); sendPort.send(e);
} }
} }
final client = ProxyWrapper().getHttpIOClient();
/// Run a payjoin sender (V2 protocol first, fallback to V1). /// Run a payjoin sender (V2 protocol first, fallback to V1).
Future<String> runSender(Sender sender) async { Future<String> runSender(Sender sender) async {
final httpClient = http.Client();
try { try {
return await _runSenderV2(sender, httpClient); return await _runSenderV2(sender);
} catch (e) { } catch (e) {
printV(e); printV(e);
if (e is PayjoinException && if (e is pj_error.FfiCreateRequestError) {
// TODO condition on error type instead of message content return await _runSenderV1(sender);
e.message?.contains('parse receiver public key') == true) {
return await _runSenderV1(sender, httpClient);
} else if (e is HttpException) { } else if (e is HttpException) {
printV(e); printV(e);
throw Exception(PayjoinSessionError.recoverable(e.toString())); throw Exception(PayjoinSessionError.recoverable(e.toString()));
@ -65,13 +65,14 @@ class PayjoinSenderWorker {
} }
/// Attempt to send payjoin using the V2 of the protocol. /// Attempt to send payjoin using the V2 of the protocol.
Future<String> _runSenderV2(Sender sender, http.Client httpClient) async { Future<String> _runSenderV2(Sender sender) async {
try { try {
final postRequest = await sender.extractV2( final postRequest = await sender.extractV2(
ohttpProxyUrl: await PayjoinManager.randomOhttpRelayUrl(), ohttpProxyUrl:
await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()),
); );
final postResult = await _postRequest(httpClient, postRequest.$1); final postResult = await _postRequest(postRequest.$1);
final getContext = final getContext =
await postRequest.$2.processResponse(response: postResult); await postRequest.$2.processResponse(response: postResult);
@ -83,7 +84,7 @@ class PayjoinSenderWorker {
final getRequest = await getContext.extractReq( final getRequest = await getContext.extractReq(
ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(), ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
); );
final getRes = await _postRequest(httpClient, getRequest.$1); final getRes = await _postRequest(getRequest.$1);
final proposalPsbt = await getContext.processResponse( final proposalPsbt = await getContext.processResponse(
response: getRes, response: getRes,
ohttpCtx: getRequest.$2, ohttpCtx: getRequest.$2,
@ -97,20 +98,20 @@ class PayjoinSenderWorker {
} }
/// Attempt to send payjoin using the V1 of the protocol. /// Attempt to send payjoin using the V1 of the protocol.
Future<String> _runSenderV1(Sender sender, http.Client httpClient) async { Future<String> _runSenderV1(Sender sender) async {
try { try {
final postRequest = await sender.extractV1(); final postRequest = await sender.extractV1();
final response = await _postRequest(httpClient, postRequest.$1); final response = await _postRequest(postRequest.$1);
sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted}); sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted});
return await postRequest.$2.processResponse(response: response); return await postRequest.$2.processResponse(response: response);
} catch (e) { } catch (e, stack) {
throw PayjoinSessionError.unrecoverable('Send V1 payjoin error: $e'); throw PayjoinSessionError.unrecoverable('Send V1 payjoin error: $e, $stack');
} }
} }
Future<List<int>> _postRequest(http.Client client, Request req) async { Future<List<int>> _postRequest(Request req) async {
final httpRequest = await client.post(Uri.parse(req.url.asString()), final httpRequest = await client.post(Uri.parse(req.url.asString()),
headers: {'Content-Type': req.contentType}, body: req.body); headers: {'Content-Type': req.contentType}, body: req.body);

View file

@ -23,6 +23,14 @@ class PayjoinStorage {
), ),
); );
PayjoinSession? getUnusedActiveReceiverSession(String walletId) =>
_payjoinSessionSources.values
.where((session) =>
session.walletId == walletId &&
session.status == PayjoinSessionStatus.created.name &&
!session.isSenderSession)
.firstOrNull;
Future<void> markReceiverSessionComplete( Future<void> markReceiverSessionComplete(
String sessionId, String txId, String amount) async { String sessionId, String txId, String amount) async {
final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!; final session = _payjoinSessionSources.get("$_receiverPrefix${sessionId}")!;
@ -76,10 +84,11 @@ class PayjoinStorage {
await session.save(); await session.save();
} }
Future<void> markSenderSessionUnrecoverable(String pjUrl) async { Future<void> markSenderSessionUnrecoverable(String pjUrl, String reason) async {
final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!; final session = _payjoinSessionSources.get("$_senderPrefix$pjUrl")!;
session.status = PayjoinSessionStatus.unrecoverable.name; session.status = PayjoinSessionStatus.unrecoverable.name;
session.error = reason;
await session.save(); await session.save();
} }

View file

@ -40,7 +40,8 @@ extension PsbtSigner on PsbtV2 {
return tx.buffer(); return tx.buffer();
} }
Future<void> signWithUTXO(List<UtxoWithPrivateKey> utxos, UTXOSignerCallBack signer, Future<void> signWithUTXO(
List<UtxoWithPrivateKey> utxos, UTXOSignerCallBack signer,
[UTXOGetterCallBack? getTaprootPair]) async { [UTXOGetterCallBack? getTaprootPair]) async {
final raw = BytesUtils.toHexString(extractUnsignedTX(getSegwit: false)); final raw = BytesUtils.toHexString(extractUnsignedTX(getSegwit: false));
final tx = BtcTransaction.fromRaw(raw); final tx = BtcTransaction.fromRaw(raw);
@ -50,10 +51,10 @@ extension PsbtSigner on PsbtV2 {
List<BigInt> taprootAmounts = []; List<BigInt> taprootAmounts = [];
List<Script> taprootScripts = []; List<Script> taprootScripts = [];
if (utxos.any((e) => e.utxo.isP2tr)) { if (utxos.any((e) => e.utxo.isP2tr())) {
for (final input in tx.inputs) { for (final input in tx.inputs) {
final utxo = utxos final utxo = utxos.firstWhereOrNull(
.firstWhereOrNull((u) => u.utxo.txHash == input.txId && u.utxo.vout == input.txIndex); (u) => u.utxo.txHash == input.txId && u.utxo.vout == input.txIndex);
if (utxo == null) { if (utxo == null) {
final trPair = await getTaprootPair!.call(input.txId, input.txIndex); final trPair = await getTaprootPair!.call(input.txId, input.txIndex);
@ -75,29 +76,37 @@ extension PsbtSigner on PsbtV2 {
/// We receive the owner's ScriptPubKey /// We receive the owner's ScriptPubKey
final script = _findLockingScript(utxo, false); final script = _findLockingScript(utxo, false);
final int sighash = final int sighash = utxo.utxo.isP2tr()
utxo.utxo.isP2tr ? BitcoinOpCodeConst.sighashDefault : BitcoinOpCodeConst.sighashAll; ? BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL
: BitcoinOpCodeConst.SIGHASH_ALL;
/// We generate transaction digest for current input /// We generate transaction digest for current input
final digest = final digest = _generateTransactionDigest(
_generateTransactionDigest(script, i, utxo.utxo, tx, taprootAmounts, taprootScripts); script, i, utxo.utxo, tx, taprootAmounts, taprootScripts);
/// now we need sign the transaction digest /// now we need sign the transaction digest
final sig = signer(digest, utxo, utxo.privateKey, sighash); final sig = signer(digest, utxo, utxo.privateKey, sighash);
if (utxo.utxo.isP2tr) { if (utxo.utxo.isP2tr()) {
setInputTapKeySig(i, Uint8List.fromList(BytesUtils.fromHexString(sig))); setInputTapKeySig(i, Uint8List.fromList(BytesUtils.fromHexString(sig)));
} else { } else {
setInputPartialSig(i, Uint8List.fromList(BytesUtils.fromHexString(utxo.public().toHex())), setInputPartialSig(
i,
Uint8List.fromList(BytesUtils.fromHexString(utxo.public().toHex())),
Uint8List.fromList(BytesUtils.fromHexString(sig))); Uint8List.fromList(BytesUtils.fromHexString(sig)));
} }
} }
} }
List<int> _generateTransactionDigest(Script scriptPubKeys, int input, BitcoinUtxo utxo, List<int> _generateTransactionDigest(
BtcTransaction transaction, List<BigInt> taprootAmounts, List<Script> tapRootPubKeys) { Script scriptPubKeys,
if (utxo.isSegwit) { int input,
if (utxo.isP2tr) { BitcoinUtxo utxo,
BtcTransaction transaction,
List<BigInt> taprootAmounts,
List<Script> tapRootPubKeys) {
if (utxo.isSegwit()) {
if (utxo.isP2tr()) {
return transaction.getTransactionTaprootDigset( return transaction.getTransactionTaprootDigset(
txIndex: input, txIndex: input,
scriptPubKeys: tapRootPubKeys, scriptPubKeys: tapRootPubKeys,
@ -107,7 +116,8 @@ extension PsbtSigner on PsbtV2 {
return transaction.getTransactionSegwitDigit( return transaction.getTransactionSegwitDigit(
txInIndex: input, script: scriptPubKeys, amount: utxo.value); txInIndex: input, script: scriptPubKeys, amount: utxo.value);
} }
return transaction.getTransactionDigest(txInIndex: input, script: scriptPubKeys); return transaction.getTransactionDigest(
txInIndex: input, script: scriptPubKeys);
} }
Script _findLockingScript(UtxoWithAddress utxo, bool isTaproot) { Script _findLockingScript(UtxoWithAddress utxo, bool isTaproot) {
@ -119,23 +129,23 @@ extension PsbtSigner on PsbtV2 {
switch (utxo.utxo.scriptType) { switch (utxo.utxo.scriptType) {
case PubKeyAddressType.p2pk: case PubKeyAddressType.p2pk:
return senderPub.toRedeemScript(); return senderPub.toRedeemScript();
case SegwitAddressType.p2wsh: case SegwitAddresType.p2wsh:
if (isTaproot) { if (isTaproot) {
return senderPub.toP2wshAddress().toScriptPubKey(); return senderPub.toP2wshAddress().toScriptPubKey();
} }
return senderPub.toP2wshRedeemScript(); return senderPub.toP2wshRedeemScript();
case P2pkhAddressType.p2pkh: case P2pkhAddressType.p2pkh:
return senderPub.toP2pkhAddress().toScriptPubKey(); return senderPub.toP2pkhAddress().toScriptPubKey();
case SegwitAddressType.p2wpkh: case SegwitAddresType.p2wpkh:
if (isTaproot) { if (isTaproot) {
return senderPub.toP2wpkhAddress().toScriptPubKey(); return senderPub.toP2wpkhAddress().toScriptPubKey();
} }
return senderPub.toP2pkhAddress().toScriptPubKey(); return senderPub.toP2pkhAddress().toScriptPubKey();
case SegwitAddressType.p2tr: case SegwitAddresType.p2tr:
return senderPub return senderPub
.toTaprootAddress(tweak: utxo.utxo.isSilentPayment != true) .toTaprootAddress(tweak: utxo.utxo.isSilentPayment != true)
.toScriptPubKey(); .toScriptPubKey();
case SegwitAddressType.mweb: case SegwitAddresType.mweb:
return Script(script: []); return Script(script: []);
case P2shAddressType.p2pkhInP2sh: case P2shAddressType.p2pkhInP2sh:
if (isTaproot) { if (isTaproot) {
@ -162,10 +172,11 @@ extension PsbtSigner on PsbtV2 {
} }
} }
typedef UTXOSignerCallBack = String Function( typedef UTXOSignerCallBack = String Function(List<int> trDigest,
List<int> trDigest, UtxoWithAddress utxo, ECPrivate privateKey, int sighash); UtxoWithAddress utxo, ECPrivate privateKey, int sighash);
typedef UTXOGetterCallBack = Future<TaprootAmountScriptPair> Function(String txId, int vout); typedef UTXOGetterCallBack = Future<TaprootAmountScriptPair> Function(
String txId, int vout);
class TaprootAmountScriptPair { class TaprootAmountScriptPair {
final BigInt value; final BigInt value;
@ -205,17 +216,23 @@ class UtxoWithPrivateKey extends UtxoWithAddress {
} }
return UtxoWithPrivateKey( return UtxoWithPrivateKey(
utxo: input.utxo, ownerDetails: input.ownerDetails, privateKey: key.privkey); utxo: input.utxo,
ownerDetails: input.ownerDetails,
privateKey: key.privkey);
} }
factory UtxoWithPrivateKey.fromUnspent(BitcoinUnspent input, BitcoinWalletBase wallet) { factory UtxoWithPrivateKey.fromUnspent(
final address = RegexUtils.addressTypeFromStr(input.address, BitcoinNetwork.mainnet); BitcoinUnspent input, BitcoinWalletBase wallet) {
final address =
RegexUtils.addressTypeFromStr(input.address, BitcoinNetwork.mainnet);
final newHd = input.bitcoinAddressRecord.isHidden ? wallet.sideHd : wallet.hd; final newHd =
input.bitcoinAddressRecord.isHidden ? wallet.sideHd : wallet.hd;
ECPrivate privkey; ECPrivate privkey;
if (input.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { if (input.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = input.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; final unspentAddress =
input.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
privkey = wallet.walletAddresses.silentAddress!.b_spend.tweakAdd( privkey = wallet.walletAddresses.silentAddress!.b_spend.tweakAdd(
BigintUtils.fromBytes( BigintUtils.fromBytes(
BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!), BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!),
@ -223,7 +240,9 @@ class UtxoWithPrivateKey extends UtxoWithAddress {
); );
} else { } else {
privkey = generateECPrivate( privkey = generateECPrivate(
hd: newHd, index: input.bitcoinAddressRecord.index, network: BitcoinNetwork.mainnet); hd: newHd,
index: input.bitcoinAddressRecord.index,
network: BitcoinNetwork.mainnet);
} }
return UtxoWithPrivateKey( return UtxoWithPrivateKey(
@ -232,7 +251,8 @@ class UtxoWithPrivateKey extends UtxoWithAddress {
value: BigInt.from(input.value), value: BigInt.from(input.value),
vout: input.vout, vout: input.vout,
scriptType: input.bitcoinAddressRecord.type, scriptType: input.bitcoinAddressRecord.type,
isSilentPayment: input.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord, isSilentPayment:
input.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord,
), ),
ownerDetails: UtxoAddressDetails( ownerDetails: UtxoAddressDetails(
publicKey: privkey.getPublic().toHex(), publicKey: privkey.getPublic().toHex(),

View file

@ -9,9 +9,7 @@ class PSBTTransactionBuild {
final PsbtV2 psbt = PsbtV2(); final PsbtV2 psbt = PsbtV2();
PSBTTransactionBuild( PSBTTransactionBuild(
{required List<PSBTReadyUtxoWithAddress> inputs, {required List<PSBTReadyUtxoWithAddress> inputs, required List<BitcoinBaseOutput> outputs, bool enableRBF = true}) {
required List<BitcoinBaseOutput> outputs,
bool enableRBF = true}) {
psbt.setGlobalTxVersion(2); psbt.setGlobalTxVersion(2);
psbt.setGlobalInputCount(inputs.length); psbt.setGlobalInputCount(inputs.length);
psbt.setGlobalOutputCount(outputs.length); psbt.setGlobalOutputCount(outputs.length);
@ -19,20 +17,20 @@ class PSBTTransactionBuild {
for (var i = 0; i < inputs.length; i++) { for (var i = 0; i < inputs.length; i++) {
final input = inputs[i]; final input = inputs[i];
printV(input.utxo.isP2tr); printV(input.utxo.isP2tr());
printV(input.utxo.isSegwit); printV(input.utxo.isSegwit());
printV(input.utxo.isP2shSegwit); printV(input.utxo.isP2shSegwit());
psbt.setInputPreviousTxId( psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
psbt.setInputOutputIndex(i, input.utxo.vout); psbt.setInputOutputIndex(i, input.utxo.vout);
psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff); psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);
if (input.utxo.isSegwit) {
if (input.utxo.isSegwit()) {
setInputSegwit(i, input); setInputSegwit(i, input);
} else if (input.utxo.isP2shSegwit) { } else if (input.utxo.isP2shSegwit()) {
setInputP2shSegwit(i, input); setInputP2shSegwit(i, input);
} else if (input.utxo.isP2tr) { } else if (input.utxo.isP2tr()) {
// ToDo: (Konsti) Handle Taproot Inputs // ToDo: (Konsti) Handle Taproot Inputs
} else { } else {
setInputP2pkh(i, input); setInputP2pkh(i, input);
@ -51,14 +49,20 @@ class PSBTTransactionBuild {
void setInputP2pkh(int i, PSBTReadyUtxoWithAddress input) { void setInputP2pkh(int i, PSBTReadyUtxoWithAddress input) {
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx))); psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)), psbt.setInputBip32Derivation(
input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray()); i,
Uint8List.fromList(hex.decode(input.ownerPublicKey)),
input.ownerMasterFingerprint,
BIPPath.fromString(input.ownerDerivationPath).toPathArray());
} }
void setInputSegwit(int i, PSBTReadyUtxoWithAddress input) { void setInputSegwit(int i, PSBTReadyUtxoWithAddress input) {
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx))); psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)), psbt.setInputBip32Derivation(
input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray()); i,
Uint8List.fromList(hex.decode(input.ownerPublicKey)),
input.ownerMasterFingerprint,
BIPPath.fromString(input.ownerDerivationPath).toPathArray());
psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)), psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes())); Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));

View file

@ -21,7 +21,7 @@ String getOutputAmountFromPsbt(String psbtV0, BitcoinWalletBase wallet) {
int amount = 0; int amount = 0;
for (var i = 0; i < psbt.getGlobalOutputCount(); i++) { for (var i = 0; i < psbt.getGlobalOutputCount(); i++) {
final script = psbt.getOutputScript(i); final script = psbt.getOutputScript(i);
if (wallet.isMine(Script.fromRaw(bytes: script))) { if (wallet.isMine(Script.fromRaw(byteData: script))) {
amount += psbt.getOutputAmount(i); amount += psbt.getOutputAmount(i);
} }
} }

View file

@ -5,34 +5,39 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "47.0.0" version: "76.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "6.11.0"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.7.0"
asn1lib: asn1lib:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5" sha256: "1c296cd268f486cabcc3930e9b93a8133169305f18d722916e675959a88f6d2c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.8" version: "1.5.9"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -79,20 +84,20 @@ packages:
dependency: "direct overridden" dependency: "direct overridden"
description: description:
path: "." path: "."
ref: cake-update-v15 ref: cake-update-v9
resolved-ref: "29160733cbc4ef2c7b8c8fe9ed0297c9bffecfe2" resolved-ref: bb4318511312a454fd91bf49042e25ecc855e4ac
url: "https://github.com/cake-tech/bitcoin_base" url: "https://github.com/cake-tech/bitcoin_base"
source: git source: git
version: "6.1.0" version: "4.7.0"
blockchain_utils: blockchain_utils:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a" resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils" url: "https://github.com/cake-tech/blockchain_utils"
source: git source: git
version: "4.3.0" version: "3.3.0"
bluez: bluez:
dependency: transitive dependency: transitive
description: description:
@ -121,10 +126,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
build_cli_annotations: build_cli_annotations:
dependency: transitive dependency: transitive
description: description:
@ -137,42 +142,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.4"
build_resolvers: build_resolvers:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_resolvers name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10" version: "2.4.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.13" version: "2.4.15"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.10" version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -185,10 +190,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.4" version: "8.10.1"
cake_backup: cake_backup:
dependency: transitive dependency: transitive
description: description:
@ -296,10 +301,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.3.8"
dart_varuint_bitcoin: dart_varuint_bitcoin:
dependency: transitive dependency: transitive
description: description:
@ -389,10 +394,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_rust_bridge name: flutter_rust_bridge
sha256: "3292ad6085552987b8b3b9a7e5805567f4013372d302736b702801acb001ee00" sha256: "5a5c7a5deeef2cc2ffe6076a33b0429f4a20ceac22a397297aed2b1eb067e611"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.1" version: "2.9.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -402,10 +407,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_web_bluetooth name: flutter_web_bluetooth
sha256: "1363831def5eed1e1064d1eca04e8ccb35446e8f758579c3c519e156b77926da" sha256: ad26a1b3fef95b86ea5f63793b9a0cdc1a33490f35d754e4e711046cae3ebbf8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -415,10 +420,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: freezed_annotation name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.4" version: "3.0.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -439,18 +444,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: google_identity_services_web name: google_identity_services_web
sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9" sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.3" version: "0.3.3+1"
googleapis_auth: googleapis_auth:
dependency: transitive dependency: transitive
description: description:
name: googleapis_auth name: googleapis_auth
sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938 sha256: b81fe352cc4a330b3710d2b7ad258d9bcef6f909bb759b306bf42973a7d046db
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.0" version: "2.0.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@ -463,10 +468,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: grpc name: grpc
sha256: "5b99b7a420937d4361ece68b798c9af8e04b5bc128a7859f2a4be87427694813" sha256: "30e1edae6846b163a64f6d8716e3443980fe1f7d2d1f086f011d24ea186f2582"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.1" version: "4.0.4"
hex: hex:
dependency: transitive dependency: transitive
description: description:
@ -487,18 +492,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: hive_generator name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.3" version: "2.0.1"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
http2: http2:
dependency: transitive dependency: transitive
description: description:
@ -519,10 +524,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.1.2"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -592,10 +597,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: ledger_flutter_plus name: ledger_flutter_plus
sha256: "1c03f3c4a9754b5f0170a9eb9552ec54fa86e985f8ee71a255ee2c5629b53d31" sha256: "531da5daba5731d9eca2732881ef2f039b97bf8aa3564e7098dfa99a9b07a8e6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.1" version: "1.5.3"
ledger_litecoin: ledger_litecoin:
dependency: "direct main" dependency: "direct main"
description: description:
@ -621,6 +626,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -665,10 +678,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: mobx_codegen name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.7.1"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -681,19 +694,19 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
url: "https://github.com/cake-tech/on_chain.git" url: "https://github.com/cake-tech/on_chain.git"
source: git source: git
version: "6.2.0" version: "3.7.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
name: package_config name: package_config
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.2.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -714,10 +727,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.15" version: "2.2.17"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -754,11 +767,11 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "6a3eb32fb9467ac12e7b75d3de47de4ca44fd88c" ref: da83a23f3a011cb49eb3b6513cd485b3fb8867ff
resolved-ref: "6a3eb32fb9467ac12e7b75d3de47de4ca44fd88c" resolved-ref: da83a23f3a011cb49eb3b6513cd485b3fb8867ff
url: "https://github.com/konstantinullrich/payjoin-flutter" url: "https://github.com/OmarHatem28/payjoin-flutter"
source: git source: git
version: "0.21.0" version: "0.23.0"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -811,26 +824,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.5"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.2.0"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
name: pubspec_parse name: pubspec_parse
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.5.0"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
@ -859,18 +872,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.6" version: "2.4.10"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@ -915,18 +928,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.2"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -935,27 +948,37 @@ packages:
socks5_proxy: socks5_proxy:
dependency: transitive dependency: transitive
description: description:
name: socks5_proxy path: "."
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://pub.dev" resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
source: hosted url: "https://github.com/LacticWhale/socks_dart"
version: "1.0.6" source: git
version: "2.1.0"
socks_socket:
dependency: "direct main"
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.6" version: "1.5.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.5"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -968,19 +991,11 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: cake-update-v4 ref: "sp_v4.0.0"
resolved-ref: f3c172a7dc5155f5e745e4630b05f197e098a5cd resolved-ref: "2554cb8bd3ee1d026bc63e76a30d1226960c7cb4"
url: "https://github.com/cake-tech/sp_scanner" url: "https://github.com/cake-tech/sp_scanner"
source: git source: git
version: "0.0.1" version: "0.0.1"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -1033,10 +1048,19 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: timing name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.1"
tor_binary:
dependency: transitive
description:
path: "."
ref: cb811c610871a9517d47134b87c2f590c15c96c5
resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5
url: "https://github.com/MrCyjaneK/flutter-tor_binary"
source: git
version: "4.7.14"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:
@ -1057,10 +1081,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: universal_ble name: universal_ble
sha256: "1fad089150a29db82b3b7d60327e18c5ad6b3a5bb509defc1c690b0a76b9c098" sha256: "35d210e93a5938c6a6d1fd3c710cf4ac90b1bdd1b11c8eb2beeb32600672e6e6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.0" version: "0.17.0"
universal_platform: universal_platform:
dependency: transitive dependency: transitive
description: description:
@ -1077,14 +1101,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.0" version: "0.3.0"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -1121,18 +1137,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web_socket name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.6" version: "1.0.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -1166,5 +1182,5 @@ packages:
source: hosted source: hosted
version: "2.2.2" version: "2.2.2"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.27.0"

View file

@ -29,21 +29,21 @@ dependencies:
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v4 ref: cake-update-v2
cw_mweb: cw_mweb:
path: ../cw_mweb path: ../cw_mweb
grpc: ^4.0.1 grpc: ^4.0.1
sp_scanner: sp_scanner:
git: git:
url: https://github.com/cake-tech/sp_scanner url: https://github.com/cake-tech/sp_scanner
ref: cake-update-v4 ref: sp_v4.0.0
bech32: bech32:
git: git:
url: https://github.com/cake-tech/bech32.git url: https://github.com/cake-tech/bech32.git
payjoin_flutter: payjoin_flutter:
git: git:
url: https://github.com/konstantinullrich/payjoin-flutter url: https://github.com/OmarHatem28/payjoin-flutter
ref: 6a3eb32fb9467ac12e7b75d3de47de4ca44fd88c #cake-v1 ref: da83a23f3a011cb49eb3b6513cd485b3fb8867ff #cake-v2
ledger_flutter_plus: ^1.4.1 ledger_flutter_plus: ^1.4.1
ledger_bitcoin: ledger_bitcoin:
git: git:
@ -54,14 +54,18 @@ dependencies:
git: git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-litecoin path: packages/ledger-litecoin
socks_socket:
git:
url: https://github.com/sneurlax/socks_socket
ref: e6232c53c1595469931ababa878759a067c02e94
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
build_resolvers: ^2.0.9 build_resolvers: ^2.4.4
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^2.0.1
dependency_overrides: dependency_overrides:
watcher: ^1.1.0 watcher: ^1.1.0
@ -69,7 +73,7 @@ dependency_overrides:
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v15 ref: cake-update-v9
pointycastle: 3.7.4 pointycastle: 3.7.4
ffi: 2.1.0 ffi: 2.1.0

View file

@ -7,7 +7,7 @@ homepage: https://cakewallet.com
environment: environment:
sdk: '>=2.19.0 <3.0.0' sdk: '>=2.19.0 <3.0.0'
flutter: ">=1.17.0" flutter: ">=1.20.0"
dependencies: dependencies:
flutter: flutter:
@ -28,21 +28,21 @@ dependencies:
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v4 ref: cake-update-v2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^2.0.1
dependency_overrides: dependency_overrides:
watcher: ^1.1.0 watcher: ^1.1.0
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v15 ref: cake-update-v9
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -111,7 +111,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.zano, CryptoCurrency.zano,
CryptoCurrency.ton, CryptoCurrency.ton,
CryptoCurrency.flip, CryptoCurrency.flip,
CryptoCurrency.deuro CryptoCurrency.deuro,
CryptoCurrency.usdtbsc,
]; ];
static const havenCurrencies = [ static const havenCurrencies = [
@ -233,6 +234,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 96, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12); static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 96, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12);
static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 97, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18); static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 97, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18);
static const deuro = CryptoCurrency(title: 'DEURO', tag: 'ETH', fullName: 'Decentralized Euro', raw: 98, name: 'deuro', iconPath: 'assets/images/deuro_icon.png', decimals: 18); static const deuro = CryptoCurrency(title: 'DEURO', tag: 'ETH', fullName: 'Decentralized Euro', raw: 98, name: 'deuro', iconPath: 'assets/images/deuro_icon.png', decimals: 18);
static const usdtbsc = CryptoCurrency(title: 'USDT', tag: 'BSC', fullName: 'USDT Binance coin', raw: 99, name: 'usdtbsc', iconPath: 'assets/images/usdtbsc_icon.png', decimals: 18);
static final Map<int, CryptoCurrency> _rawCurrencyMap = static final Map<int, CryptoCurrency> _rawCurrencyMap =
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) { [...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {

View file

@ -1,7 +1,7 @@
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http;
// FIXME: Hardcoded values; Works only for monero // FIXME: Hardcoded values; Works only for monero
@ -234,10 +234,14 @@ int getHavenHeightByDate({required DateTime date}) {
} }
Future<int> getHavenCurrentHeight() async { Future<int> getHavenCurrentHeight() async {
final response = await http.get(Uri.parse('https://explorer.havenprotocol.org/api/networkinfo')); final req = await ProxyWrapper().getHttpClient()
.getUrl(Uri.parse('https://explorer.havenprotocol.org/api/networkinfo'))
.timeout(Duration(seconds: 15));
final response = await req.close();
final stringResponse = await response.transform(utf8.decoder).join();
if (response.statusCode == 200) { if (response.statusCode == 200) {
final info = jsonDecode(response.body); final info = jsonDecode(stringResponse);
return info['data']['height'] as int; return info['data']['height'] as int;
} else { } else {
throw Exception('Failed to load current blockchain height'); throw Exception('Failed to load current blockchain height');
@ -269,13 +273,13 @@ const bitcoinDates = {
}; };
Future<int> getBitcoinHeightByDateAPI({required DateTime date}) async { Future<int> getBitcoinHeightByDateAPI({required DateTime date}) async {
final response = await http.get( final req = await ProxyWrapper().getHttpClient()
Uri.parse( .getUrl(Uri.parse("https://mempool.cakewallet.com/api/v1/mining/blocks/timestamp/${(date.millisecondsSinceEpoch / 1000).round()}"))
"https://mempool.cakewallet.com/api/v1/mining/blocks/timestamp/${(date.millisecondsSinceEpoch / 1000).round()}", .timeout(Duration(seconds: 15));
), final response = await req.close();
); final stringResponse = await response.transform(utf8.decoder).join();
return jsonDecode(response.body)['height'] as int; return jsonDecode(stringResponse)['height'] as int;
} }
int getBitcoinHeightByDate({required DateTime date}) { int getBitcoinHeightByDate({required DateTime date}) {

View file

@ -1,12 +1,12 @@
import 'dart:io'; import 'dart:io';
import 'package:cw_core/keyable.dart'; import 'package:cw_core/keyable.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'dart:convert'; import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:hive/hive.dart'; 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 'dart:math' as math; import 'dart:math' as math;
import 'package:convert/convert.dart'; import 'package:convert/convert.dart';
@ -184,23 +184,17 @@ class Node extends HiveObject with Keyable {
final body = {'jsonrpc': '2.0', 'id': '0', 'method': "getinfo"}; final body = {'jsonrpc': '2.0', 'id': '0', 'method': "getinfo"};
try { try {
final authenticatingClient = HttpClient();
authenticatingClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
final http.Client client = ioc.IOClient(authenticatingClient);
final jsonBody = json.encode(body); final jsonBody = json.encode(body);
final response = await client.post( final response = await ProxyWrapper().post(
rpcUri, clearnetUri: rpcUri,
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: jsonBody, body: jsonBody,
); );
printV("node check response: ${response.body}");
final resBody = json.decode(response.body) as Map<String, dynamic>; final resBody = json.decode(response.body) as Map<String, dynamic>;
return resBody['result']['height'] != null; return resBody['result']['height'] != null;
} catch (e) { } catch (e) {
printV("error: $e"); printV("error: $e");
@ -218,11 +212,7 @@ class Node extends HiveObject with Keyable {
final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName}; final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
try { try {
final authenticatingClient = HttpClient(); final client = ProxyWrapper().getHttpIOClient();
authenticatingClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
final http.Client client = ioc.IOClient(authenticatingClient);
final jsonBody = json.encode(body); final jsonBody = json.encode(body);
@ -242,15 +232,15 @@ class Node extends HiveObject with Keyable {
return !(response['offline'] as bool); return !(response['offline'] as bool);
} }
printV("node check response: ${response.body}"); final responseString = await response.body;
if ((response.body.contains("400 Bad Request") // Some other generic error if ((responseString.contains("400 Bad Request") // Some other generic error
|| ||
response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare responseString.contains("plain HTTP request was sent to HTTPS port") // Cloudflare
|| ||
response.headers["location"] != null // Generic reverse proxy response.headers["location"] != null // Generic reverse proxy
|| ||
response.body responseString
.contains("301 Moved Permanently") // Poorly configured generic reverse proxy .contains("301 Moved Permanently") // Poorly configured generic reverse proxy
) && ) &&
!(useSSL ?? false)) { !(useSSL ?? false)) {
@ -277,15 +267,16 @@ class Node extends HiveObject with Keyable {
} }
Future<bool> requestNodeWithProxy() async { Future<bool> requestNodeWithProxy() async {
if (!isValidProxyAddress /* && !Tor.instance.enabled*/) { if (!isValidProxyAddress && !CakeTor.instance.enabled) {
return false; return false;
} }
String? proxy = socksProxyAddress; String? proxy = socksProxyAddress;
// if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) { if ((proxy?.isEmpty ?? true) && CakeTor.instance.enabled) {
// proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; proxy = "${InternetAddress.loopbackIPv4.address}:${CakeTor.instance.port}";
// } }
printV("proxy: $proxy");
if (proxy == null) { if (proxy == null) {
return false; return false;
} }
@ -305,13 +296,9 @@ class Node extends HiveObject with Keyable {
// you try to communicate with it // you try to communicate with it
Future<bool> requestElectrumServer() async { Future<bool> requestElectrumServer() async {
try { try {
final Socket socket; final ProxySocket socket;
if (useSSL == true) { socket = await ProxyWrapper().getSocksSocket(useSSL ?? false, uri.host, uri.port);
socket = await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
} else {
socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
}
socket.destroy(); socket.destroy();
return true; return true;
@ -322,8 +309,8 @@ class Node extends HiveObject with Keyable {
Future<bool> requestNanoNode() async { Future<bool> requestNanoNode() async {
try { try {
final response = await http.post( final response = await ProxyWrapper().post(
uri, clearnetUri: uri,
headers: {"Content-Type": "application/json", "nano-app": "cake-wallet"}, headers: {"Content-Type": "application/json", "nano-app": "cake-wallet"},
body: jsonEncode( body: jsonEncode(
{ {
@ -332,7 +319,8 @@ class Node extends HiveObject with Keyable {
}, },
), ),
); );
final data = await jsonDecode(response.body);
final data = jsonDecode(response.body);
if (response.statusCode != 200 || if (response.statusCode != 200 ||
data["error"] != null || data["error"] != null ||
data["balance"] == null || data["balance"] == null ||
@ -348,13 +336,14 @@ class Node extends HiveObject with Keyable {
Future<bool> requestEthereumServer() async { Future<bool> requestEthereumServer() async {
try { try {
final response = await http.get( final req = await ProxyWrapper().getHttpClient()
uri, .getUrl(uri,)
headers: {'Content-Type': 'application/json'}, .timeout(Duration(seconds: 15));
); final response = await req.close();
return response.statusCode >= 200 && response.statusCode < 300; return response.statusCode >= 200 && response.statusCode < 300;
} catch (_) { } catch (err) {
printV("Failed to request ethereum server: $err");
return false; return false;
} }
} }
@ -462,7 +451,7 @@ class DaemonRpc {
/// Perform a JSON-RPC call with Digest Authentication. /// Perform a JSON-RPC call with Digest Authentication.
Future<Map<String, dynamic>> call(String method, Map<String, dynamic> params) async { Future<Map<String, dynamic>> call(String method, Map<String, dynamic> params) async {
final http.Client client = http.Client(); final client = ProxyWrapper().getHttpIOClient();
final DigestAuth digestAuth = DigestAuth(username, password); final DigestAuth digestAuth = DigestAuth(username, password);
// Initial request to get the `WWW-Authenticate` header. // Initial request to get the `WWW-Authenticate` header.

View file

@ -1,33 +1,25 @@
import 'package:http/http.dart'; import 'dart:convert';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:on_chain/solana/solana.dart'; import 'package:on_chain/solana/solana.dart';
class SolanaRPCHTTPService implements SolanaServiceProvider { class SolanaRPCHTTPService implements SolanaJSONRPCService {
SolanaRPCHTTPService( SolanaRPCHTTPService(
{required this.url, Client? client, this.defaultRequestTimeout = const Duration(seconds: 30)}) {required this.url,
: client = client ?? Client(); this.defaultRequestTimeout = const Duration(seconds: 30)});
@override
final String url; final String url;
final Client client;
final Duration defaultRequestTimeout; final Duration defaultRequestTimeout;
@override Future<Map<String, dynamic>> call(SolanaRequestDetails params,
Future<SolanaServiceResponse<T>> doRequest<T>(SolanaRequestDetails params, [Duration? timeout]) async {
{Duration? timeout}) async { final response = await ProxyWrapper().post(
if (!params.type.isPostRequest) { clearnetUri: Uri.parse(url),
final response = await client.get( body: params.toRequestBody(),
params.toUri(url), headers: {
headers: {'Content-Type': 'application/json'}, 'Content-Type': 'application/json',
},
).timeout(timeout ?? defaultRequestTimeout); ).timeout(timeout ?? defaultRequestTimeout);
return params.toResponse(response.bodyBytes, response.statusCode); final data = json.decode(response.body) as Map<String, dynamic>;
} return data;
final response = await client
.post(
params.toUri(url),
headers: {'Content-Type': 'application/json'},
body: params.body(),
)
.timeout(timeout ?? defaultRequestTimeout);
return params.toResponse(response.bodyBytes, response.statusCode);
} }
} }

View file

@ -0,0 +1,29 @@
import 'dart:typed_data';
import 'package:http/http.dart' as very_insecure_http_do_not_use;
enum RequestNetwork {
clearnet,
tor,
}
enum RequestMethod {
get,
post,
put,
delete,
newHttpClient,
newHttpIOClient,
newProxySocket,
}
abstract class ProxyLogger {
void log({
required Uri? uri,
required RequestMethod method,
required Uint8List body,
required very_insecure_http_do_not_use.Response? response,
required RequestNetwork network,
required String? error,
});
}

View file

@ -0,0 +1,63 @@
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_logger/abstract.dart';
import 'package:http/http.dart' as very_insecure_http_do_not_use;
class MemoryProxyLoggerEntry {
MemoryProxyLoggerEntry({
required this.trace,
required this.uri,
required this.body,
required this.network,
required this.method,
required this.response,
required this.error,
}) : time = DateTime.now();
final StackTrace trace;
final Uri? uri;
final Uint8List body;
final RequestNetwork network;
final very_insecure_http_do_not_use.Response? response;
final RequestMethod method;
final String? error;
final DateTime time;
@override
String toString() => """MemoryProxyLoggerEntry(
uri: $uri,
body: $body,
network: $network,
method: $method,
response:
code: ${response?.statusCode},
headers: ${response?.headers},
body: ${response?.body},
error: $error,
time: $time,
trace: ${trace}
);""";
}
class MemoryProxyLogger implements ProxyLogger {
static List<MemoryProxyLoggerEntry> logs = [];
@override
void log({
required Uri? uri,
required RequestMethod method,
required Uint8List body,
required very_insecure_http_do_not_use.Response? response,
required RequestNetwork network,
required String? error,
}) {
final trace = StackTrace.current;
logs.add(MemoryProxyLoggerEntry(
method: method,
trace: trace,
uri: uri,
body: body,
network: network,
response: response,
error: error,),
);
}
}

View file

@ -0,0 +1,17 @@
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_logger/abstract.dart';
import 'package:http/http.dart' as very_insecure_http_do_not_use;
// we are not doing anything
class SilentProxyLogger implements ProxyLogger {
@override
void log({
required Uri? uri,
required RequestMethod method,
required Uint8List body,
required very_insecure_http_do_not_use.Response? response,
required RequestNetwork network,
required String? error,
}) {}
}

View file

@ -0,0 +1,47 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_socket/insecure.dart';
import 'package:cw_core/utils/proxy_socket/secure.dart';
import 'package:cw_core/utils/proxy_socket/socks.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:socks_socket/socks_socket.dart';
class ProxyAddress {
final String host;
final int port;
ProxyAddress({required this.host, required this.port});
}
abstract class ProxySocket {
static Future<ProxySocket> connect(bool sslEnabled, ProxyAddress address, {Duration? connectionTimeout}) async {
if (CakeTor.instance.started) {
var socksSocket = await SOCKSSocket.create(
proxyHost: InternetAddress.loopbackIPv4.address,
proxyPort: CakeTor.instance.port,
sslEnabled: sslEnabled,
);
await socksSocket.connect();
await socksSocket.connectTo(address.host, address.port);
return ProxySocketSocks(socksSocket);
}
if (sslEnabled == false) {
return ProxySocketInsecure(await Socket.connect(address.host, address.port, timeout: connectionTimeout));
} else {
return ProxySocketSecure(await SecureSocket.connect(
address.host,
address.port,
timeout: connectionTimeout,
onBadCertificate: (_) => true,
));
}
}
Future<void> close();
Future<void> destroy();
Future<void> write(String data);
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function (Object error)? onError, Function ()? onDone, bool cancelOnError = true});
ProxyAddress get address;
}

View file

@ -0,0 +1,34 @@
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'dart:async';
import 'dart:typed_data';
import 'dart:io';
class ProxySocketInsecure implements ProxySocket {
final Socket socket;
ProxySocketInsecure(this.socket);
ProxyAddress get address => ProxyAddress(host: socket.remoteAddress.host, port: socket.remotePort);
@override
Future<void> close() => socket.close();
@override
Future<void> destroy() async => socket.destroy();
@override
Future<void> write(String data) async => socket.write(data);
@override
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) {
return socket.listen(
(data) {
onData(Uint8List.fromList(data));
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
}

View file

@ -0,0 +1,34 @@
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
class ProxySocketSecure implements ProxySocket {
final SecureSocket socket;
ProxySocketSecure(this.socket);
ProxyAddress get address => ProxyAddress(host: socket.remoteAddress.host, port: socket.remotePort);
@override
Future<void> close() => socket.close();
@override
Future<void> destroy() async => socket.destroy();
@override
Future<void> write(String data) async => socket.write(data);
@override
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) {
return socket.listen(
(data) {
onData(Uint8List.fromList(data));
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
}

View file

@ -0,0 +1,36 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:socks_socket/socks_socket.dart';
class ProxySocketSocks implements ProxySocket {
final SOCKSSocket socket;
ProxySocketSocks(this.socket);
@override
ProxyAddress get address => ProxyAddress(host: socket.proxyHost, port: socket.proxyPort);
@override
Future<void> close() => socket.close();
@override
Future<void> destroy() => close();
@override
Future<void> write(String data) async => socket.write(data);
@override
StreamSubscription<List<int>> listen(Function(Uint8List event) onData, {Function(Object error)? onError, Function()? onDone, bool cancelOnError = true}) {
return socket.listen(
(data) {
onData(Uint8List.fromList(data));
},
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
}

View file

@ -0,0 +1,447 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:cw_core/utils/proxy_logger/abstract.dart';
import 'package:cw_core/utils/proxy_socket/abstract.dart';
import 'package:cw_core/utils/tor/abstract.dart';
import 'package:cw_core/utils/tor/android.dart';
import 'package:cw_core/utils/tor/disabled.dart';
import 'package:http/http.dart';
import 'package:socks5_proxy/socks_client.dart';
import 'package:http/io_client.dart' as ioc;
class ProxyWrapper {
static final ProxyWrapper _proxyWrapper = ProxyWrapper._internal();
static ProxyLogger? logger;
factory ProxyWrapper() {
return _proxyWrapper;
}
ProxyWrapper._internal();
Future<ProxySocket> getSocksSocket(bool sslEnabled, String host, int port, {Duration? connectionTimeout}) async {
logger?.log(
uri: Uri(
scheme: sslEnabled ? "https" : "http",
host: host,
port: port,
),
method: RequestMethod.newProxySocket,
body: Uint8List(0),
response: null,
network: requestNetwork(),
error: null
);
return ProxySocket.connect(sslEnabled, ProxyAddress(host: host, port: port), connectionTimeout: connectionTimeout);
}
RequestNetwork requestNetwork() {
return CakeTor.instance.started ? RequestNetwork.tor : RequestNetwork.clearnet;
}
ioc.IOClient getHttpIOClient({int? portOverride, bool internal = false}) {
if (!internal) {
logger?.log(
uri: null,
method: RequestMethod.newHttpIOClient,
body: Uint8List(0),
response: null,
network: requestNetwork(),
error: null,
);
}
// ignore: deprecated_member_use_from_same_package
final httpClient = ProxyWrapper().getHttpClient(portOverride: portOverride, internal: true);
return ioc.IOClient(httpClient);
}
int getPort() => CakeTor.instance.port;
@Deprecated('Use ProxyWrapper().get/post/put methods instead, and provide proper clearnet and onion uri.')
HttpClient getHttpClient({int? portOverride, bool internal = false}) {
if (!internal) {
logger?.log(
uri: null,
method: RequestMethod.newProxySocket,
body: Uint8List(0),
response: null,
network: requestNetwork(),
error: null
);
}
if (CakeTor.instance.started) {
// Assign connection factory.
final client = HttpClient();
SocksTCPClient.assignToHttpClient(client, [
ProxySettings(
InternetAddress.loopbackIPv4,
CakeTor.instance.port,
password: null,
),
]);
return client;
} else {
return HttpClient();
}
}
Future<Response> _make({
required RequestMethod method,
required ioc.IOClient client,
required Uri uri,
required Map<String, String>? headers,
String? body,
}) async {
Object? error;
Response? resp;
try {
switch (method) {
case RequestMethod.get:
resp = await client. get(
uri,
headers: headers,
);
break;
case RequestMethod.delete:
resp = await client.delete(
uri,
headers: headers,
body: body,
);
break;
case RequestMethod.post:
resp = await client.post(
uri,
headers: headers,
body: body,
);
break;
case RequestMethod.put:
resp = await client.put(
uri,
headers: headers,
body: body,
);
break;
case RequestMethod.newHttpClient:
case RequestMethod.newHttpIOClient:
case RequestMethod.newProxySocket:
throw UnimplementedError();
}
return resp;
} catch (e) {
error = e;
rethrow;
} finally {
logger?.log(
uri: uri,
method: RequestMethod.get,
body: utf8.encode(body ?? ''),
response: resp,
network: requestNetwork(),
error: error?.toString(),
);
}
}
Future<Response> get({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
}) async {
ioc.IOClient? torClient;
bool torEnabled = CakeTor.instance.started;
if (CakeTor.instance.started) {
torEnabled = true;
} else {
torEnabled = false;
}
// if tor is enabled, try to connect to the onion url first:
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
} catch (_) {
rethrow;
}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.get,
client: torClient,
uri: onionUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.get,
client: torClient,
uri: clearnetUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.get,
client: ioc.IOClient(),
uri: clearnetUri,
headers: headers,
);
},
);
} catch (_) {
// we weren't able to get a response:
rethrow;
}
}
throw Exception("Unable to connect to server");
}
Future<Response> post({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
String? body,
bool allowMitmMoneroBypassSSLCheck = false,
}) async {
HttpClient? torHttpClient;
HttpClient cleatnetHttpClient = HttpClient();
if (allowMitmMoneroBypassSSLCheck) {
cleatnetHttpClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
}
ioc.IOClient clearnetClient = ioc.IOClient(cleatnetHttpClient);
bool torEnabled = CakeTor.instance.started;
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torHttpClient = await getHttpClient(portOverride: portOverride);
} catch (_) {
rethrow;
}
if (allowMitmMoneroBypassSSLCheck) {
torHttpClient.badCertificateCallback =
((X509Certificate cert, String host, int port) => true);
}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.post,
client: ioc.IOClient(torHttpClient),
uri: onionUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.post,
client: ioc.IOClient(torHttpClient),
uri: clearnetUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.post,
client: clearnetClient,
uri: clearnetUri,
headers: headers,
body: body,
);
},
);
} catch (_) {
rethrow;
}
}
throw Exception("Unable to connect to server");
}
Future<Response> put({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
String? body,
}) async {
ioc.IOClient? torClient;
bool torEnabled = CakeTor.instance.started;
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
} catch (_) {}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.put,
client: torClient!,
uri: onionUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.put,
client: torClient!,
uri: clearnetUri,
headers: headers,
body: body,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.put,
client: ioc.IOClient(),
uri: clearnetUri,
headers: headers,
body: body,
);
},
);
} catch (_) {
// we weren't able to get a response:
rethrow;
}
}
throw Exception("Unable to connect to server");
}
Future<Response> delete({
Map<String, String>? headers,
int? portOverride,
Uri? clearnetUri,
Uri? onionUri,
}) async {
ioc.IOClient? torClient;
bool torEnabled = CakeTor.instance.started;
if (CakeTor.instance.started) {
torEnabled = true;
} else {
torEnabled = false;
}
// if tor is enabled, try to connect to the onion url first:
if (torEnabled) {
try {
// ignore: deprecated_member_use_from_same_package
torClient = await getHttpIOClient(portOverride: portOverride, internal: true);
} catch (_) {
rethrow;
}
if (onionUri != null) {
try {
return await _make(
method: RequestMethod.delete,
client: torClient,
uri: onionUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
if (clearnetUri != null) {
try {
return await _make(
method: RequestMethod.delete,
client: torClient,
uri: clearnetUri,
headers: headers,
);
} catch (_) {
rethrow;
}
}
}
if (clearnetUri != null) {
try {
return HttpOverrides.runZoned(
() async {
return await _make(
method: RequestMethod.delete,
client: ioc.IOClient(),
uri: clearnetUri,
headers: headers,
);
},
);
} catch (_) {
// we weren't able to get a response:
rethrow;
}
}
throw Exception("Unable to connect to server");
}
}
class CakeTor {
static final CakeTorInstance instance = CakeTorInstance.getInstance();
}

View file

@ -0,0 +1,38 @@
import 'dart:io';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/tor/android.dart';
import 'package:cw_core/utils/tor/disabled.dart';
import 'package:cw_core/utils/tor/tails.dart';
abstract class CakeTorInstance {
bool get started;
int get port => -1;
bool get enabled => false;
bool get bootstrapped => false;
Future<void> start();
Future<void> stop();
static CakeTorInstance getInstance() {
if (Platform.isAndroid) {
return CakeTorAndroid();
}
if (Platform.isLinux) {
try {
final os = File("/etc/os-release").readAsLinesSync();
for (var line in os) {
if (!line.startsWith("ID=")) continue;
if (!line.contains("tails")) continue;
return CakeTorTails();
}
} catch (e) {
printV("Failed to identify linux version - /etc/os-release missing");
}
}
return CakeTorDisabled();
}
}

View file

@ -0,0 +1,73 @@
import 'dart:convert';
import 'dart:io';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/tor/abstract.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:tor_binary/tor_binary_platform_interface.dart';
class CakeTorAndroid implements CakeTorInstance {
@override
bool get bootstrapped => _proc != null;
@override
bool get enabled => _proc != null;
@override
int get port => 42142;
@override
Future<void> start() async {
await _runEmbeddedTor();
}
@override
bool get started => _proc != null;
@override
Future<void> stop() async {
_proc?.kill();
await _proc?.exitCode;
_proc = null;
}
static Process? _proc;
Future<void> _runEmbeddedTor() async {
final dir = await getApplicationCacheDirectory();
final torBinPath = p.join((await TorBinaryPlatform.instance.getBinaryPath())!, "libtor.so");
printV("torPath: $torBinPath");
if (started) {
printV("Proxy is running");
return;
}
printV("Starting embedded tor");
printV("app docs: $dir");
final torrc = """
SocksPort $port
Log notice file ${p.join(dir.path, "tor.log")}
RunAsDaemon 0
DataDirectory ${p.join(dir.path, "tor-data")}
""";
final torrcPath = p.join(dir.absolute.path, "torrc");
File(torrcPath).writeAsStringSync(torrc);
if (_proc != null) {
try {
_proc?.kill();
await _proc?.exitCode;
_proc = null;
} catch (e) {
printV(e);
}
}
printV("path: $torBinPath -f $torrcPath");
_proc = await Process.start(torBinPath, ["-f", torrcPath]);
_proc?.stdout.transform(utf8.decoder).forEach(printV);
_proc?.stderr.transform(utf8.decoder).forEach(printV);
}
}

View file

@ -0,0 +1,21 @@
import 'package:cw_core/utils/tor/abstract.dart';
class CakeTorDisabled implements CakeTorInstance {
@override
bool get bootstrapped => false;
@override
bool get enabled => false;
@override
int get port => -1;
@override
Future<void> start() => throw UnimplementedError();
@override
bool get started => false;
@override
Future<void> stop() => throw UnimplementedError();
}

View file

@ -0,0 +1,21 @@
import 'package:cw_core/utils/tor/abstract.dart';
class CakeTorTails implements CakeTorInstance {
@override
bool get bootstrapped => true;
@override
bool get enabled => true;
@override
int get port => 9150;
@override
Future<void> start() async {}
@override
bool get started => true;
@override
Future<void> stop() async {}
}

View file

@ -26,18 +26,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.7.0"
asn1lib: asn1lib:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5" sha256: "1c296cd268f486cabcc3930e9b93a8133169305f18d722916e675959a88f6d2c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.8" version: "1.5.9"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -50,11 +50,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a" resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils" url: "https://github.com/cake-tech/blockchain_utils"
source: git source: git
version: "4.3.0" version: "3.3.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -67,50 +67,50 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.4"
build_resolvers: build_resolvers:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_resolvers name: build_resolvers
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.4.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.13" version: "2.4.15"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.3.2" version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -123,10 +123,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.4" version: "8.10.1"
cake_backup: cake_backup:
dependency: "direct main" dependency: "direct main"
description: description:
@ -212,10 +212,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.7" version: "2.3.8"
decimal: decimal:
dependency: "direct main" dependency: "direct main"
description: description:
@ -326,10 +326,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -342,10 +342,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.1.2"
intl: intl:
dependency: "direct main" dependency: "direct main"
description: description:
@ -462,10 +462,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: mobx_codegen name: mobx_codegen
sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc" sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.7.0" version: "2.7.1"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -478,19 +478,19 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273"
url: "https://github.com/cake-tech/on_chain.git" url: "https://github.com/cake-tech/on_chain.git"
source: git source: git
version: "6.2.0" version: "3.7.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
name: package_config name: package_config
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.2.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -511,10 +511,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.15" version: "2.2.17"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -583,26 +583,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.5"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.2.0"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
name: pubspec_parse name: pubspec_parse
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.5.0"
rational: rational:
dependency: transitive dependency: transitive
description: description:
@ -615,18 +615,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.2"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -635,11 +635,21 @@ packages:
socks5_proxy: socks5_proxy:
dependency: "direct main" dependency: "direct main"
description: description:
name: socks5_proxy path: "."
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://pub.dev" resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
source: hosted url: "https://github.com/LacticWhale/socks_dart"
version: "1.0.6" source: git
version: "2.1.0"
socks_socket:
dependency: "direct main"
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
@ -716,10 +726,19 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: timing name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.1"
tor_binary:
dependency: "direct main"
description:
path: "."
ref: cb811c610871a9517d47134b87c2f590c15c96c5
resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5
url: "https://github.com/MrCyjaneK/flutter-tor_binary"
source: git
version: "4.7.14"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:
@ -780,18 +799,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web_socket name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.6" version: "1.0.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -809,5 +828,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.27.0"

View file

@ -25,22 +25,29 @@ dependencies:
url: https://github.com/cake-tech/cake_backup.git url: https://github.com/cake-tech/cake_backup.git
ref: main ref: main
version: 1.0.0 version: 1.0.0
socks5_proxy: ^1.0.4 socks5_proxy:
git:
url: https://github.com/LacticWhale/socks_dart
ref: 27ad7c2efae8d7460325c74b90f660085cbd0685
unorm_dart: ^0.3.0 unorm_dart: ^0.3.0
on_chain: on_chain:
git: git:
url: https://github.com/cake-tech/on_chain.git url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v4 ref: cake-update-v2
# tor: socks_socket:
# git: git:
# url: https://github.com/cake-tech/tor.git url: https://github.com/sneurlax/socks_socket
# ref: main ref: e6232c53c1595469931ababa878759a067c02e94
tor_binary:
git:
url: https://github.com/MrCyjaneK/flutter-tor_binary
ref: cb811c610871a9517d47134b87c2f590c15c96c5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
build_resolvers: ^2.0.9 build_resolvers: ^2.4.4
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^2.0.1 hive_generator: ^2.0.1

View file

@ -5,6 +5,8 @@ import 'package:path/path.dart' as p;
import 'package:cw_core/exceptions.dart'; import 'package:cw_core/exceptions.dart';
import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_decred/amount_format.dart'; import 'package:cw_decred/amount_format.dart';
import 'package:cw_decred/pending_transaction.dart'; import 'package:cw_decred/pending_transaction.dart';
import 'package:cw_decred/transaction_credentials.dart'; import 'package:cw_decred/transaction_credentials.dart';
@ -307,9 +309,10 @@ abstract class DecredWalletBase
persistantPeer = addr; persistantPeer = addr;
await _libwallet.closeWallet(walletInfo.name); await _libwallet.closeWallet(walletInfo.name);
final network = isTestnet ? "testnet" : "mainnet"; final network = isTestnet ? "testnet" : "mainnet";
final dirPath = await pathForWalletDir(name: walletInfo.name, type: WalletType.decred);
final config = { final config = {
"name": walletInfo.name, "name": walletInfo.name,
"datadir": walletInfo.dirPath, "datadir": dirPath,
"net": network, "net": network,
"unsyncedaddrs": true, "unsyncedaddrs": true,
}; };
@ -611,7 +614,7 @@ abstract class DecredWalletBase
} }
await for (final entity in sourceDir.list(recursive: true)) { await for (final entity in sourceDir.list(recursive: true)) {
final relativePath = entity.path.substring(sourceDir.path.length+1); final relativePath = entity.path.substring(sourceDir.path.length + 1);
final targetPath = p.join(targetDir.path, relativePath); final targetPath = p.join(targetDir.path, relativePath);
if (entity is File) { if (entity is File) {

View file

@ -8,6 +8,7 @@ import 'package:cw_core/wallet_service.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/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:path/path.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
@ -57,42 +58,93 @@ class DecredWalletService extends WalletService<
@override @override
Future<DecredWallet> create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async { Future<DecredWallet> create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async {
await this.init(); await this.init();
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
final network = isTestnet == true ? testnet : mainnet;
final config = { final config = {
"name": credentials.walletInfo!.name, "name": credentials.walletInfo!.name,
"datadir": credentials.walletInfo!.dirPath, "datadir": dirPath,
"pass": credentials.password!, "pass": credentials.password!,
"net": isTestnet == true ? testnet : mainnet, "net": network,
"unsyncedaddrs": true, "unsyncedaddrs": true,
}; };
await libwallet!.createWallet(jsonEncode(config)); await libwallet!.createWallet(jsonEncode(config));
final di = DerivationInfo( final di = DerivationInfo(
derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath); derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
credentials.walletInfo!.derivationInfo = di; credentials.walletInfo!.derivationInfo = di;
credentials.walletInfo!.network = network;
// ios will move our wallet directory when updating. Since we must
// recalculate the new path every time we open the wallet, ensure this path
// is not used. An older wallet will have a directory here which is a
// condition for moving the wallet when opening, so this must be kept blank
// going forward.
credentials.walletInfo!.dirPath = "";
credentials.walletInfo!.path = "";
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
this.unspentCoinsInfoSource, libwallet!, closeLibwallet); this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
await wallet.init(); await wallet.init();
return wallet; return wallet;
} }
void copyDirectorySync(Directory source, Directory destination) {
/// create destination folder if not exist
if (!destination.existsSync()) {
destination.createSync(recursive: true);
}
/// get all files from source (recursive: false is important here)
source.listSync(recursive: false).forEach((entity) {
final newPath = destination.path + Platform.pathSeparator + basename(entity.path);
if (entity is File) {
entity.rename(newPath);
} else if (entity is Directory) {
copyDirectorySync(entity, Directory(newPath));
}
});
}
Future<void> moveWallet(String fromPath, String toPath) async {
final oldWalletDir = new Directory(fromPath);
final newWalletDir = new Directory(toPath);
copyDirectorySync(oldWalletDir, newWalletDir);
// It would be ideal to delete the old directory here, but ios will error
// sometimes with "OS Error: No such file or directory, errno = 2" even
// after checking if it exists.
}
@override @override
Future<DecredWallet> openWallet(String name, String password) async { Future<DecredWallet> openWallet(String name, String password) async {
final walletInfo = walletInfoSource.values final walletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
final network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet || if (walletInfo.network == null || walletInfo.network == "") {
walletInfo.network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet ||
walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet
? testnet ? testnet
: mainnet; : mainnet;
}
await this.init(); await this.init();
final walletDirExists = Directory(walletInfo.dirPath).existsSync();
if (!walletDirExists) { // Cake wallet version 4.27.0 and earlier gave a wallet dir that did not
walletInfo.dirPath = await pathForWalletDir(name: name, type: getType()); // match the name. Move those to the correct place.
final dirPath = await pathForWalletDir(name: name, type: getType());
if (walletInfo.path != "") {
// On ios the stored dir no longer exists. We can only trust the basename.
// dirPath may already be updated and lost the basename, so look at path.
final randomBasename = basename(walletInfo.path);
final oldDir = await pathForWalletDir(name: randomBasename, type: getType());
if (oldDir != dirPath) {
await this.moveWallet(oldDir, dirPath);
}
// Clear the path so this does not trigger again.
walletInfo.dirPath = "";
walletInfo.path = "";
await walletInfo.save();
} }
final config = { final config = {
"name": walletInfo.name, "name": name,
"datadir": walletInfo.dirPath, "datadir": dirPath,
"net": network, "net": walletInfo.network,
"unsyncedaddrs": true, "unsyncedaddrs": true,
}; };
await libwallet!.loadWallet(jsonEncode(config)); await libwallet!.loadWallet(jsonEncode(config));
@ -127,12 +179,11 @@ class DecredWalletService extends WalletService<
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
final newDirPath = await pathForWalletDir(name: newName, type: getType());
final newWalletInfo = currentWalletInfo; final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType()); newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName; newWalletInfo.name = newName;
newWalletInfo.dirPath = newDirPath; newWalletInfo.dirPath = "";
newWalletInfo.network = network; newWalletInfo.path = "";
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
} }
@ -141,18 +192,23 @@ class DecredWalletService extends WalletService<
Future<DecredWallet> restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials, Future<DecredWallet> restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async { {bool? isTestnet}) async {
await this.init(); await this.init();
final network = isTestnet == true ? testnet : mainnet;
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
final config = { final config = {
"name": credentials.walletInfo!.name, "name": credentials.walletInfo!.name,
"datadir": credentials.walletInfo!.dirPath, "datadir": dirPath,
"pass": credentials.password!, "pass": credentials.password!,
"mnemonic": credentials.mnemonic, "mnemonic": credentials.mnemonic,
"net": isTestnet == true ? testnet : mainnet, "net": network,
"unsyncedaddrs": true, "unsyncedaddrs": true,
}; };
await libwallet!.createWallet(jsonEncode(config)); await libwallet!.createWallet(jsonEncode(config));
final di = DerivationInfo( final di = DerivationInfo(
derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath); derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
credentials.walletInfo!.derivationInfo = di; credentials.walletInfo!.derivationInfo = di;
credentials.walletInfo!.network = network;
credentials.walletInfo!.dirPath = "";
credentials.walletInfo!.path = "";
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
this.unspentCoinsInfoSource, libwallet!, closeLibwallet); this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
await wallet.init(); await wallet.init();
@ -165,17 +221,22 @@ class DecredWalletService extends WalletService<
Future<DecredWallet> restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials, Future<DecredWallet> restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials,
{bool? isTestnet}) async { {bool? isTestnet}) async {
await this.init(); await this.init();
final network = isTestnet == true ? testnet : mainnet;
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
final config = { final config = {
"name": credentials.walletInfo!.name, "name": credentials.walletInfo!.name,
"datadir": credentials.walletInfo!.dirPath, "datadir": dirPath,
"pubkey": credentials.pubkey, "pubkey": credentials.pubkey,
"net": isTestnet == true ? testnet : mainnet, "net": network,
"unsyncedaddrs": true, "unsyncedaddrs": true,
}; };
await libwallet!.createWatchOnlyWallet(jsonEncode(config)); await libwallet!.createWatchOnlyWallet(jsonEncode(config));
final di = DerivationInfo( final di = DerivationInfo(
derivationPath: isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath); derivationPath: isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath);
credentials.walletInfo!.derivationInfo = di; credentials.walletInfo!.derivationInfo = di;
credentials.walletInfo!.network = network;
credentials.walletInfo!.dirPath = "";
credentials.walletInfo!.path = "";
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
this.unspentCoinsInfoSource, libwallet!, closeLibwallet); this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
await wallet.init(); await wallet.init();

View file

@ -50,11 +50,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a" resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils" url: "https://github.com/cake-tech/blockchain_utils"
source: git source: git
version: "4.3.0" version: "3.3.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -91,26 +91,26 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_resolvers name: build_resolvers
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.2" version: "2.4.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.13" version: "2.4.15"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.3.2" version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -501,11 +501,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
url: "https://github.com/cake-tech/on_chain.git" url: "https://github.com/cake-tech/on_chain.git"
source: git source: git
version: "6.2.0" version: "3.7.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -666,11 +666,21 @@ packages:
socks5_proxy: socks5_proxy:
dependency: transitive dependency: transitive
description: description:
name: socks5_proxy path: "."
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://pub.dev" resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
source: hosted url: "https://github.com/LacticWhale/socks_dart"
version: "1.0.6" source: git
version: "2.1.0"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
@ -751,6 +761,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
tor_binary:
dependency: transitive
description:
path: "."
ref: cb811c610871a9517d47134b87c2f590c15c96c5
resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5
url: "https://github.com/MrCyjaneK/flutter-tor_binary"
source: git
version: "4.7.14"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:
@ -848,5 +867,5 @@ packages:
source: hosted source: hosted
version: "2.2.2" version: "2.2.2"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.24.0"

View file

@ -19,8 +19,8 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.1.11 build_runner: ^2.4.15
build_resolvers: ^2.0.9 build_resolvers: ^2.4.4
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^2.0.1 hive_generator: ^2.0.1
ffigen: ^16.1.0 ffigen: ^16.1.0

View file

@ -0,0 +1,170 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_ethereum/deuro/deuro_savings_contract.dart';
import 'package:cw_ethereum/ethereum_wallet.dart';
import 'package:cw_evm/contract/erc20.dart';
import 'package:cw_evm/evm_chain_exceptions.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
const String savingsGatewayAddress = "0x073493d73258C4BEb6542e8dd3e1b2891C972303";
const String dEuroAddress = "0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea";
const String frontendCode = "0x00000000000000000000000000000000000000000043616b652057616c6c6574";
class DEuro {
final SavingsGateway _savingsGateway;
final ERC20 _dEuro;
final EthereumWallet _wallet;
DEuro(EthereumWallet wallet)
: _wallet = wallet,
_savingsGateway = _getSavingsGateway(wallet.getWeb3Client()!),
_dEuro = _getDEuroToken(wallet.getWeb3Client()!);
static SavingsGateway _getSavingsGateway(Web3Client client) => SavingsGateway(
address: EthereumAddress.fromHex(savingsGatewayAddress),
client: client,
);
static ERC20 _getDEuroToken(Web3Client client) => ERC20(
address: EthereumAddress.fromHex(dEuroAddress),
client: client,
);
EthereumAddress get _address => EthereumAddress.fromHex(_wallet.walletAddresses.primaryAddress);
Future<BigInt> get savingsBalance async =>
(await _savingsGateway.savings(accountOwner: _address)).saved;
Future<BigInt> get accruedInterest => _savingsGateway.accruedInterest(accountOwner: _address);
Future<BigInt> get interestRate => _savingsGateway.currentRatePPM();
Future<BigInt> get approvedBalance => _dEuro.allowance(_address, _savingsGateway.self.address);
Future<void> _checkEthBalanceForGasFees(EVMChainTransactionPriority priority) async {
final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address);
final currentBalance = ethBalance.getInWei;
final gasFeesModel = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: BigInt.zero,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[17]
.encodeCall([BigInt.zero, hexToBytes(frontendCode)]),
);
final estimatedGasFee = BigInt.from(gasFeesModel.estimatedGasFee);
final requiredBalance = estimatedGasFee;
if (currentBalance < requiredBalance) {
throw DeuroGasFeeException(
requiredGasFee: requiredBalance,
currentBalance: currentBalance,
);
}
}
Future<PendingEVMChainTransaction> depositSavings(
BigInt amount, EVMChainTransactionPriority priority) async {
try {
await _checkEthBalanceForGasFees(priority);
final signedTransaction = await _savingsGateway.save(
(amount: amount, frontendCode: hexToBytes(frontendCode)),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[17].encodeCall([amount, hexToBytes(frontendCode)]),
);
final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: BigInt.from(fee.estimatedGasFee),
amount: amount.toString(),
exponent: 18,
);
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address);
throw DeuroGasFeeException(
currentBalance: ethBalance.getInWei,
);
}
rethrow;
}
}
Future<PendingEVMChainTransaction> withdrawSavings(
BigInt amount, EVMChainTransactionPriority priority) async {
try {
await _checkEthBalanceForGasFees(priority);
final signedTransaction = await _savingsGateway.withdraw(
(target: _address, amount: amount, frontendCode: hexToBytes(frontendCode)),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[17].encodeCall([amount, hexToBytes(frontendCode)]),
);
final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: BigInt.from(fee.estimatedGasFee),
amount: amount.toString(),
exponent: 18);
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address);
throw DeuroGasFeeException(
currentBalance: ethBalance.getInWei,
);
}
rethrow;
}
}
// Set an infinite approval to save gas in the future
Future<PendingEVMChainTransaction> enableSavings(EVMChainTransactionPriority priority) async {
try {
await _checkEthBalanceForGasFees(priority);
return (await _wallet.createApprovalTransaction(
BigInt.parse(
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
radix: 16,
),
_savingsGateway.self.address.hexEip55,
CryptoCurrency.deuro,
priority,
)) as PendingEVMChainTransaction;
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address);
throw DeuroGasFeeException(
currentBalance: ethBalance.getInWei,
);
}
rethrow;
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -19,7 +19,7 @@ class EthereumClient extends EVMChainClient {
Future<List<EVMChainTransactionModel>> fetchTransactions(String address, Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async { {String? contractAddress}) async {
try { try {
final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
"chainid": "$chainId", "chainid": "$chainId",
"module": "account", "module": "account",
"action": contractAddress != null ? "tokentx" : "txlist", "action": contractAddress != null ? "tokentx" : "txlist",
@ -51,7 +51,7 @@ class EthereumClient extends EVMChainClient {
@override @override
Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async { Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
try { try {
final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
"chainid": "$chainId", "chainid": "$chainId",
"module": "account", "module": "account",
"action": "txlistinternal", "action": "txlistinternal",

View file

@ -6,7 +6,7 @@ author: Cake Wallet
homepage: https://cakewallet.com homepage: https://cakewallet.com
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: ^3.5.0
flutter: ">=1.17.0" flutter: ">=1.17.0"
dependencies: dependencies:
@ -29,7 +29,7 @@ dependency_overrides:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
flutter: flutter:
# assets: # assets:

View file

@ -5,6 +5,7 @@ import 'dart:developer';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_erc20_balance.dart'; import 'package:cw_evm/evm_erc20_balance.dart';
@ -12,13 +13,12 @@ import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets; import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hex/hex.dart' as hex; import 'package:hex/hex.dart' as hex;
import 'package:http/http.dart';
import 'package:web3dart/web3dart.dart'; import 'package:web3dart/web3dart.dart';
import 'contract/erc20.dart'; import 'contract/erc20.dart';
abstract class EVMChainClient { abstract class EVMChainClient {
final httpClient = Client(); late final client = ProxyWrapper().getHttpIOClient();
Web3Client? _client; Web3Client? _client;
//! To be overridden by all child classes //! To be overridden by all child classes
@ -47,7 +47,7 @@ abstract class EVMChainClient {
} }
_client = _client =
Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), httpClient); Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client);
return true; return true;
} catch (e) { } catch (e) {
@ -101,6 +101,7 @@ abstract class EVMChainClient {
String? contractAddress, String? contractAddress,
EtherAmount? gasPrice, EtherAmount? gasPrice,
EtherAmount? maxFeePerGas, EtherAmount? maxFeePerGas,
Uint8List? data,
}) async { }) async {
try { try {
if (contractAddress == null) { if (contractAddress == null) {
@ -108,7 +109,6 @@ abstract class EVMChainClient {
sender: senderAddress, sender: senderAddress,
to: toAddress, to: toAddress,
value: value, value: value,
// maxFeePerGas: maxFeePerGas,
); );
return estimatedGas.toInt(); return estimatedGas.toInt();
@ -124,7 +124,7 @@ abstract class EVMChainClient {
final gasEstimate = await _client!.estimateGas( final gasEstimate = await _client!.estimateGas(
sender: senderAddress, sender: senderAddress,
to: EthereumAddress.fromHex(contractAddress), to: EthereumAddress.fromHex(contractAddress),
data: transfer.encodeCall([ data: data ?? transfer.encodeCall([
toAddress, toAddress,
value.getInWei, value.getInWei,
]), ]),
@ -137,6 +137,21 @@ abstract class EVMChainClient {
} }
} }
Uint8List getEncodedDataForApprovalTransaction({
required EthereumAddress toAddress,
required EtherAmount value,
required EthereumAddress contractAddress,
}) {
final contract = DeployedContract(ethereumContractAbi, contractAddress);
final approve = contract.function('approve');
return approve.encodeCall([
toAddress,
value.getInWei,
]);
}
Future<PendingEVMChainTransaction> signTransaction({ Future<PendingEVMChainTransaction> signTransaction({
required Credentials privateKey, required Credentials privateKey,
required String toAddress, required String toAddress,
@ -149,6 +164,7 @@ abstract class EVMChainClient {
required int exponent, required int exponent,
String? contractAddress, String? contractAddress,
String? data, String? data,
int? gasPrice,
}) async { }) async {
assert(currency == CryptoCurrency.eth || assert(currency == CryptoCurrency.eth ||
currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.maticpoly ||
@ -164,6 +180,7 @@ abstract class EVMChainClient {
data: data != null ? hexToBytes(data) : null, data: data != null ? hexToBytes(data) : null,
maxGas: estimatedGasUnits, maxGas: estimatedGasUnits,
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas), maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
gasPrice: gasPrice != null ? EtherAmount.fromInt(EtherUnit.wei, gasPrice) : null,
); );
Uint8List signedTransaction; Uint8List signedTransaction;
@ -198,6 +215,52 @@ abstract class EVMChainClient {
); );
} }
Future<PendingEVMChainTransaction> signApprovalTransaction({
required Credentials privateKey,
required String spender,
required BigInt amount,
required BigInt gasFee,
required int estimatedGasUnits,
required int maxFeePerGas,
required EVMChainTransactionPriority priority,
required int exponent,
required String contractAddress,
int? gasPrice,
}) async {
final Transaction transaction = createTransaction(
from: privateKey.address,
to: EthereumAddress.fromHex(contractAddress),
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
amount: EtherAmount.zero(),
maxGas: estimatedGasUnits,
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
gasPrice: gasPrice != null ? EtherAmount.fromInt(EtherUnit.wei, gasPrice) : null,
);
final erc20 = ERC20(
client: _client!,
address: EthereumAddress.fromHex(contractAddress),
chainId: chainId,
);
final signedTransaction = await erc20.approve(
EthereumAddress.fromHex(spender),
amount,
credentials: privateKey,
transaction: transaction,
);
return PendingEVMChainTransaction(
signedTransaction: prepareSignedTransactionForSending(signedTransaction),
amount: amount.toString(),
fee: gasFee,
sendTransaction: () => sendTransaction(signedTransaction),
exponent: exponent,
isInfiniteApproval: amount.toRadixString(16) == 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
);
}
Transaction createTransaction({ Transaction createTransaction({
required EthereumAddress from, required EthereumAddress from,
required EthereumAddress to, required EthereumAddress to,
@ -293,7 +356,7 @@ abstract class EVMChainClient {
}, },
); );
final response = await httpClient.get( final response = await client.get(
uri, uri,
headers: { headers: {
"Accept": "application/json", "Accept": "application/json",

View file

@ -22,3 +22,32 @@ class EVMChainTransactionFeesException implements Exception {
@override @override
String toString() => exceptionMessage; String toString() => exceptionMessage;
} }
class DeuroGasFeeException implements Exception {
final String exceptionMessage;
final BigInt? requiredGasFee;
final BigInt? currentBalance;
DeuroGasFeeException({
this.requiredGasFee,
this.currentBalance,
}) : exceptionMessage = _buildMessage(requiredGasFee, currentBalance);
static String _buildMessage(BigInt? requiredGasFee, BigInt? currentBalance) {
const baseMessage = 'Insufficient ETH for gas fees.';
const addEthMessage = ' Please add ETH to your wallet to cover transaction fees.';
if (requiredGasFee != null) {
final requiredEth = (requiredGasFee / BigInt.from(10).pow(18)).toStringAsFixed(8);
final balanceInfo = currentBalance != null
? ', Available: ${(currentBalance / BigInt.from(10).pow(18)).toStringAsFixed(8)} ETH'
: '';
return '$baseMessage Required: ~$requiredEth ETH$balanceInfo.$addEthMessage';
}
return '$baseMessage$addEthMessage';
}
@override
String toString() => exceptionMessage;
}

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:bip32/bip32.dart' as bip32; import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
@ -197,8 +198,8 @@ abstract class EVMChainWalletBase
for (var token in erc20Currencies) { for (var token in erc20Currencies) {
bool isPotentialScam = false; bool isPotentialScam = false;
bool isWhitelisted = bool isWhitelisted = getDefaultTokenContractAddresses
getDefaultTokenContractAddresses.any((element) => element == token.contractAddress); .any((element) => element.toLowerCase() == token.contractAddress.toLowerCase());
final tokenSymbol = token.title.toUpperCase(); final tokenSymbol = token.title.toUpperCase();
@ -213,6 +214,16 @@ abstract class EVMChainWalletBase
token.iconPath = null; token.iconPath = null;
await token.save(); await token.save();
} }
// For fixing wrongly classified tokens
if (!isPotentialScam && token.isPotentialScam) {
token.isPotentialScam = false;
final iconPath = CryptoCurrency.all
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
token.iconPath = iconPath;
await token.save();
}
} }
} }
@ -255,36 +266,72 @@ abstract class EVMChainWalletBase
required String? contractAddress, required String? contractAddress,
required String receivingAddressHex, required String receivingAddressHex,
required TransactionPriority priority, required TransactionPriority priority,
Uint8List? data,
}) async { }) async {
try { try {
if (priority is EVMChainTransactionPriority) { if (priority is EVMChainTransactionPriority) {
final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
int maxFeePerGas; int maxFeePerGas;
int adjustedGasPrice;
bool isPolygon = _client.chainId == 137;
if (gasBaseFee != null) { if (gasBaseFee != null) {
// MaxFeePerGas with EIP1559; // MaxFeePerGas with EIP1559;
maxFeePerGas = gasBaseFee! + priorityFee; maxFeePerGas = gasBaseFee! + priorityFee;
} else { } else {
// MaxFeePerGas with gasPrice // MaxFeePerGas with gasPrice
maxFeePerGas = gasPrice; maxFeePerGas = gasPrice + priorityFee;
}
adjustedGasPrice = maxFeePerGas;
// Polygon has a minimum priority fee of 25 gwei
if (isPolygon) {
int minPriorityFee = 25;
int minPriorityFeeWei =
EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
// Calculate user selected priority-based additional fee on top of minimum
int additionalPriorityFee = 0;
switch (priority) {
case EVMChainTransactionPriority.slow:
// We use minimum priority fee only
additionalPriorityFee = 0;
break;
case EVMChainTransactionPriority.medium:
// We add 15 gwei on top of minimum
additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
break;
case EVMChainTransactionPriority.fast:
// We add 35 gwei on top of minimum
additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
break;
}
int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
adjustedGasPrice = gasPrice + totalPriorityFee;
maxFeePerGas = gasPrice + totalPriorityFee;
} }
final estimatedGas = await _client.getEstimatedGasUnitsForTransaction( final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
contractAddress: contractAddress, contractAddress: contractAddress,
senderAddress: _evmChainPrivateKey.address, senderAddress: _evmChainPrivateKey.address,
value: EtherAmount.fromBigInt(EtherUnit.wei, amount!), value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice), gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice),
toAddress: EthereumAddress.fromHex(receivingAddressHex), toAddress: EthereumAddress.fromHex(receivingAddressHex),
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas), maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
data: data,
); );
final totalGasFee = estimatedGas * maxFeePerGas; final totalGasFee = estimatedGas * adjustedGasPrice;
return GasParamsHandler( return GasParamsHandler(
estimatedGasUnits: estimatedGas, estimatedGasUnits: estimatedGas,
estimatedGasFee: totalGasFee, estimatedGasFee: totalGasFee,
maxFeePerGas: maxFeePerGas, maxFeePerGas: maxFeePerGas,
gasPrice: gasPrice, gasPrice: adjustedGasPrice,
); );
} }
return GasParamsHandler.zero(); return GasParamsHandler.zero();
@ -473,11 +520,46 @@ abstract class EVMChainWalletBase
contractAddress: contractAddress:
transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
data: hexOpReturnMemo, data: hexOpReturnMemo,
gasPrice: maxFeePerGasForTransaction,
); );
return pendingEVMChainTransaction; return pendingEVMChainTransaction;
} }
Future<PendingTransaction> createApprovalTransaction(BigInt amount, String spender,
CryptoCurrency token, EVMChainTransactionPriority priority) async {
final CryptoCurrency transactionCurrency =
balance.keys.firstWhere((element) => element.title == token.title);
assert(transactionCurrency is Erc20Token);
final data = _client.getEncodedDataForApprovalTransaction(
contractAddress: EthereumAddress.fromHex((transactionCurrency as Erc20Token).contractAddress),
value: EtherAmount.fromBigInt(EtherUnit.wei, amount),
toAddress: EthereumAddress.fromHex(spender),
);
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
receivingAddressHex: spender,
priority: priority,
contractAddress: transactionCurrency.contractAddress,
data: data,
);
return _client.signApprovalTransaction(
privateKey: _evmChainPrivateKey,
spender: spender,
amount: amount,
priority: priority,
gasFee: BigInt.from(gasFeesModel.estimatedGasFee),
maxFeePerGas: gasFeesModel.maxFeePerGas,
estimatedGasUnits: gasFeesModel.estimatedGasUnits,
exponent: transactionCurrency.decimal,
contractAddress: transactionCurrency.contractAddress,
gasPrice: gasFeesModel.gasPrice,
);
}
Future<void> _updateTransactions() async { Future<void> _updateTransactions() async {
try { try {
if (_isTransactionUpdating) { if (_isTransactionUpdating) {

View file

@ -11,6 +11,7 @@ class PendingEVMChainTransaction with PendingTransaction {
final BigInt fee; final BigInt fee;
final String amount; final String amount;
final int exponent; final int exponent;
final bool isInfiniteApproval;
PendingEVMChainTransaction({ PendingEVMChainTransaction({
required this.sendTransaction, required this.sendTransaction,
@ -18,10 +19,12 @@ class PendingEVMChainTransaction with PendingTransaction {
required this.fee, required this.fee,
required this.amount, required this.amount,
required this.exponent, required this.exponent,
this.isInfiniteApproval = false,
}); });
@override @override
String get amountFormatted { String get amountFormatted {
if (isInfiniteApproval) return "";
final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString(); final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
return _amount.substring(0, min(10, _amount.length)); return _amount.substring(0, min(10, _amount.length));
} }

View file

@ -30,6 +30,7 @@ dependencies:
git: git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-ethereum path: packages/ledger-ethereum
ref: f4761cd5171d4c1e2e42fd3298261650539fb2db
dependency_overrides: dependency_overrides:
web3dart: web3dart:
@ -41,9 +42,9 @@ dependency_overrides:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^2.0.1
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
flutter: flutter:

View file

@ -5,13 +5,11 @@ class PendingTransactionDescription {
required this.fee, required this.fee,
required this.hash, required this.hash,
required this.hex, required this.hex,
required this.txKey,
required this.pointerAddress}); required this.pointerAddress});
final int amount; final int amount;
final int fee; final int fee;
final String hash; final String hash;
final String hex; final String hex;
final String txKey;
final int pointerAddress; final int pointerAddress;
} }

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:ffi'; import 'dart:ffi';
import 'dart:isolate'; import 'dart:isolate';
@ -194,14 +195,12 @@ Future<PendingTransactionDescription> createTransactionSync(
final rFee = pendingTx.fee(); final rFee = pendingTx.fee();
final rHash = pendingTx.txid(''); final rHash = pendingTx.txid('');
final rHex = pendingTx.hex(''); final rHex = pendingTx.hex('');
final rTxKey = rHash;
return PendingTransactionDescription( return PendingTransactionDescription(
amount: rAmt, amount: rAmt,
fee: rFee, fee: rFee,
hash: rHash, hash: rHash,
hex: rHex, hex: rHex,
txKey: rTxKey,
pointerAddress: pendingTx.ffiAddress(), pointerAddress: pendingTx.ffiAddress(),
); );
} }
@ -246,7 +245,6 @@ Future<PendingTransactionDescription> createTransactionMultDest(
fee: tx.fee(), fee: tx.fee(),
hash: tx.txid(''), hash: tx.txid(''),
hex: tx.hex(''), hex: tx.hex(''),
txKey: tx.txid(''),
pointerAddress: tx.ffiAddress(), pointerAddress: tx.ffiAddress(),
); );
} }
@ -263,6 +261,7 @@ Future<String?> commitTransaction({required Wallet2PendingTransaction tx, requir
filename: '', filename: '',
overwrite: false, overwrite: false,
); );
return null;
}); });
String? error = (() { String? error = (() {
@ -285,11 +284,12 @@ Future<String?> commitTransaction({required Wallet2PendingTransaction tx, requir
if (error != null && error != "no tx keys found for this txid") { if (error != null && error != "no tx keys found for this txid") {
throw CreationTransactionException(message: error); throw CreationTransactionException(message: error);
} }
if (useUR) { unawaited(() async {
return Future.value(txCommit as String?); storeSync(force: true);
} else { await Future.delayed(Duration(seconds: 5));
return Future.value(null); storeSync(force: true);
} }());
return Future.value(txCommit);
} }
class Transaction { class Transaction {

View file

@ -116,12 +116,17 @@ String getSeedLegacy(String? language) {
} }
String getPassphrase() { String getPassphrase() {
return currentWallet!.getCacheAttribute(key: "cakewallet.passphrase"); return currentWallet?.getCacheAttribute(key: "cakewallet.passphrase") ?? "";
} }
Map<int, Map<int, Map<int, String>>> addressCache = {}; Map<int, Map<int, Map<int, String>>> addressCache = {};
String getAddress({int accountIndex = 0, int addressIndex = 0}) { String getAddress({int accountIndex = 0, int addressIndex = 0}) {
// this is a workaround for when we switch the wallet pointer,
// it should never reach UI but should be good enough to prevent gray screen
// or other errors because of forced null check.
if (currentWallet == null) return "<wallet not ready ($accountIndex:$addressIndex)>";
// printV("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}"); // printV("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}");
// this could be a while loop, but I'm in favor of making it if to not cause freezes // this could be a while loop, but I'm in favor of making it if to not cause freezes
if (currentWallet!.numSubaddresses(accountIndex: accountIndex)-1 < addressIndex) { if (currentWallet!.numSubaddresses(accountIndex: accountIndex)-1 < addressIndex) {
@ -146,7 +151,25 @@ int getUnlockedBalance({int accountIndex = 0}) =>
int getCurrentHeight() => currentWallet?.blockChainHeight() ?? 0; int getCurrentHeight() => currentWallet?.blockChainHeight() ?? 0;
int getNodeHeightSync() => currentWallet?.daemonBlockChainHeight() ?? 0;
int cachedNodeHeight = 0;
bool isHeightRefreshing = false;
int getNodeHeightSync() {
if (isHeightRefreshing == false) {
(() async {
try {
isHeightRefreshing = true;
final wptrAddress = currentWallet!.ffiAddress();
cachedNodeHeight = await Isolate.run(() async {
return monero.Wallet_daemonBlockChainHeight(Pointer.fromAddress(wptrAddress));
});
} finally {
isHeightRefreshing = false;
}
})();
}
return cachedNodeHeight;
}
bool isConnectedSync() => currentWallet?.connected() != 0; bool isConnectedSync() => currentWallet?.connected() != 0;
@ -202,7 +225,6 @@ Future<bool> setupNodeSync(
} }
void startRefreshSync() { void startRefreshSync() {
currentWallet!.refreshAsync();
currentWallet!.startRefresh(); currentWallet!.startRefresh();
} }
@ -228,7 +250,7 @@ void storeSync({bool force = false}) async {
return monero.Wallet_synchronized(Pointer.fromAddress(addr)); return monero.Wallet_synchronized(Pointer.fromAddress(addr));
}); });
if (lastStorePointer == addr && if (lastStorePointer == addr &&
lastStoreHeight + 5000 > currentWallet!.blockChainHeight() && lastStoreHeight + 75000 > currentWallet!.blockChainHeight() &&
!synchronized && !synchronized &&
!force) { !force) {
return; return;
@ -255,13 +277,13 @@ void closeCurrentWallet() {
currentWallet!.stop(); currentWallet!.stop();
} }
String getSecretViewKey() => currentWallet!.secretViewKey(); String getSecretViewKey() => currentWallet?.secretViewKey() ?? "";
String getPublicViewKey() => currentWallet!.publicViewKey(); String getPublicViewKey() => currentWallet?.publicViewKey() ?? "";
String getSecretSpendKey() => currentWallet!.secretSpendKey(); String getSecretSpendKey() => currentWallet?.secretSpendKey() ?? "";
String getPublicSpendKey() => currentWallet!.publicSpendKey(); String getPublicSpendKey() => currentWallet?.publicSpendKey() ?? "";
class SyncListener { class SyncListener {
SyncListener(this.onNewBlock, this.onNewTransaction) SyncListener(this.onNewBlock, this.onNewTransaction)

View file

@ -17,6 +17,7 @@ import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart'; 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/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.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';
@ -207,6 +208,14 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
@override @override
Future<void> connectToNode({required Node node}) async { Future<void> connectToNode({required Node node}) async {
String socksProxy = node.socksProxyAddress ?? '';
printV("bootstrapped: ${CakeTor.instance.bootstrapped}");
printV(" enabled: ${CakeTor.instance.enabled}");
printV(" port: ${CakeTor.instance.port}");
printV(" started: ${CakeTor.instance.started}");
if (CakeTor.instance.enabled) {
socksProxy = "127.0.0.1:${CakeTor.instance.port}";
}
try { try {
syncStatus = ConnectingSyncStatus(); syncStatus = ConnectingSyncStatus();
await monero_wallet.setupNodeSync( await monero_wallet.setupNodeSync(
@ -216,7 +225,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
useSSL: node.isSSL, useSSL: node.isSSL,
isLightWallet: false, isLightWallet: false,
// FIXME: hardcoded value // FIXME: hardcoded value
socksProxyAddress: node.socksProxyAddress); socksProxyAddress: socksProxy);
await monero_wallet.setTrustedDaemon(node.trusted); await monero_wallet.setTrustedDaemon(node.trusted);
syncStatus = ConnectedSyncStatus(); syncStatus = ConnectedSyncStatus();

View file

@ -31,8 +31,6 @@ class PendingMoneroTransaction with PendingTransaction {
@override @override
String get hex => pendingTransactionDescription.hex; String get hex => pendingTransactionDescription.hex;
String get txKey => pendingTransactionDescription.txKey;
@override @override
String get amountFormatted => AmountConverter.amountIntToString( String get amountFormatted => AmountConverter.amountIntToString(
CryptoCurrency.xmr, pendingTransactionDescription.amount); CryptoCurrency.xmr, pendingTransactionDescription.amount);

View file

@ -131,18 +131,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.13" version: "2.4.15"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.10" version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -598,7 +598,7 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e"
url: "https://github.com/cake-tech/on_chain.git" url: "https://github.com/cake-tech/on_chain.git"
source: git source: git
@ -779,11 +779,21 @@ packages:
socks5_proxy: socks5_proxy:
dependency: transitive dependency: transitive
description: description:
name: socks5_proxy path: "."
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://pub.dev" resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
source: hosted url: "https://github.com/LacticWhale/socks_dart"
version: "1.0.6" source: git
version: "2.1.0"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
@ -860,10 +870,19 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: timing name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.1"
tor_binary:
dependency: transitive
description:
path: "."
ref: cb811c610871a9517d47134b87c2f590c15c96c5
resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5
url: "https://github.com/MrCyjaneK/flutter-tor_binary"
source: git
version: "4.7.14"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:

View file

@ -34,8 +34,8 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
build_resolvers: ^2.0.9 build_resolvers: ^2.4.4
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
mockito: ^5.4.5 mockito: ^5.4.5
hive_generator: ^2.0.1 hive_generator: ^2.0.1

View file

@ -3,11 +3,11 @@ import 'dart:convert';
import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_nano/nano_block_info_response.dart'; import 'package:cw_nano/nano_block_info_response.dart';
import 'package:cw_core/n2_node.dart'; import 'package:cw_core/n2_node.dart';
import 'package:cw_nano/nano_balance.dart'; import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_transaction_model.dart'; import 'package:cw_nano/nano_transaction_model.dart';
import 'package:http/http.dart' as http;
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:nanoutil/nanoutil.dart'; import 'package:nanoutil/nanoutil.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -66,8 +66,8 @@ class NanoClient {
} }
Future<NanoBalance> getBalance(String address) async { Future<NanoBalance> getBalance(String address) async {
final response = await http.post( final response = await ProxyWrapper().post(
_node!.uri, clearnetUri: _node!.uri,
headers: getHeaders(_node!.uri.host), headers: getHeaders(_node!.uri.host),
body: jsonEncode( body: jsonEncode(
{ {
@ -76,7 +76,8 @@ class NanoClient {
}, },
), ),
); );
final data = await jsonDecode(response.body);
final data = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode != 200 || if (response.statusCode != 200 ||
data["error"] != null || data["error"] != null ||
data["balance"] == null || data["balance"] == null ||
@ -93,8 +94,8 @@ class NanoClient {
Future<AccountInfoResponse?> getAccountInfo(String address) async { Future<AccountInfoResponse?> getAccountInfo(String address) async {
try { try {
final response = await http.post( final response = await ProxyWrapper().post(
_node!.uri, clearnetUri: _node!.uri,
headers: getHeaders(_node!.uri.host), headers: getHeaders(_node!.uri.host),
body: jsonEncode( body: jsonEncode(
{ {
@ -104,8 +105,9 @@ class NanoClient {
}, },
), ),
); );
final data = await jsonDecode(response.body);
return AccountInfoResponse.fromJson(data as Map<String, dynamic>); final data = jsonDecode(response.body) as Map<String, dynamic>;
return AccountInfoResponse.fromJson(data);
} catch (e) { } catch (e) {
printV("error while getting account info $e"); printV("error while getting account info $e");
return null; return null;
@ -114,8 +116,8 @@ class NanoClient {
Future<BlockContentsResponse?> getBlockContents(String block) async { Future<BlockContentsResponse?> getBlockContents(String block) async {
try { try {
final response = await http.post( final response = await ProxyWrapper().post(
_node!.uri, clearnetUri: _node!.uri,
headers: getHeaders(_node!.uri.host), headers: getHeaders(_node!.uri.host),
body: jsonEncode( body: jsonEncode(
{ {
@ -125,7 +127,8 @@ class NanoClient {
}, },
), ),
); );
final data = await jsonDecode(response.body);
final data = jsonDecode(response.body) as Map<String, dynamic>;
return BlockContentsResponse.fromJson(data["contents"] as Map<String, dynamic>); return BlockContentsResponse.fromJson(data["contents"] as Map<String, dynamic>);
} catch (e) { } catch (e) {
printV("error while getting block info $e"); printV("error while getting block info $e");
@ -181,8 +184,8 @@ class NanoClient {
} }
Future<String> requestWork(String hash) async { Future<String> requestWork(String hash) async {
final response = await http.post( final response = await ProxyWrapper().post(
_powNode!.uri, clearnetUri: _powNode!.uri,
headers: getHeaders(_powNode!.uri.host), headers: getHeaders(_powNode!.uri.host),
body: json.encode( body: json.encode(
{ {
@ -191,8 +194,9 @@ class NanoClient {
}, },
), ),
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>; final decoded = jsonDecode(response.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) { if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}"); throw Exception("Received error ${decoded["error"]}");
} }
@ -224,13 +228,13 @@ class NanoClient {
"block": block, "block": block,
}); });
final processResponse = await http.post( final processResponse = await ProxyWrapper().post(
_node!.uri, clearnetUri: _node!.uri,
headers: getHeaders(_node!.uri.host), headers: getHeaders(_node!.uri.host),
body: processBody, body: processBody,
); );
final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>; final Map<String, dynamic> decoded = jsonDecode(processResponse.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) { if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}"); throw Exception("Received error ${decoded["error"]}");
} }
@ -423,12 +427,11 @@ class NanoClient {
"subtype": "receive", "subtype": "receive",
"block": receiveBlock, "block": receiveBlock,
}); });
final processResponse = await http.post( final processResponse = await ProxyWrapper().post(
_node!.uri, clearnetUri: _node!.uri,
headers: getHeaders(_node!.uri.host), headers: getHeaders(_node!.uri.host),
body: processBody, body: processBody,
); );
final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>; final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
if (decoded.containsKey("error")) { if (decoded.containsKey("error")) {
throw Exception("Received error ${decoded["error"]}"); throw Exception("Received error ${decoded["error"]}");
@ -440,16 +443,17 @@ class NanoClient {
required String destinationAddress, required String destinationAddress,
required String privateKey, required String privateKey,
}) async { }) async {
final receivableResponse = await http.post(_node!.uri, final receivableResponse = await ProxyWrapper().post(
clearnetUri: _node!.uri,
headers: getHeaders(_node!.uri.host), headers: getHeaders(_node!.uri.host),
body: jsonEncode({ body: jsonEncode({
"action": "receivable", "action": "receivable",
"account": destinationAddress, "account": destinationAddress,
"count": "-1", "count": "-1",
"source": true, "source": true,
})); }),
);
final receivableData = await jsonDecode(receivableResponse.body); final receivableData = jsonDecode(receivableResponse.body) as Map<String, dynamic>;
if (receivableData["blocks"] == "" || receivableData["blocks"] == null) { if (receivableData["blocks"] == "" || receivableData["blocks"] == null) {
return 0; return 0;
} }
@ -492,15 +496,18 @@ class NanoClient {
Future<List<NanoTransactionModel>> fetchTransactions(String address) async { Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
try { try {
final response = await http.post(_node!.uri, final response = await ProxyWrapper().post(
clearnetUri: _node!.uri,
headers: getHeaders(_node!.uri.host), headers: getHeaders(_node!.uri.host),
body: jsonEncode({ body: jsonEncode({
"action": "account_history", "action": "account_history",
"account": address, "account": address,
"count": "100", "count": "100",
// "raw": true, // "raw": true,
})); }),
final data = await jsonDecode(response.body); );
final data = jsonDecode(response.body) as Map<String, dynamic>;
final transactions = data["history"] is List ? data["history"] as List<dynamic> : []; final transactions = data["history"] is List ? data["history"] as List<dynamic> : [];
// Map the transactions list to NanoTransactionModel using the factory // Map the transactions list to NanoTransactionModel using the factory
@ -516,13 +523,14 @@ class NanoClient {
Future<List<N2Node>> getN2Reps() async { Future<List<N2Node>> getN2Reps() async {
final uri = Uri.parse(N2_REPS_ENDPOINT); final uri = Uri.parse(N2_REPS_ENDPOINT);
final response = await http.post( final response = await ProxyWrapper().post(
uri, clearnetUri: uri,
headers: getHeaders(uri.host), headers: getHeaders(uri.host),
body: jsonEncode({"action": "reps"}), body: jsonEncode({"action": "reps"}),
); );
try { try {
final List<N2Node> nodes = (json.decode(response.body) as List<dynamic>)
final List<N2Node> nodes = (jsonDecode(response.body) as List<dynamic>)
.map((dynamic e) => N2Node.fromJson(e as Map<String, dynamic>)) .map((dynamic e) => N2Node.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
return nodes; return nodes;
@ -533,8 +541,8 @@ class NanoClient {
Future<int> getRepScore(String rep) async { Future<int> getRepScore(String rep) async {
final uri = Uri.parse(N2_REPS_ENDPOINT); final uri = Uri.parse(N2_REPS_ENDPOINT);
final response = await http.post( final response = await ProxyWrapper().post(
uri, clearnetUri: uri,
headers: getHeaders(uri.host), headers: getHeaders(uri.host),
body: jsonEncode({ body: jsonEncode({
"action": "rep_info", "action": "rep_info",
@ -542,7 +550,8 @@ class NanoClient {
}), }),
); );
try { try {
final N2Node node = N2Node.fromJson(json.decode(response.body) as Map<String, dynamic>);
final N2Node node = N2Node.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
return node.score ?? 100; return node.score ?? 100;
} catch (error) { } catch (error) {
return 100; return 100;

View file

@ -5,34 +5,39 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "47.0.0" version: "76.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "6.11.0"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.6.0" version: "2.7.0"
asn1lib: asn1lib:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5" sha256: "1c296cd268f486cabcc3930e9b93a8133169305f18d722916e675959a88f6d2c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.8" version: "1.5.9"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -61,11 +66,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a" resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils" url: "https://github.com/cake-tech/blockchain_utils"
source: git source: git
version: "4.3.0" version: "3.3.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -86,50 +91,50 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.2"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.4"
build_resolvers: build_resolvers:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10" version: "2.4.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.13" version: "2.4.15"
build_runner_core: build_runner_core:
dependency: "direct overridden" dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.7+1" version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -142,10 +147,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: "8b158ab94ec6913e480dc3f752418348b5ae099eb75868b5f4775f0572999c61" sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.9.4" version: "8.10.1"
cake_backup: cake_backup:
dependency: transitive dependency: transitive
description: description:
@ -238,10 +243,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.3.8"
decimal: decimal:
dependency: "direct main" dependency: "direct main"
description: description:
@ -373,18 +378,18 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: hive_generator name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.3" version: "2.0.1"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.4.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -397,10 +402,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.1.2"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@ -473,6 +478,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -517,10 +530,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: mobx_codegen name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.7.1"
nanodart: nanodart:
dependency: transitive dependency: transitive
description: description:
@ -550,19 +563,19 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" resolved-ref: "01cbbacbb05d2113aafa8b7c4a2bb766f749d8d8"
url: "https://github.com/cake-tech/on_chain.git" url: "https://github.com/cake-tech/on_chain.git"
source: git source: git
version: "6.2.0" version: "3.7.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
name: package_config name: package_config
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.2.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -583,10 +596,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.15" version: "2.2.17"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@ -663,26 +676,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: provider name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.5"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
name: pub_semver name: pub_semver
sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.2.0"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
name: pubspec_parse name: pubspec_parse
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0" sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.5.0"
rational: rational:
dependency: transitive dependency: transitive
description: description:
@ -695,18 +708,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_android name: shared_preferences_android
sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.6" version: "2.4.10"
shared_preferences_foundation: shared_preferences_foundation:
dependency: transitive dependency: transitive
description: description:
@ -751,18 +764,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.2"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "3.0.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -771,27 +784,37 @@ packages:
socks5_proxy: socks5_proxy:
dependency: transitive dependency: transitive
description: description:
name: socks5_proxy path: "."
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://pub.dev" resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
source: hosted url: "https://github.com/LacticWhale/socks_dart"
version: "1.0.6" source: git
version: "2.1.0"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.6" version: "1.5.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.5"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -852,10 +875,19 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: timing name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.1"
tor_binary:
dependency: transitive
description:
path: "."
ref: cb811c610871a9517d47134b87c2f590c15c96c5
resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5
url: "https://github.com/MrCyjaneK/flutter-tor_binary"
source: git
version: "4.7.14"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:
@ -916,18 +948,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web_socket name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.6" version: "1.0.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -945,5 +977,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.27.0"

View file

@ -31,13 +31,12 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^2.0.1
dependency_overrides: dependency_overrides:
watcher: ^1.1.0 watcher: ^1.1.0
build_runner_core: 7.2.7+1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -84,6 +84,6 @@ class DefaultPolygonErc20Tokens {
.iconPath; .iconPath;
} catch (_) {} } catch (_) {}
return Erc20Token.copyWith(token, iconPath, 'POLY'); return Erc20Token.copyWith(token, iconPath, 'POL');
}).toList(); }).toList();
} }

View file

@ -18,13 +18,20 @@ class PolygonClient extends EVMChainClient {
EtherAmount? gasPrice, EtherAmount? gasPrice,
EtherAmount? maxFeePerGas, EtherAmount? maxFeePerGas,
}) { }) {
EtherAmount? finalGasPrice = gasPrice;
if (gasPrice == null && maxFeePerGas != null) {
// If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
finalGasPrice = maxFeePerGas;
}
return Transaction( return Transaction(
from: from, from: from,
to: to, to: to,
value: amount, value: amount,
// data: data, data: data,
maxGas: maxGas, maxGas: maxGas,
// gasPrice: gasPrice, gasPrice: finalGasPrice,
// maxFeePerGas: maxFeePerGas, // maxFeePerGas: maxFeePerGas,
// maxPriorityFeePerGas: maxPriorityFeePerGas, // maxPriorityFeePerGas: maxPriorityFeePerGas,
); );
@ -40,7 +47,7 @@ class PolygonClient extends EVMChainClient {
Future<List<EVMChainTransactionModel>> fetchTransactions(String address, Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async { {String? contractAddress}) async {
try { try {
final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
"chainid": "$chainId", "chainid": "$chainId",
"module": "account", "module": "account",
"action": contractAddress != null ? "tokentx" : "txlist", "action": contractAddress != null ? "tokentx" : "txlist",
@ -68,7 +75,7 @@ class PolygonClient extends EVMChainClient {
@override @override
Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async { Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
try { try {
final response = await httpClient.get(Uri.https("api.etherscan.io", "/v2/api", { final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
"chainid": "$chainId", "chainid": "$chainId",
"module": "account", "module": "account",
"action": "txlistinternal", "action": "txlistinternal",

View file

@ -45,7 +45,12 @@ class PolygonWallet extends EVMChainWallet {
final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens; final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
for (final token in initialErc20Tokens) { for (final token in initialErc20Tokens) {
if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) { if (evmChainErc20TokensBox.containsKey(token.contractAddress)) {
final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
if (existingToken?.tag != token.tag) {
evmChainErc20TokensBox.put(token.contractAddress, token);
}
} else {
if (isMigration) token.enabled = false; if (isMigration) token.enabled = false;
evmChainErc20TokensBox.put(token.contractAddress, token); evmChainErc20TokensBox.put(token.contractAddress, token);
} }

View file

@ -34,7 +34,7 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
build_runner: ^2.4.7 build_runner: ^2.4.15
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the

View file

@ -2,8 +2,10 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/solana_rpc_http_service.dart'; import 'package:cw_core/solana_rpc_http_service.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_solana/pending_solana_transaction.dart'; import 'package:cw_solana/pending_solana_transaction.dart';
@ -11,15 +13,18 @@ import 'package:cw_solana/solana_balance.dart';
import 'package:cw_solana/solana_exceptions.dart'; import 'package:cw_solana/solana_exceptions.dart';
import 'package:cw_solana/solana_transaction_model.dart'; import 'package:cw_solana/solana_transaction_model.dart';
import 'package:cw_solana/spl_token.dart'; import 'package:cw_solana/spl_token.dart';
import 'package:http/http.dart' as http;
import 'package:on_chain/solana/solana.dart'; import 'package:on_chain/solana/solana.dart';
import 'package:on_chain/solana/src/instructions/associated_token_account/constant.dart';
import 'package:on_chain/solana/src/models/pda/pda.dart'; import 'package:on_chain/solana/src/models/pda/pda.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:on_chain/solana/src/rpc/models/models/confirmed_transaction_meta.dart';
import '.secrets.g.dart' as secrets; import '.secrets.g.dart' as secrets;
class SolanaWalletClient { class SolanaWalletClient {
final httpClient = http.Client(); // Minimum amount in SOL to consider a transaction valid (to filter spam)
SolanaProvider? _provider; static const double minValidAmount = 0.00000003;
final httpClient = ProxyWrapper().getHttpClient();
SolanaRPC? _provider;
bool connect(Node node) { bool connect(Node node) {
try { try {
@ -38,7 +43,7 @@ class SolanaWalletClient {
formattedUrl = '$protocolUsed://${node.uriRaw}'; formattedUrl = '$protocolUsed://${node.uriRaw}';
} }
_provider = SolanaProvider(SolanaRPCHTTPService(url: formattedUrl)); _provider = SolanaRPC(SolanaRPCHTTPService(url: formattedUrl));
return true; return true;
} catch (e) { } catch (e) {
@ -49,7 +54,7 @@ class SolanaWalletClient {
Future<double> getBalance(String walletAddress) async { Future<double> getBalance(String walletAddress) async {
try { try {
final balance = await _provider!.requestWithContext( final balance = await _provider!.requestWithContext(
SolanaRequestGetBalance( SolanaRPCGetBalance(
account: SolAddress(walletAddress), account: SolAddress(walletAddress),
), ),
); );
@ -68,11 +73,11 @@ class SolanaWalletClient {
String mintAddress, String publicKey) async { String mintAddress, String publicKey) async {
try { try {
final result = await _provider!.request( final result = await _provider!.request(
SolanaRequestGetTokenAccountsByOwner( SolanaRPCGetTokenAccountsByOwner(
account: SolAddress(publicKey), account: SolAddress(publicKey),
mint: SolAddress(mintAddress), mint: SolAddress(mintAddress),
commitment: Commitment.confirmed, commitment: Commitment.confirmed,
encoding: SolanaRequestEncoding.base64, encoding: SolanaRPCEncoding.base64,
), ),
); );
@ -82,7 +87,8 @@ class SolanaWalletClient {
} }
} }
Future<SolanaBalance?> getSplTokenBalance(String mintAddress, String walletAddress) async { Future<SolanaBalance?> getSplTokenBalance(
String mintAddress, String walletAddress) async {
// Fetch the token accounts (a token can have multiple accounts for various uses) // Fetch the token accounts (a token can have multiple accounts for various uses)
final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress); final tokenAccounts = await getSPLTokenAccounts(mintAddress, walletAddress);
@ -96,7 +102,7 @@ class SolanaWalletClient {
for (var tokenAccount in tokenAccounts) { for (var tokenAccount in tokenAccounts) {
final tokenAmountResult = await _provider!.request( final tokenAmountResult = await _provider!.request(
SolanaRequestGetTokenAccountBalance(account: tokenAccount.pubkey), SolanaRPCGetTokenAccountBalance(account: tokenAccount.pubkey),
); );
final balance = tokenAmountResult.uiAmountString; final balance = tokenAmountResult.uiAmountString;
@ -112,20 +118,22 @@ class SolanaWalletClient {
Future<double> getFeeForMessage(String message, Commitment commitment) async { Future<double> getFeeForMessage(String message, Commitment commitment) async {
try { try {
final feeForMessage = await _provider!.request( final feeForMessage = await _provider!.request(
SolanaRequestGetFeeForMessage( SolanaRPCGetFeeForMessage(
encodedMessage: message, encodedMessage: message,
commitment: commitment, commitment: commitment,
), ),
); );
final fee = (feeForMessage?.toDouble() ?? 0.0) / SolanaUtils.lamportsPerSol; final fee =
(feeForMessage?.toDouble() ?? 0.0) / SolanaUtils.lamportsPerSol;
return fee; return fee;
} catch (_) { } catch (_) {
return 0.0; return 0.0;
} }
} }
Future<double> getEstimatedFee(SolanaPublicKey publicKey, Commitment commitment) async { Future<double> getEstimatedFee(
SolanaPublicKey publicKey, Commitment commitment) async {
final message = await _getMessageForNativeTransaction( final message = await _getMessageForNativeTransaction(
publicKey: publicKey, publicKey: publicKey,
destinationAddress: publicKey.toAddress().address, destinationAddress: publicKey.toAddress().address,
@ -155,105 +163,170 @@ class SolanaWalletClient {
if (meta == null || transaction == null) return null; if (meta == null || transaction == null) return null;
final int fee = meta.fee; final int fee = meta.fee;
final feeInSol = fee / SolanaUtils.lamportsPerSol;
final message = transaction.message; final message = transaction.message;
final instructions = message.compiledInstructions; final instructions = message.compiledInstructions;
String sender = "";
String receiver = "";
String signature = (txResponse.transaction?.signatures.isEmpty ?? true) String signature = (txResponse.transaction?.signatures.isEmpty ?? true)
? "" ? ""
: Base58Encoder.encode(txResponse.transaction!.signatures.first); : Base58Encoder.encode(txResponse.transaction!.signatures.first);
for (final instruction in instructions) { for (final instruction in instructions) {
final programId = message.accountKeys[instruction.programIdIndex]; final programId = message.accountKeys[instruction.programIdIndex];
if (programId == SystemProgramConst.programId) { if (programId == SystemProgramConst.programId ||
programId == ComputeBudgetConst.programId) {
// For native solana transactions // For native solana transactions
if (txResponse.version == TransactionType.legacy) {
// For legacy transfers, the fee payer (index 0) is the sender.
sender = message.accountKeys[0].address;
final senderPreBalance = meta.preBalances[0];
final senderPostBalance = meta.postBalances[0];
final feeForTx = fee / SolanaUtils.lamportsPerSol;
// The loss on the sender's account would include both the transfer amount and the fee.
// So we would subtract the fee to calculate the actual amount that was transferred (in lamports).
final transferLamports = (senderPreBalance - senderPostBalance) - BigInt.from(fee);
// Next, we attempt to find the receiver by comparing the balance changes.
// (The index 0 is for the sender so we skip it.)
bool foundReceiver = false;
for (int i = 1; i < meta.preBalances.length; i++) {
// The increase in balance on the receiver account should correspond to the transfer amount we calculated earlieer.
final pre = meta.preBalances[i];
final post = meta.postBalances[i];
if ((post - pre) == transferLamports) {
receiver = message.accountKeys[i].address;
foundReceiver = true;
break;
}
}
if (!foundReceiver) {
// Optionally (and rarely), if no account shows the exact expected change,
// we set the receiver address to unknown.
receiver = "unknown";
}
final amount = transferLamports / BigInt.from(1e9);
return SolanaTransactionModel(
isOutgoingTx: sender == walletAddress,
from: sender,
to: receiver,
id: signature,
amount: amount.abs(),
programId: SystemProgramConst.programId.address,
tokenSymbol: 'SOL',
blockTimeInInt: blockTime?.toInt() ?? 0,
fee: feeForTx,
);
} else {
if (instruction.accounts.length < 2) continue; if (instruction.accounts.length < 2) continue;
final senderIndex = instruction.accounts[0];
final receiverIndex = instruction.accounts[1];
sender = message.accountKeys[senderIndex].address; // Get the fee payer index based on transaction type
receiver = message.accountKeys[receiverIndex].address; // For legacy transfers, the first account is usually the fee payer
// For versioned, the first account in instruction is usually the fee payer
final feePayerIndex =
txResponse.version == TransactionType.legacy ? 0 : instruction.accounts[0];
final feeForTx = fee / SolanaUtils.lamportsPerSol; final transactionModel = await _parseNativeTransaction(
message: message,
final preBalances = meta.preBalances; meta: meta,
final postBalances = meta.postBalances; fee: fee,
feeInSol: feeInSol,
final amountInString = feePayerIndex: feePayerIndex,
(((preBalances[senderIndex] - postBalances[senderIndex]) / BigInt.from(1e9)) walletAddress: walletAddress,
.toDouble() - signature: signature,
feeForTx) blockTime: blockTime,
.toStringAsFixed(6);
final amount = double.parse(amountInString);
return SolanaTransactionModel(
isOutgoingTx: sender == walletAddress,
from: sender,
to: receiver,
id: signature,
amount: amount.abs(),
programId: SystemProgramConst.programId.address,
tokenSymbol: 'SOL',
blockTimeInInt: blockTime?.toInt() ?? 0,
fee: feeForTx,
); );
if (transactionModel != null) {
return transactionModel;
} }
} else if (programId == SPLTokenProgramConst.tokenProgramId) { } else if (programId == SPLTokenProgramConst.tokenProgramId) {
// For SPL Token transactions // For SPL Token transactions
if (instruction.accounts.length < 2) continue; if (instruction.accounts.length < 2) continue;
final transactionModel = await _parseSPLTokenTransaction(
message: message,
meta: meta,
fee: fee,
feeInSol: feeInSol,
instruction: instruction,
walletAddress: walletAddress,
signature: signature,
blockTime: blockTime,
splTokenSymbol: splTokenSymbol,
);
if (transactionModel != null) {
return transactionModel;
}
} else if (programId == AssociatedTokenAccountProgramConst.associatedTokenProgramId) {
// For ATA program, we need to check if this is a create account transaction
// or if it's part of a normal token transfer
// We skip this transaction if this is the only instruction (this means that it's a create account transaction)
if (instructions.length == 1) {
return null;
}
// We look for a token transfer instruction in the same transaction
bool hasTokenTransfer = false;
for (final otherInstruction in instructions) {
final otherProgramId = message.accountKeys[otherInstruction.programIdIndex];
if (otherProgramId == SPLTokenProgramConst.tokenProgramId) {
hasTokenTransfer = true;
break;
}
}
// If there's no token transfer instruction, it means this is just an ATA creation transaction
if (!hasTokenTransfer) {
return null;
}
continue;
} else {
return null;
}
}
} catch (e, s) {
printV("Error parsing transaction: $e\n$s");
}
return null;
}
Future<SolanaTransactionModel?> _parseNativeTransaction({
required VersionedMessage message,
required ConfirmedTransactionMeta meta,
required int fee,
required double feeInSol,
required int feePayerIndex,
required String walletAddress,
required String signature,
required BigInt? blockTime,
}) async {
// Calculate total balance changes across all accounts
BigInt totalBalanceChange = BigInt.zero;
String? sender;
String? receiver;
for (int i = 0; i < meta.preBalances.length; i++) {
final preBalance = meta.preBalances[i];
final postBalance = meta.postBalances[i];
final balanceChange = preBalance - postBalance;
if (balanceChange > BigInt.zero) {
// This account sent funds
sender = message.accountKeys[i].address;
totalBalanceChange += balanceChange;
} else if (balanceChange < BigInt.zero) {
// This account received funds
receiver = message.accountKeys[i].address;
}
}
// We subtract the fee from total balance change if the fee payer is the sender
if (sender == message.accountKeys[feePayerIndex].address) {
totalBalanceChange -= BigInt.from(fee);
}
if (sender == null || receiver == null) {
return null;
}
final amount = totalBalanceChange / BigInt.from(1e9);
final amountInSol = amount.abs().toDouble();
// Skip transactions with very small amounts (likely spam)
if (amountInSol < minValidAmount) {
return null;
}
return SolanaTransactionModel(
isOutgoingTx: sender == walletAddress,
from: sender,
to: receiver,
id: signature,
amount: amountInSol,
programId: SystemProgramConst.programId.address,
tokenSymbol: 'SOL',
blockTimeInInt: blockTime?.toInt() ?? 0,
fee: feeInSol,
);
}
Future<SolanaTransactionModel?> _parseSPLTokenTransaction({
required VersionedMessage message,
required ConfirmedTransactionMeta meta,
required int fee,
required double feeInSol,
required CompiledInstruction instruction,
required String walletAddress,
required String signature,
required BigInt? blockTime,
String? splTokenSymbol,
}) async {
final preBalances = meta.preTokenBalances; final preBalances = meta.preTokenBalances;
final postBalances = meta.postTokenBalances; final postBalances = meta.postTokenBalances;
@ -317,17 +390,8 @@ class SolanaWalletClient {
programId: SPLTokenProgramConst.tokenProgramId.address, programId: SPLTokenProgramConst.tokenProgramId.address,
blockTimeInInt: blockTime?.toInt() ?? 0, blockTimeInInt: blockTime?.toInt() ?? 0,
tokenSymbol: tokenSymbol ?? '', tokenSymbol: tokenSymbol ?? '',
fee: fee / SolanaUtils.lamportsPerSol, fee: feeInSol,
); );
} else {
return null;
}
}
} catch (e, s) {
printV("Error parsing transaction: $e\n$s");
}
return null;
} }
/// Load the Address's transactions into the account /// Load the Address's transactions into the account
@ -342,7 +406,7 @@ class SolanaWalletClient {
List<SolanaTransactionModel> transactions = []; List<SolanaTransactionModel> transactions = [];
try { try {
final signatures = await _provider!.request( final signatures = await _provider!.request(
SolanaRequestGetSignaturesForAddress( SolanaRPCGetSignaturesForAddress(
account: address, account: address,
commitment: commitment, commitment: commitment,
), ),
@ -357,9 +421,9 @@ class SolanaWalletClient {
final batchResponses = await Future.wait(batch.map((signature) async { final batchResponses = await Future.wait(batch.map((signature) async {
try { try {
return await _provider!.request( return await _provider!.request(
SolanaRequestGetTransaction( SolanaRPCGetTransaction(
transactionSignature: signature['signature'], transactionSignature: signature['signature'],
encoding: SolanaRequestEncoding.jsonParsed, encoding: SolanaRPCEncoding.jsonParsed,
maxSupportedTransactionVersion: 0, maxSupportedTransactionVersion: 0,
), ),
); );
@ -369,9 +433,11 @@ class SolanaWalletClient {
} }
})); }));
final versionedBatchResponses = batchResponses.whereType<VersionedTransactionResponse>(); final versionedBatchResponses =
batchResponses.whereType<VersionedTransactionResponse>();
final parsedTransactionsFutures = versionedBatchResponses.map((tx) => parseTransaction( final parsedTransactionsFutures =
versionedBatchResponses.map((tx) => parseTransaction(
txResponse: tx, txResponse: tx,
splTokenSymbol: splTokenSymbol, splTokenSymbol: splTokenSymbol,
walletAddress: walletAddress?.address ?? address.address, walletAddress: walletAddress?.address ?? address.address,
@ -379,13 +445,16 @@ class SolanaWalletClient {
final parsedTransactions = await Future.wait(parsedTransactionsFutures); final parsedTransactions = await Future.wait(parsedTransactionsFutures);
transactions.addAll(parsedTransactions.whereType<SolanaTransactionModel>().toList()); transactions.addAll(
parsedTransactions.whereType<SolanaTransactionModel>().toList());
// Calling the callback after each batch is processed, therefore passing the current list of transactions. // Only update UI if we have new valid transactions
if (parsedTransactions.isNotEmpty) {
onUpdate(List<SolanaTransactionModel>.from(transactions)); onUpdate(List<SolanaTransactionModel>.from(transactions));
}
if (i + batchSize < signatures.length) { if (i + batchSize < signatures.length) {
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 300));
} }
} }
@ -446,8 +515,8 @@ class SolanaWalletClient {
} }
Future<SPLToken?> fetchSPLTokenInfo(String mintAddress) async { Future<SPLToken?> fetchSPLTokenInfo(String mintAddress) async {
final programAddress = final programAddress = MetaplexTokenMetaDataProgramUtils.findMetadataPda(
MetaplexTokenMetaDataProgramUtils.findMetadataPda(mint: SolAddress(mintAddress)); mint: SolAddress(mintAddress));
final token = await _provider!.request( final token = await _provider!.request(
SolanaRPCGetMetadataAccount( SolanaRPCGetMetadataAccount(
@ -468,8 +537,9 @@ class SolanaWalletClient {
// iconPath = await _client.getIconImageFromTokenUri(metadata.uri); // iconPath = await _client.getIconImageFromTokenUri(metadata.uri);
// } catch (_) {} // } catch (_) {}
String filteredTokenSymbol = String filteredTokenSymbol = metadata.symbol
metadata.symbol.replaceFirst(RegExp('^\\\$'), '').replaceAll('\u0000', ''); .replaceFirst(RegExp('^\\\$'), '')
.replaceAll('\u0000', '');
return SPLToken.fromMetadata( return SPLToken.fromMetadata(
name: metadata.name, name: metadata.name,
@ -482,7 +552,7 @@ class SolanaWalletClient {
void stop() {} void stop() {}
SolanaProvider? get getSolanaProvider => _provider; SolanaRPC? get getSolanaProvider => _provider;
Future<PendingSolanaTransaction> signSolanaTransaction({ Future<PendingSolanaTransaction> signSolanaTransaction({
required String tokenTitle, required String tokenTitle,
@ -523,7 +593,7 @@ class SolanaWalletClient {
Future<SolAddress> _getLatestBlockhash(Commitment commitment) async { Future<SolAddress> _getLatestBlockhash(Commitment commitment) async {
final latestBlockhash = await _provider!.request( final latestBlockhash = await _provider!.request(
const SolanaRequestGetLatestBlockhash(), const SolanaRPCGetLatestBlockhash(),
); );
return latestBlockhash.blockhash; return latestBlockhash.blockhash;
@ -585,7 +655,8 @@ class SolanaWalletClient {
return message; return message;
} }
Future<double> _getFeeFromCompiledMessage(Message message, Commitment commitment) async { Future<double> _getFeeFromCompiledMessage(
Message message, Commitment commitment) async {
final base64Message = base64Encode(message.serialize()); final base64Message = base64Encode(message.serialize());
final fee = await getFeeForMessage(base64Message, commitment); final fee = await getFeeForMessage(base64Message, commitment);
@ -599,7 +670,7 @@ class SolanaWalletClient {
required double fee, required double fee,
}) async { }) async {
final rent = await _provider!.request( final rent = await _provider!.request(
SolanaRequestGetMinimumBalanceForRentExemption( SolanaRPCGetMinimumBalanceForRentExemption(
size: SolanaTokenAccountUtils.accountSize, size: SolanaTokenAccountUtils.accountSize,
), ),
); );
@ -724,7 +795,8 @@ class SolanaWalletClient {
required SolAddress mintAddress, required SolAddress mintAddress,
required bool shouldCreateATA, required bool shouldCreateATA,
}) async { }) async {
final associatedTokenAccount = AssociatedTokenAccountProgramUtils.associatedTokenAccount( final associatedTokenAccount =
AssociatedTokenAccountProgramUtils.associatedTokenAccount(
mint: mintAddress, mint: mintAddress,
owner: ownerAddress, owner: ownerAddress,
); );
@ -732,19 +804,24 @@ class SolanaWalletClient {
SolanaAccountInfo? accountInfo; SolanaAccountInfo? accountInfo;
try { try {
accountInfo = await _provider!.request( accountInfo = await _provider!.request(
SolanaRequestGetAccountInfo(account: associatedTokenAccount.address), SolanaRPCGetAccountInfo(
account: associatedTokenAccount.address,
commitment: Commitment.confirmed,
),
); );
} catch (e) { } catch (e) {
accountInfo = null; accountInfo = null;
} }
// If aacountInfo is null, signifies that the associatedTokenAccount has only been created locally and not been broadcasted to the blockchain. // If account exists, we return the associated token account
if (accountInfo != null) return associatedTokenAccount; if (accountInfo != null) return associatedTokenAccount;
if (!shouldCreateATA) return null; if (!shouldCreateATA) return null;
final payerAddress = payerPrivateKey.publicKey().toAddress();
final createAssociatedTokenAccount = AssociatedTokenAccountProgram.associatedTokenAccount( final createAssociatedTokenAccount = AssociatedTokenAccountProgram.associatedTokenAccount(
payer: payerPrivateKey.publicKey().toAddress(), payer: payerAddress,
associatedToken: associatedTokenAccount.address, associatedToken: associatedTokenAccount.address,
owner: ownerAddress, owner: ownerAddress,
mint: mintAddress, mint: mintAddress,
@ -753,19 +830,23 @@ class SolanaWalletClient {
final blockhash = await _getLatestBlockhash(Commitment.confirmed); final blockhash = await _getLatestBlockhash(Commitment.confirmed);
final transaction = SolanaTransaction( final transaction = SolanaTransaction(
payerKey: payerPrivateKey.publicKey().toAddress(), payerKey: payerAddress,
instructions: [createAssociatedTokenAccount], instructions: [createAssociatedTokenAccount],
recentBlockhash: blockhash, recentBlockhash: blockhash,
type: TransactionType.v0,
); );
transaction.sign([payerPrivateKey]); final serializedTransaction = await _signTransactionInternal(
ownerPrivateKey: payerPrivateKey,
transaction: transaction,
);
await sendTransaction( await sendTransaction(
serializedTransaction: transaction.serializeString(), serializedTransaction: serializedTransaction,
commitment: Commitment.confirmed, commitment: Commitment.confirmed,
); );
// Delay for propagation on the blockchain for newly created associated token addresses // Wait for confirmation
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
return associatedTokenAccount; return associatedTokenAccount;
@ -786,7 +867,8 @@ class SolanaWalletClient {
final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt(); final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt();
ProgramDerivedAddress? associatedSenderAccount; ProgramDerivedAddress? associatedSenderAccount;
try { try {
associatedSenderAccount = AssociatedTokenAccountProgramUtils.associatedTokenAccount( associatedSenderAccount =
AssociatedTokenAccountProgramUtils.associatedTokenAccount(
mint: mintAddress, mint: mintAddress,
owner: ownerPrivateKey.publicKey().toAddress(), owner: ownerPrivateKey.publicKey().toAddress(),
); );
@ -906,7 +988,7 @@ class SolanaWalletClient {
try { try {
/// Send the transaction to the Solana network. /// Send the transaction to the Solana network.
final signature = await _provider!.request( final signature = await _provider!.request(
SolanaRequestSendTransaction( SolanaRPCSendTransaction(
encodedTransaction: serializedTransaction, encodedTransaction: serializedTransaction,
commitment: commitment, commitment: commitment,
), ),
@ -921,7 +1003,8 @@ class SolanaWalletClient {
if (uri.isEmpty || uri == '') return null; if (uri.isEmpty || uri == '') return null;
try { try {
final response = await httpClient.get(Uri.parse(uri)); final client = ProxyWrapper().getHttpIOClient();
final response = await client.get(Uri.parse(uri));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>; final jsonResponse = json.decode(response.body) as Map<String, dynamic>;

View file

@ -611,7 +611,7 @@ abstract class SolanaWalletBase
); );
} }
SolanaProvider? get solanaProvider => _client.getSolanaProvider; SolanaRPC? get solanaProvider => _client.getSolanaProvider;
@override @override
String get password => _password; String get password => _password;

View file

@ -23,19 +23,19 @@ dependencies:
on_chain: on_chain:
git: git:
url: https://github.com/cake-tech/on_chain.git url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v4 ref: cake-update-v2
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v4 ref: cake-update-v2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
build_runner: ^2.4.7 build_runner: ^2.4.15
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^2.0.1
dependency_overrides: dependency_overrides:
watcher: ^1.1.0 watcher: ^1.1.0

View file

@ -5,6 +5,7 @@ import 'dart:developer';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_tron/pending_tron_transaction.dart'; import 'package:cw_tron/pending_tron_transaction.dart';
import 'package:cw_tron/tron_abi.dart'; import 'package:cw_tron/tron_abi.dart';
import 'package:cw_tron/tron_balance.dart'; import 'package:cw_tron/tron_balance.dart';
@ -13,12 +14,12 @@ import 'package:cw_tron/tron_token.dart';
import 'package:cw_tron/tron_transaction_model.dart'; import 'package:cw_tron/tron_transaction_model.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:http/http.dart';
import '.secrets.g.dart' as secrets; import '.secrets.g.dart' as secrets;
import 'package:on_chain/on_chain.dart'; import 'package:on_chain/on_chain.dart';
class TronClient { class TronClient {
final httpClient = Client(); late final client = ProxyWrapper().getHttpIOClient();
TronProvider? _provider; TronProvider? _provider;
// This is an internal tracker, so we don't have to "refetch". // This is an internal tracker, so we don't have to "refetch".
int _nativeTxEstimatedFee = 0; int _nativeTxEstimatedFee = 0;
@ -28,7 +29,7 @@ class TronClient {
Future<List<TronTransactionModel>> fetchTransactions(String address, Future<List<TronTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async { {String? contractAddress}) async {
try { try {
final response = await httpClient.get( final response = await client.get(
Uri.https( Uri.https(
"api.trongrid.io", "api.trongrid.io",
"/v1/accounts/$address/transactions", "/v1/accounts/$address/transactions",
@ -61,7 +62,7 @@ class TronClient {
Future<List<TronTRC20TransactionModel>> fetchTrc20ExcludedTransactions(String address) async { Future<List<TronTRC20TransactionModel>> fetchTrc20ExcludedTransactions(String address) async {
try { try {
final response = await httpClient.get( final response = await client.get(
Uri.https( Uri.https(
"api.trongrid.io", "api.trongrid.io",
"/v1/accounts/$address/transactions/trc20", "/v1/accounts/$address/transactions/trc20",
@ -235,7 +236,7 @@ class TronClient {
String contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t'; String contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
String constantAmount = String constantAmount =
'0'; // We're using 0 as the base amount here as we get an error when balance is zero i.e for new wallets. '0'; // We're using 0 as the base amount here as we get an error when balance is zero i.e for new wallets.
final contract = ContractABI.fromJson(trc20Abi); final contract = ContractABI.fromJson(trc20Abi, isTron: true);
final function = contract.functionFromName("transfer"); final function = contract.functionFromName("transfer");
@ -405,7 +406,7 @@ class TronClient {
String contractAddress, String contractAddress,
BigInt tronBalance, BigInt tronBalance,
) async { ) async {
final contract = ContractABI.fromJson(trc20Abi); final contract = ContractABI.fromJson(trc20Abi, isTron: true);
final function = contract.functionFromName("transfer"); final function = contract.functionFromName("transfer");
@ -483,7 +484,7 @@ class TronClient {
final tokenAddress = TronAddress(contractAddress); final tokenAddress = TronAddress(contractAddress);
final contract = ContractABI.fromJson(trc20Abi); final contract = ContractABI.fromJson(trc20Abi, isTron: true);
final function = contract.functionFromName("balanceOf"); final function = contract.functionFromName("balanceOf");
@ -510,7 +511,7 @@ class TronClient {
final ownerAddress = TronAddress(userAddress); final ownerAddress = TronAddress(userAddress);
final contract = ContractABI.fromJson(trc20Abi); final contract = ContractABI.fromJson(trc20Abi, isTron: true);
final name = final name =
(await getTokenDetail(contract, "name", ownerAddress, tokenAddress) as String?) ?? ''; (await getTokenDetail(contract, "name", ownerAddress, tokenAddress) as String?) ?? '';

View file

@ -1,44 +1,46 @@
import 'package:http/http.dart' as http; import 'dart:convert';
import '.secrets.g.dart' as secrets;
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:on_chain/tron/tron.dart'; import 'package:on_chain/tron/tron.dart';
import '.secrets.g.dart' as secrets;
class TronHTTPProvider implements TronServiceProvider { class TronHTTPProvider implements TronServiceProvider {
TronHTTPProvider( TronHTTPProvider(
{required this.url, {required this.url,
http.Client? client, this.defaultRequestTimeout = const Duration(seconds: 30)});
this.defaultRequestTimeout = const Duration(seconds: 30)})
: client = client ?? http.Client();
@override
final String url; final String url;
final http.Client client; late final client = ProxyWrapper().getHttpIOClient();
final Duration defaultRequestTimeout; final Duration defaultRequestTimeout;
@override @override
Future<TronServiceResponse<T>> doRequest<T>(TronRequestDetails params, Future<Map<String, dynamic>> get(TronRequestDetails params,
{Duration? timeout}) async { [Duration? timeout]) async {
if (!params.type.isPostRequest) { final response = await client.get(Uri.parse(params.url(url)), headers: {
final response = await client.get(
params.toUri(url),
headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
if (url.contains("trongrid")) 'TRON-PRO-API-KEY': secrets.tronGridApiKey, if (url.contains("trongrid")) 'TRON-PRO-API-KEY': secrets.tronGridApiKey,
if (url.contains("nownodes")) 'api-key': secrets.tronNowNodesApiKey, if (url.contains("nownodes")) 'api-key': secrets.tronNowNodesApiKey,
}, }).timeout(timeout ?? defaultRequestTimeout);
).timeout(timeout ?? defaultRequestTimeout); final data = json.decode(response.body) as Map<String, dynamic>;
return params.toResponse(response.bodyBytes, response.statusCode); return data;
} }
@override
Future<Map<String, dynamic>> post(TronRequestDetails params,
[Duration? timeout]) async {
final response = await client final response = await client
.post( .post(Uri.parse(params.url(url)),
params.toUri(url),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
if (url.contains("trongrid")) 'TRON-PRO-API-KEY': secrets.tronGridApiKey, if (url.contains("trongrid"))
if (url.contains("nownodes")) 'api-key': secrets.tronNowNodesApiKey, 'TRON-PRO-API-KEY': secrets.tronGridApiKey,
if (url.contains("nownodes"))
'api-key': secrets.tronNowNodesApiKey,
}, },
body: params.body(), body: params.toRequestBody())
)
.timeout(timeout ?? defaultRequestTimeout); .timeout(timeout ?? defaultRequestTimeout);
return params.toResponse(response.bodyBytes, response.statusCode); final data = json.decode(response.body) as Map<String, dynamic>;
return data;
} }
} }

View file

@ -1,6 +1,5 @@
import 'package:blockchain_utils/hex/hex.dart'; import 'package:blockchain_utils/hex/hex.dart';
import 'package:on_chain/on_chain.dart'; import 'package:on_chain/on_chain.dart';
import 'package:on_chain/solidity/address/core.dart';
class TronTRC20TransactionModel extends TronTransactionModel { class TronTRC20TransactionModel extends TronTransactionModel {
String? transactionId; String? transactionId;
@ -189,7 +188,7 @@ class Value {
output = output.replaceFirst('0x', '').substring(8); output = output.replaceFirst('0x', '').substring(8);
final abiCoder = ABICoder.fromType('address'); final abiCoder = ABICoder.fromType('address');
final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output)); final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output));
final tronAddress = TronAddress.fromEthAddress((decoded.result as SolidityAddress).toBytes()); final tronAddress = TronAddress.fromEthAddress((decoded.result as ETHAddress).toBytes());
return tronAddress.toString(); return tronAddress.toString();
} }

View file

@ -31,7 +31,7 @@ import 'package:cw_tron/tron_transaction_info.dart';
import 'package:cw_tron/tron_wallet_addresses.dart'; import 'package:cw_tron/tron_wallet_addresses.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:on_chain/on_chain.dart' as on_chain; import 'package:on_chain/on_chain.dart';
part 'tron_wallet.g.dart'; part 'tron_wallet.g.dart';
@ -74,13 +74,13 @@ abstract class TronWalletBase
late final Box<TronToken> tronTokensBox; late final Box<TronToken> tronTokensBox;
late final on_chain.TronPrivateKey _tronPrivateKey; late final TronPrivateKey _tronPrivateKey;
late final on_chain.TronPublicKey _tronPublicKey; late final TronPublicKey _tronPublicKey;
on_chain.TronPublicKey get tronPublicKey => _tronPublicKey; TronPublicKey get tronPublicKey => _tronPublicKey;
on_chain.TronPrivateKey get tronPrivateKey => _tronPrivateKey; TronPrivateKey get tronPrivateKey => _tronPrivateKey;
late String _tronAddress; late String _tronAddress;
@ -190,7 +190,7 @@ abstract class TronWalletBase
String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name'; String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
Future<on_chain.TronPrivateKey> getPrivateKey({ Future<TronPrivateKey> getPrivateKey({
String? mnemonic, String? mnemonic,
String? privateKey, String? privateKey,
required String password, required String password,
@ -198,7 +198,7 @@ abstract class TronWalletBase
}) async { }) async {
assert(mnemonic != null || privateKey != null); assert(mnemonic != null || privateKey != null);
if (privateKey != null) return on_chain.TronPrivateKey(privateKey); if (privateKey != null) return TronPrivateKey(privateKey);
final seed = bip39.mnemonicToSeed(mnemonic!, passphrase: passphrase ?? ''); final seed = bip39.mnemonicToSeed(mnemonic!, passphrase: passphrase ?? '');
@ -207,7 +207,7 @@ abstract class TronWalletBase
final childKey = bip44.deriveDefaultPath; final childKey = bip44.deriveDefaultPath;
return on_chain.TronPrivateKey.fromBytes(childKey.privateKey.raw); return TronPrivateKey.fromBytes(childKey.privateKey.raw);
} }
@override @override
@ -242,10 +242,10 @@ abstract class TronWalletBase
Future<void> _getEstimatedFees() async { Future<void> _getEstimatedFees() async {
final nativeFee = await _getNativeTxFee(); final nativeFee = await _getNativeTxFee();
nativeTxEstimatedFee = on_chain.TronHelper.fromSun(BigInt.from(nativeFee)); nativeTxEstimatedFee = TronHelper.fromSun(BigInt.from(nativeFee));
final trc20Fee = await _getTrc20TxFee(); final trc20Fee = await _getTrc20TxFee();
trc20EstimatedFee = on_chain.TronHelper.fromSun(BigInt.from(trc20Fee)); trc20EstimatedFee = TronHelper.fromSun(BigInt.from(trc20Fee));
log('Native Estimated Fee: $nativeTxEstimatedFee'); log('Native Estimated Fee: $nativeTxEstimatedFee');
log('TRC20 Estimated Fee: $trc20EstimatedFee'); log('TRC20 Estimated Fee: $trc20EstimatedFee');
@ -323,7 +323,7 @@ abstract class TronWalletBase
totalAmount = walletBalanceForCurrency; totalAmount = walletBalanceForCurrency;
} else { } else {
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0'); final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
totalAmount = on_chain.TronHelper.toSun(totalOriginalAmount.toString()); totalAmount = TronHelper.toSun(totalOriginalAmount.toString());
} }
if (walletBalanceForCurrency < totalAmount || totalAmount < BigInt.zero) { if (walletBalanceForCurrency < totalAmount || totalAmount < BigInt.zero) {
@ -338,7 +338,7 @@ abstract class TronWalletBase
toAddress: tronCredentials.outputs.first.isParsedAddress toAddress: tronCredentials.outputs.first.isParsedAddress
? tronCredentials.outputs.first.extractedAddress! ? tronCredentials.outputs.first.extractedAddress!
: tronCredentials.outputs.first.address, : tronCredentials.outputs.first.address,
amount: on_chain.TronHelper.fromSun(totalAmount), amount: TronHelper.fromSun(totalAmount),
currency: transactionCurrency, currency: transactionCurrency,
tronBalance: tronBalance, tronBalance: tronBalance,
sendAll: shouldSendAll, sendAll: shouldSendAll,
@ -355,9 +355,9 @@ abstract class TronWalletBase
final Map<String, TronTransactionInfo> result = {}; final Map<String, TronTransactionInfo> result = {};
final contract = on_chain.ContractABI.fromJson(trc20Abi); final contract = ContractABI.fromJson(trc20Abi, isTron: true);
final ownerAddress = on_chain.TronAddress(_tronAddress); final ownerAddress = TronAddress(_tronAddress);
for (var transactionModel in transactions) { for (var transactionModel in transactions) {
if (transactionModel.isError) { if (transactionModel.isError) {
@ -371,7 +371,7 @@ abstract class TronWalletBase
String? tokenSymbol; String? tokenSymbol;
if (transactionModel.contractAddress != null) { if (transactionModel.contractAddress != null) {
final tokenAddress = on_chain.TronAddress(transactionModel.contractAddress!); final tokenAddress = TronAddress(transactionModel.contractAddress!);
tokenSymbol = (await _client.getTokenDetail( tokenSymbol = (await _client.getTokenDetail(
contract, contract,
@ -385,8 +385,7 @@ abstract class TronWalletBase
result[transactionModel.hash] = TronTransactionInfo( result[transactionModel.hash] = TronTransactionInfo(
id: transactionModel.hash, id: transactionModel.hash,
tronAmount: transactionModel.amount ?? BigInt.zero, tronAmount: transactionModel.amount ?? BigInt.zero,
direction: direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address
on_chain.TronAddress(transactionModel.from!, visible: false).toAddress() == address
? TransactionDirection.outgoing ? TransactionDirection.outgoing
: TransactionDirection.incoming, : TransactionDirection.incoming,
blockTime: transactionModel.date, blockTime: transactionModel.date,
@ -605,13 +604,11 @@ abstract class TronWalletBase
if (address == null) { if (address == null) {
return false; return false;
} }
on_chain.TronPublicKey pubKey = TronPublicKey pubKey = TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!;
on_chain.TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!;
return pubKey.toAddress().toString() == address; return pubKey.toAddress().toString() == address;
} }
String getTronBase58AddressFromHex(String hexAddress) => String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress();
on_chain.TronAddress(hexAddress).toAddress();
void updateScanProviderUsageState(bool isEnabled) { void updateScanProviderUsageState(bool isEnabled) {
if (isEnabled) { if (isEnabled) {

View file

@ -18,11 +18,11 @@ dependencies:
on_chain: on_chain:
git: git:
url: https://github.com/cake-tech/on_chain.git url: https://github.com/cake-tech/on_chain.git
ref: cake-update-v4 ref: cake-update-v2
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v4 ref: cake-update-v2
mobx: ^2.3.0+1 mobx: ^2.3.0+1
bip39: ^1.0.6 bip39: ^1.0.6
hive: ^2.2.3 hive: ^2.2.3
@ -31,9 +31,9 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0
build_runner: ^2.3.3 build_runner: ^2.4.15
mobx_codegen: ^2.1.1 mobx_codegen: ^2.1.1
hive_generator: ^1.1.3 hive_generator: ^2.0.1
flutter: flutter:
# assets: # assets:
# - images/a_dot_burr.jpeg # - images/a_dot_burr.jpeg

View file

@ -110,7 +110,16 @@ int getUnlockedBalance({int accountIndex = 0}) =>
int getCurrentHeight() => wownero.Wallet_blockChainHeight(wptr!); int getCurrentHeight() => wownero.Wallet_blockChainHeight(wptr!);
int getNodeHeightSync() => wownero.Wallet_daemonBlockChainHeight(wptr!); int cachedNodeHeight = 0;
int getNodeHeightSync() {
(() async {
final wptrAddress = wptr!.address;
cachedNodeHeight = await Isolate.run(() async {
return wownero.Wallet_daemonBlockChainHeight(Pointer.fromAddress(wptrAddress));
});
})();
return cachedNodeHeight;
}
bool isConnectedSync() => wownero.Wallet_connected(wptr!) != 0; bool isConnectedSync() => wownero.Wallet_connected(wptr!) != 0;
@ -154,7 +163,7 @@ Future<bool> setupNodeSync(
} }
void startRefreshSync() { void startRefreshSync() {
wownero.Wallet_refreshAsync(wptr!); // wownero.Wallet_refreshAsync(wptr!);
wownero.Wallet_startRefresh(wptr!); wownero.Wallet_startRefresh(wptr!);
} }

View file

@ -16,6 +16,7 @@ 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/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.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_core/wownero_amount_format.dart'; import 'package:cw_core/wownero_amount_format.dart';
@ -183,6 +184,14 @@ abstract class WowneroWalletBase
@override @override
Future<void> connectToNode({required Node node}) async { Future<void> connectToNode({required Node node}) async {
String socksProxy = node.socksProxyAddress ?? '';
printV("bootstrapped: ${CakeTor.instance.bootstrapped}");
printV(" enabled: ${CakeTor.instance.enabled}");
printV(" port: ${CakeTor.instance.port}");
printV(" started: ${CakeTor.instance.started}");
if (CakeTor.instance.enabled) {
socksProxy = "127.0.0.1:${CakeTor.instance.port}";
}
try { try {
syncStatus = ConnectingSyncStatus(); syncStatus = ConnectingSyncStatus();
await wownero_wallet.setupNode( await wownero_wallet.setupNode(
@ -192,7 +201,7 @@ abstract class WowneroWalletBase
useSSL: node.isSSL, useSSL: node.isSSL,
isLightWallet: false, isLightWallet: false,
// FIXME: hardcoded value // FIXME: hardcoded value
socksProxyAddress: node.socksProxyAddress); socksProxyAddress: socksProxy);
wownero_wallet.setTrustedDaemon(node.trusted); wownero_wallet.setTrustedDaemon(node.trusted);
syncStatus = ConnectedSyncStatus(); syncStatus = ConnectedSyncStatus();

View file

@ -5,18 +5,23 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "47.0.0" version: "76.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "6.11.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -45,11 +50,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a" resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils" url: "https://github.com/cake-tech/blockchain_utils"
source: git source: git
version: "4.3.0" version: "3.3.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -86,26 +91,26 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_resolvers name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10" version: "2.4.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.13" version: "2.4.15"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.10" version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -214,10 +219,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.3.8"
decimal: decimal:
dependency: transitive dependency: transitive
description: description:
@ -336,10 +341,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: hive_generator name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.3" version: "2.0.1"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -428,6 +433,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -472,10 +485,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: mobx_codegen name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.7.1"
monero: monero:
dependency: "direct main" dependency: "direct main"
description: description:
@ -505,11 +518,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
url: "https://github.com/cake-tech/on_chain.git" url: "https://github.com/cake-tech/on_chain.git"
source: git source: git
version: "6.2.0" version: "3.7.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -670,27 +683,37 @@ packages:
socks5_proxy: socks5_proxy:
dependency: transitive dependency: transitive
description: description:
name: socks5_proxy path: "."
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://pub.dev" resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
source: hosted url: "https://github.com/LacticWhale/socks_dart"
version: "1.0.6" source: git
version: "2.1.0"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.6" version: "1.5.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.5"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -751,10 +774,19 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: timing name: timing
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.1"
tor_binary:
dependency: transitive
description:
path: "."
ref: cb811c610871a9517d47134b87c2f590c15c96c5
resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5
url: "https://github.com/MrCyjaneK/flutter-tor_binary"
source: git
version: "4.7.14"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:
@ -844,5 +876,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.24.0"

View file

@ -31,10 +31,10 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
build_resolvers: ^2.0.9 build_resolvers: ^2.4.4
mobx_codegen: ^2.0.7 mobx_codegen: ^2.0.7
hive_generator: ^1.1.3 hive_generator: ^2.0.1
dependency_overrides: dependency_overrides:
watcher: ^1.1.0 watcher: ^1.1.0

View file

@ -5,18 +5,23 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "47.0.0" version: "76.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.7.0" version: "6.11.0"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -45,11 +50,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "437dadd0bd9bf73ec6a551299577799341f6486a" resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
url: "https://github.com/cake-tech/blockchain_utils" url: "https://github.com/cake-tech/blockchain_utils"
source: git source: git
version: "4.3.0" version: "3.3.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -86,26 +91,26 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_resolvers name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.10" version: "2.4.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.13" version: "2.4.15"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.10" version: "8.0.0"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -214,10 +219,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.3.8"
decimal: decimal:
dependency: "direct main" dependency: "direct main"
description: description:
@ -333,10 +338,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: hive_generator name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.3" version: "2.0.1"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -433,6 +438,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -477,10 +490,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: mobx_codegen name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c sha256: e0abbbc651a69550440f6b65c99ec222a1e2a4afd7baec8ba0f3088c7ca582a8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.7.1"
monero: monero:
dependency: "direct main" dependency: "direct main"
description: description:
@ -502,11 +515,11 @@ packages:
dependency: transitive dependency: transitive
description: description:
path: "." path: "."
ref: cake-update-v4 ref: cake-update-v2
resolved-ref: "084fb7bf13ec42d74f26ac08c883ce07c10fca7e" resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
url: "https://github.com/cake-tech/on_chain.git" url: "https://github.com/cake-tech/on_chain.git"
source: git source: git
version: "6.2.0" version: "3.7.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -667,27 +680,37 @@ packages:
socks5_proxy: socks5_proxy:
dependency: transitive dependency: transitive
description: description:
name: socks5_proxy path: "."
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://pub.dev" resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
source: hosted url: "https://github.com/LacticWhale/socks_dart"
version: "1.0.6" source: git
version: "2.1.0"
socks_socket:
dependency: transitive
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
name: source_gen name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.6" version: "1.5.0"
source_helper: source_helper:
dependency: transitive dependency: transitive
description: description:
name: source_helper name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.3" version: "1.3.5"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -752,6 +775,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
tor_binary:
dependency: transitive
description:
path: "."
ref: cb811c610871a9517d47134b87c2f590c15c96c5
resolved-ref: cb811c610871a9517d47134b87c2f590c15c96c5
url: "https://github.com/MrCyjaneK/flutter-tor_binary"
source: git
version: "4.7.14"
tuple: tuple:
dependency: transitive dependency: transitive
description: description:
@ -841,5 +873,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.5.0 <4.0.0" dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0" flutter: ">=3.24.0"

View file

@ -30,10 +30,10 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^2.4.7 build_runner: ^2.4.15
mobx_codegen: ^2.1.1 mobx_codegen: ^2.1.1
build_resolvers: ^2.0.9 build_resolvers: ^2.4.4
hive_generator: ^1.1.3 hive_generator: ^2.0.1
dependency_overrides: dependency_overrides:
watcher: ^1.1.0 watcher: ^1.1.0

View file

@ -18,8 +18,8 @@ In order to build the latest version of Cake Wallet, simply run the following:
git clone --branch main https://github.com/cake-tech/cake_wallet.git git clone --branch main https://github.com/cake-tech/cake_wallet.git
# NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest. # NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest.
cd cake_wallet cd cake_wallet
# docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1 . # Uncomment to build the docker image yourself instead of pulling it from the registry # docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry
docker run -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1 bash -x << EOF docker run -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly bash -x << EOF
set -x -e set -x -e
pushd scripts/android pushd scripts/android
source ./app_env.sh cakewallet source ./app_env.sh cakewallet

View file

@ -7,7 +7,7 @@ The following are the system requirements to build Cake Wallet for your iOS devi
```txt ```txt
macOS 15.3.1 macOS 15.3.1
Xcode 16.2 Xcode 16.2
Flutter 3.27.4 Flutter 3.27.0
``` ```
NOTE: Newer versions of macOS and Xcode may also work, but have not been confirmed to work by the Cake team. NOTE: Newer versions of macOS and Xcode may also work, but have not been confirmed to work by the Cake team.
@ -43,9 +43,9 @@ To enable iOS build support for Xcode, perform the following:
### 3. Installing Flutter ### 3. Installing Flutter
Install Flutter, specifically version `3.27.4` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download). Install Flutter, specifically version `3.27.0` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download).
NOTE: as `3.27.4` is not the latest version, you'll need to download it from <https://docs.flutter.dev/release/archive> instead of the link in the docs above. NOTE: as `3.27.0` is not the latest version, you'll need to download it from <https://docs.flutter.dev/release/archive> instead of the link in the docs above.
### 4. Installing Rust ### 4. Installing Rust
@ -65,7 +65,7 @@ The output of this command should appear like this, indicating successful instal
```zsh ```zsh
Doctor summary (to see all details, run flutter doctor -v): Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.27.4, on macOS 15.x.x) [✓] Flutter (Channel stable, 3.27.0, on macOS 15.x.x)
[✓] Xcode - develop for iOS and macOS (Xcode 16.2) [✓] Xcode - develop for iOS and macOS (Xcode 16.2)
``` ```

View file

@ -20,8 +20,8 @@ In order to build the latest version of Cake Wallet, simply run the following:
git clone --branch main https://github.com/cake-tech/cake_wallet.git git clone --branch main https://github.com/cake-tech/cake_wallet.git
# NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest. # NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest.
cd cake_wallet cd cake_wallet
# docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1 . # Uncomment to build the docker image yourself instead of pulling it from the registry # docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry
docker run --privileged -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1 bash -x << EOF docker run --privileged -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly bash -x << EOF
set -x -e set -x -e
pushd scripts pushd scripts
./gen_android_manifest.sh ./gen_android_manifest.sh

View file

@ -7,7 +7,7 @@ The following are the system requirements to build Cake Wallet for your macOS de
```txt ```txt
macOS 15.3.1 macOS 15.3.1
Xcode 16.2 Xcode 16.2
Flutter 3.27.4 Flutter 3.27.0
``` ```
### 1. Installing dependencies ### 1. Installing dependencies
@ -34,9 +34,9 @@ sudo xcodebuild -runFirstLaunch
### 3. Installing Flutter ### 3. Installing Flutter
Install Flutter, specifically version `3.27.4` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download). Install Flutter, specifically version `3.27.0` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download).
NOTE: as `3.27.4` is not the latest version, you'll need to download it from <https://docs.flutter.dev/release/archive> instead of the link in the docs above. NOTE: as `3.27.0` is not the latest version, you'll need to download it from <https://docs.flutter.dev/release/archive> instead of the link in the docs above.
### 4. Installing Rust ### 4. Installing Rust
@ -56,7 +56,7 @@ The output of this command should appear like this, indicating successful instal
```zsh ```zsh
Doctor summary (to see all details, run flutter doctor -v): Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.27.4, on macOS 15.x.x) [✓] Flutter (Channel stable, 3.27.0, on macOS 15.x.x)
... ...
[✓] Xcode - develop for iOS and macOS (Xcode 16.2) [✓] Xcode - develop for iOS and macOS (Xcode 16.2)
... ...

View file

@ -6,18 +6,18 @@ The following are the system requirements to build Cake Wallet for your Windows
```txt ```txt
Windows 10 or later (64-bit), x86-64 based Windows 10 or later (64-bit), x86-64 based
Flutter 3.27.4 Flutter 3.27.0
``` ```
### 1. Installing Flutter ### 1. Installing Flutter
Install Flutter, specifically version `3.27.4` by following the [official docs](https://docs.flutter.dev/get-started/install/windows). Install Flutter, specifically version `3.27.0` by following the [official docs](https://docs.flutter.dev/get-started/install/windows).
In order for Flutter to function, you'll also need to enable Developer Mode: In order for Flutter to function, you'll also need to enable Developer Mode:
Start Menu > search for "Run" > type `ms-settings:developers`, and turn on Developer Mode. Start Menu > search for "Run" > type `ms-settings:developers`, and turn on Developer Mode.
NOTE: as `3.27.4` is not the latest version, you'll need to download it from <https://docs.flutter.dev/release/archive> instead of the link in the docs above. NOTE: as `3.27.0` is not the latest version, you'll need to download it from <https://docs.flutter.dev/release/archive> instead of the link in the docs above.
### 2. Install Development Tools ### 2. Install Development Tools

View file

@ -70,7 +70,10 @@ class WalletKeysAndSeedPageRobot {
if (walletType == WalletType.bitcoin || if (walletType == WalletType.bitcoin ||
walletType == WalletType.litecoin || walletType == WalletType.litecoin ||
walletType == WalletType.bitcoinCash) { walletType == WalletType.bitcoinCash) {
commonTestCases.hasText(appStore.wallet!.seed!); final seedWords = appStore.wallet!.seed!.split(" ");
for (var seedWord in seedWords) {
commonTestCases.hasTextAtLestOnce(seedWord);
}
tester.printToConsole('$walletName wallet has seeds properly displayed'); tester.printToConsole('$walletName wallet has seeds properly displayed');
} }
@ -78,10 +81,14 @@ class WalletKeysAndSeedPageRobot {
walletType == WalletType.solana || walletType == WalletType.solana ||
walletType == WalletType.tron) { walletType == WalletType.tron) {
if (hasSeed) { if (hasSeed) {
commonTestCases.hasText(appStore.wallet!.seed!); final seedWords = appStore.wallet!.seed!.split(" ");
for (var seedWord in seedWords) {
commonTestCases.hasTextAtLestOnce(seedWord);
}
tester.printToConsole('$walletName wallet has seeds properly displayed'); tester.printToConsole('$walletName wallet has seeds properly displayed');
} }
if (hasPrivateKey) { if (hasPrivateKey) {
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
commonTestCases.hasText(appStore.wallet!.privateKey!); commonTestCases.hasText(appStore.wallet!.privateKey!);
tester.printToConsole('$walletName wallet has private key properly displayed'); tester.printToConsole('$walletName wallet has private key properly displayed');
} }
@ -89,14 +96,19 @@ class WalletKeysAndSeedPageRobot {
if (walletType == WalletType.nano || walletType == WalletType.banano) { if (walletType == WalletType.nano || walletType == WalletType.banano) {
if (hasSeed) { if (hasSeed) {
commonTestCases.hasText(appStore.wallet!.seed!); final seedWords = appStore.wallet!.seed!.split(" ");
for (var seedWord in seedWords) {
commonTestCases.hasTextAtLestOnce(seedWord);
}
tester.printToConsole('$walletName wallet has seeds properly displayed'); tester.printToConsole('$walletName wallet has seeds properly displayed');
} }
if (hasHexSeed) { if (hasHexSeed) {
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
commonTestCases.hasText(appStore.wallet!.hexSeed!); commonTestCases.hasText(appStore.wallet!.hexSeed!);
tester.printToConsole('$walletName wallet has hexSeed properly displayed'); tester.printToConsole('$walletName wallet has hexSeed properly displayed');
} }
if (hasPrivateKey) { if (hasPrivateKey) {
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
commonTestCases.hasText(appStore.wallet!.privateKey!); commonTestCases.hasText(appStore.wallet!.privateKey!);
tester.printToConsole('$walletName wallet has private key properly displayed'); tester.printToConsole('$walletName wallet has private key properly displayed');
} }
@ -129,35 +141,39 @@ class WalletKeysAndSeedPageRobot {
final hasSeedLegacy = Polyseed.isValidSeed(seed); final hasSeedLegacy = Polyseed.isValidSeed(seed);
if (hasPublicSpendKey) { if (hasPublicSpendKey) {
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
commonTestCases.hasText(keys.publicSpendKey); commonTestCases.hasText(keys.publicSpendKey);
tester.printToConsole('$walletName wallet has public spend key properly displayed'); tester.printToConsole('$walletName wallet has public spend key properly displayed');
} }
if (hasPrivateSpendKey) { if (hasPrivateSpendKey) {
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
commonTestCases.hasText(keys.privateSpendKey); commonTestCases.hasText(keys.privateSpendKey);
tester.printToConsole('$walletName wallet has private spend key properly displayed'); tester.printToConsole('$walletName wallet has private spend key properly displayed');
} }
if (hasPublicViewKey) { if (hasPublicViewKey) {
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
commonTestCases.hasText(keys.publicViewKey); commonTestCases.hasText(keys.publicViewKey);
tester.printToConsole('$walletName wallet has public view key properly displayed'); tester.printToConsole('$walletName wallet has public view key properly displayed');
} }
if (hasPrivateViewKey) { if (hasPrivateViewKey) {
await commonTestCases.tapItemByKey('wallet_keys_page_keys');
commonTestCases.hasText(keys.privateViewKey); commonTestCases.hasText(keys.privateViewKey);
tester.printToConsole('$walletName wallet has private view key properly displayed'); tester.printToConsole('$walletName wallet has private view key properly displayed');
} }
if (hasSeeds) { if (hasSeeds) {
await commonTestCases.dragUntilVisible( await commonTestCases.tapItemByKey('wallet_keys_page_seed');
'${walletName}_wallet_seed_item_key', final seedWords = seed.split(" ");
'wallet_keys_page_credentials_list_view_key', for (var seedWord in seedWords) {
); commonTestCases.hasTextAtLestOnce(seedWord);
commonTestCases.hasText(seed); }
tester.printToConsole('$walletName wallet has seeds properly displayed'); tester.printToConsole('$walletName wallet has seeds properly displayed');
} }
if (hasSeedLegacy) { if (hasSeedLegacy) {
await commonTestCases.dragUntilVisible( await commonTestCases.tapItemByKey('wallet_keys_page_seed_legacy');
'${walletName}_wallet_seed_legacy_item_key', final seedWords = legacySeed.split(" ");
'wallet_keys_page_credentials_list_view_key', for (var seedWord in seedWords) {
); commonTestCases.hasTextAtLestOnce(seedWord);
commonTestCases.hasText(legacySeed); }
tester.printToConsole('$walletName wallet has legacy seeds properly displayed'); tester.printToConsole('$walletName wallet has legacy seeds properly displayed');
} }
} }

Some files were not shown because too many files have changed in this diff Show more