forked from github-such-software/hash-wallet
dev #10
9
.github/workflows/build-linux.yml
vendored
9
.github/workflows/build-linux.yml
vendored
@@ -50,9 +50,9 @@ jobs:
|
||||
# IMPORTANT: don't pre-write any of the tool/.*secrets-config.json
|
||||
# files. The generator at tool/generate_secrets_config.dart has an
|
||||
# early-return if tool/.secrets-config.json already exists (lines
|
||||
# 57-63), which then ALSO skips creating the per-module configs (evm,
|
||||
# solana, nano, tron, bitcoin) — every cw_* module then fails to
|
||||
# compile with "Undefined name secrets.xxx" for dozens of keys.
|
||||
# 57-63), which then ALSO skips creating the per-module configs
|
||||
# (evm, nano, bitcoin) — every cw_* module then fails to compile
|
||||
# with "Undefined name secrets.xxx" for dozens of keys.
|
||||
#
|
||||
# Instead: run the generator first so it creates all configs from the
|
||||
# full SecretKey list (with empty defaults), then sed-inject Trocador
|
||||
@@ -93,7 +93,8 @@ jobs:
|
||||
# ---- Native crypto cores (monero_c prebuilt bundle) ------------------
|
||||
# Skips the full simplybs toolchain bootstrap. The bundle has libmonero,
|
||||
# libwownero, AND libzano .so files; CMake only installs the ones we
|
||||
# ship (monero + wownero).
|
||||
# ship (monero + wownero) — libzano is dropped at install time even
|
||||
# though monero_c builds it.
|
||||
- name: Fetch prebuilt monero_c .so bundle
|
||||
run: |
|
||||
set -x -e
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -229,3 +229,7 @@ scripts/zcash_lib
|
||||
scripts/android/yttrium
|
||||
scripts/bitbox_flutter
|
||||
scripts/reown_flutter
|
||||
|
||||
# Hash Wallet: untracked notes / internal playbooks
|
||||
docs/links.md
|
||||
docs/UPSTREAM_SYNC.md
|
||||
|
||||
78
CONTRIBUTING.md
Normal file
78
CONTRIBUTING.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Contributing to Hash Wallet
|
||||
|
||||
Thanks for the interest. Before opening a PR, please read this.
|
||||
|
||||
## Project shape
|
||||
|
||||
Hash Wallet is a **friendly fork** of [Cake Wallet](https://github.com/cake-tech/cake_wallet). We periodically merge upstream changes and try to keep our divergence narrow. Every change you propose should be evaluated through that lens: will this make future upstream syncs harder, and if so, is the value worth the cost?
|
||||
|
||||
## Branch model
|
||||
|
||||
- `dev` is the integration branch — open PRs against `dev`.
|
||||
- `main` is reserved for tagged releases.
|
||||
- We do not use long-lived feature branches. Iterate on `dev` directly via small focused PRs.
|
||||
|
||||
## Build environment
|
||||
|
||||
See the build section of [`README.md`](README.md). Flutter version is pinned (`.tool-versions` + `Dockerfile`). Don't use a newer Flutter SDK — Cake's `hive_generator` / `build_resolvers` deps fail on Dart 3.10+ because the `_macros` pseudo-package was removed.
|
||||
|
||||
If you use `mise` or `asdf`, the right Flutter version will activate automatically.
|
||||
|
||||
## Generated code
|
||||
|
||||
Several layers use code generation (MobX stores, Hive type adapters, JSON serializers, configure.dart-generated wallet entry points). Regenerate with:
|
||||
|
||||
```bash
|
||||
dart pub run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
For the wallet-types and pubspec generation:
|
||||
|
||||
```bash
|
||||
dart run tool/configure.dart --monero --wownero --bitcoin --bitcoinCash \
|
||||
--ethereum --polygon --base --arbitrum --bsc --nano --dogecoin
|
||||
```
|
||||
|
||||
(That's the canonical Hash Wallet build flag set. Don't pass `--solana`, `--tron`, `--zano`, `--decred`, or `--zcash` — those packages were deleted.)
|
||||
|
||||
## Code style
|
||||
|
||||
- Match the surrounding style. We have not introduced new lints beyond Cake's.
|
||||
- New observable state goes through MobX; persisted state through Hive.
|
||||
- Don't add new top-level analytics. Hash Wallet ships with no telemetry by design.
|
||||
- Don't import `package:cw_zano/`, `package:cw_tron/`, `package:cw_solana/`, `package:cw_decred/` — those packages are deleted. The corresponding `WalletType.*` enum values still exist in `cw_core/lib/wallet_type.dart` for backwards-compat but are never instantiated.
|
||||
- Avoid adding `cake_*`-prefixed file or symbol names in new code. Use `hash_wallet`-prefixed where the symbol is ours, and leave Cake-prefixed names alone where they're inherited (so upstream merges still apply cleanly).
|
||||
|
||||
## What we generally won't accept
|
||||
|
||||
- Re-enabling Solana, Tron, Zano, Decred, or Zcash. The fork exists in part to drop these — opening that door defeats the purpose.
|
||||
- New buy/sell provider integrations that require routing through Cake's `exchange-helper.cakewallet.com` proxy. Direct provider APIs are fine if we can register Hash Wallet / Such Software LLC as the partner.
|
||||
- Lightning Network integration. Greenlight (and self-hosted alternatives) require running infrastructure we're not in a position to operate yet.
|
||||
- Cake Pay re-enable. The infrastructure is Cake Labs's; we can't run it.
|
||||
- New defaults that point at `cakewallet.com` hosts. We're moving away from depending on Cake's infrastructure — community nodes, our own proxies (`exchange-helper.such.software`, `prices.neroswap.com`), or both, are preferred.
|
||||
- Anything that adds telemetry, push notifications via third-party SDKs, or remote bulletin services that the user can't audit.
|
||||
|
||||
## What we want
|
||||
|
||||
- Wownero polish (every paper cut counts — it's the reason this fork exists).
|
||||
- Build reproducibility improvements (especially Docker / CI parity).
|
||||
- Privacy-leaning defaults (own-node guidance, Tor/I2P integration improvements, fewer outbound calls).
|
||||
- Wallet-core bug fixes — these we will happily upstream to Cake.
|
||||
- Documentation, especially around the build pipeline and the Cake → Hash Wallet diff.
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
Open an issue at https://github.com/Such-Software/hash-wallet/issues. Please include:
|
||||
|
||||
- Build artifact ID (the CI workflow names the artifact by commit SHA).
|
||||
- Platform + OS version.
|
||||
- Terminal output if you built and ran from a terminal — many error popups don't print to stderr; check `<appDir>/error.txt` (typically `~/.config/hash_wallet/error.txt` on Linux).
|
||||
- Whether the bug also reproduces in the latest Cake Wallet build (so we know whether it's a Cake-upstream bug or a Hash Wallet regression).
|
||||
|
||||
## Security disclosures
|
||||
|
||||
See [`docs/SECURITY.md`](docs/SECURITY.md). Email `support@such.software` for anything sensitive; do not open a public issue for unpatched vulnerabilities.
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree your contribution is licensed under the [MIT License](LICENSE.md), the same as the rest of Hash Wallet.
|
||||
@@ -58,10 +58,9 @@ flutter --version # confirm 3.32.0 / Dart 3.8.0
|
||||
```bash
|
||||
./scripts/prepare_torch.sh
|
||||
./scripts/prepare_moneroc.sh
|
||||
./scripts/prepare_zcash.sh # still required by build, even though Zcash is disabled
|
||||
./scripts/build_bitbox_flutter.sh
|
||||
# Then fetch the prebuilt reown_flutter tarball (CI does this; reproduce locally
|
||||
# with the URL in .github/workflows/pr_test_build_linux.yml if you have it).
|
||||
# with the URL in .github/workflows/build-linux.yml).
|
||||
```
|
||||
|
||||
Then run the platform-specific configure script, e.g.:
|
||||
@@ -72,7 +71,7 @@ APP_LINUX_TYPE=cakewallet ./configure_hash_wallet.sh linux
|
||||
|
||||
## Contributing
|
||||
|
||||
Issues and PRs welcome at https://github.com/Such-Software/hash-wallet.
|
||||
Issues and PRs welcome at https://github.com/Such-Software/hash-wallet. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for ground rules.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
-
|
||||
uri: electrum.blockstream.info:50002
|
||||
useSSL: true
|
||||
isDefault: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: btc-electrum.cakewallet.com:50002
|
||||
useSSL: true
|
||||
isDefault: true
|
||||
isDefault: false
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: electrs.cakewallet.com:50001
|
||||
@@ -17,10 +22,6 @@
|
||||
uri: bitcoin.stackwallet.com:50002
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: electrum.blockstream.info:50002
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: electrum.emzy.de:50002
|
||||
useSSL: true
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
-
|
||||
uri: electrum-ltc.bysh.me:50002
|
||||
useSSL: true
|
||||
isDefault: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: ltc-electrum.cakewallet.com:50002
|
||||
useSSL: true
|
||||
isDefault: true
|
||||
isDefault: false
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: litecoin.stackwallet.com:20063
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: electrum-ltc.bysh.me:50002
|
||||
useSSL: true
|
||||
-
|
||||
uri: lightweight.fiatfaucet.com:50002
|
||||
useSSL: true
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
-
|
||||
uri: node.sethforprivacy.com:443
|
||||
is_default: true
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: xmr-node.cakewallet.com:18081
|
||||
is_default: true
|
||||
is_default: false
|
||||
trusted: true
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081
|
||||
is_default: false
|
||||
-
|
||||
uri: node.sethforprivacy.com:443
|
||||
useSSL: true
|
||||
is_default: false
|
||||
isEnabledForAutoSwitching: true
|
||||
-
|
||||
uri: node.monerodevs.org:18089
|
||||
useSSL: true
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Bug fixes
|
||||
@@ -1,188 +0,0 @@
|
||||
Last Modified: January 3, 2022
|
||||
|
||||
Acceptance of the Terms of Use
|
||||
==============================
|
||||
These terms of use are entered into by and between You and Cake Technologies LLC ("Company," "we," or "us"). The following terms and conditions "Terms of Use") govern your access to and use of the Monero.com app, including any content, functionality, and services offered on or through the Monero.com app (the “App").
|
||||
Please read the Terms of Use carefully before you start to use the App. By using the App you accept and agree to be bound and abide by these Terms of Use and our Privacy Policy, incorporated herein by reference. If you do not wish to agree to these Terms of Use or the Privacy Policy, you must not access or use the App.
|
||||
|
||||
Changes to the Terms of Use
|
||||
===========================
|
||||
|
||||
We may revise and update these Terms of Use from time to time in our sole discretion. All changes are effective immediately when we post them, and apply to all access to and use of the App thereafter.
|
||||
Your continued use of the App following the posting of revised Terms of Use means that you accept and agree to the changes. You are expected to check this page from time to time so you are aware of any changes, as they are binding on you.
|
||||
|
||||
Accessing the App and Security
|
||||
==============================
|
||||
|
||||
We reserve the right to withdraw or amend this App, and any service or material we provide on the App, in our sole discretion without notice. We will not be liable if for any reason all or any part of the App is unavailable at any time or for any period.
|
||||
|
||||
You are responsible for the following:
|
||||
|
||||
- Making all arrangements necessary for you to have access to the App.
|
||||
|
||||
- Ensuring that all persons who access the App through your internet connection are aware of these Terms of Use and comply with them.
|
||||
|
||||
- To access the App or some of the resources it offers, you may be asked to provide certain registration details or other information. It is a condition of your use of the App that all the information you provide on the App is correct, current, and complete. You agree that all information you provide during the use of this App or otherwise, including, but not limited to, through the use of any interactive features on the App, is governed by our Privacy Policy, and you consent to all actions we take with respect to your information consistent with our Privacy Policy.
|
||||
|
||||
- If you choose, or are provided with, a seed or private keys for any wallet within the App, you MUST treat such information as confidential, and you MUST NOT disclose it to any other person or entity.
|
||||
|
||||
- You MUST keep the app up to date. Failure to update the Monero.com application means you will not be receiving the latest security fixes and features.
|
||||
|
||||
Intellectual Property Rights
|
||||
============================
|
||||
|
||||
The App and its entire contents, features, and functionality (including but not limited to all information, software, text, displays, images, video, and audio, and the design, selection, and arrangement thereof) are owned by the Company, its licensors, or other providers of such material and are protected by United States and international copyright, trademark, patent, trade secret, and other intellectual property or proprietary rights laws.
|
||||
|
||||
These Terms of Use permit you to use the App for your personal and commercial use. You are permitted to download, store, publicly display, publicly perform, republish and transmit any of the code in our App.
|
||||
You are also permitted to reproduce, distribute, modify, or create derivative works of, any of the code in our App, under the condition that any derivative work of this App remains under an open source license.
|
||||
|
||||
You must not:
|
||||
- Modify copies of any images from the Application.
|
||||
- Use any illustrations, photographs, video or audio sequences, or any graphics separately from the accompanying text.
|
||||
- Delete or modify any copyright, trademark, or other rights notices from copies of materials from this site.
|
||||
- Claim ownership of any code, image, text or any other information authored or created by Cake Technologies.
|
||||
|
||||
Trademarks
|
||||
==========
|
||||
|
||||
The Company name, the term Monero.com, the Company logo, and all related names, logos, product and service names, designs, and slogans are trademarks of the Company or its affiliates or licensors. You must not use such marks without the prior written permission of the Company. All other names, logos, product and service names, designs, and slogans on this App are the trademarks of their respective owners.
|
||||
|
||||
Prohibited Uses
|
||||
===============
|
||||
You may use the App only for lawful purposes and in accordance with these Terms of Use. You agree not to use the App:
|
||||
|
||||
- In any way that violates any applicable federal, state, local, or international law or regulation (including, without limitation, any laws regarding the export of data or software to and from the US or other countries).
|
||||
|
||||
- For the purpose of exploiting, harming, or attempting to exploit or harm minors in any way by exposing them to inappropriate content, asking for personally identifiable information, or otherwise.
|
||||
|
||||
- To impersonate or attempt to impersonate the Company, a Company employee, another user, or any other person or entity (including, without limitation, by using email addresses associated with any of the foregoing).
|
||||
|
||||
- To engage in any other conduct that restricts or inhibits anyone's use or enjoyment of the App, or which, as determined by us, may harm the Company or users of the App, or expose them to liability.
|
||||
|
||||
Additionally, you agree not to:
|
||||
|
||||
- Use the App in any manner that could disable, overburden, damage, or impair the App or interfere with any other party's use of the App, including their ability to engage in real time activities through the App.
|
||||
|
||||
- Use any robot, spider, or other automatic device, process, or means to access the App for any purpose, including monitoring or copying any of the material on the App.
|
||||
|
||||
- Use any manual process to monitor or copy any of the material on the App, or for any other purpose not expressly authorized in these Terms of Use, without our prior written consent.
|
||||
|
||||
- Use any device, software, or routine that interferes with the proper working of the App or the Nodes operated by Cake Technologies.
|
||||
|
||||
- Introduce any viruses, Trojan horses, worms, logic bombs, or other material that is malicious or technologically harmful.
|
||||
|
||||
- Attempt to gain unauthorized access to, interfere with, damage, or disrupt any parts of the App, or any node, server, computer, or database connected to the App.
|
||||
|
||||
- Attack the App or the Nodes operated by Cake Technologies via a denial-of-service attack or a distributed denial-of-service attack.
|
||||
|
||||
- Otherwise attempt to interfere with the proper working of the App or the Nodes operated by Cake Technologies.
|
||||
|
||||
Information About You and Your Visits to the App
|
||||
================================================
|
||||
|
||||
All information we collect on this App is subject to our Privacy Policy. By using the App, you consent to all actions taken by us with respect to your information in compliance with the Privacy Policy.
|
||||
|
||||
Links from the App
|
||||
==================
|
||||
|
||||
If the App contains links to other sites and resources provided by third parties, these links are provided for your convenience only. We have no control over the contents of those sites or resources, and accept no responsibility for them or for any loss or damage that may arise from your use of them. If you decide to access any of the third-party Apps or services linked to this App, you do so entirely at your own risk and subject to the terms and conditions of use for such Apps.
|
||||
|
||||
Geographic Restrictions
|
||||
=======================
|
||||
|
||||
The owner of the App is based in the State of Florida in the United States. Please consult with qualified legal counsel to assess the appropriate use of the App or any of its contents in the jurisdiction(s) you intend to use the App. The owner of the App makes no representation or warranty as to the suitability for use, compliance or other matter of law with respect to use in any jurisdiction. If you access the App from outside the United States, you do so on your own initiative and are responsible for compliance with local laws and regulations.
|
||||
|
||||
Translations
|
||||
============
|
||||
|
||||
The App may contain translations of the English version of the content available on the App. These translations are provided only as a convenience. In the event of any conflict between the English language version and the translated version, the English language version shall take precedence. If you notice any inconsistencies, please report them on GitHub.
|
||||
|
||||
Risks Related to the use of the App
|
||||
===================================
|
||||
|
||||
The App, the Company and the Company’s owners, partners, employees, contributors, and any affiliates will not be responsible for any losses, damages or claims arising from:
|
||||
|
||||
- Mistakes made by the user of any Monero-related and/or Bitcoin-related and/or Litecoin-related software or service, e.g., forgotten passwords, payments sent to wrong Monero and/or Bitcoin and/or Litecoin addresses, or accidental deletion of wallets;
|
||||
|
||||
- Software problems of the App and/or any Monero-related or Bitcoin-related or Litecoin-related software or service, e.g., corrupted wallet file, incorrectly constructed transactions, unsafe cryptographic libraries, malware affecting the App and/or any Monero-related or Bitcoin-related or Litecoin-related software or service;
|
||||
|
||||
- Technical failures in the hardware of the user of any Monero-related and/or Bitcoin-related and/or Litecoin-related software or service, e.g., data loss due to a faulty or damaged storage device;
|
||||
|
||||
- Security problems experienced by the user of any Monero-related and/or Bitcoin-related and/or Litecoin-related software or service, e.g., unauthorized access to users' wallets and/or accounts; or
|
||||
|
||||
- Actions or inactions of third parties and/or events experienced by third parties, e.g., bankruptcy of service providers, information security attacks on service providers, and fraud conducted by third parties.
|
||||
|
||||
Investment Risks
|
||||
================
|
||||
|
||||
All investments, including investments in Monero, Litecoin and Bitcoin, are speculative in nature and involve substantial risk of loss. We encourage investors to invest carefully. We also encourage investors to get personal advice from professional investment advisors and to make independent investigations before making any investment. We do not in any way guarantee the success of any action you take with respect to investments on the App or otherwise. Past performance is not necessarily indicative of future results. All investments, including investments in Monero, Litecoin or Bitcoin, carry risk and all investment decisions of an individual remain the responsibility of that individual. Do not enter any investment without fully understanding the worst-case scenario of that investment, including but not limited to, large market fluctuations or total loss.
|
||||
|
||||
Tax Matters
|
||||
===========
|
||||
|
||||
The users of the App are solely responsible in determining what, if any, taxes apply to their Monero, Litecoin and/or Bitcoin transactions. Cake Technologies is not responsible for determining any taxes that apply to such transactions. You agree not to hold Cake Technologies responsible for any issues relating to the taxation of purchases, sale, exchanges, transfers, or any other transactions related to any cryptocurrency.
|
||||
|
||||
Monero, Bitcoin & Litecoin Transactions
|
||||
=======================================
|
||||
|
||||
The App does not store Monero, Litecoin or Bitcoin. Monero, Litecoin and Bitcoin exist only by virtue of the ownership record maintained in the Monero, Litecoin and Bitcoin networks. Any transfer of title in Monero, Litecoin or Bitcoin occurs within a decentralized Monero, Litecoin or Bitcoin network, and not in the App.
|
||||
|
||||
Disclaimer of Warranties
|
||||
========================
|
||||
|
||||
You understand that we cannot and do not guarantee or warrant that files available for downloading from the internet or the App will be free of viruses or other destructive code. You are responsible for implementing sufficient procedures and checkpoints to satisfy your particular requirements for anti-virus protection and accuracy of data input and output, and for maintaining a means external to our site for any reconstruction of any lost data. TO THE FULLEST EXTENT PROVIDED BY LAW, WE WILL NOT BE LIABLE FOR ANY LOSS OR DAMAGE CAUSED BY A DISTRIBUTED DENIAL-OF-SERVICE ATTACK, VIRUSES, OR OTHER TECHNOLOGICALLY HARMFUL MATERIAL THAT MAY INFECT YOUR COMPUTER EQUIPMENT, COMPUTER PROGRAMS, DATA, OR OTHER PROPRIETARY MATERIAL DUE TO YOUR USE OF THE APP OR ANY SERVICES OR ITEMS OBTAINED THROUGH THE APP OR TO YOUR DOWNLOADING OF ANY MATERIAL POSTED ON IT, OR ON ANY APP LINKED TO IT.
|
||||
|
||||
YOUR USE OF THE APP, ITS CONTENT, AND ANY SERVICES OR ITEMS OBTAINED THROUGH THE APP IS AT YOUR OWN RISK. THE APP, ITS CONTENT, AND ANY SERVICES OR ITEMS OBTAINED THROUGH THE APP ARE PROVIDED ON AN "AS IS" AND "AS AVAILABLE" BASIS, WITHOUT ANY WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. NEITHER THE COMPANY NOR ANY PERSON ASSOCIATED WITH THE COMPANY MAKES ANY WARRANTY OR REPRESENTATION WITH RESPECT TO THE COMPLETENESS, SECURITY, RELIABILITY, QUALITY, ACCURACY, OR AVAILABILITY OF THE APP. WITHOUT LIMITING THE FOREGOING, NEITHER THE COMPANY NOR ANYONE ASSOCIATED WITH THE COMPANY REPRESENTS OR WARRANTS THAT THE APP, ITS CONTENT, OR ANY SERVICES OR ITEMS OBTAINED THROUGH THE APP WILL BE ACCURATE, RELIABLE, ERROR-FREE, OR UNINTERRUPTED, THAT DEFECTS WILL BE CORRECTED, THAT OUR SITE OR THE SERVER THAT MAKES IT AVAILABLE ARE FREE OF VIRUSES OR OTHER HARMFUL COMPONENTS, OR THAT THE APP OR ANY SERVICES OR ITEMS OBTAINED THROUGH THE APP WILL OTHERWISE MEET YOUR NEEDS OR EXPECTATIONS.
|
||||
|
||||
TO THE FULLEST EXTENT PROVIDED BY LAW, THE COMPANY HEREBY DISCLAIMS ALL WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, STATUTORY, OR OTHERWISE, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, NON-INFRINGEMENT, AND FITNESS FOR PARTICULAR PURPOSE. THE APP IS PROVIDED ON AN “AS IS” BASIS.
|
||||
|
||||
THE FOREGOING DOES NOT AFFECT ANY WARRANTIES THAT CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW.
|
||||
|
||||
Limitation on Liability
|
||||
=======================
|
||||
|
||||
TO THE FULLEST EXTENT PROVIDED BY LAW, IN NO EVENT WILL THE COMPANY, ITS AFFILIATES, OR THEIR LICENSORS, SERVICE PROVIDERS, EMPLOYEES, AGENTS, OFFICERS, OR DIRECTORS BE LIABLE FOR DAMAGES OF ANY KIND, UNDER ANY LEGAL THEORY, ARISING OUT OF OR IN CONNECTION WITH YOUR USE, OR INABILITY TO USE, THE APP, ANY APPS LINKED TO IT, ANY CONTENT ON THE APP OR SUCH OTHER APPS, INCLUDING ANY DIRECT, INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, INCLUDING BUT NOT LIMITED TO, PERSONAL INJURY, PAIN AND SUFFERING, EMOTIONAL DISTRESS, LOSS OF REVENUE, LOSS OF PROFITS, LOSS OF BUSINESS OR ANTICIPATED SAVINGS, LOSS OF USE, LOSS OF GOODWILL, LOSS OF DATA, AND WHETHER CAUSED BY TORT (INCLUDING NEGLIGENCE), BREACH OF CONTRACT, OR OTHERWISE, EVEN IF FORESEEABLE.
|
||||
|
||||
The limitation of liability set out above does not apply to liability resulting from our gross negligence or willful misconduct.
|
||||
|
||||
THE FOREGOING DOES NOT AFFECT ANY LIABILITY THAT CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW.
|
||||
|
||||
Indemnification
|
||||
===============
|
||||
|
||||
You agree to defend, indemnify, and hold harmless the Company, its affiliates, licensors, and service providers, and its and their respective officers, directors, employees, contractors, agents, licensors, suppliers, successors, and assigns from and against any claims, liabilities, damages, judgments, awards, losses, costs, expenses, or fees (including reasonable attorneys' fees) arising out of or relating to your violation of these Terms of Use or your use of the App, including, but not limited to, your User Contributions, any use of the App's content, services, and products other than as expressly authorized in these Terms of Use, or your use of any information obtained from the App.
|
||||
|
||||
Governing Law and Jurisdiction
|
||||
==============================
|
||||
|
||||
All matters relating to the App and these Terms of Use, and any dispute or claim arising therefrom or related thereto (in each case, including non-contractual disputes or claims), shall be governed by and construed in accordance with the internal laws of the State of Florida without giving effect to any choice or conflict of law provision or rule (whether of the State of Florida or any other jurisdiction).
|
||||
|
||||
Any legal suit, action, or proceeding arising out of, or related to, these Terms of Use or the App shall be instituted exclusively in the federal courts of the United States or the courts of the State of Florida, although we retain the right to bring any suit, action, or proceeding against you for breach of these Terms of Use in your country of residence or any other relevant country. You waive any and all objections to the exercise of jurisdiction over you by such courts and to venue in such courts.
|
||||
|
||||
Arbitration
|
||||
===========
|
||||
|
||||
At Company's sole discretion, it may require You to submit any disputes arising from these Terms of Use or use of the App, including disputes arising from or concerning their interpretation, violation, invalidity, non-performance, or termination, to final and binding arbitration under the Rules of Arbitration of the American Arbitration Association applying Florida law.
|
||||
|
||||
Limitation on Time to File Claims
|
||||
=================================
|
||||
|
||||
ANY CAUSE OF ACTION OR CLAIM YOU MAY HAVE ARISING OUT OF OR RELATING TO THESE TERMS OF USE OR THE APP MUST BE COMMENCED WITHIN ONE (1) YEAR AFTER THE CAUSE OF ACTION ACCRUES; OTHERWISE, SUCH CAUSE OF ACTION OR CLAIM IS PERMANENTLY BARRED.
|
||||
|
||||
Waiver and Severability
|
||||
=======================
|
||||
|
||||
No waiver by the Company of any term or condition set out in these Terms of Use shall be deemed a further or continuing waiver of such term or condition or a waiver of any other term or condition, and any failure of the Company to assert a right or provision under these Terms of Use shall not constitute a waiver of such right or provision.
|
||||
|
||||
If any provision of these Terms of Use is held by a court or other tribunal of competent jurisdiction to be invalid, illegal, or unenforceable for any reason, such provision shall be eliminated or limited to the minimum extent such that the remaining provisions of the Terms of Use will continue in full force and effect.
|
||||
|
||||
Entire Agreement
|
||||
================
|
||||
|
||||
The Terms of Use and our Privacy Policy constitute the sole and entire agreement between you and Cake Technologies Inc. regarding the App and supersede all prior and contemporaneous understandings, agreements, representations, and warranties, both written and oral, regarding the App.
|
||||
|
||||
Your Comments and Concerns
|
||||
==========================
|
||||
|
||||
This App is operated by Cake Technologies Inc.
|
||||
All feedback, comments, and other communications relating to the App should be directed to info@cakewallet.com. All requests for technical support should be directed to support@cakewallet.com.
|
||||
@@ -24,7 +24,10 @@ bool get isNonAmnesticTails {
|
||||
bool showNotice = true;
|
||||
|
||||
void setRootDirFromEnv() =>
|
||||
_rootDirPath = Platform.environment['CAKE_WALLET_DIR'];
|
||||
// HASH_WALLET_DIR is the canonical override; CAKE_WALLET_DIR kept as a
|
||||
// fallback so users migrating from a Cake install via env-var override
|
||||
// don't lose their path.
|
||||
_rootDirPath = Platform.environment['HASH_WALLET_DIR'] ?? Platform.environment['CAKE_WALLET_DIR'];
|
||||
|
||||
void copyDirectory(Directory source, Directory destination) {
|
||||
source.listSync(recursive: false).forEach((var entity) {
|
||||
@@ -42,8 +45,11 @@ void copyDirectory(Directory source, Directory destination) {
|
||||
Future<void> linuxSymlinkSharedPreferences() async {
|
||||
if (!Platform.isLinux) return; // nuh-uh
|
||||
final dataHome = Platform.environment["XDG_DATA_HOME"] ?? p.join(Platform.environment["HOME"] ?? "", ".local", "share");
|
||||
var cakeNames = ['com.example.cake_wallet', 'cake_wallet'];
|
||||
for (String name in cakeNames) {
|
||||
// Names to migrate from on startup. Includes the legacy Cake paths so users
|
||||
// forking from a previous Cake install get their data automatically
|
||||
// symlinked into Hash Wallet's new home (~/.local/share/hash_wallet).
|
||||
var legacyNames = ['com.example.cake_wallet', 'cake_wallet'];
|
||||
for (String name in legacyNames) {
|
||||
final oldPath = p.join(dataHome, name);
|
||||
final newPath = p.join((await getAppDir()).path, "_local_share");
|
||||
final oldDir = Directory(oldPath);
|
||||
@@ -70,7 +76,7 @@ Future<void> linuxSymlinkSharedPreferences() async {
|
||||
}
|
||||
|
||||
Future<Directory> getAppDir() async {
|
||||
const String appName = 'cake_wallet';
|
||||
const String appName = 'hash_wallet';
|
||||
Directory dir;
|
||||
|
||||
if (_rootDirPath != null && _rootDirPath!.isNotEmpty) {
|
||||
|
||||
39
cw_decred/.gitignore
vendored
39
cw_decred/.gitignore
vendored
@@ -1,39 +0,0 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
android/.externalNativeBuild/
|
||||
android/.cxx/
|
||||
android/libs
|
||||
ios/External/
|
||||
macos/External/
|
||||
|
||||
*libdcrwallet.h
|
||||
libdcrwallet_bindings.dart
|
||||
@@ -1,36 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: unknown
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: android
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: ios
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: macos
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@@ -1,3 +0,0 @@
|
||||
## [0.0.1] - TODO: Add release date.
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,47 +0,0 @@
|
||||
# cw_decred
|
||||
|
||||
Decred wallet module that bridges to the native `libdcrwallet` via FFI. Provides high‑level methods to create/load wallets, sync, query balances/transactions, build and broadcast transactions, and sign/verify messages.
|
||||
|
||||
## Features
|
||||
|
||||
- FFI bindings to `libdcrwallet` with an isolate‑based request/response model.
|
||||
- Initialize, create, load, close wallets; watch‑only creation.
|
||||
- Start sync with optional peer list; query sync status and best block.
|
||||
- Query balances, list transactions and unspents, rescan from height.
|
||||
- Create signed transactions and broadcast raw transactions.
|
||||
- Export wallet seed; change wallet password.
|
||||
- Address management (new external address, default pubkey, address lists).
|
||||
- Message signing and verification.
|
||||
|
||||
## Getting started
|
||||
|
||||
Ensure the platform library is available:
|
||||
|
||||
- Android/Linux: `libdcrwallet.so`
|
||||
- Apple: embedded `cw_decred.framework/cw_decred`
|
||||
|
||||
Initialize and load a wallet:
|
||||
|
||||
```dart
|
||||
final lib = await Libwallet.spawn();
|
||||
await lib.initLibdcrwallet('', 'info');
|
||||
await lib.loadWallet(jsonEncode({ /* libdcrwallet config */ }));
|
||||
await lib.startSync('wallet.db', '');
|
||||
final status = await lib.syncStatus('wallet.db');
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Create, sign, and broadcast a transaction:
|
||||
|
||||
```dart
|
||||
final signed = await lib.createSignedTransaction('wallet.db', jsonEncode({
|
||||
// inputs/outputs and policy for libdcrwallet
|
||||
}));
|
||||
final txid = await lib.sendRawTransaction('wallet.db', signed);
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
- See `lib/api/` for the isolate wrapper (`libdcrwallet.dart`) and low‑level bindings.
|
||||
- Errors are surfaced via the `PayloadResult` struct; some calls support `throwOnError` in higher‑level wrappers.
|
||||
@@ -1,4 +0,0 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
9
cw_decred/android/.gitignore
vendored
9
cw_decred/android/.gitignore
vendored
@@ -1,9 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -1,59 +0,0 @@
|
||||
group 'com.cakewallet.cw_decred'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '2.0.21'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.7.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace 'com.cakewallet.cw_decred'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'src/main/kotlin'
|
||||
jniLibs.srcDirs 'libs' // contains libdcrwallet.so shared libraries
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
rootProject.name = 'cw_decred'
|
||||
@@ -1,4 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.cakewallet.cw_decred">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.cakewallet.cw_decred
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
|
||||
/** CwDecredPlugin */
|
||||
class CwDecredPlugin: FlutterPlugin, MethodCallHandler {
|
||||
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
///
|
||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||
/// when the Flutter Engine is detached from the Activity
|
||||
private lateinit var channel : MethodChannel
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_decred")
|
||||
channel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||
if (call.method == "getPlatformVersion") {
|
||||
result.success("Android ${android.os.Build.VERSION.RELEASE}")
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
38
cw_decred/ios/.gitignore
vendored
38
cw_decred/ios/.gitignore
vendored
@@ -1,38 +0,0 @@
|
||||
.idea/
|
||||
.vagrant/
|
||||
.sconsign.dblite
|
||||
.svn/
|
||||
|
||||
.DS_Store
|
||||
*.swp
|
||||
profile
|
||||
|
||||
DerivedData/
|
||||
build/
|
||||
GeneratedPluginRegistrant.h
|
||||
GeneratedPluginRegistrant.m
|
||||
|
||||
.generated/
|
||||
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.perspectivev3
|
||||
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
|
||||
xcuserdata
|
||||
|
||||
*.moved-aside
|
||||
|
||||
*.pyc
|
||||
*sync/
|
||||
Icon?
|
||||
.tags*
|
||||
|
||||
/Flutter/Generated.xcconfig
|
||||
/Flutter/ephemeral/
|
||||
/Flutter/flutter_export_environment.sh
|
||||
@@ -1,19 +0,0 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
public class CwDecredPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger())
|
||||
let instance = CwDecredPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("iOS " + UIDevice.current.systemVersion)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_decred.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_decred'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'Cake Wallet Decred'
|
||||
s.description = 'Cake Wallet wrapper over Decred project'
|
||||
s.homepage = 'http://cakewallet.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '11.0'
|
||||
|
||||
s.vendored_libraries = 'External/lib/libdcrwallet.a'
|
||||
# Flutter.framework does not contain a i386 slice.
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
|
||||
s.swift_version = '5.0'
|
||||
end
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
|
||||
const decredAmountLength = 8;
|
||||
const decredAmountDivider = 100000000;
|
||||
final decredAmountFormat = NumberFormat()
|
||||
..maximumFractionDigits = decredAmountLength
|
||||
..minimumFractionDigits = 1;
|
||||
|
||||
String decredAmountToString({required int amount}) =>
|
||||
decredAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider));
|
||||
|
||||
double decredAmountToDouble({required int amount}) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: decredAmountDivider);
|
||||
|
||||
int stringDoubleToDecredAmount(String amount) {
|
||||
int result = 0;
|
||||
|
||||
try {
|
||||
result = (double.parse(amount) * decredAmountDivider).round();
|
||||
} catch (e) {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,702 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_decred/api/libdcrwallet_bindings.dart';
|
||||
import 'package:cw_decred/api/util.dart';
|
||||
|
||||
final int ErrCodeNotSynced = 1;
|
||||
|
||||
final String libraryName = Platform.isAndroid || Platform.isLinux // TODO: Linux.
|
||||
? 'libdcrwallet.so'
|
||||
: 'cw_decred.framework/cw_decred';
|
||||
|
||||
class Libwallet {
|
||||
final SendPort _commands;
|
||||
final ReceivePort _responses;
|
||||
final Map<int, Completer<Object?>> _activeRequests = {};
|
||||
int _idCounter = 0;
|
||||
bool _closed = false;
|
||||
|
||||
static Future<Libwallet> spawn() async {
|
||||
// Create a receive port and add its initial message handler.
|
||||
final initPort = RawReceivePort();
|
||||
final connection = Completer<(ReceivePort, SendPort)>.sync();
|
||||
initPort.handler = (initialMessage) {
|
||||
final commandPort = initialMessage as SendPort;
|
||||
connection.complete((
|
||||
ReceivePort.fromRawReceivePort(initPort),
|
||||
commandPort,
|
||||
));
|
||||
};
|
||||
// Spawn the isolate.
|
||||
try {
|
||||
await Isolate.spawn(_startRemoteIsolate, (initPort.sendPort));
|
||||
} on Object {
|
||||
initPort.close();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final (ReceivePort receivePort, SendPort sendPort) = await connection.future;
|
||||
|
||||
return Libwallet._(receivePort, sendPort);
|
||||
}
|
||||
|
||||
Libwallet._(this._responses, this._commands) {
|
||||
_responses.listen(_handleResponsesFromIsolate);
|
||||
}
|
||||
|
||||
void _handleResponsesFromIsolate(dynamic message) {
|
||||
final (int id, Object? response) = message as (int, Object?);
|
||||
final completer = _activeRequests.remove(id)!;
|
||||
|
||||
if (response is RemoteError) {
|
||||
completer.completeError(response);
|
||||
} else {
|
||||
completer.complete(response);
|
||||
}
|
||||
|
||||
if (_closed && _activeRequests.isEmpty) _responses.close();
|
||||
}
|
||||
|
||||
static void _handleCommandsToIsolate(
|
||||
ReceivePort receivePort,
|
||||
SendPort sendPort,
|
||||
) {
|
||||
final dcrwalletApi = libdcrwallet(DynamicLibrary.open(libraryName));
|
||||
receivePort.listen((message) {
|
||||
if (message == 'shutdown') {
|
||||
receivePort.close();
|
||||
return;
|
||||
}
|
||||
final (int id, Map<String, String> args) = message as (int, Map<String, String>);
|
||||
var res = PayloadResult("", "", 0);
|
||||
final method = args["method"] ?? "";
|
||||
try {
|
||||
switch (method) {
|
||||
case "initlibdcrwallet":
|
||||
final logDir = args["logdir"] ?? "";
|
||||
final level = args["level"] ?? "";
|
||||
final cLogDir = logDir.toCString();
|
||||
final cLevel = level.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.initialize(cLogDir, cLevel),
|
||||
ptrsToFree: [cLogDir, cLevel],
|
||||
);
|
||||
break;
|
||||
case "createwallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.createWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "createwatchonlywallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.createWatchOnlyWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "loadwallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.loadWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "startsync":
|
||||
final name = args["name"] ?? "";
|
||||
final peers = args["peers"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cPeers = peers.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.syncWallet(cName, cPeers),
|
||||
ptrsToFree: [cName, cPeers],
|
||||
);
|
||||
break;
|
||||
case "closewallet":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.closeWallet(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "changewalletpassword":
|
||||
final name = args["name"] ?? "";
|
||||
final oldPass = args["oldpass"] ?? "";
|
||||
final newPass = args["newpass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cOldPass = oldPass.toCString();
|
||||
final cNewPass = newPass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.changePassphrase(cName, cOldPass, cNewPass),
|
||||
ptrsToFree: [cName, cOldPass, cNewPass],
|
||||
);
|
||||
break;
|
||||
case "walletseed":
|
||||
final name = args["name"] ?? "";
|
||||
final pass = args["pass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cPass = pass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.walletSeed(cName, cPass),
|
||||
ptrsToFree: [cName, cPass],
|
||||
);
|
||||
break;
|
||||
case "syncstatus":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.syncWalletStatus(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "balance":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.walletBalance(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "estimatefee":
|
||||
final name = args["name"] ?? "";
|
||||
final numBlocks = args["numblocks"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cNumBlocks = numBlocks.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.estimateFee(cName, cNumBlocks),
|
||||
ptrsToFree: [cName, cNumBlocks],
|
||||
);
|
||||
break;
|
||||
case "createsignedtransaction":
|
||||
final name = args["name"] ?? "";
|
||||
final signReq = args["signreq"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cSignReq = signReq.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.createSignedTransaction(cName, cSignReq),
|
||||
ptrsToFree: [cName, cSignReq],
|
||||
);
|
||||
break;
|
||||
case "sendrawtransaction":
|
||||
final name = args["name"] ?? "";
|
||||
final txHex = args["txhex"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cTxHex = txHex.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.sendRawTransaction(cName, cTxHex),
|
||||
ptrsToFree: [cName, cTxHex],
|
||||
);
|
||||
break;
|
||||
case "listtransactions":
|
||||
final name = args["name"] ?? "";
|
||||
final from = args["from"] ?? "";
|
||||
final count = args["count"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cFrom = from.toCString();
|
||||
final cCount = count.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.listTransactions(cName, cFrom, cCount),
|
||||
ptrsToFree: [cName, cFrom, cCount],
|
||||
);
|
||||
break;
|
||||
case "bestblock":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.bestBlock(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "listunspents":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.listUnspents(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "rescanfromheight":
|
||||
final name = args["name"] ?? "";
|
||||
final height = args["height"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cHeight = height.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.rescanFromHeight(cName, cHeight),
|
||||
ptrsToFree: [cName, cHeight],
|
||||
);
|
||||
break;
|
||||
case "signmessage":
|
||||
final name = args["name"] ?? "";
|
||||
final message = args["message"] ?? "";
|
||||
final address = args["address"] ?? "";
|
||||
final pass = args["pass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cMessage = message.toCString();
|
||||
final cAddress = address.toCString();
|
||||
final cPass = pass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.signMessage(cName, cMessage, cAddress, cPass),
|
||||
ptrsToFree: [cName, cMessage, cAddress, cPass],
|
||||
);
|
||||
break;
|
||||
case "verifymessage":
|
||||
final name = args["name"] ?? "";
|
||||
final message = args["message"] ?? "";
|
||||
final address = args["address"] ?? "";
|
||||
final sig = args["sig"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cMessage = message.toCString();
|
||||
final cAddress = address.toCString();
|
||||
final cSig = sig.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.verifyMessage(cName, cMessage, cAddress, cSig),
|
||||
ptrsToFree: [cName, cMessage, cAddress, cSig],
|
||||
);
|
||||
break;
|
||||
case "newexternaladdress":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.newExternalAddress(cName),
|
||||
ptrsToFree: [cName],
|
||||
skipErrorCheck: true,
|
||||
);
|
||||
break;
|
||||
case "defaultpubkey":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.defaultPubkey(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "addresses":
|
||||
final name = args["name"] ?? "";
|
||||
final nUsed = args["nused"] ?? "";
|
||||
final nUnused = args["nunused"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cNUsed = nUsed.toCString();
|
||||
final cNUnused = nUnused.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.addresses(cName, cNUsed, cNUnused),
|
||||
ptrsToFree: [cName, cNUsed, cNUnused],
|
||||
);
|
||||
break;
|
||||
case "birthstate":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.birthState(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "shutdown":
|
||||
final name = args["name"] ?? "";
|
||||
// final cName = name.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.shutdown(),
|
||||
ptrsToFree: [],
|
||||
);
|
||||
break;
|
||||
default:
|
||||
res = PayloadResult("", "unknown libwallet method ${method}", 0);
|
||||
}
|
||||
sendPort.send((id, res));
|
||||
} catch (e) {
|
||||
final errMsg = e.toString();
|
||||
printV("decred libwallet returned an error for method ${method}: ${errMsg}");
|
||||
sendPort.send((id, PayloadResult("", errMsg, 0)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void _startRemoteIsolate(SendPort sendPort) {
|
||||
final receivePort = ReceivePort();
|
||||
sendPort.send(receivePort.sendPort);
|
||||
_handleCommandsToIsolate(receivePort, sendPort);
|
||||
}
|
||||
|
||||
// initLibdcrwallet initializes libdcrwallet using the provided logDir and gets
|
||||
// it ready for use. This must be done before attempting to create, load or use
|
||||
// a wallet. An empty string can be used to log to stdout and create no log files.
|
||||
Future<void> initLibdcrwallet(String logDir, String level) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "initlibdcrwallet",
|
||||
"logdir": logDir,
|
||||
"level": level,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> createWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createwallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> createWatchOnlyWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createwatchonlywallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> loadWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "loadwallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> startSync(String walletName, String peers) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "startsync",
|
||||
"name": walletName,
|
||||
"peers": peers,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> closeWallet(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "closewallet",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<String> changeWalletPassword(
|
||||
String walletName, String currentPassword, String newPassword) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "changewalletpassword",
|
||||
"name": walletName,
|
||||
"oldpass": currentPassword,
|
||||
"newpass": newPassword
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String?> walletSeed(String walletName, String walletPassword) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "walletseed",
|
||||
"name": walletName,
|
||||
"pass": walletPassword,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> syncStatus(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "syncstatus",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<Map> balance(String walletName, {bool throwOnError = false}) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "balance",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
try {
|
||||
return jsonDecode(res.payload);
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> estimateFee(String walletName, int numBlocks) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "estimatefee",
|
||||
"name": walletName,
|
||||
"numblocks": numBlocks.toString(),
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> createSignedTransaction(
|
||||
String walletName, String createSignedTransactionReq) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createsignedtransaction",
|
||||
"name": walletName,
|
||||
"signreq": createSignedTransactionReq,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> sendRawTransaction(String walletName, String txHex) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "sendrawtransaction",
|
||||
"name": walletName,
|
||||
"txhex": txHex,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> listTransactions(String walletName, String from, String count) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "listtransactions",
|
||||
"name": walletName,
|
||||
"from": from,
|
||||
"count": count,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> bestBlock(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "bestblock",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> listUnspents(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "listunspents",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> rescanFromHeight(String walletName, String height) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "rescanfromheight",
|
||||
"name": walletName,
|
||||
"height": height,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> signMessage(
|
||||
String walletName, String message, String address, String walletPass) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "signmessage",
|
||||
"name": walletName,
|
||||
"message": message,
|
||||
"address": address,
|
||||
"pass": walletPass,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> verifyMessage(
|
||||
String walletName, String message, String address, String sig) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "verifymessage",
|
||||
"name": walletName,
|
||||
"message": message,
|
||||
"address": address,
|
||||
"sig": sig,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String?> newExternalAddress(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "newexternaladdress",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
if (res.errCode == ErrCodeNotSynced) {
|
||||
// Wallet is not synced. We do not want to give out a used address so give
|
||||
// nothing.
|
||||
return null;
|
||||
}
|
||||
checkErr(res.err);
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> defaultPubkey(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "defaultpubkey",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> addresses(String walletName, String nUsed, String nUnused) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "addresses",
|
||||
"name": walletName,
|
||||
"nused": nUsed,
|
||||
"nunused": nUnused,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> birthState(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "birthstate",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<void> shutdown() async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "shutdown",
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future as PayloadResult;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!_closed) {
|
||||
_closed = true;
|
||||
_commands.send('shutdown');
|
||||
if (_activeRequests.isEmpty) _responses.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class PayloadResult {
|
||||
final String payload;
|
||||
final String err;
|
||||
final int errCode;
|
||||
|
||||
const PayloadResult(this.payload, this.err, this.errCode);
|
||||
}
|
||||
|
||||
// Executes the provided fn and converts the string response to a PayloadResult.
|
||||
// Returns payload, error code, and error.
|
||||
PayloadResult executePayloadFn({
|
||||
required Pointer<Char> fn(),
|
||||
required List<Pointer> ptrsToFree,
|
||||
bool skipErrorCheck = false,
|
||||
}) {
|
||||
final jsonStr = fn().toDartString();
|
||||
freePointers(ptrsToFree);
|
||||
if (jsonStr == null) throw Exception("no json return from wallet library");
|
||||
final decoded = json.decode(jsonStr);
|
||||
|
||||
final err = decoded["error"] ?? "";
|
||||
if (!skipErrorCheck) {
|
||||
checkErr(err);
|
||||
}
|
||||
|
||||
final payload = decoded["payload"] ?? "";
|
||||
final errCode = decoded["errorcode"] ?? -1;
|
||||
return new PayloadResult(payload, err, errCode);
|
||||
}
|
||||
|
||||
void freePointers(List<Pointer> ptrsToFree) {
|
||||
for (final ptr in ptrsToFree) {
|
||||
malloc.free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void checkErr(String err) {
|
||||
if (err == "") return;
|
||||
throw Exception(err);
|
||||
}
|
||||
|
||||
extension StringUtil on String {
|
||||
Pointer<Char> toCString() => toNativeUtf8().cast<Char>();
|
||||
}
|
||||
|
||||
extension CStringUtil on Pointer<Char> {
|
||||
bool get isNull => address == nullptr.address;
|
||||
|
||||
free() {
|
||||
malloc.free(this);
|
||||
}
|
||||
|
||||
String? toDartString() {
|
||||
if (isNull) return null;
|
||||
|
||||
final str = cast<Utf8>().toDartString();
|
||||
free();
|
||||
return str;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class DecredBalance extends Balance {
|
||||
DecredBalance({required this.confirmed, required this.unconfirmed, required int frozen})
|
||||
: _frozen = frozen, super.fromInt(confirmed, unconfirmed);
|
||||
|
||||
factory DecredBalance.zero() => DecredBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
final int _frozen;
|
||||
BigInt get frozen => BigInt.from(_frozen);
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance => decredAmountToString(amount: confirmed - _frozen);
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance => decredAmountToString(amount: unconfirmed);
|
||||
|
||||
@override
|
||||
String get formattedUnAvailableBalance {
|
||||
final frozenFormatted = decredAmountToString(amount: _frozen);
|
||||
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
|
||||
class DecredPendingTransaction with PendingTransaction {
|
||||
DecredPendingTransaction(
|
||||
{required this.txid,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
required this.rawHex,
|
||||
required this.send});
|
||||
|
||||
final int amount;
|
||||
final int fee;
|
||||
final String txid;
|
||||
final String rawHex;
|
||||
final Future<void> Function() send;
|
||||
|
||||
@override
|
||||
String get id => txid;
|
||||
|
||||
@override
|
||||
String get amountFormatted => decredAmountToString(amount: amount);
|
||||
|
||||
@override
|
||||
String get feeFormatted => "$feeFormattedValue DCR";
|
||||
|
||||
@override
|
||||
String get feeFormattedValue => decredAmountToString(amount: fee);
|
||||
|
||||
@override
|
||||
String get hex => rawHex;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
return send();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import 'package:cw_decred/transaction_priority.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class DecredTransactionCredentials {
|
||||
DecredTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final DecredTransactionPriority? priority;
|
||||
final int? feeRate;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
class DecredTransactionHistory extends TransactionHistoryBase<TransactionInfo> {
|
||||
DecredTransactionHistory() {
|
||||
transactions = ObservableMap<String, TransactionInfo>();
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(TransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, TransactionInfo> transactions) => this.transactions.addAll(transactions);
|
||||
|
||||
@override
|
||||
Future<void> save() async {}
|
||||
|
||||
// update returns true if a known transaction that is not pending was found.
|
||||
bool update(Map<String, TransactionInfo> txs) {
|
||||
var foundOldTx = false;
|
||||
txs.forEach((_, tx) {
|
||||
if (!this.transactions.containsKey(tx.id) || this.transactions[tx.id]!.isPending) {
|
||||
this.transactions[tx.id] = tx;
|
||||
} else {
|
||||
foundOldTx = true;
|
||||
}
|
||||
});
|
||||
return foundOldTx;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
|
||||
class DecredTransactionInfo extends TransactionInfo {
|
||||
DecredTransactionInfo({
|
||||
required String id,
|
||||
required int amount,
|
||||
required int fee,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int height,
|
||||
required int confirmations,
|
||||
required String to,
|
||||
}) {
|
||||
this.id = id;
|
||||
this.amount = amount;
|
||||
this.fee = fee;
|
||||
this.height = height;
|
||||
this.direction = direction;
|
||||
this.date = date;
|
||||
this.isPending = isPending;
|
||||
this.confirmations = confirmations;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
String amountFormatted() =>
|
||||
'${formatAmount(decredAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
|
||||
|
||||
@override
|
||||
String? feeFormatted() =>
|
||||
'${formatAmount(decredAmountToString(amount: fee ?? 0))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class DecredTransactionPriority extends TransactionPriority {
|
||||
const DecredTransactionPriority({required String title, required int raw})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
static const List<DecredTransactionPriority> all = [fast, medium, slow];
|
||||
static const DecredTransactionPriority slow = DecredTransactionPriority(title: 'Slow', raw: 0);
|
||||
static const DecredTransactionPriority medium =
|
||||
DecredTransactionPriority(title: 'Medium', raw: 1);
|
||||
static const DecredTransactionPriority fast = DecredTransactionPriority(title: 'Fast', raw: 2);
|
||||
|
||||
static DecredTransactionPriority deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
case 0:
|
||||
return slow;
|
||||
case 1:
|
||||
return medium;
|
||||
case 2:
|
||||
return fast;
|
||||
default:
|
||||
if (kDebugMode) {
|
||||
throw Exception('Unexpected token: $raw for DecredTransactionPriority deserialize');
|
||||
}
|
||||
return medium;
|
||||
}
|
||||
}
|
||||
|
||||
String get units => 'atom';
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var label = '';
|
||||
|
||||
switch (this) {
|
||||
case DecredTransactionPriority.slow:
|
||||
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||
break;
|
||||
case DecredTransactionPriority.medium:
|
||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
||||
break;
|
||||
case DecredTransactionPriority.fast:
|
||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
|
||||
}
|
||||
|
||||
class FeeCache {
|
||||
int _feeRate;
|
||||
DateTime stamp;
|
||||
FeeCache(this._feeRate) : this.stamp = DateTime(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
bool isOld() {
|
||||
return this.stamp.add(const Duration(minutes: 30)).isBefore(DateTime.now());
|
||||
}
|
||||
|
||||
void update(int feeRate) {
|
||||
this._feeRate = feeRate;
|
||||
this.stamp = DateTime.now();
|
||||
}
|
||||
|
||||
int feeRate() {
|
||||
return this._feeRate;
|
||||
}
|
||||
}
|
||||
@@ -1,770 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/transaction_direction.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/pending_transaction.dart';
|
||||
import 'package:cw_decred/transaction_credentials.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
import 'package:cw_decred/transaction_history.dart';
|
||||
import 'package:cw_decred/wallet_addresses.dart';
|
||||
import 'package:cw_decred/transaction_priority.dart';
|
||||
import 'package:cw_decred/wallet_service.dart';
|
||||
import 'package:cw_decred/balance.dart';
|
||||
import 'package:cw_decred/transaction_info.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
|
||||
part 'wallet.g.dart';
|
||||
|
||||
class DecredWallet = DecredWalletBase with _$DecredWallet;
|
||||
|
||||
abstract class DecredWalletBase
|
||||
extends WalletBase<DecredBalance, DecredTransactionHistory, DecredTransactionInfo> with Store {
|
||||
DecredWalletBase(WalletInfo walletInfo, DerivationInfo derivationInfo, String password, Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
Libwallet libwallet, Function() closeLibwallet)
|
||||
: _password = password,
|
||||
_libwallet = libwallet,
|
||||
_closeLibwallet = closeLibwallet,
|
||||
this.syncStatus = NotConnectedSyncStatus(),
|
||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||
this.watchingOnly =
|
||||
derivationInfo.derivationPath == DecredWalletService.pubkeyRestorePath ||
|
||||
derivationInfo.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
this.balance = ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}),
|
||||
this.isTestnet = derivationInfo.derivationPath ==
|
||||
DecredWalletService.seedRestorePathTestnet ||
|
||||
derivationInfo.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
super(walletInfo, derivationInfo) {
|
||||
walletAddresses = DecredWalletAddresses(walletInfo, libwallet, isTestnet);
|
||||
transactionHistory = DecredTransactionHistory();
|
||||
|
||||
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = enabled;
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: Hitting this max fee would be unexpected with current on chain use
|
||||
// but this may need to be updated in the future.
|
||||
final maxFeeRate = 100000;
|
||||
|
||||
// syncIntervalSyncing is used up until synced, then transactions are checked
|
||||
// every syncIntervalSynced.
|
||||
final syncIntervalSyncing = 5; // seconds
|
||||
final syncIntervalSynced = 30; // seconds
|
||||
static final defaultFeeRate = 10000;
|
||||
final String _password;
|
||||
final Libwallet _libwallet;
|
||||
final Function() _closeLibwallet;
|
||||
final idPrefix = "decred_";
|
||||
|
||||
// TODO: Encrypt this.
|
||||
var _seed = "";
|
||||
var _pubkey = "";
|
||||
var _unspents = <Unspent>[];
|
||||
|
||||
// synced is used to set the syncTimer interval.
|
||||
bool synced = false;
|
||||
bool watchingOnly;
|
||||
bool connecting = false;
|
||||
String persistantPeer = "default-spv-nodes";
|
||||
FeeCache feeRateFast = FeeCache(defaultFeeRate);
|
||||
FeeCache feeRateMedium = FeeCache(defaultFeeRate);
|
||||
FeeCache feeRateSlow = FeeCache(defaultFeeRate);
|
||||
Timer? syncTimer;
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
late ObservableMap<CryptoCurrency, DecredBalance> balance;
|
||||
|
||||
@override
|
||||
late DecredWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
String? get seed {
|
||||
if (watchingOnly) {
|
||||
return null;
|
||||
}
|
||||
return _seed;
|
||||
}
|
||||
|
||||
@override
|
||||
Object get keys => {};
|
||||
|
||||
@override
|
||||
bool isTestnet;
|
||||
|
||||
String get pubkey {
|
||||
return _pubkey;
|
||||
}
|
||||
|
||||
@override
|
||||
String formatCryptoAmount(String amount) => decredAmountToString(amount: int.parse(amount));
|
||||
|
||||
Future<void> init() async {
|
||||
final getSeed = () async {
|
||||
if (!watchingOnly) {
|
||||
_seed = await _libwallet.walletSeed(walletInfo.name, _password) ?? "";
|
||||
}
|
||||
_pubkey = await _libwallet.defaultPubkey(walletInfo.name);
|
||||
};
|
||||
await Future.wait([
|
||||
updateBalance(),
|
||||
updateTransactionHistory(),
|
||||
walletAddresses.init(),
|
||||
fetchTransactions(),
|
||||
updateFees(),
|
||||
fetchUnspents(),
|
||||
getSeed(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> performBackgroundTasks() async {
|
||||
if (!await checkSync()) {
|
||||
if (synced == true) {
|
||||
synced = false;
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
}
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Set sync check interval lower since we are synced.
|
||||
if (synced == false) {
|
||||
synced = true;
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
}
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSynced), (Timer t) => performBackgroundTasks());
|
||||
}
|
||||
await Future.wait([
|
||||
updateTransactionHistory(),
|
||||
updateFees(),
|
||||
fetchUnspents(),
|
||||
updateBalance(),
|
||||
walletAddresses.updateAddressesInBox(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> updateFees() async {
|
||||
final feeForNb = (int nb) async {
|
||||
try {
|
||||
final feeStr = await _libwallet.estimateFee(walletInfo.name, nb);
|
||||
var fee = int.parse(feeStr);
|
||||
if (fee > maxFeeRate) {
|
||||
throw "dcr fee returned from estimate fee was over max";
|
||||
} else if (fee <= 0) {
|
||||
throw "dcr fee returned from estimate fee was zero";
|
||||
}
|
||||
return fee;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return defaultFeeRate;
|
||||
}
|
||||
};
|
||||
if (feeRateSlow.isOld()) {
|
||||
feeRateSlow.update(await feeForNb(4));
|
||||
}
|
||||
if (feeRateMedium.isOld()) {
|
||||
feeRateMedium.update(await feeForNb(2));
|
||||
}
|
||||
if (feeRateFast.isOld()) {
|
||||
feeRateFast.update(await feeForNb(1));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTransactionHistory() async {
|
||||
// from is the number of transactions skipped from most recent, not block
|
||||
// height.
|
||||
var from = 0;
|
||||
while (true) {
|
||||
// Transactions are returned from newest to oldest. Loop fetching 5 txn
|
||||
// at a time until we find a batch with txn that no longer need to be
|
||||
// updated.
|
||||
final txs = await this.fetchFiveTransactions(from);
|
||||
if (txs.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (this.transactionHistory.update(txs)) {
|
||||
return;
|
||||
}
|
||||
from += 5;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkSync() async {
|
||||
final syncStatusJSON = await _libwallet.syncStatus(walletInfo.name);
|
||||
final decoded = json.decode(syncStatusJSON.isEmpty ? "{}" : syncStatusJSON);
|
||||
|
||||
final syncStatusCode = decoded["syncstatuscode"] ?? 0;
|
||||
// final syncStatusStr = decoded["syncstatus"] ?? "";
|
||||
final targetHeight = decoded["targetheight"] ?? 1;
|
||||
final numPeers = decoded["numpeers"] ?? 0;
|
||||
// final cFiltersHeight = decoded["cfiltersheight"] ?? 0;
|
||||
final headersHeight = decoded["headersheight"] ?? 0;
|
||||
final rescanHeight = decoded["rescanheight"] ?? 0;
|
||||
|
||||
if (numPeers == 0) {
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sync codes:
|
||||
// NotStarted = 0
|
||||
// FetchingCFilters = 1
|
||||
// FetchingHeaders = 2
|
||||
// DiscoveringAddrs = 3
|
||||
// Rescanning = 4
|
||||
// Complete = 5
|
||||
|
||||
if (syncStatusCode > 4) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 0) {
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 1) {
|
||||
syncStatus = SyncingSyncStatus(targetHeight, 0.0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 2) {
|
||||
final headersProg = headersHeight / targetHeight;
|
||||
// Only allow headers progress to go up half way.
|
||||
syncStatus = SyncingSyncStatus(targetHeight - headersHeight, headersProg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: This step takes a while so should really get more info to the UI
|
||||
// that we are discovering addresses.
|
||||
if (syncStatusCode == 3) {
|
||||
// Hover at half.
|
||||
syncStatus = ProcessingSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 4) {
|
||||
// Start at 75%.
|
||||
final rescanProg = rescanHeight / targetHeight / 4;
|
||||
syncStatus = SyncingSyncStatus(targetHeight - rescanHeight, .75 + rescanProg);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
String addr = "default-spv-nodes";
|
||||
if (node.uri.host != addr) {
|
||||
addr = node.uri.host;
|
||||
if (node.uri.port != "") {
|
||||
addr += ":" + node.uri.port.toString();
|
||||
}
|
||||
}
|
||||
if (addr != persistantPeer) {
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
syncTimer = null;
|
||||
}
|
||||
persistantPeer = addr;
|
||||
await _libwallet.closeWallet(walletInfo.name);
|
||||
final network = isTestnet ? "testnet" : "mainnet";
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: WalletType.decred);
|
||||
final config = {
|
||||
"name": walletInfo.name,
|
||||
"datadir": dirPath,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await _libwallet.loadWallet(jsonEncode(config));
|
||||
}
|
||||
await this._startSync();
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
await this._startSync();
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
Future<void> _startSync() async {
|
||||
if (syncTimer != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
await _libwallet.startSync(
|
||||
walletInfo.name,
|
||||
persistantPeer == "default-spv-nodes" ? "" : persistantPeer,
|
||||
);
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
if (watchingOnly) {
|
||||
return DecredPendingTransaction(
|
||||
txid: "",
|
||||
amount: 0,
|
||||
fee: 0,
|
||||
rawHex: "",
|
||||
send: () async {
|
||||
throw "unable to send with watching only wallet";
|
||||
});
|
||||
}
|
||||
var totalIn = 0;
|
||||
final ignoreInputs = [];
|
||||
this.unspentCoinsInfo.values.forEach((unspent) {
|
||||
if (unspent.isFrozen || !unspent.isSending) {
|
||||
final input = {"txid": unspent.hash, "vout": unspent.vout};
|
||||
ignoreInputs.add(input);
|
||||
return;
|
||||
}
|
||||
totalIn += unspent.value;
|
||||
});
|
||||
|
||||
final creds = credentials as DecredTransactionCredentials;
|
||||
var totalAmt = 0;
|
||||
var sendAll = false;
|
||||
final outputs = [];
|
||||
for (final out in creds.outputs) {
|
||||
var amt = 0;
|
||||
if (out.sendAll) {
|
||||
if (creds.outputs.length != 1) {
|
||||
throw "can only send all to one output";
|
||||
}
|
||||
sendAll = true;
|
||||
totalAmt = totalIn;
|
||||
} else if (out.cryptoAmount != null) {
|
||||
final coins = double.parse(out.cryptoAmount!);
|
||||
amt = (coins * 1e8).round();
|
||||
}
|
||||
totalAmt += amt;
|
||||
final o = {
|
||||
"address": out.isParsedAddress ? out.extractedAddress! : out.address,
|
||||
"amount": amt
|
||||
};
|
||||
outputs.add(o);
|
||||
}
|
||||
|
||||
// throw exception if no selected coins under coin control
|
||||
// or if the total coins selected, is less than the amount the user wants to spend
|
||||
if (ignoreInputs.length == unspentCoinsInfo.values.length || totalIn < totalAmt) {
|
||||
throw TransactionNoInputsException();
|
||||
}
|
||||
|
||||
// The inputs are always used. Currently we don't have use for this
|
||||
// argument. sendall ingores output value and sends everything.
|
||||
final signReq = {
|
||||
// "inputs": inputs,
|
||||
"ignoreInputs": ignoreInputs,
|
||||
"outputs": outputs,
|
||||
"feerate": creds.feeRate ?? defaultFeeRate,
|
||||
"password": _password,
|
||||
"sendall": sendAll,
|
||||
};
|
||||
final res = await _libwallet.createSignedTransaction(walletInfo.name, jsonEncode(signReq));
|
||||
final decoded = json.decode(res);
|
||||
final signedHex = decoded["signedhex"];
|
||||
final send = () async {
|
||||
await _libwallet.sendRawTransaction(walletInfo.name, signedHex);
|
||||
await updateBalance();
|
||||
};
|
||||
final fee = decoded["fee"] ?? 0;
|
||||
if (sendAll) {
|
||||
totalAmt = (totalAmt - fee).round();
|
||||
}
|
||||
return DecredPendingTransaction(
|
||||
txid: decoded["txid"] ?? "", amount: totalAmt, fee: fee, rawHex: signedHex, send: send);
|
||||
}
|
||||
|
||||
int feeRate(TransactionPriority priority) {
|
||||
if (!(priority is DecredTransactionPriority)) {
|
||||
return defaultFeeRate;
|
||||
}
|
||||
final p = priority;
|
||||
switch (p) {
|
||||
case DecredTransactionPriority.slow:
|
||||
return feeRateSlow.feeRate();
|
||||
case DecredTransactionPriority.medium:
|
||||
return feeRateMedium.feeRate();
|
||||
case DecredTransactionPriority.fast:
|
||||
return feeRateFast.feeRate();
|
||||
}
|
||||
return defaultFeeRate;
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||
if (priority is DecredTransactionPriority) {
|
||||
final P2PKHOutputSize =
|
||||
36; // 8 bytes value + 2 bytes version + at least 1 byte varint script size + P2PKHPkScriptSize
|
||||
// MsgTxOverhead is 4 bytes version (lower 2 bytes for the real transaction
|
||||
// version and upper 2 bytes for the serialization type) + 4 bytes locktime
|
||||
// + 4 bytes expiry + 3 bytes of varints for the number of transaction
|
||||
// inputs (x2 for witness and prefix) and outputs
|
||||
final MsgTxOverhead = 15;
|
||||
// TxInOverhead is the overhead for a wire.TxIn with a scriptSig length <
|
||||
// 254. prefix (41 bytes) + ValueIn (8 bytes) + BlockHeight (4 bytes) +
|
||||
// BlockIndex (4 bytes) + sig script var int (at least 1 byte)
|
||||
final TxInOverhead = 57;
|
||||
final P2PKHInputSize =
|
||||
TxInOverhead + 109; // TxInOverhead (57) + var int (1) + P2PKHSigScriptSize (108)
|
||||
|
||||
int inputsCount = 1;
|
||||
if (amount != null) {
|
||||
inputsCount += _unspents.where((e) {
|
||||
amount = (amount!) - e.value;
|
||||
return (amount!) > 0;
|
||||
}).length;
|
||||
}
|
||||
|
||||
// Estimate using a transaction consuming inoutsCount and paying to one address with change.
|
||||
return (this.feeRate(priority) / 1000).round() *
|
||||
(MsgTxOverhead + P2PKHInputSize * inputsCount + P2PKHOutputSize * 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, DecredTransactionInfo>> fetchTransactions() async {
|
||||
return this.fetchFiveTransactions(0);
|
||||
}
|
||||
|
||||
Future<Map<String, DecredTransactionInfo>> fetchFiveTransactions(int from) async {
|
||||
try {
|
||||
final res = await _libwallet.listTransactions(walletInfo.name, from.toString(), "5");
|
||||
final decoded = json.decode(res);
|
||||
var txs = <String, DecredTransactionInfo>{};
|
||||
for (final d in decoded) {
|
||||
final txid = uniqueTxID(d["txid"] ?? "", d["vout"] ?? 0);
|
||||
var direction = TransactionDirection.outgoing;
|
||||
if (d["category"] == "receive") {
|
||||
direction = TransactionDirection.incoming;
|
||||
}
|
||||
final amountDouble = d["amount"] ?? 0.0;
|
||||
final amount = (amountDouble * 1e8).round().abs();
|
||||
final feeDouble = d["fee"] ?? 0.0;
|
||||
final fee = (feeDouble * 1e8).round().abs();
|
||||
final confs = d["confirmations"] ?? 0;
|
||||
final sendTime = d["time"] ?? 0;
|
||||
final height = d["height"] ?? 0;
|
||||
final txInfo = DecredTransactionInfo(
|
||||
id: txid,
|
||||
amount: amount,
|
||||
fee: fee,
|
||||
direction: direction,
|
||||
isPending: confs == 0,
|
||||
date: DateTime.fromMillisecondsSinceEpoch(sendTime * 1000, isUtc: false),
|
||||
height: height,
|
||||
confirmations: confs,
|
||||
to: d["address"] ?? "",
|
||||
);
|
||||
txs[txid] = txInfo;
|
||||
}
|
||||
return txs;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// uniqueTxID combines the tx id and vout to create a unique id.
|
||||
String uniqueTxID(String id, int vout) {
|
||||
return id + ":" + vout.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {}
|
||||
|
||||
@override
|
||||
bool get hasRescan => walletBirthdayBlockHeight() != -1;
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) async {
|
||||
// The required height is not used. A birthday time is recorded in the
|
||||
// mnemonic. As long as not private data is imported into the wallet, we
|
||||
// can always rescan from there.
|
||||
var rescanHeight = 0;
|
||||
if (!watchingOnly) {
|
||||
rescanHeight = await walletBirthdayBlockHeight();
|
||||
// Sync has not yet reached the birthday block.
|
||||
if (rescanHeight == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await _libwallet.rescanFromHeight(walletInfo.name, rescanHeight.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
syncTimer = null;
|
||||
}
|
||||
await _libwallet.closeWallet(walletInfo.name);
|
||||
if (shouldCleanup) {
|
||||
await _libwallet.shutdown();
|
||||
_closeLibwallet();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) async {
|
||||
if (watchingOnly) {
|
||||
return;
|
||||
}
|
||||
return () async {
|
||||
await _libwallet.changeWalletPassword(walletInfo.name, _password, password);
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
final balanceMap = await _libwallet.balance(walletInfo.name);
|
||||
|
||||
var totalFrozen = 0;
|
||||
|
||||
unspentCoinsInfo.values.forEach((info) {
|
||||
_unspents.forEach((element) {
|
||||
if (element.hash == info.hash &&
|
||||
element.vout == info.vout &&
|
||||
info.isFrozen &&
|
||||
element.value == info.value) {
|
||||
totalFrozen += element.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
balance[CryptoCurrency.dcr] = DecredBalance(
|
||||
confirmed: balanceMap["confirmed"] ?? 0,
|
||||
unconfirmed: balanceMap["unconfirmed"] ?? 0,
|
||||
frozen: totalFrozen,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> checkNodeHealth() async => await checkSync();
|
||||
|
||||
@override
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => onError;
|
||||
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
|
||||
if (File(newDirPath).existsSync()) {
|
||||
throw "wallet already exists at $newDirPath";
|
||||
}
|
||||
|
||||
final sourceDir = Directory(currentDirPath);
|
||||
final targetDir = Directory(newDirPath);
|
||||
|
||||
if (!targetDir.existsSync()) {
|
||||
await targetDir.create(recursive: true);
|
||||
}
|
||||
|
||||
await for (final entity in sourceDir.list(recursive: true)) {
|
||||
final relativePath = entity.path.substring(sourceDir.path.length + 1);
|
||||
final targetPath = p.join(targetDir.path, relativePath);
|
||||
|
||||
if (entity is File) {
|
||||
await entity.rename(targetPath);
|
||||
} else if (entity is Directory) {
|
||||
await Directory(targetPath).create(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
await sourceDir.delete(recursive: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address = null}) async {
|
||||
if (watchingOnly) {
|
||||
throw "a watching only wallet cannot sign";
|
||||
}
|
||||
var addr = address;
|
||||
if (addr == null) {
|
||||
addr = walletAddresses.address;
|
||||
}
|
||||
if (addr == "") {
|
||||
throw "unable to get an address from unsynced wallet";
|
||||
}
|
||||
return await _libwallet.signMessage(walletInfo.name, message, addr, _password);
|
||||
}
|
||||
|
||||
Future<void> fetchUnspents() async {
|
||||
try {
|
||||
final res = await _libwallet.listUnspents(walletInfo.name);
|
||||
final decoded = json.decode(res);
|
||||
var unspents = <Unspent>[];
|
||||
for (final d in decoded) {
|
||||
final spendable = d["spendable"] ?? false;
|
||||
if (!spendable) {
|
||||
continue;
|
||||
}
|
||||
final amountDouble = d["amount"] ?? 0.0;
|
||||
final amount = (amountDouble * 1e8).round().abs();
|
||||
final utxo = Unspent(d["address"] ?? "", d["txid"] ?? "", amount, d["vout"] ?? 0, null);
|
||||
utxo.isChange = d["ischange"] ?? false;
|
||||
unspents.add(utxo);
|
||||
}
|
||||
_unspents = unspents;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
}
|
||||
|
||||
List<Unspent> unspents() {
|
||||
this.updateUnspents(_unspents);
|
||||
return _unspents;
|
||||
}
|
||||
|
||||
void updateUnspents(List<Unspent> unspentCoins) {
|
||||
if (this.unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => this.addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isEmpty) {
|
||||
this.unspentCoinsInfo.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final walletID = idPrefix + walletInfo.name;
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = this.unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId == walletID && element.hash == coin.hash && element.vout == coin.vout);
|
||||
|
||||
if (coinInfoList.isEmpty) {
|
||||
this.addCoinInfo(coin);
|
||||
} else {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final List<dynamic> keys = <dynamic>[];
|
||||
this.unspentCoinsInfo.values.forEach((element) {
|
||||
final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
|
||||
|
||||
if (existUnspentCoins.isEmpty) {
|
||||
keys.add(element.key);
|
||||
}
|
||||
});
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
unspentCoinsInfo.deleteAll(keys);
|
||||
}
|
||||
}
|
||||
|
||||
void addCoinInfo(Unspent coin) {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: idPrefix + walletInfo.name,
|
||||
hash: coin.hash,
|
||||
isFrozen: false,
|
||||
isSending: coin.isSending,
|
||||
noteRaw: "",
|
||||
address: coin.address,
|
||||
value: coin.value,
|
||||
vout: coin.vout,
|
||||
isChange: coin.isChange,
|
||||
keyImage: coin.keyImage,
|
||||
);
|
||||
|
||||
unspentCoinsInfo.add(newInfo);
|
||||
}
|
||||
|
||||
// walletBirthdayBlockHeight checks if the wallet birthday is set and returns
|
||||
// it. Returns -1 if not.
|
||||
Future<int> walletBirthdayBlockHeight() async {
|
||||
try {
|
||||
final res = await _libwallet.birthState(walletInfo.name);
|
||||
final decoded = json.decode(res);
|
||||
// Having these values set indicates that sync has not reached the birthday
|
||||
// yet, so no birthday is set.
|
||||
if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) {
|
||||
return -1;
|
||||
}
|
||||
return decoded["height"] ?? 0;
|
||||
} on FormatException catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
|
||||
var addr = address;
|
||||
if (addr == null) {
|
||||
throw "an address is required to verify message";
|
||||
}
|
||||
return () async {
|
||||
final verified = await _libwallet.verifyMessage(walletInfo.name, message, addr, signature);
|
||||
if (verified == "true") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
bool canSend() => seed != null;
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
|
||||
part 'wallet_addresses.g.dart';
|
||||
|
||||
class DecredWalletAddresses = DecredWalletAddressesBase with _$DecredWalletAddresses;
|
||||
|
||||
abstract class DecredWalletAddressesBase extends WalletAddresses with Store {
|
||||
DecredWalletAddressesBase(super.walletInfo, this._libwallet, super.isTestnet);
|
||||
|
||||
final Libwallet _libwallet;
|
||||
String _currentAddr = '';
|
||||
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@observable
|
||||
String selectedAddr = '';
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get address => selectedAddr;
|
||||
|
||||
@override
|
||||
set address(value) => selectedAddr = value;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
addressesMap = await walletInfo.getAddresses();
|
||||
addressInfos = await walletInfo.getAddressInfos();
|
||||
usedAddresses = await walletInfo.getUsedAddresses();
|
||||
manualAddresses = await walletInfo.getManualAddresses();
|
||||
hiddenAddresses = await walletInfo.getHiddenAddresses();
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
final addrs = await _libAddresses();
|
||||
final allAddrs = List.from(addrs.usedAddrs)..addAll(addrs.unusedAddrs);
|
||||
|
||||
// Add all addresses.
|
||||
allAddrs.forEach((addr) {
|
||||
if (addressesMap.containsKey(addr)) return;
|
||||
|
||||
addressesMap[addr] = "";
|
||||
addressInfos[0] ??= [];
|
||||
addressInfos[0]?.add(
|
||||
WalletInfoAddressInfo(
|
||||
walletInfoId: walletInfo.internalId,
|
||||
mapKey: 0,
|
||||
address: addr,
|
||||
label: "",
|
||||
accountIndex: 0,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// Add used addresses.
|
||||
addrs.usedAddrs.forEach((addr) {
|
||||
if (!usedAddresses.contains(addr)) usedAddresses.add(addr);
|
||||
});
|
||||
|
||||
if (addrs.unusedAddrs.length > 0 && addrs.unusedAddrs[0] != _currentAddr) {
|
||||
_currentAddr = addrs.unusedAddrs[0];
|
||||
selectedAddr = _currentAddr;
|
||||
}
|
||||
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
List<WalletInfoAddressInfo> getAddressInfos() {
|
||||
if (addressInfos.containsKey(0)) return addressInfos[0]!;
|
||||
|
||||
return <WalletInfoAddressInfo>[];
|
||||
}
|
||||
|
||||
Future<void> updateAddress(String address, String label) async {
|
||||
if (!addressInfos.containsKey(0)) return;
|
||||
|
||||
addressInfos[0]!.forEach((info) {
|
||||
if (info.address == address) info.label = label;
|
||||
});
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
Future<_LibAddresses> _libAddresses() async {
|
||||
final nUsed = "10";
|
||||
var nUnused = "1";
|
||||
if (this.isEnabledAutoGenerateSubaddress) nUnused = "3";
|
||||
|
||||
try {
|
||||
final res = await _libwallet.addresses(walletInfo.name, nUsed, nUnused);
|
||||
final decoded = json.decode(res);
|
||||
final usedAddrs = List<String>.from(decoded["used"] ?? []);
|
||||
final unusedAddrs = List<String>.from(decoded["unused"] ?? []);
|
||||
// index is the index of the first unused address.
|
||||
final index = decoded["index"] ?? 0;
|
||||
return _LibAddresses(usedAddrs, unusedAddrs, index);
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return _LibAddresses([], [], 0);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> generateNewAddress(String label) async {
|
||||
// NOTE: This will ignore the gap limit and may cause problems when restoring from seed if too
|
||||
// many addresses are taken and not used.
|
||||
final addr = await _libwallet.newExternalAddress(walletInfo.name) ?? '';
|
||||
if (addr == "") return;
|
||||
|
||||
if (!addressesMap.containsKey(addr)) {
|
||||
addressesMap[addr] = "";
|
||||
addressInfos[0] ??= [];
|
||||
addressInfos[0]?.add(
|
||||
WalletInfoAddressInfo(
|
||||
walletInfoId: walletInfo.internalId,
|
||||
mapKey: 0,
|
||||
address: addr,
|
||||
label: label,
|
||||
accountIndex: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
selectedAddr = addr;
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> get receivePageOptions => isTestnet
|
||||
? [
|
||||
ReceivePageOption.testnet,
|
||||
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
||||
]
|
||||
: ReceivePageOptions;
|
||||
|
||||
@override
|
||||
PaymentURI getPaymentUri(String amount) => DecredURI(address: address, amount: amount);
|
||||
}
|
||||
|
||||
class _LibAddresses {
|
||||
final List<String> usedAddrs, unusedAddrs;
|
||||
final int firstUnusedAddrIndex;
|
||||
|
||||
_LibAddresses(this.usedAddrs, this.unusedAddrs, this.firstUnusedAddrIndex);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
|
||||
class DecredNewWalletCredentials extends WalletCredentials {
|
||||
DecredNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||
: super(name: name, walletInfo: walletInfo);
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromPubkeyCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromPubkeyCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required String this.pubkey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String pubkey;
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromHardwareCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromHardwareCredentials(
|
||||
{required String name, required this.hwAccountData, WalletInfo? walletInfo})
|
||||
: t = throw UnimplementedError(),
|
||||
super(name: name, walletInfo: walletInfo);
|
||||
|
||||
final HardwareAccountData hwAccountData;
|
||||
final void t;
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
import 'package:cw_decred/wallet_creation_credentials.dart';
|
||||
import 'package:cw_decred/wallet.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
|
||||
class DecredWalletService extends WalletService<
|
||||
DecredNewWalletCredentials,
|
||||
DecredRestoreWalletFromSeedCredentials,
|
||||
DecredRestoreWalletFromPubkeyCredentials,
|
||||
DecredRestoreWalletFromHardwareCredentials> {
|
||||
DecredWalletService(this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final seedRestorePath = "m/44'/42'";
|
||||
static final seedRestorePathTestnet = "m/44'/1'";
|
||||
static final pubkeyRestorePath = "m/44'/42'/0'";
|
||||
static final pubkeyRestorePathTestnet = "m/44'/1'/0'";
|
||||
final mainnet = "mainnet";
|
||||
final testnet = "testnet";
|
||||
static Libwallet? libwallet;
|
||||
|
||||
Future<void> init() async {
|
||||
if (libwallet != null) {
|
||||
return;
|
||||
}
|
||||
libwallet = await Libwallet.spawn();
|
||||
// Init logging with no directory to force printing to stdout and only
|
||||
// print ERROR level logs.
|
||||
libwallet!.initLibdcrwallet("", "err");
|
||||
}
|
||||
|
||||
void closeLibwallet() {
|
||||
if (libwallet == null) {
|
||||
return;
|
||||
}
|
||||
libwallet!.close();
|
||||
libwallet = null;
|
||||
}
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.decred;
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<DecredWallet> create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
|
||||
final network = isTestnet == true ? testnet : mainnet;
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": dirPath,
|
||||
"pass": credentials.password!,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = await credentials.walletInfo!.getDerivationInfo();
|
||||
di.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath;
|
||||
await di.save();
|
||||
credentials.walletInfo!.save();
|
||||
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!, di, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
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
|
||||
Future<DecredWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = await WalletInfo.get(name, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final di = await walletInfo.getDerivationInfo();
|
||||
if (walletInfo.network == null || walletInfo.network == "") {
|
||||
walletInfo.network = di.derivationPath == seedRestorePathTestnet ||
|
||||
di.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
walletInfo.save();
|
||||
}
|
||||
|
||||
await this.init();
|
||||
|
||||
// Cake wallet version 4.27.0 and earlier gave a wallet dir that did not
|
||||
// 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 = {
|
||||
"name": name,
|
||||
"datadir": dirPath,
|
||||
"net": walletInfo.network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.loadWallet(jsonEncode(config));
|
||||
final wallet =
|
||||
DecredWallet(walletInfo, di, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = await WalletInfo.get(wallet, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
await WalletInfo.delete(walletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = await WalletInfo.get(currentName, getType());
|
||||
if (currentWalletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final di = await currentWalletInfo.getDerivationInfo();
|
||||
final network = di.derivationPath == seedRestorePathTestnet ||
|
||||
di.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
currentWalletInfo.network = network;
|
||||
currentWalletInfo.save();
|
||||
if (libwallet == null) {
|
||||
libwallet = await Libwallet.spawn();
|
||||
libwallet!.initLibdcrwallet("", "err");
|
||||
}
|
||||
final currentWallet = DecredWallet(
|
||||
currentWalletInfo, di, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
newWalletInfo.dirPath = "";
|
||||
newWalletInfo.path = "";
|
||||
|
||||
await newWalletInfo.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final network = isTestnet == true ? testnet : mainnet;
|
||||
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": dirPath,
|
||||
"pass": credentials.password!,
|
||||
"mnemonic": credentials.mnemonic,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = await credentials.walletInfo!.getDerivationInfo();
|
||||
di.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath;
|
||||
await di.save();
|
||||
credentials.walletInfo!.network = network;
|
||||
credentials.walletInfo!.dirPath = "";
|
||||
credentials.walletInfo!.path = "";
|
||||
final wallet = DecredWallet(credentials.walletInfo!, di, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
// restoreFromKeys only supports restoring a watch only wallet from an account
|
||||
// pubkey.
|
||||
@override
|
||||
Future<DecredWallet> restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final network = isTestnet == true ? testnet : mainnet;
|
||||
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": dirPath,
|
||||
"pubkey": credentials.pubkey,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWatchOnlyWallet(jsonEncode(config));
|
||||
final di = await credentials.walletInfo!.getDerivationInfo();
|
||||
di.derivationPath = isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath;
|
||||
await di.save();
|
||||
credentials.walletInfo!.network = network;
|
||||
credentials.walletInfo!.dirPath = "";
|
||||
credentials.walletInfo!.path = "";
|
||||
final wallet = DecredWallet(credentials.walletInfo!, di, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromHardwareWallet(
|
||||
DecredRestoreWalletFromHardwareCredentials credentials) async =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
public class CwDecredPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger)
|
||||
let instance = CwDecredPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_decred.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_decred'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'Cake Wallet Decred'
|
||||
s.description = 'Cake Wallet wrapper over Decred project'
|
||||
s.homepage = 'http://cakewallet.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
|
||||
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'FlutterMacOS'
|
||||
|
||||
s.platform = :osx, '10.11'
|
||||
s.vendored_libraries = 'External/lib/libdcrwallet.a'
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
|
||||
s.swift_version = '5.0'
|
||||
end
|
||||
@@ -1,950 +0,0 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "76.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.3"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.11.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.8"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
bech32:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
|
||||
url: "https://github.com/cake-tech/bech32.git"
|
||||
source: git
|
||||
version: "0.2.2"
|
||||
blockchain_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
|
||||
url: "https://github.com/cake-tech/blockchain_utils"
|
||||
source: git
|
||||
version: "3.3.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
build_resolvers:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.15"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.5"
|
||||
cake_backup:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||
url: "https://github.com/cake-tech/cake_backup.git"
|
||||
source: git
|
||||
version: "1.0.0+1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
cryptography:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
cupertino_icons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
cw_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../cw_core"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
decimal:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: decimal
|
||||
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
encrypt:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: encrypt
|
||||
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
ffigen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: ffigen
|
||||
sha256: "2119b4fe3aad0db94dc9531b90283c4640a6231070e613c400b426a4da08c704"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.1.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_mobx:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_mobx
|
||||
sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
hive_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_generator
|
||||
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mobx:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mobx
|
||||
sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mobx_codegen
|
||||
sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
on_chain:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "096865a8c6b89c260beadfec04f7e184c40a3273"
|
||||
resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273"
|
||||
url: "https://github.com/cake-tech/on_chain.git"
|
||||
source: git
|
||||
version: "3.7.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.15"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
rational:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rational
|
||||
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
socks5_proxy:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
|
||||
resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
|
||||
url: "https://github.com/LacticWhale/socks_dart"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4+6"
|
||||
sqflite_common_ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common_ffi
|
||||
sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.4+4"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1+1"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.40"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "../scripts/torch_dart"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
unorm_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unorm_dart
|
||||
sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
yaml_edit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml_edit
|
||||
sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
sdks:
|
||||
dart: ">=3.7.0-0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
@@ -1,83 +0,0 @@
|
||||
name: cw_decred
|
||||
description: A new Flutter plugin project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
author: Hash Wallet
|
||||
homepage: https://github.com/Such-Software/hash-wallet
|
||||
|
||||
environment:
|
||||
sdk: ">=3.2.0-0 <4.0.0"
|
||||
flutter: ">=3.19.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.4.15
|
||||
build_resolvers: ^2.4.4
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^2.0.1
|
||||
ffigen: ^16.1.0
|
||||
|
||||
ffigen:
|
||||
name: libdcrwallet
|
||||
description: Bindings for dcrwallet go library.
|
||||
output: "lib/api/libdcrwallet_bindings.dart"
|
||||
headers:
|
||||
entry-points:
|
||||
- "lib/api/libdcrwallet.h"
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The androidPackage and pluginClass identifiers should not ordinarily
|
||||
# be modified. They are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.cakewallet.cw_decred
|
||||
pluginClass: CwDecredPlugin
|
||||
ios:
|
||||
pluginClass: CwDecredPlugin
|
||||
macos:
|
||||
pluginClass: CwDecredPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
||||
30
cw_solana/.gitignore
vendored
30
cw_solana/.gitignore
vendored
@@ -1,30 +0,0 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
@@ -1,10 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,63 +0,0 @@
|
||||
## cw_solana
|
||||
|
||||
Solana wallet module for Cake Wallet. Provides native SOL and SPL token support built on `on_chain/solana` with high-throughput RPC usage and safe transaction parsing.
|
||||
|
||||
### Features
|
||||
|
||||
- Connect to Solana RPC (Ankr/Chainstack/custom) via `SolanaRPC` over HTTP.
|
||||
- Fetch SOL balances and aggregate SPL token balances across accounts.
|
||||
- Parse and stream native and SPL token transactions (filters ATA-only and spam-like micro txs).
|
||||
- Estimate fees per compiled message and enforce rent-exemption checks.
|
||||
- Create/sign/broadcast SOL and SPL transfers; auto-create recipient ATA when necessary.
|
||||
- Manage SPL tokens; fetch on-chain metadata (symbol/name) for unknown mints.
|
||||
- Sign and verify messages.
|
||||
- Node health checks for SOL and a known SPL token (USDC).
|
||||
|
||||
### Getting started
|
||||
|
||||
If you use hosted RPC providers, add a secrets file for keys (optional unless using those hosts):
|
||||
|
||||
```dart
|
||||
// cw_solana/lib/.secrets.g.dart (DO NOT COMMIT)
|
||||
const String ankrApiKey = 'YOUR_ANKR_KEY';
|
||||
const String chainStackApiKey = 'YOUR_CHAINSTACK_KEY';
|
||||
```
|
||||
|
||||
Connect and sync:
|
||||
|
||||
```dart
|
||||
final service = SolanaWalletService(walletInfoBox, true);
|
||||
final wallet = await service.create(SolanaNewWalletCredentials(name: 'My SOL', password: 'secret'));
|
||||
await wallet.connectToNode(node: Node(uriRaw: 'api.mainnet-beta.solana.com', isSSL: true));
|
||||
await wallet.startSync();
|
||||
final sol = wallet.balance[CryptoCurrency.sol]?.balance;
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Send SOL:
|
||||
|
||||
```dart
|
||||
final pending = await wallet.createTransaction(
|
||||
SolanaTransactionCredentials.single(
|
||||
address: 'SoL...',
|
||||
cryptoAmount: '0.05',
|
||||
currency: CryptoCurrency.sol,
|
||||
),
|
||||
);
|
||||
final sig = await pending.commit();
|
||||
```
|
||||
|
||||
Add an SPL token by mint:
|
||||
|
||||
```dart
|
||||
final token = await wallet.getSPLToken('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); // USDC
|
||||
if (token != null) {
|
||||
await wallet.addSPLToken(token);
|
||||
}
|
||||
```
|
||||
|
||||
### Additional information
|
||||
|
||||
- When using `rpc.ankr.com` or `solana-mainnet.core.chainstack.com`, the client reads API keys from `.secrets.g.dart`.
|
||||
- See `lib/` for APIs: `SolanaWalletClient`, `SolanaWallet`, `SolanaWalletService`, and credential types.
|
||||
@@ -1,4 +0,0 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,7 +0,0 @@
|
||||
library cw_solana;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
||||
@@ -1,703 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
|
||||
class DefaultSPLTokens {
|
||||
final List<SPLToken> _defaultTokens = [
|
||||
SPLToken(
|
||||
name: 'USDT Tether',
|
||||
symbol: 'USDT',
|
||||
mintAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
|
||||
decimal: 6,
|
||||
mint: 'usdtsol',
|
||||
enabled: true,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'USD Coin',
|
||||
symbol: 'USDC',
|
||||
mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
decimal: 6,
|
||||
mint: 'usdcsol',
|
||||
enabled: true,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Bonk',
|
||||
symbol: 'Bonk',
|
||||
mintAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263',
|
||||
decimal: 5,
|
||||
mint: 'Bonk',
|
||||
iconPath: 'assets/images/bonk_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Raydium',
|
||||
symbol: 'RAY',
|
||||
mintAddress: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
|
||||
decimal: 6,
|
||||
mint: 'ray',
|
||||
iconPath: 'assets/images/ray_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Wrapped Ethereum (Sollet)',
|
||||
symbol: 'soETH',
|
||||
mintAddress: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk',
|
||||
decimal: 6,
|
||||
mint: 'soEth',
|
||||
iconPath: 'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Wrapped SOL',
|
||||
symbol: 'WSOL',
|
||||
mintAddress: 'So11111111111111111111111111111111111111112',
|
||||
decimal: 9,
|
||||
mint: 'WSOL',
|
||||
iconPath: 'assets/new-ui/crypto_full_icons/solana.svg',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Wrapped Bitcoin (Sollet)',
|
||||
symbol: 'BTC',
|
||||
mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
|
||||
decimal: 6,
|
||||
mint: 'btcsol',
|
||||
iconPath: 'assets/new-ui/crypto_full_icons/bitcoin.svg',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Helium Network Token',
|
||||
symbol: 'HNT',
|
||||
mintAddress: 'hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux',
|
||||
decimal: 8,
|
||||
mint: 'hnt',
|
||||
iconPath: 'assets/images/hnt_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Pyth Network',
|
||||
symbol: 'PYTH',
|
||||
mintAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3',
|
||||
decimal: 6,
|
||||
mint: 'pyth',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'GMT',
|
||||
symbol: 'GMT',
|
||||
mintAddress: '7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx',
|
||||
decimal: 6,
|
||||
mint: 'ray',
|
||||
iconPath: 'assets/images/gmt_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AvocadoCoin',
|
||||
symbol: 'AVDO',
|
||||
mintAddress: 'EE5L8cMU4itTsCSuor7NLK6RZx6JhsBe8GGV3oaAHm3P',
|
||||
decimal: 8,
|
||||
mint: 'avdo',
|
||||
iconPath: 'assets/images/avdo_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: "Tether Gold",
|
||||
symbol: "XAUT0",
|
||||
mintAddress: "AymATz4TCL9sWNEEV9Kvyz45CHVhDZ6kUgjTJPzLpU9P",
|
||||
decimal: 6,
|
||||
mint: 'xaut0',
|
||||
enabled: false,
|
||||
iconPath: "assets/images/xau_sol.png",
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Abbott xStock',
|
||||
symbol: 'ABTx',
|
||||
mintAddress: 'XsHtf5RpxsQ7jeJ9ivNewouZKJHbPxhPoEy6yYvULr7',
|
||||
decimal: 8,
|
||||
mint: 'abtx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/abtx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AbbVie xStock',
|
||||
symbol: 'ABBVx',
|
||||
mintAddress: 'XswbinNKyPmzTa5CskMbCPvMW6G5CMnZXZEeQSSQoie',
|
||||
decimal: 8,
|
||||
mint: 'abbvx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/abbv.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Accenture xStock',
|
||||
symbol: 'ACNx',
|
||||
mintAddress: 'Xs5UJzmCRQ8DWZjskExdSQDnbE6iLkRu2jjrRAB1JSU',
|
||||
decimal: 8,
|
||||
mint: 'acnx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/acnx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Alphabet xStock',
|
||||
symbol: 'GOOGLx',
|
||||
mintAddress: 'XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN',
|
||||
decimal: 8,
|
||||
mint: 'googlx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/googlx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Amazon xStock',
|
||||
symbol: 'AMZNx',
|
||||
mintAddress: 'Xs3eBt7uRfJX8QUs4suhyU8p2M6DoUDrJyWBa8LLZsg',
|
||||
decimal: 8,
|
||||
mint: 'amznx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/amznx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Amber xStock',
|
||||
symbol: 'AMBRx',
|
||||
mintAddress: 'XsaQTCgebC2KPbf27KUhdv5JFvHhQ4GDAPURwrEhAzb',
|
||||
decimal: 8,
|
||||
mint: 'ambrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/ambrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Apple xStock',
|
||||
symbol: 'AAPLx',
|
||||
mintAddress: 'XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp',
|
||||
decimal: 8,
|
||||
mint: 'aaplx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/apple.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AppLovin xStock',
|
||||
symbol: 'APPx',
|
||||
mintAddress: 'XsPdAVBi8Zc1xvv53k4JcMrQaEDTgkGqKYeh7AYgPHV',
|
||||
decimal: 8,
|
||||
mint: 'appx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/appx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AstraZeneca xStock',
|
||||
symbol: 'AZNx',
|
||||
mintAddress: 'Xs3ZFkPYT2BN7qBMqf1j1bfTeTm1rFzEFSsQ1z3wAKU',
|
||||
decimal: 8,
|
||||
mint: 'aznx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/aznx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Bank of America xStock',
|
||||
symbol: 'BACx',
|
||||
mintAddress: 'XswsQk4duEQmCbGzfqUUWYmi7pV7xpJ9eEmLHXCaEQP',
|
||||
decimal: 8,
|
||||
mint: 'bacx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/bacx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Berkshire Hathaway xStock',
|
||||
symbol: 'BRK.Bx',
|
||||
mintAddress: 'Xs6B6zawENwAbWVi7w92rjazLuAr5Az59qgWKcNb45x',
|
||||
decimal: 8,
|
||||
mint: 'brkbx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/brkbx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Broadcom xStock',
|
||||
symbol: 'AVGOx',
|
||||
mintAddress: 'XsgSaSvNSqLTtFuyWPBhK9196Xb9Bbdyjj4fH3cPJGo',
|
||||
decimal: 8,
|
||||
mint: 'avgox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/avgox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Chevron xStock',
|
||||
symbol: 'CVXx',
|
||||
mintAddress: 'XsNNMt7WTNA2sV3jrb1NNfNgapxRF5i4i6GcnTRRHts',
|
||||
decimal: 8,
|
||||
mint: 'cvxx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/cvxx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Circle xStock',
|
||||
symbol: 'CRCLx',
|
||||
mintAddress: 'XsueG8BtpquVJX9LVLLEGuViXUungE6WmK5YZ3p3bd1',
|
||||
decimal: 8,
|
||||
mint: 'crclx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/crclx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Cisco xStock',
|
||||
symbol: 'CSCOx',
|
||||
mintAddress: 'Xsr3pdLQyXvDJBFgpR5nexCEZwXvigb8wbPYp4YoNFf',
|
||||
decimal: 8,
|
||||
mint: 'cscx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/cscox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Coca-Cola xStock',
|
||||
symbol: 'KOx',
|
||||
mintAddress: 'XsaBXg8dU5cPM6ehmVctMkVqoiRG2ZjMo1cyBJ3AykQ',
|
||||
decimal: 8,
|
||||
mint: 'kox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/kox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Coinbase xStock',
|
||||
symbol: 'COINx',
|
||||
mintAddress: 'Xs7ZdzSHLU9ftNJsii5fCeJhoRWSC32SQGzGQtePxNu',
|
||||
decimal: 8,
|
||||
mint: 'coinx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/coinx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Comcast xStock',
|
||||
symbol: 'CMCSAx',
|
||||
mintAddress: 'XsvKCaNsxg2GN8jjUmq71qukMJr7Q1c5R2Mk9P8kcS8',
|
||||
decimal: 8,
|
||||
mint: 'cmcsax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/cmcsax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'CrowdStrike xStock',
|
||||
symbol: 'CRWDx',
|
||||
mintAddress: 'Xs7xXqkcK7K8urEqGg52SECi79dRp2cEKKuYjUePYDw',
|
||||
decimal: 8,
|
||||
mint: 'crwdx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/crwdx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Danaher xStock',
|
||||
symbol: 'DHRx',
|
||||
mintAddress: 'Xseo8tgCZfkHxWS9xbFYeKFyMSbWEvZGFV1Gh53GtCV',
|
||||
decimal: 8,
|
||||
mint: 'dhrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/dhrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'DFDV xStock',
|
||||
symbol: 'DFDVx',
|
||||
mintAddress: 'Xs2yquAgsHByNzx68WJC55WHjHBvG9JsMB7CWjTLyPy',
|
||||
decimal: 8,
|
||||
mint: 'dfdvx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/dfdvx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Eli Lilly xStock',
|
||||
symbol: 'LLYx',
|
||||
mintAddress: 'Xsnuv4omNoHozR6EEW5mXkw8Nrny5rB3jVfLqi6gKMH',
|
||||
decimal: 8,
|
||||
mint: 'llyx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/llyx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Exxon Mobil xStock',
|
||||
symbol: 'XOMx',
|
||||
mintAddress: 'XsaHND8sHyfMfsWPj6kSdd5VwvCayZvjYgKmmcNL5qh',
|
||||
decimal: 8,
|
||||
mint: 'xomx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/xomx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Gamestop xStock',
|
||||
symbol: 'GMEx',
|
||||
mintAddress: 'Xsf9mBktVB9BSU5kf4nHxPq5hCBJ2j2ui3ecFGxPRGc',
|
||||
decimal: 8,
|
||||
mint: 'gmex',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/gmex.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Gold xStock',
|
||||
symbol: 'GLDx',
|
||||
mintAddress: 'Xsv9hRk1z5ystj9MhnA7Lq4vjSsLwzL2nxrwmwtD3re',
|
||||
decimal: 8,
|
||||
mint: 'gldx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/gldx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Goldman Sachs xStock',
|
||||
symbol: 'GSx',
|
||||
mintAddress: 'XsgaUyp4jd1fNBCxgtTKkW64xnnhQcvgaxzsbAq5ZD1',
|
||||
decimal: 8,
|
||||
mint: 'gsx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/gsx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Home Depot xStock',
|
||||
symbol: 'HDx',
|
||||
mintAddress: 'XszjVtyhowGjSC5odCqBpW1CtXXwXjYokymrk7fGKD3',
|
||||
decimal: 8,
|
||||
mint: 'hdx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/hdx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Honeywell xStock',
|
||||
symbol: 'HONx',
|
||||
mintAddress: 'XsRbLZthfABAPAfumWNEJhPyiKDW6TvDVeAeW7oKqA2',
|
||||
decimal: 8,
|
||||
mint: 'honx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/honx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Intel xStock',
|
||||
symbol: 'INTCx',
|
||||
mintAddress: 'XshPgPdXFRWB8tP1j82rebb2Q9rPgGX37RuqzohmArM',
|
||||
decimal: 8,
|
||||
mint: 'intcx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/intcx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'International Business Machines xStock',
|
||||
symbol: 'IBMx',
|
||||
mintAddress: 'XspwhyYPdWVM8XBHZnpS9hgyag9MKjLRyE3tVfmCbSr',
|
||||
decimal: 8,
|
||||
mint: 'ibmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/ibmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Johnson & Johnson xStock',
|
||||
symbol: 'JNJx',
|
||||
mintAddress: 'XsGVi5eo1Dh2zUpic4qACcjuWGjNv8GCt3dm5XcX6Dn',
|
||||
decimal: 8,
|
||||
mint: 'jnjx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/jnjx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'JPMorgan Chase xStock',
|
||||
symbol: 'JPMx',
|
||||
mintAddress: 'XsMAqkcKsUewDrzVkait4e5u4y8REgtyS7jWgCpLV2C',
|
||||
decimal: 8,
|
||||
mint: 'jpmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/jpmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Linde xStock',
|
||||
symbol: 'LINx',
|
||||
mintAddress: 'XsSr8anD1hkvNMu8XQiVcmiaTP7XGvYu7Q58LdmtE8Z',
|
||||
decimal: 8,
|
||||
mint: 'linx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/linx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Marvell xStock',
|
||||
symbol: 'MRVLx',
|
||||
mintAddress: 'XsuxRGDzbLjnJ72v74b7p9VY6N66uYgTCyfwwRjVCJA',
|
||||
decimal: 8,
|
||||
mint: 'mrvlx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mrvlx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Mastercard xStock',
|
||||
symbol: 'MAx',
|
||||
mintAddress: 'XsApJFV9MAktqnAc6jqzsHVujxkGm9xcSUffaBoYLKC',
|
||||
decimal: 8,
|
||||
mint: 'max',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mastercard.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'McDonald\'s xStock',
|
||||
symbol: 'MCDx',
|
||||
mintAddress: 'XsqE9cRRpzxcGKDXj1BJ7Xmg4GRhZoyY1KpmGSxAWT2',
|
||||
decimal: 8,
|
||||
mint: 'mcdx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mcdonalds.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Medtronic xStock',
|
||||
symbol: 'MDTx',
|
||||
mintAddress: 'XsDgw22qRLTv5Uwuzn6T63cW69exG41T6gwQhEK22u2',
|
||||
decimal: 8,
|
||||
mint: 'mdtx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mdtx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Merck xStock',
|
||||
symbol: 'MRKx',
|
||||
mintAddress: 'XsnQnU7AdbRZYe2akqqpibDdXjkieGFfSkbkjX1Sd1X',
|
||||
decimal: 8,
|
||||
mint: 'mrkx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mrkx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Meta xStock',
|
||||
symbol: 'METAx',
|
||||
mintAddress: 'Xsa62P5mvPszXL1krVUnU5ar38bBSVcWAB6fmPCo5Zu',
|
||||
decimal: 8,
|
||||
mint: 'metax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/metax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Microsoft xStock',
|
||||
symbol: 'MSFTx',
|
||||
mintAddress: 'XspzcW1PRtgf6Wj92HCiZdjzKCyFekVD8P5Ueh3dRMX',
|
||||
decimal: 8,
|
||||
mint: 'msftx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/msftx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'MicroStrategy xStock',
|
||||
symbol: 'MSTRx',
|
||||
mintAddress: 'XsP7xzNPvEHS1m6qfanPUGjNmdnmsLKEoNAnHjdxxyZ',
|
||||
decimal: 8,
|
||||
mint: 'mstrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mstrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Nasdaq xStock',
|
||||
symbol: 'QQQx',
|
||||
mintAddress: 'Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ',
|
||||
decimal: 8,
|
||||
mint: 'qqqx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/qqqx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Netflix xStock',
|
||||
symbol: 'NFLXx',
|
||||
mintAddress: 'XsEH7wWfJJu2ZT3UCFeVfALnVA6CP5ur7Ee11KmzVpL',
|
||||
decimal: 8,
|
||||
mint: 'nflxx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/nflxx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Novo Nordisk xStock',
|
||||
symbol: 'NVOx',
|
||||
mintAddress: 'XsfAzPzYrYjd4Dpa9BU3cusBsvWfVB9gBcyGC87S57n',
|
||||
decimal: 8,
|
||||
mint: 'nvox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/nvox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'NVIDIA xStock',
|
||||
symbol: 'NVDAx',
|
||||
mintAddress: 'Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh',
|
||||
decimal: 8,
|
||||
mint: 'nvdax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/nvdax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'OPEN xStock',
|
||||
symbol: 'OPENx',
|
||||
mintAddress: 'XsGtpmjhmC8kyjVSWL4VicGu36ceq9u55PTgF8bhGv6',
|
||||
decimal: 8,
|
||||
mint: 'openx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/openx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Oracle xStock',
|
||||
symbol: 'ORCLx',
|
||||
mintAddress: 'XsjFwUPiLofddX5cWFHW35GCbXcSu1BCUGfxoQAQjeL',
|
||||
decimal: 8,
|
||||
mint: 'orclx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/orclx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Palantir xStock',
|
||||
symbol: 'PLTRx',
|
||||
mintAddress: 'XsoBhf2ufR8fTyNSjqfU71DYGaE6Z3SUGAidpzriAA4',
|
||||
decimal: 8,
|
||||
mint: 'pltrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pltrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'PepsiCo xStock',
|
||||
symbol: 'PEPx',
|
||||
mintAddress: 'Xsv99frTRUeornyvCfvhnDesQDWuvns1M852Pez91vF',
|
||||
decimal: 8,
|
||||
mint: 'pepx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pepx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Pfizer xStock',
|
||||
symbol: 'PFEx',
|
||||
mintAddress: 'XsAtbqkAP1HJxy7hFDeq7ok6yM43DQ9mQ1Rh861X8rw',
|
||||
decimal: 8,
|
||||
mint: 'pfex',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pfex.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Philip Morris xStock',
|
||||
symbol: 'PMx',
|
||||
mintAddress: 'Xsba6tUnSjDae2VcopDB6FGGDaxRrewFCDa5hKn5vT3',
|
||||
decimal: 8,
|
||||
mint: 'pmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Procter & Gamble xStock',
|
||||
symbol: 'PGx',
|
||||
mintAddress: 'XsYdjDjNUygZ7yGKfQaB6TxLh2gC6RRjzLtLAGJrhzV',
|
||||
decimal: 8,
|
||||
mint: 'pgx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pgx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Robinhood xStock',
|
||||
symbol: 'HOODx',
|
||||
mintAddress: 'XsvNBAYkrDRNhA7wPHQfX3ZUXZyZLdnCQDfHZ56bzpg',
|
||||
decimal: 8,
|
||||
mint: 'hoodx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/hoodx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Salesforce xStock',
|
||||
symbol: 'CRMx',
|
||||
mintAddress: 'XsczbcQ3zfcgAEt9qHQES8pxKAVG5rujPSHQEXi4kaN',
|
||||
decimal: 8,
|
||||
mint: 'crmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/crmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'SP500 xStock',
|
||||
symbol: 'SPYx',
|
||||
mintAddress: 'XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W',
|
||||
decimal: 8,
|
||||
mint: 'spyx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/spyx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'TBLL xStock',
|
||||
symbol: 'TBLLx',
|
||||
mintAddress: 'XsqBC5tcVQLYt8wqGCHRnAUUecbRYXoJCReD6w7QEKp',
|
||||
decimal: 8,
|
||||
mint: 'tbllx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tbllx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Tesla xStock',
|
||||
symbol: 'TSLAx',
|
||||
mintAddress: 'XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB',
|
||||
decimal: 8,
|
||||
mint: 'tslax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tslax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Thermo Fisher xStock',
|
||||
symbol: 'TMOx',
|
||||
mintAddress: 'Xs8drBWy3Sd5QY3aifG9kt9KFs2K3PGZmx7jWrsrk57',
|
||||
decimal: 8,
|
||||
mint: 'tmox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tmox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'TON xStock',
|
||||
symbol: 'TONXx',
|
||||
mintAddress: 'XscE4GUcsYhcyZu5ATiGUMmhxYa1D5fwbpJw4K6K4dp',
|
||||
decimal: 8,
|
||||
mint: 'tonxx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tonxx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'TQQQ xStock',
|
||||
symbol: 'TQQQx',
|
||||
mintAddress: 'XsjQP3iMAaQ3kQScQKthQpx9ALRbjKAjQtHg6TFomoc',
|
||||
decimal: 8,
|
||||
mint: 'tqqqx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tqqqx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'UnitedHealth xStock',
|
||||
symbol: 'UNHx',
|
||||
mintAddress: 'XszvaiXGPwvk2nwb3o9C1CX4K6zH8sez11E6uyup6fe',
|
||||
decimal: 8,
|
||||
mint: 'unhx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/unhx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Vanguard xStock',
|
||||
symbol: 'VTIx',
|
||||
mintAddress: 'XsssYEQjzxBCFgvYFFNuhJFBeHNdLWYeUSP8F45cDr9',
|
||||
decimal: 8,
|
||||
mint: 'vtix',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/vtix.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Visa xStock',
|
||||
symbol: 'Vx',
|
||||
mintAddress: 'XsqgsbXwWogGJsNcVZ3TyVouy2MbTkfCFhCGGGcQZ2p',
|
||||
decimal: 8,
|
||||
mint: 'vx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/vx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Walmart xStock',
|
||||
symbol: 'WMTx',
|
||||
mintAddress: 'Xs151QeqTCiuKtinzfRATnUESM2xTU6V9Wy8Vy538ci',
|
||||
decimal: 8,
|
||||
mint: 'wmtx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/wmtx.webp',
|
||||
),
|
||||
];
|
||||
|
||||
List<SPLToken> get initialSPLTokens => _defaultTokens.map((token) {
|
||||
String? iconPath;
|
||||
if (token.iconPath?.isEmpty ?? true) {
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
iconPath = token.iconPath;
|
||||
}
|
||||
|
||||
return SPLToken.copyWith(token, icon: iconPath, tag: 'SOL');
|
||||
}).toList();
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
|
||||
class PendingSolanaTransaction with PendingTransaction {
|
||||
final double amount;
|
||||
final String serializedTransaction;
|
||||
final String destinationAddress;
|
||||
final Function sendTransaction;
|
||||
final double fee;
|
||||
String? _sig;
|
||||
|
||||
PendingSolanaTransaction({
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
required this.serializedTransaction,
|
||||
required this.destinationAddress,
|
||||
required this.sendTransaction,
|
||||
});
|
||||
|
||||
@override
|
||||
String get amountFormatted {
|
||||
String stringifiedAmount = amount.toString();
|
||||
|
||||
if (stringifiedAmount.toString().length >= 6) {
|
||||
stringifiedAmount = stringifiedAmount.substring(0, 6);
|
||||
}
|
||||
|
||||
return stringifiedAmount;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
_sig = await sendTransaction();
|
||||
}
|
||||
|
||||
@override
|
||||
String get feeFormatted => "$feeFormattedValue SOL";
|
||||
|
||||
@override
|
||||
String get feeFormattedValue => fee.toString();
|
||||
|
||||
@override
|
||||
String get hex => serializedTransaction;
|
||||
|
||||
@override
|
||||
String get id => _sig ?? '';
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class SolanaBalance extends Balance {
|
||||
SolanaBalance(this.balance, bool isToken) : super(
|
||||
BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0),
|
||||
BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0));
|
||||
|
||||
// Using raw amount from RPC to avoid decimals mismatch for SPL tokens.
|
||||
SolanaBalance.forToken(BigInt rawAmount, double uiAmount)
|
||||
: balance = uiAmount,
|
||||
super(rawAmount, rawAmount);
|
||||
|
||||
final double balance;
|
||||
|
||||
String get formattedAdditionalBalance => _balanceFormatted();
|
||||
|
||||
String get formattedAvailableBalance => _balanceFormatted();
|
||||
|
||||
String _balanceFormatted() {
|
||||
String stringBalance = balance.toString();
|
||||
if (stringBalance.toString().length >= 12) {
|
||||
stringBalance = stringBalance.substring(0, 12);
|
||||
}
|
||||
return stringBalance;
|
||||
}
|
||||
|
||||
static SolanaBalance? fromJSON(String? jsonSource, bool isToken) {
|
||||
if (jsonSource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
try {
|
||||
return SolanaBalance(decoded['balance'], isToken);
|
||||
} catch (e) {
|
||||
return SolanaBalance(0.0, isToken);
|
||||
}
|
||||
}
|
||||
|
||||
String toJSON() => json.encode({'balance': balance.toString()});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
|
||||
class SolanaTransactionCreationException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
SolanaTransactionCreationException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Error creating ${currency.title} transaction.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
||||
|
||||
class SolanaTransactionWrongBalanceException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
SolanaTransactionWrongBalanceException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
||||
|
||||
class SolanaSignNativeTokenTransactionRentException
|
||||
extends SignNativeTokenTransactionRentException {}
|
||||
|
||||
class SolanaCreateAssociatedTokenAccountException extends CreateAssociatedTokenAccountException {
|
||||
SolanaCreateAssociatedTokenAccountException(super.errorMessage);
|
||||
}
|
||||
|
||||
class SolanaSignSPLTokenTransactionRentException extends SignSPLTokenTransactionRentException {}
|
||||
|
||||
class SolanaNoAssociatedTokenAccountException extends NoAssociatedTokenAccountException {
|
||||
SolanaNoAssociatedTokenAccountException(this.account, this.mint);
|
||||
|
||||
final String account;
|
||||
final String mint;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class SolanaTransactionCredentials {
|
||||
SolanaTransactionCredentials(
|
||||
this.outputs, {
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final CryptoCurrency currency;
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:core';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_solana/solana_transaction_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
part 'solana_transaction_history.g.dart';
|
||||
|
||||
const transactionsHistoryFileName = 'solana_transactions.json';
|
||||
|
||||
class SolanaTransactionHistory = SolanaTransactionHistoryBase with _$SolanaTransactionHistory;
|
||||
|
||||
abstract class SolanaTransactionHistoryBase extends TransactionHistoryBase<SolanaTransactionInfo>
|
||||
with Store {
|
||||
SolanaTransactionHistoryBase(
|
||||
{required this.walletInfo, required String password, required this.encryptionFileUtils})
|
||||
: _password = password {
|
||||
transactions = ObservableMap<String, SolanaTransactionInfo>();
|
||||
}
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
String _password;
|
||||
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
try {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||
} catch (e, s) {
|
||||
printV('Error while saving solana transaction history: ${e.toString()}');
|
||||
printV(s);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(SolanaTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, SolanaTransactionInfo> transactions) =>
|
||||
this.transactions.addAll(transactions);
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final content = await encryptionFileUtils.read(path: path, password: _password);
|
||||
if (content.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final content = await _read();
|
||||
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
txs.entries.forEach((entry) {
|
||||
final val = entry.value;
|
||||
|
||||
if (val is Map<String, dynamic>) {
|
||||
final tx = SolanaTransactionInfo.fromJson(val);
|
||||
_update(tx);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
}
|
||||
|
||||
void _update(SolanaTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
|
||||
class SolanaTransactionInfo extends TransactionInfo {
|
||||
SolanaTransactionInfo({
|
||||
required this.id,
|
||||
required this.blockTime,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.direction,
|
||||
required this.solAmount,
|
||||
this.tokenSymbol = "SOL",
|
||||
required this.isPending,
|
||||
required this.txFee,
|
||||
}) : amount = solAmount.toInt();
|
||||
|
||||
final String id;
|
||||
final String? to;
|
||||
final String? from;
|
||||
final int amount;
|
||||
final bool isPending;
|
||||
final double solAmount;
|
||||
final String tokenSymbol;
|
||||
final DateTime blockTime;
|
||||
final double txFee;
|
||||
final TransactionDirection direction;
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
DateTime get date => blockTime;
|
||||
|
||||
@override
|
||||
String amountFormatted() {
|
||||
String stringBalance = solAmount.toString();
|
||||
if (stringBalance.toString().length >= 12) {
|
||||
stringBalance = stringBalance.substring(0, 12);
|
||||
}
|
||||
return '$stringBalance $tokenSymbol';
|
||||
}
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
@override
|
||||
String feeFormatted() => '${txFee.toString()} SOL';
|
||||
|
||||
factory SolanaTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||
return SolanaTransactionInfo(
|
||||
id: data['id'] as String,
|
||||
solAmount: data['solAmount'],
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
tokenSymbol: data['tokenSymbol'] as String,
|
||||
to: data['to'],
|
||||
from: data['from'],
|
||||
txFee: data['txFee'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'solAmount': solAmount,
|
||||
'direction': direction.index,
|
||||
'blockTime': blockTime.millisecondsSinceEpoch,
|
||||
'isPending': isPending,
|
||||
'tokenSymbol': tokenSymbol,
|
||||
'to': to,
|
||||
'from': from,
|
||||
'txFee': txFee,
|
||||
};
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
class SolanaTransactionModel {
|
||||
final String id;
|
||||
|
||||
final String from;
|
||||
|
||||
final String to;
|
||||
|
||||
final double amount;
|
||||
|
||||
// If this is an outgoing transaction
|
||||
final bool isOutgoingTx;
|
||||
|
||||
// The Program ID of this transaction, e.g, System Program, Token Program...
|
||||
final String programId;
|
||||
|
||||
// The DateTime from the UNIX timestamp of the block where the transaction was included
|
||||
final DateTime blockTime;
|
||||
|
||||
// The Transaction fee
|
||||
final double fee;
|
||||
|
||||
// The token symbol
|
||||
final String tokenSymbol;
|
||||
|
||||
SolanaTransactionModel({
|
||||
required this.id,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.amount,
|
||||
required this.programId,
|
||||
required int blockTimeInInt,
|
||||
this.isOutgoingTx = false,
|
||||
required this.tokenSymbol,
|
||||
required this.fee,
|
||||
}) : blockTime = DateTime.fromMillisecondsSinceEpoch(blockTimeInInt * 1000);
|
||||
|
||||
factory SolanaTransactionModel.fromJson(Map<String, dynamic> json) => SolanaTransactionModel(
|
||||
id: json['id'],
|
||||
blockTimeInInt: int.parse(json["timeStamp"]) * 1000,
|
||||
from: json["from"],
|
||||
to: json["to"],
|
||||
amount: double.parse(json["value"]),
|
||||
programId: json["programId"],
|
||||
fee: json['fee'],
|
||||
tokenSymbol: json['tokenSymbol'],
|
||||
);
|
||||
}
|
||||
@@ -1,845 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_solana/default_spl_tokens.dart';
|
||||
import 'package:cw_solana/solana_balance.dart';
|
||||
import 'package:cw_solana/solana_client.dart';
|
||||
import 'package:cw_solana/solana_exceptions.dart';
|
||||
import 'package:cw_solana/solana_transaction_credentials.dart';
|
||||
import 'package:cw_solana/solana_transaction_history.dart';
|
||||
import 'package:cw_solana/solana_transaction_info.dart';
|
||||
import 'package:cw_solana/solana_transaction_model.dart';
|
||||
import 'package:cw_solana/solana_wallet_addresses.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:on_chain/solana/solana.dart' hide Store;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
|
||||
part 'solana_wallet.g.dart';
|
||||
|
||||
class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
|
||||
|
||||
abstract class SolanaWalletBase
|
||||
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo>
|
||||
with Store, WalletKeysFile {
|
||||
SolanaWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
required DerivationInfo derivationInfo,
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
SolanaBalance? initialBalance,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_hexPrivateKey = privateKey,
|
||||
_client = SolanaWalletClient(),
|
||||
walletAddresses = SolanaWalletAddresses(walletInfo),
|
||||
balance = ObservableMap<CryptoCurrency, SolanaBalance>.of(
|
||||
{CryptoCurrency.sol: initialBalance ?? SolanaBalance(BigInt.zero.toDouble(), false)}),
|
||||
super(walletInfo, derivationInfo) {
|
||||
this.walletInfo = walletInfo;
|
||||
transactionHistory = SolanaTransactionHistory(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
);
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(SPLToken.typeId)) {
|
||||
CakeHive.registerAdapter(SPLTokenAdapter());
|
||||
}
|
||||
|
||||
_sharedPrefs.complete(SharedPreferences.getInstance());
|
||||
}
|
||||
|
||||
final String _password;
|
||||
final String? _mnemonic;
|
||||
final String? _hexPrivateKey;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
|
||||
late final SolanaWalletClient _client;
|
||||
|
||||
SolanaWalletClient get client => _client;
|
||||
|
||||
@observable
|
||||
double? estimatedFee;
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
late final Box<SPLToken> splTokensBox;
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
ObservableMap<CryptoCurrency, SolanaBalance> balance =
|
||||
ObservableMap<CryptoCurrency, SolanaBalance>();
|
||||
|
||||
final Completer<SharedPreferences> _sharedPrefs = Completer();
|
||||
|
||||
@override
|
||||
Object get keys => throw UnimplementedError("keys");
|
||||
|
||||
late final SolanaPrivateKey _solanaPrivateKey;
|
||||
|
||||
late final SolanaPublicKey _solanaPublicKey;
|
||||
|
||||
SolanaPublicKey get solanaPublicKey => _solanaPublicKey;
|
||||
|
||||
SolanaPrivateKey get solanaPrivateKey => _solanaPrivateKey;
|
||||
|
||||
String get solanaAddress => _solanaPublicKey.toAddress().address;
|
||||
|
||||
@override
|
||||
String? get seed => _mnemonic;
|
||||
|
||||
@override
|
||||
String get privateKey => _solanaPrivateKey.seedHex();
|
||||
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: privateKey,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
Future<void> init() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
|
||||
|
||||
splTokensBox = await CakeHive.openBox<SPLToken>(boxName);
|
||||
|
||||
// Create the privatekey using either the mnemonic or the privateKey
|
||||
_solanaPrivateKey = await getPrivateKey(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
// Extract the public key and wallet address
|
||||
_solanaPublicKey = _solanaPrivateKey.publicKey();
|
||||
|
||||
walletInfo.address = _solanaPublicKey.toAddress().address;
|
||||
|
||||
await walletAddresses.init();
|
||||
await transactionHistory.init();
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<SolanaPrivateKey> getPrivateKey({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
String? passphrase,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (mnemonic != null) {
|
||||
final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
|
||||
|
||||
// Derive a Solana private key from the seed
|
||||
final bip44 = Bip44.fromSeed(seed, Bip44Coins.solana);
|
||||
|
||||
final childKey = bip44.deriveDefaultPath.change(Bip44Changes.chainExt);
|
||||
|
||||
return SolanaPrivateKey.fromSeed(childKey.privateKey.raw);
|
||||
}
|
||||
|
||||
try {
|
||||
final keypairBytes = Base58Decoder.decode(privateKey!);
|
||||
return SolanaPrivateKey.fromBytes(keypairBytes);
|
||||
} catch (_) {
|
||||
final privateKeyBytes = HEX.decode(privateKey!);
|
||||
return SolanaPrivateKey.fromSeed(privateKeyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_client.stop();
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
final isConnected = _client.connect(node);
|
||||
|
||||
if (!isConnected) {
|
||||
throw Exception("Solana Node connection failed");
|
||||
}
|
||||
|
||||
_setTransactionUpdateTimer();
|
||||
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getEstimatedFees() async {
|
||||
try {
|
||||
estimatedFee = await _client.getEstimatedFee(_solanaPublicKey, Commitment.confirmed);
|
||||
printV(estimatedFee.toString());
|
||||
} catch (e) {
|
||||
estimatedFee = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
final solCredentials = credentials as SolanaTransactionCredentials;
|
||||
|
||||
final outputs = solCredentials.outputs;
|
||||
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
await updateTokenBalance();
|
||||
|
||||
final transactionCurrency = balance.keys.firstWhere(
|
||||
(currency) =>
|
||||
currency.title == credentials.currency.title &&
|
||||
currency.tag == credentials.currency.tag,
|
||||
orElse: () => throw Exception(
|
||||
'Currency ${credentials.currency.title} ${credentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
|
||||
|
||||
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||
|
||||
final solBalance = balance[CryptoCurrency.sol]!.balance;
|
||||
|
||||
double totalAmount = 0.0;
|
||||
|
||||
bool isSendAll = false;
|
||||
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||
}
|
||||
|
||||
final totalAmountFromCredentials =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
totalAmount = totalAmountFromCredentials.toDouble();
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
|
||||
isSendAll = output.sendAll;
|
||||
|
||||
if (isSendAll) {
|
||||
totalAmount = walletBalanceForCurrency;
|
||||
} else {
|
||||
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
|
||||
|
||||
totalAmount = totalOriginalAmount;
|
||||
}
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
String? tokenMint;
|
||||
// Token Mint is only needed for transactions that are not native tokens(non-SOL transactions)
|
||||
if (transactionCurrency.title != CryptoCurrency.sol.title) {
|
||||
tokenMint = (transactionCurrency as SPLToken).mintAddress;
|
||||
}
|
||||
|
||||
final pendingSolanaTransaction = await _client.signSolanaTransaction(
|
||||
tokenMint: tokenMint,
|
||||
tokenTitle: transactionCurrency.title,
|
||||
inputAmount: totalAmount,
|
||||
ownerPrivateKey: _solanaPrivateKey,
|
||||
tokenDecimals: transactionCurrency.decimals,
|
||||
destinationAddress: solCredentials.outputs.first.isParsedAddress
|
||||
? solCredentials.outputs.first.extractedAddress!
|
||||
: solCredentials.outputs.first.address,
|
||||
isSendAll: isSendAll,
|
||||
solBalance: solBalance,
|
||||
);
|
||||
|
||||
return pendingSolanaTransaction;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, SolanaTransactionInfo>> fetchTransactions() async => {};
|
||||
|
||||
@override
|
||||
Future<void> updateTransactionsHistory({List<String>? specificTokenMints}) async {
|
||||
await Future.wait([
|
||||
_updateNativeSOLTransactions(),
|
||||
updateSPLTokenTransactions(specificMints: specificTokenMints),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Polls for a specific transaction by signature with exponential backoff
|
||||
/// I'm using this in case we make the call to fetch the transaction and it has not finished its confirmations on the solana network and been indexed by the node networks we use.
|
||||
Future<void> pollForTransaction({
|
||||
required String signature,
|
||||
Duration initialDelay = const Duration(seconds: 1),
|
||||
int maxRetries = 5,
|
||||
}) async {
|
||||
final walletAddress = _solanaPublicKey.toAddress().address;
|
||||
|
||||
for (int i = 0; i < maxRetries; i++) {
|
||||
await Future.delayed(initialDelay * (i + 1));
|
||||
|
||||
try {
|
||||
final result = await _client.fetchTransactionBySignature(
|
||||
signature: signature,
|
||||
walletAddress: walletAddress,
|
||||
);
|
||||
|
||||
if (result != null && result.transactions.isNotEmpty) {
|
||||
await addTransactionsToTransactionHistory(result.transactions);
|
||||
|
||||
// Update only the tokens involved in this transaction
|
||||
if (result.tokenMints.isNotEmpty) {
|
||||
await Future.wait([
|
||||
updateSPLTokenTransactions(specificMints: result.tokenMints),
|
||||
updateTokenBalance(tokenMints: result.tokenMints),
|
||||
]);
|
||||
} else {
|
||||
// If no token mints, still update SOL balance
|
||||
await updateTokenBalance(tokenMints: []);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
printV('Error polling for transaction (attempt ${i + 1}/$maxRetries): $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to full refresh if not found after max retries
|
||||
printV('Transaction not found after $maxRetries attempts, falling back to full refresh');
|
||||
await updateTransactionsHistory();
|
||||
}
|
||||
|
||||
void updateTransactions(List<SolanaTransactionModel> updatedTx) {
|
||||
addTransactionsToTransactionHistory(updatedTx);
|
||||
}
|
||||
|
||||
/// Fetches the native SOL transactions linked to the wallet Public Key
|
||||
Future<void> _updateNativeSOLTransactions() async {
|
||||
final transactions =
|
||||
await _client.fetchTransactions(_solanaPublicKey.toAddress(), onUpdate: updateTransactions);
|
||||
|
||||
await addTransactionsToTransactionHistory(transactions);
|
||||
}
|
||||
|
||||
Future<void> updateSPLTokenTransactions({List<String>? specificMints}) async {
|
||||
final allTokens = balance.keys.whereType<SPLToken>().toList(growable: false);
|
||||
|
||||
// Filter to specific mints if provided
|
||||
final tokens = specificMints != null
|
||||
? allTokens.where((t) => specificMints.contains(t.mintAddress)).toList(growable: false)
|
||||
: allTokens;
|
||||
|
||||
if (tokens.isEmpty) return;
|
||||
|
||||
const int batchSize = 5;
|
||||
|
||||
for (var i = 0; i < tokens.length; i += batchSize) {
|
||||
final batch = tokens.sublist(
|
||||
i,
|
||||
i + batchSize > tokens.length ? tokens.length : i + batchSize,
|
||||
);
|
||||
final results = await Future.wait(
|
||||
batch.map((token) async {
|
||||
try {
|
||||
return await _client.getSPLTokenTransfers(
|
||||
mintAddress: token.mintAddress,
|
||||
splTokenSymbol: token.symbol,
|
||||
splTokenDecimal: token.decimal,
|
||||
privateKey: _solanaPrivateKey,
|
||||
onUpdate: updateTransactions,
|
||||
);
|
||||
} catch (_) {
|
||||
return <SolanaTransactionModel>[];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
for (final list in results) {
|
||||
await addTransactionsToTransactionHistory(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addTransactionsToTransactionHistory(
|
||||
List<SolanaTransactionModel> transactions,
|
||||
) async {
|
||||
final Map<String, SolanaTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
result[transactionModel.id] = SolanaTransactionInfo(
|
||||
id: transactionModel.id,
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
blockTime: transactionModel.blockTime,
|
||||
direction: transactionModel.isOutgoingTx
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
solAmount: transactionModel.amount,
|
||||
isPending: false,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: transactionModel.tokenSymbol,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) => throw UnimplementedError("rescan");
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password, encryptionFileUtils);
|
||||
saveKeysFile(_password, encryptionFileUtils, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
|
||||
// Verify node health before attempting to sync
|
||||
final isHealthy = await checkNodeHealth();
|
||||
if (!isHealthy) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.wait([
|
||||
updateTokenBalance(),
|
||||
_updateNativeSOLTransactions(),
|
||||
updateSPLTokenTransactions(),
|
||||
_getEstimatedFees(),
|
||||
]);
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': _hexPrivateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
|
||||
static Future<SolanaWallet> open({
|
||||
required String name,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = SolanaBalance.fromJSON(data?['balance'] as String?, false) ?? SolanaBalance(0.0, false);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final passphrase = data['passphrase'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(
|
||||
name,
|
||||
walletInfo.type,
|
||||
password,
|
||||
encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
final derivationInfo = await walletInfo.getDerivationInfo();
|
||||
|
||||
return SolanaWallet(
|
||||
walletInfo: walletInfo,
|
||||
derivationInfo: derivationInfo,
|
||||
password: password,
|
||||
passphrase: keysData.passphrase,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
initialBalance: balance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateTokenBalance({List<String>? tokenMints}) async {
|
||||
// Fetch SOL and SPL token balances in parallel for better performance
|
||||
await Future.wait([
|
||||
_fetchSOLBalance().then((solBalance) {
|
||||
balance[CryptoCurrency.sol] = solBalance;
|
||||
}),
|
||||
_updateSplTokenBalancesInternal(tokenMints: tokenMints),
|
||||
]);
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<SolanaBalance> _fetchSOLBalance() async {
|
||||
final balance = await _client.getBalance(solanaAddress);
|
||||
|
||||
return SolanaBalance(balance, false);
|
||||
}
|
||||
|
||||
/// Internal helper to update SPL token balances.
|
||||
/// When [tokenMints] is null or empty, updates all enabled tokens.
|
||||
Future<void> _updateSplTokenBalancesInternal({
|
||||
List<String>? tokenMints,
|
||||
}) async {
|
||||
// Remove disabled tokens first to keep state clean
|
||||
for (var token in splTokensBox.values.where((t) => !t.enabled)) {
|
||||
balance.remove(token);
|
||||
}
|
||||
|
||||
final enabledTokens = splTokensBox.values.where((t) => t.enabled).toList(growable: false);
|
||||
if (enabledTokens.isEmpty) return;
|
||||
|
||||
final tokens = tokenMints == null || tokenMints.isEmpty
|
||||
? enabledTokens
|
||||
: enabledTokens.where((t) => tokenMints.contains(t.mintAddress)).toList(growable: false);
|
||||
|
||||
if (tokens.isEmpty) return;
|
||||
|
||||
const int batchSize = 5;
|
||||
|
||||
for (var i = 0; i < tokens.length; i += batchSize) {
|
||||
final batch = tokens.sublist(
|
||||
i,
|
||||
i + batchSize > tokens.length ? tokens.length : i + batchSize,
|
||||
);
|
||||
|
||||
final results = await Future.wait(batch.map((token) async {
|
||||
try {
|
||||
final fetched = await _client.getSplTokenBalance(token.mintAddress, solanaAddress);
|
||||
return MapEntry(token, fetched);
|
||||
} catch (e) {
|
||||
printV('Error fetching spl token (${token.symbol}) balance ${e.toString()}');
|
||||
return MapEntry<SPLToken, SolanaBalance?>(token, null);
|
||||
}
|
||||
}));
|
||||
|
||||
for (final entry in results) {
|
||||
final token = entry.key;
|
||||
final fetchedBalance = entry.value;
|
||||
final currentBalance = balance[token] ??
|
||||
SolanaBalance.forToken(BigInt.zero, 0.0);
|
||||
balance[token] = fetchedBalance ?? currentBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void>? updateBalance() async => await updateTokenBalance();
|
||||
|
||||
@override
|
||||
Future<bool> checkNodeHealth() async {
|
||||
try {
|
||||
// Check native balance
|
||||
await _client.getBalance(solanaAddress, throwOnError: true);
|
||||
|
||||
// Check USDC token balance
|
||||
const usdcMintAddress = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
||||
await _client.getSplTokenBalance(usdcMintAddress, solanaAddress, throwOnError: true);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<SPLToken> get splTokenCurrencies => splTokensBox.values.toList();
|
||||
|
||||
void addInitialTokens() {
|
||||
final initialSPLTokens = DefaultSPLTokens().initialSPLTokens;
|
||||
|
||||
for (var token in initialSPLTokens) {
|
||||
if (!splTokensBox.containsKey(token.mintAddress)) {
|
||||
splTokensBox.put(token.mintAddress, token);
|
||||
} else {
|
||||
// update existing token
|
||||
final existingToken = splTokensBox.get(token.mintAddress);
|
||||
splTokensBox.put(
|
||||
token.mintAddress, SPLToken.copyWith(token, enabled: existingToken!.enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<SolanaMoralisDiscoveryResult> discoverTokensFromMoralis() async {
|
||||
try {
|
||||
if (!splTokensBox.isOpen) return SolanaMoralisDiscoveryResult.empty;
|
||||
|
||||
final address = walletAddresses.address;
|
||||
if (address.isEmpty) return SolanaMoralisDiscoveryResult.empty;
|
||||
|
||||
final walletTokens = await _client.fetchWalletTokensFromMoralis(address);
|
||||
if (walletTokens.isEmpty) return SolanaMoralisDiscoveryResult.empty;
|
||||
|
||||
final existingMints = {
|
||||
for (final token in splTokensBox.values) token.mintAddress: token,
|
||||
};
|
||||
|
||||
final defaultMints = DefaultSPLTokens().initialSPLTokens.map((t) => t.mintAddress).toSet();
|
||||
|
||||
final newTokens = <DiscoveredSPLToken>[];
|
||||
|
||||
for (final moralisToken in walletTokens) {
|
||||
final mint = moralisToken.mint;
|
||||
|
||||
final existingToken = existingMints[mint];
|
||||
if (existingToken != null) {
|
||||
if (defaultMints.contains(mint) && !existingToken.enabled) {
|
||||
existingToken.enabled = true;
|
||||
await existingToken.save();
|
||||
await addSPLToken(existingToken);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final tokenInfo = await _client.fetchSPLTokenInfo(mint);
|
||||
if (tokenInfo == null) continue;
|
||||
|
||||
final discoveredToken = SPLToken(
|
||||
name: tokenInfo.name,
|
||||
symbol: tokenInfo.symbol,
|
||||
mintAddress: mint,
|
||||
decimal: moralisToken.decimals,
|
||||
mint: tokenInfo.mint,
|
||||
iconPath: tokenInfo.iconPath,
|
||||
tag: 'SOL',
|
||||
);
|
||||
|
||||
newTokens.add(
|
||||
DiscoveredSPLToken(
|
||||
token: discoveredToken,
|
||||
balance: moralisToken.amount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SolanaMoralisDiscoveryResult(newTokens: newTokens);
|
||||
} catch (e) {
|
||||
printV('Error discovering SPL tokens from Moralis: ${e.toString()}');
|
||||
return SolanaMoralisDiscoveryResult.empty;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addSPLToken(SPLToken token) async {
|
||||
await splTokensBox.put(token.mintAddress, token);
|
||||
|
||||
if (token.enabled) {
|
||||
final tokenBalance = await _client.getSplTokenBalance(token.mintAddress, solanaAddress) ??
|
||||
balance[token] ??
|
||||
SolanaBalance.forToken(BigInt.zero, 0.0);
|
||||
|
||||
balance[token] = tokenBalance;
|
||||
} else {
|
||||
balance.remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteSPLToken(SPLToken token) async {
|
||||
if (splTokensBox.isOpen) {
|
||||
await splTokensBox.delete(token.mintAddress);
|
||||
}
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
updateTokenBalance();
|
||||
}
|
||||
|
||||
Future<void> _removeTokenTransactionsInHistory(SPLToken token) async {
|
||||
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
Future<SPLToken?> getSPLToken(String mintAddress) async {
|
||||
try {
|
||||
return await _client.fetchSPLTokenInfo(mintAddress);
|
||||
} catch (e, s) {
|
||||
printV('Error fetching token: ${e.toString()}, ${s.toString()}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
|
||||
|
||||
// Copies current wallet files into new wallet name's dir and files
|
||||
if (currentWalletFile.existsSync()) {
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
await currentWalletFile.copy(newWalletPath);
|
||||
}
|
||||
if (currentTransactionsFile.existsSync()) {
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
|
||||
}
|
||||
|
||||
// Delete old name's dir and files
|
||||
await Directory(currentDirPath).delete(recursive: true);
|
||||
}
|
||||
|
||||
void _setTransactionUpdateTimer() {
|
||||
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||
_transactionsUpdateTimer!.cancel();
|
||||
}
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
updateTokenBalance();
|
||||
_updateNativeSOLTransactions();
|
||||
updateSPLTokenTransactions();
|
||||
_getEstimatedFees();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address}) async {
|
||||
// Convert the message to bytes
|
||||
final messageBytes = utf8.encode(message);
|
||||
|
||||
// Sign the message bytes with the wallet's private key
|
||||
final signature = (_solanaPrivateKey.sign(messageBytes));
|
||||
|
||||
return Base58Encoder.encode(signature);
|
||||
}
|
||||
|
||||
List<List<int>> bytesFromSigString(String signatureString) {
|
||||
final regex = RegExp(r'Signature\(\[(.+)\], publicKey: (.+)\)');
|
||||
final match = regex.firstMatch(signatureString);
|
||||
|
||||
if (match != null) {
|
||||
final bytesString = match.group(1)!;
|
||||
final base58EncodedPublicKeyString = match.group(2)!;
|
||||
final sigBytes = bytesString.split(', ').map(int.parse).toList();
|
||||
|
||||
List<int> pubKeyBytes = SolAddrDecoder().decodeAddr(base58EncodedPublicKeyString);
|
||||
|
||||
return [sigBytes, pubKeyBytes];
|
||||
} else {
|
||||
throw const FormatException('Invalid Signature string format');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address}) async {
|
||||
String signatureString = utf8.decode(HEX.decode(signature));
|
||||
|
||||
List<List<int>> bytes = bytesFromSigString(signatureString);
|
||||
|
||||
final messageBytes = utf8.encode(message);
|
||||
final sigBytes = bytes[0];
|
||||
final pubKeyBytes = bytes[1];
|
||||
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the address derived from the public key provided matches the one we expect
|
||||
final pub = SolanaPublicKey.fromBytes(pubKeyBytes);
|
||||
if (address != pub.toAddress().address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pub.verify(
|
||||
message: messageBytes,
|
||||
signature: sigBytes,
|
||||
);
|
||||
}
|
||||
|
||||
SolanaRPC? get solanaProvider => _client.getSolanaProvider;
|
||||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
|
||||
class DiscoveredSPLToken {
|
||||
final SPLToken token;
|
||||
final double balance;
|
||||
|
||||
const DiscoveredSPLToken({
|
||||
required this.token,
|
||||
required this.balance,
|
||||
});
|
||||
}
|
||||
|
||||
class SolanaMoralisDiscoveryResult {
|
||||
final List<DiscoveredSPLToken> newTokens;
|
||||
|
||||
const SolanaMoralisDiscoveryResult({required this.newTokens});
|
||||
|
||||
static const SolanaMoralisDiscoveryResult empty = SolanaMoralisDiscoveryResult(newTokens: []);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'solana_wallet_addresses.g.dart';
|
||||
|
||||
class SolanaWalletAddresses = SolanaWalletAddressesBase with _$SolanaWalletAddresses;
|
||||
|
||||
abstract class SolanaWalletAddressesBase extends WalletAddresses with Store {
|
||||
SolanaWalletAddressesBase(WalletInfo walletInfo)
|
||||
: address = '',
|
||||
super(walletInfo);
|
||||
|
||||
@override
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
PaymentURI getPaymentUri(String amount) => SolanaURI(address: address, amount: amount);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class SolanaNewWalletCredentials extends WalletCredentials {
|
||||
SolanaNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
this.mnemonic,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class SolanaRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
SolanaRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class SolanaRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||
SolanaRestoreWalletFromPrivateKey(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.privateKey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String privateKey;
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_solana/solana_mnemonics.dart';
|
||||
import 'package:cw_solana/solana_wallet.dart';
|
||||
import 'package:cw_solana/solana_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
|
||||
SolanaRestoreWalletFromSeedCredentials, SolanaRestoreWalletFromPrivateKey, SolanaNewWalletCredentials> {
|
||||
SolanaWalletService(this.isDirect);
|
||||
|
||||
final bool isDirect;
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> create(SolanaNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
|
||||
|
||||
final wallet = SolanaWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.solana;
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = await WalletInfo.get(name, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
|
||||
try {
|
||||
final wallet = await SolanaWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await SolanaWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = await WalletInfo.get(wallet, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
await WalletInfo.delete(walletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> restoreFromKeys(SolanaRestoreWalletFromPrivateKey credentials,
|
||||
{bool? isTestnet}) async {
|
||||
final wallet = SolanaWallet(
|
||||
password: credentials.password!,
|
||||
privateKey: credentials.privateKey,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> restoreFromSeed(SolanaRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw SolanaMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = SolanaWallet(
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = await WalletInfo.get(currentName, getType());
|
||||
if (currentWalletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final currentWallet = await SolanaWalletBase.open(
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
|
||||
await newWalletInfo.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(SolanaNewWalletCredentials credentials) {
|
||||
// TODO: implement restoreFromHardwareWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
name: cw_solana
|
||||
description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
homepage: https://github.com/Such-Software/hash-wallet
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.6 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
http: ^1.1.0
|
||||
hive: ^2.2.3
|
||||
bip39: ^1.0.6
|
||||
mobx: ^2.3.0+1
|
||||
shared_preferences: ^2.0.15
|
||||
bip32: ^2.0.0
|
||||
hex: ^0.2.0
|
||||
on_chain:
|
||||
git:
|
||||
url: https://github.com/cake-tech/on_chain.git
|
||||
ref: 096865a8c6b89c260beadfec04f7e184c40a3273
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
build_runner: ^2.4.15
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^2.0.1
|
||||
|
||||
dependency_overrides:
|
||||
watcher: ^1.1.0
|
||||
|
||||
flutter:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:cw_solana/cw_solana.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
final calculator = Calculator();
|
||||
expect(calculator.addOne(2), 3);
|
||||
expect(calculator.addOne(-7), -6);
|
||||
expect(calculator.addOne(0), 1);
|
||||
});
|
||||
}
|
||||
30
cw_tron/.gitignore
vendored
30
cw_tron/.gitignore
vendored
@@ -1,30 +0,0 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
@@ -1,10 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,63 +0,0 @@
|
||||
## cw_tron
|
||||
|
||||
TRON wallet module for Cake Wallet. Implements TRX and TRC-20 token support on top of `on_chain` (Tron), with transaction history and fee estimation via TronGrid.
|
||||
|
||||
### Features
|
||||
|
||||
- Connect to TRON nodes over HTTP(S) via `TronProvider`/`TronHTTPProvider`.
|
||||
- Fetch TRX balance and TRC-20 token balances.
|
||||
- Estimate fees using bandwidth/energy and memo fee; accounts for available account resources.
|
||||
- Create and sign TRX and TRC-20 transfers (supports send-all and optional memo).
|
||||
- Broadcast signed transactions.
|
||||
- Load account history (TRX and TRC-20), filtering spam and TRC10-only events.
|
||||
- Manage TRC-20 tokens: add/remove tokens and fetch token metadata.
|
||||
- Sign/verify messages.
|
||||
- Node health checks for both native and token balance endpoints.
|
||||
|
||||
### Getting started
|
||||
|
||||
Provide a TRON RPC endpoint and a TronGrid API key. Add a secrets file:
|
||||
|
||||
```dart
|
||||
// cw_tron/lib/.secrets.g.dart (DO NOT COMMIT)
|
||||
const String tronGridApiKey = 'YOUR_TRONGRID_API_KEY';
|
||||
```
|
||||
|
||||
Basic connect and sync:
|
||||
|
||||
```dart
|
||||
final service = TronWalletService(walletInfoBox, client: TronClient(), isDirect: true);
|
||||
final wallet = await service.create(TronNewWalletCredentials(name: 'My TRON', password: 'secret'));
|
||||
await wallet.connectToNode(node: Node(uriRaw: 'api.trongrid.io', isSSL: true));
|
||||
await wallet.startSync();
|
||||
final trxBalance = wallet.balance[CryptoCurrency.trx];
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Send TRX:
|
||||
|
||||
```dart
|
||||
final pending = await wallet.createTransaction(
|
||||
TronTransactionCredentials.single(
|
||||
address: 'T...',
|
||||
cryptoAmount: '1.5',
|
||||
currency: CryptoCurrency.trx,
|
||||
),
|
||||
);
|
||||
final txHash = await pending.commit();
|
||||
```
|
||||
|
||||
Add USDT (TRC-20):
|
||||
|
||||
```dart
|
||||
final usdt = await wallet.getTronToken('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t');
|
||||
if (usdt != null) {
|
||||
await wallet.addTronToken(usdt);
|
||||
}
|
||||
```
|
||||
|
||||
### Additional information
|
||||
|
||||
- History and token queries use TronGrid; set `tronGridApiKey`.
|
||||
- See `lib/` for APIs: `TronClient`, `TronWallet`, `TronWalletService`, and credential types.
|
||||
@@ -1,4 +0,0 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,7 +0,0 @@
|
||||
library cw_tron;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
|
||||
class DefaultTronTokens {
|
||||
final List<TronToken> _defaultTokens = [
|
||||
TronToken(
|
||||
name: "Tether USD",
|
||||
symbol: "USDT",
|
||||
contractAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
|
||||
decimal: 6,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "USD Coin",
|
||||
symbol: "USDC",
|
||||
contractAddress: "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8",
|
||||
decimal: 6,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Bitcoin",
|
||||
symbol: "BTC",
|
||||
contractAddress: "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
|
||||
decimal: 8,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "Ethereum",
|
||||
symbol: "ETH",
|
||||
contractAddress: "TRFe3hT5oYhjSZ6f3ji5FJ7YCfrkWnHRvh",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "Wrapped BTC",
|
||||
symbol: "WBTC",
|
||||
contractAddress: "TXpw8XeWYeTUd4quDskoUqeQPowRh4jY65",
|
||||
decimal: 8,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Dogecoin",
|
||||
symbol: "DOGE",
|
||||
contractAddress: "THbVQp8kMjStKNnf2iCY6NEzThKMK5aBHg",
|
||||
decimal: 8,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "JUST Stablecoin",
|
||||
symbol: "USDJ",
|
||||
contractAddress: "TMwFHYXLJaRUPeW6421aqXL4ZEzPRFGkGT",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "SUN",
|
||||
symbol: "SUN",
|
||||
contractAddress: "TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "Wrapped TRX",
|
||||
symbol: "WTRX",
|
||||
contractAddress: "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR",
|
||||
decimal: 6,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "BitTorent",
|
||||
symbol: "BTT",
|
||||
contractAddress: "TAFjULxiVgT4qWk6UZwjqwZXTSaGaqnVp4",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "BUSD Token",
|
||||
symbol: "BUSD",
|
||||
contractAddress: "TMz2SWatiAtZVVcH2ebpsbVtYwUPT9EdjH",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "HTX",
|
||||
symbol: "HTX",
|
||||
contractAddress: "TUPM7K8REVzD2UdV4R5fe5M8XbnR2DdoJ6",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
];
|
||||
|
||||
List<TronToken> get initialTronTokens => _defaultTokens.map((token) {
|
||||
String? iconPath;
|
||||
if (token.iconPath?.isEmpty ?? true) {
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) =>
|
||||
element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
iconPath = token.iconPath;
|
||||
}
|
||||
|
||||
return TronToken.copyWith(token, icon: iconPath, tag: 'TRX');
|
||||
}).toList();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:web3dart/crypto.dart';
|
||||
|
||||
class PendingTronTransaction with PendingTransaction {
|
||||
final Function sendTransaction;
|
||||
final List<int> signedTransaction;
|
||||
final String fee;
|
||||
final String amount;
|
||||
|
||||
PendingTronTransaction({
|
||||
required this.sendTransaction,
|
||||
required this.signedTransaction,
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
@override
|
||||
String get amountFormatted => amount;
|
||||
|
||||
@override
|
||||
Future<void> commit() async => await sendTransaction();
|
||||
|
||||
@override
|
||||
String get feeFormatted => "$feeFormattedValue TRX";
|
||||
|
||||
@override
|
||||
String get feeFormattedValue => fee;
|
||||
|
||||
@override
|
||||
String get hex => bytesToHex(signedTransaction);
|
||||
|
||||
@override
|
||||
String get id => '';
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,436 +0,0 @@
|
||||
final trc20Abi = [
|
||||
{"inputs": [], "stateMutability": "nonpayable", "type": "constructor"},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "owner", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "spender", "type": "address"},
|
||||
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "uint256", "name": "total", "type": "uint256"},
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "contract_address", "type": "address"}
|
||||
],
|
||||
"name": "OrderPaid",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "previousOwner", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "newOwner", "type": "address"}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "address", "name": "token", "type": "address"},
|
||||
{"indexed": false, "internalType": "bool", "name": "active", "type": "bool"}
|
||||
],
|
||||
"name": "TokenUpdate",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "from", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "to", "type": "address"},
|
||||
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "string", "name": "username", "type": "string"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "UserRegistred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "WBuyer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "buyer", "type": "address"}
|
||||
],
|
||||
"name": "WSeller",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "CONTRACTPERCENTAGE",
|
||||
"outputs": [
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "uint256", "name": "order_total", "type": "uint256"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "PayWithTokens",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "TOKENINCREAMENT",
|
||||
"outputs": [
|
||||
{"internalType": "uint16", "name": "", "type": "uint16"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_signer",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_tokens",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "active", "type": "bool"},
|
||||
{"internalType": "uint16", "name": "token", "type": "uint16"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_users",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "active", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "owner", "type": "address"},
|
||||
{"internalType": "address", "name": "spender", "type": "address"}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "account", "type": "address"}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "token", "type": "address"}
|
||||
],
|
||||
"name": "balanceOfContract",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "burn",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "account", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "burnFrom",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint256", "name": "value", "type": "uint256"},
|
||||
{"internalType": "address", "name": "_contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "contractWithdraw",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "subtractedValue", "type": "uint256"}
|
||||
],
|
||||
"name": "decreaseAllowance",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "addedValue", "type": "uint256"}
|
||||
],
|
||||
"name": "increaseAllowance",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{"internalType": "string", "name": "", "type": "string"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "token", "type": "address"},
|
||||
{"internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "payToContract",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "payWithNativeToken",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "string", "name": "username", "type": "string"}
|
||||
],
|
||||
"name": "regiserUser",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "selectOrder",
|
||||
"outputs": [
|
||||
{"internalType": "uint232", "name": "", "type": "uint232"},
|
||||
{"internalType": "uint16", "name": "", "type": "uint16"},
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{"internalType": "string", "name": "", "type": "string"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "signer", "type": "address"}
|
||||
],
|
||||
"name": "toggleSigner",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "tokenAddress", "type": "address"}
|
||||
],
|
||||
"name": "toggleToken",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "from", "type": "address"},
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "newOwner", "type": "address"}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint8", "name": "newPercentage", "type": "uint8"}
|
||||
],
|
||||
"name": "updateContractPercentage",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address[]", "name": "buyer", "type": "address[]"},
|
||||
{"internalType": "bytes[]", "name": "signature", "type": "bytes[]"},
|
||||
{"internalType": "uint16[]", "name": "order_id", "type": "uint16[]"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "widthrawForSellers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "seller", "type": "address"},
|
||||
{"internalType": "bytes", "name": "signature", "type": "bytes"},
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "widthrowForBuyers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
];
|
||||
@@ -1,25 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class TronBalance extends Balance {
|
||||
TronBalance(this.balance) : super(balance, balance);
|
||||
|
||||
final BigInt balance;
|
||||
|
||||
String toJSON() => json.encode({ 'balance': balance.toString() });
|
||||
|
||||
static TronBalance? fromJSON(String? jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
try {
|
||||
return TronBalance(BigInt.parse(decoded['balance']));
|
||||
} catch (e) {
|
||||
return TronBalance(BigInt.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,568 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/crypto_currency.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/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_http_provider.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronClient {
|
||||
late final client = ProxyWrapper().getHttpIOClient();
|
||||
|
||||
TronProvider? _provider;
|
||||
// This is an internal tracker, so we don't have to "refetch".
|
||||
int _nativeTxEstimatedFee = 0;
|
||||
|
||||
Future<List<TronTransactionModel>> fetchTransactions(String address,
|
||||
{String? contractAddress}) async {
|
||||
try {
|
||||
final response = await client.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<TronTRC20TransactionModel>> fetchTrc20ExcludedTransactions(String address) async {
|
||||
try {
|
||||
final response = await client.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions/trc20",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTRC20TransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting trc20 tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
bool connect(Node node) {
|
||||
try {
|
||||
final formattedUrl = '${node.isSSL ? 'https' : 'http'}://${node.uriRaw}';
|
||||
_provider = TronProvider(TronHTTPProvider(url: formattedUrl));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<BigInt> getBalance(TronAddress address, {bool throwOnError = false}) async {
|
||||
try {
|
||||
final accountDetails = await _provider!.request(TronRequestGetAccount(address: address));
|
||||
|
||||
return accountDetails?.balance ?? BigInt.zero;
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return BigInt.zero;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getFeeLimit(
|
||||
TransactionRaw rawTransaction,
|
||||
TronAddress address,
|
||||
TronAddress receiverAddress, {
|
||||
int energyUsed = 0,
|
||||
bool isEstimatedFeeFlow = false,
|
||||
}) async {
|
||||
try {
|
||||
// Get the tron chain parameters.
|
||||
final chainParams = await _provider!.request(TronRequestGetChainParameters());
|
||||
|
||||
final bandWidthInSun = chainParams.getTransactionFee!;
|
||||
log('BandWidth In Sun: $bandWidthInSun');
|
||||
|
||||
final energyInSun = chainParams.getEnergyFee!;
|
||||
log('Energy In Sun: $energyInSun');
|
||||
|
||||
final fakeTransaction = Transaction(
|
||||
rawData: rawTransaction,
|
||||
signature: [Uint8List(65)],
|
||||
);
|
||||
|
||||
// Calculate the total size of the fake transaction, considering the required network overhead.
|
||||
final transactionSize = fakeTransaction.length + 64;
|
||||
|
||||
// Assign the calculated size to the variable representing the required bandwidth.
|
||||
int neededBandWidth = transactionSize;
|
||||
log('Initial Needed Bandwidth: $neededBandWidth');
|
||||
|
||||
int neededEnergy = energyUsed;
|
||||
log('Initial Needed Energy: $neededEnergy');
|
||||
|
||||
// Fetch account resources to assess the available bandwidth and energy
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: address));
|
||||
|
||||
neededEnergy -= accountResource.howManyEnergy.toInt();
|
||||
log('Account resource energy: ${accountResource.howManyEnergy.toInt()}');
|
||||
log('Needed Energy after deducting from account resource energy: $neededEnergy');
|
||||
|
||||
// Deduct the bandwidth from the account's available bandwidth.
|
||||
final BigInt accountBandWidth = accountResource.howManyBandwIth;
|
||||
log('Account resource bandwidth: ${accountResource.howManyBandwIth.toInt()}');
|
||||
|
||||
if (accountBandWidth >= BigInt.from(neededBandWidth) && !isEstimatedFeeFlow) {
|
||||
log('Account has more bandwidth than required');
|
||||
neededBandWidth = 0;
|
||||
}
|
||||
|
||||
if (neededEnergy < 0) {
|
||||
neededEnergy = 0;
|
||||
}
|
||||
|
||||
final energyBurn = neededEnergy * energyInSun.toInt();
|
||||
log('Energy Burn: $energyBurn');
|
||||
|
||||
final bandWidthBurn = neededBandWidth * bandWidthInSun;
|
||||
log('Bandwidth Burn: $bandWidthBurn');
|
||||
|
||||
int totalBurn = energyBurn + bandWidthBurn;
|
||||
log('Total Burn: $totalBurn');
|
||||
|
||||
/// If there is a note (memo), calculate the memo fee.
|
||||
if (rawTransaction.data != null) {
|
||||
totalBurn += chainParams.getMemoFee!;
|
||||
}
|
||||
|
||||
log('Final total burn: $totalBurn');
|
||||
|
||||
return totalBurn;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getEstimatedFee(TronAddress ownerAddress) async {
|
||||
const constantAmount = '1000';
|
||||
// Fetch the latest Tron block
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(constantAmount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: ownerAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().add(const Duration(minutes: 30));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final estimatedFee = await getFeeLimit(
|
||||
rawTransaction,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
|
||||
_nativeTxEstimatedFee = estimatedFee;
|
||||
|
||||
return estimatedFee;
|
||||
}
|
||||
|
||||
Future<int> getTRCEstimatedFee(TronAddress ownerAddress) async {
|
||||
String contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
|
||||
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.
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
ownerAddress,
|
||||
TronHelper.toSun(constantAmount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
return feeLimit;
|
||||
}
|
||||
|
||||
Future<PendingTronTransaction> signTransaction({
|
||||
required TronPrivateKey ownerPrivKey,
|
||||
required String toAddress,
|
||||
required String amount,
|
||||
required CryptoCurrency currency,
|
||||
required BigInt tronBalance,
|
||||
required bool sendAll,
|
||||
}) async {
|
||||
// Get the owner tron address from the key
|
||||
final ownerAddress = ownerPrivKey.publicKey().toAddress();
|
||||
|
||||
// Define the receiving Tron address for the transaction.
|
||||
final receiverAddress = TronAddress(toAddress);
|
||||
|
||||
bool isNativeTransaction = currency == CryptoCurrency.trx;
|
||||
|
||||
String totalAmount;
|
||||
TransactionRaw rawTransaction;
|
||||
if (isNativeTransaction) {
|
||||
if (sendAll) {
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: ownerAddress));
|
||||
|
||||
final availableBandWidth = accountResource.howManyBandwIth.toInt();
|
||||
|
||||
// 269 is the current middle ground for bandwidth per transaction
|
||||
if (availableBandWidth >= 269) {
|
||||
totalAmount = amount;
|
||||
} else {
|
||||
final amountInSun = TronHelper.toSun(amount).toInt();
|
||||
|
||||
// 5000 added here is a buffer since we're working with "estimated" value of the fee.
|
||||
final result = amountInSun - (_nativeTxEstimatedFee + 5000);
|
||||
|
||||
totalAmount = TronHelper.fromSun(BigInt.from(result));
|
||||
}
|
||||
} else {
|
||||
totalAmount = amount;
|
||||
}
|
||||
rawTransaction = await _signNativeTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tronBalance,
|
||||
sendAll,
|
||||
);
|
||||
} else {
|
||||
final tokenAddress = (currency as TronToken).contractAddress;
|
||||
totalAmount = amount;
|
||||
rawTransaction = await _signTrcTokenTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tokenAddress,
|
||||
tronBalance,
|
||||
);
|
||||
}
|
||||
|
||||
final signature = ownerPrivKey.sign(rawTransaction.toBuffer());
|
||||
|
||||
sendTx() async => await sendTransaction(
|
||||
rawTransaction: rawTransaction,
|
||||
signature: signature,
|
||||
);
|
||||
|
||||
return PendingTronTransaction(
|
||||
signedTransaction: signature,
|
||||
amount: totalAmount,
|
||||
fee: TronHelper.fromSun(rawTransaction.feeLimit ?? BigInt.zero),
|
||||
sendTransaction: sendTx,
|
||||
);
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signNativeTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
BigInt tronBalance,
|
||||
bool sendAll,
|
||||
) async {
|
||||
// This is introduce to server as a limit in cases where feeLimit is 0
|
||||
// The transaction signing will fail if the feeLimit is explicitly 0.
|
||||
int defaultFeeLimit = 269000;
|
||||
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(amount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: receiverAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().add(const Duration(minutes: 30));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final feeLimit = await getFeeLimit(rawTransaction, ownerAddress, receiverAddress);
|
||||
final feeLimitToUse = feeLimit != 0 ? feeLimit : defaultFeeLimit;
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up.\nTransaction fee: $feeInTrx TRX',
|
||||
);
|
||||
}
|
||||
|
||||
rawTransaction = rawTransaction.copyWith(
|
||||
feeLimit: BigInt.from(feeLimitToUse),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signTrcTokenTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
String contractAddress,
|
||||
BigInt tronBalance,
|
||||
) async {
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
receiverAddress,
|
||||
TronHelper.toSun(amount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
throw Exception(
|
||||
'An error occurred while creating the transfer request. Please try again.',
|
||||
);
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
);
|
||||
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up. Transaction fee: $feeInTrx TRX',
|
||||
);
|
||||
}
|
||||
|
||||
final rawTransaction = request.transactionRaw!.copyWith(
|
||||
feeLimit: BigInt.from(feeLimit),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<String> sendTransaction({
|
||||
required TransactionRaw rawTransaction,
|
||||
required List<int> signature,
|
||||
}) async {
|
||||
try {
|
||||
final transaction = Transaction(rawData: rawTransaction, signature: [signature]);
|
||||
|
||||
final raw = BytesUtils.toHexString(transaction.toBuffer());
|
||||
|
||||
final txBroadcastResult = await _provider!.request(TronRequestBroadcastHex(transaction: raw));
|
||||
|
||||
if (txBroadcastResult.isSuccess) {
|
||||
return txBroadcastResult.txId!;
|
||||
} else {
|
||||
throw Exception(txBroadcastResult.error);
|
||||
}
|
||||
} catch (e) {
|
||||
log('Send block Exception: ${e.toString()}');
|
||||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronBalance> fetchTronTokenBalances(String userAddress, String contractAddress, {bool throwOnError = false}) async {
|
||||
try {
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("balanceOf");
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [ownerAddress],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first ?? BigInt.zero;
|
||||
|
||||
return TronBalance(outputResult);
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return TronBalance(BigInt.zero);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress, String userAddress) async {
|
||||
try {
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final name =
|
||||
(await getTokenDetail(contract, "name", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final symbol =
|
||||
(await getTokenDetail(contract, "symbol", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final decimal =
|
||||
(await getTokenDetail(contract, "decimals", ownerAddress, tokenAddress) as BigInt?) ??
|
||||
BigInt.zero;
|
||||
|
||||
return TronToken(
|
||||
name: name,
|
||||
symbol: symbol,
|
||||
contractAddress: contractAddress,
|
||||
decimal: decimal.toInt(),
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getTokenDetail(
|
||||
ContractABI contract,
|
||||
String functionName,
|
||||
TronAddress ownerAddress,
|
||||
TronAddress tokenAddress,
|
||||
) async {
|
||||
final function = contract.functionFromName(functionName);
|
||||
|
||||
try {
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first;
|
||||
|
||||
return outputResult;
|
||||
} catch (_) {
|
||||
log('Erorr fetching detail: ${_.toString()}');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class TronMnemonicIsIncorrectException implements Exception {
|
||||
@override
|
||||
String toString() =>
|
||||
'Tron mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||
}
|
||||
class TronTransactionCreationException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
TronTransactionCreationException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
|
||||
class TronHTTPProvider implements TronServiceProvider {
|
||||
TronHTTPProvider(
|
||||
{required this.url,
|
||||
this.defaultRequestTimeout = const Duration(seconds: 30)});
|
||||
|
||||
@override
|
||||
final String url;
|
||||
late final client = ProxyWrapper().getHttpIOClient();
|
||||
final Duration defaultRequestTimeout;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> get(TronRequestDetails params,
|
||||
[Duration? timeout]) async {
|
||||
final response = await client.get(Uri.parse(params.url(url)), headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (url.contains("trongrid")) 'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
if (url.contains("nownodes")) 'api-key': secrets.tronNowNodesApiKey,
|
||||
}).timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> post(TronRequestDetails params,
|
||||
[Duration? timeout]) async {
|
||||
final response = await client
|
||||
.post(Uri.parse(params.url(url)),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (url.contains("trongrid"))
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
if (url.contains("nownodes"))
|
||||
'api-key': secrets.tronNowNodesApiKey,
|
||||
},
|
||||
body: params.toRequestBody())
|
||||
.timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class TronTransactionCredentials {
|
||||
TronTransactionCredentials(
|
||||
this.outputs, {
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final CryptoCurrency currency;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:core';
|
||||
import 'dart:developer';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
part 'tron_transaction_history.g.dart';
|
||||
|
||||
class TronTransactionHistory = TronTransactionHistoryBase with _$TronTransactionHistory;
|
||||
|
||||
abstract class TronTransactionHistoryBase extends TransactionHistoryBase<TronTransactionInfo>
|
||||
with Store {
|
||||
TronTransactionHistoryBase(
|
||||
{required this.walletInfo, required String password, required this.encryptionFileUtils})
|
||||
: _password = password {
|
||||
transactions = ObservableMap<String, TronTransactionInfo>();
|
||||
}
|
||||
|
||||
String _password;
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
try {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||
} catch (e, s) {
|
||||
log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
|
||||
log(s.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, TronTransactionInfo> transactions) =>
|
||||
this.transactions.addAll(transactions);
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final content = await encryptionFileUtils.read(path: path, password: _password);
|
||||
if (content.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final content = await _read();
|
||||
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
for (var entry in txs.entries) {
|
||||
final val = entry.value;
|
||||
|
||||
if (val is Map<String, dynamic>) {
|
||||
final tx = TronTransactionInfo.fromJson(val);
|
||||
_update(tx);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _update(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:on_chain/on_chain.dart' as onchain;
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
|
||||
class TronTransactionInfo extends TransactionInfo {
|
||||
TronTransactionInfo({
|
||||
required this.id,
|
||||
required this.tronAmount,
|
||||
required this.txFee,
|
||||
required this.direction,
|
||||
required this.blockTime,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.isPending,
|
||||
this.tokenSymbol = 'TRX',
|
||||
}) : amount = tronAmount.toInt();
|
||||
|
||||
final String id;
|
||||
final String? to;
|
||||
final String? from;
|
||||
final int amount;
|
||||
final BigInt tronAmount;
|
||||
final String tokenSymbol;
|
||||
final DateTime blockTime;
|
||||
final bool isPending;
|
||||
final int? txFee;
|
||||
final TransactionDirection direction;
|
||||
|
||||
factory TronTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||
return TronTransactionInfo(
|
||||
id: data['id'] as String,
|
||||
tronAmount: BigInt.parse(data['tronAmount']),
|
||||
txFee: data['txFee'],
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int),
|
||||
tokenSymbol: data['tokenSymbol'] as String,
|
||||
to: data['to'],
|
||||
from: data['from'],
|
||||
isPending: data['isPending'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'tronAmount': tronAmount.toString(),
|
||||
'txFee': txFee,
|
||||
'direction': direction.index,
|
||||
'blockTime': blockTime.millisecondsSinceEpoch,
|
||||
'tokenSymbol': tokenSymbol,
|
||||
'to': to,
|
||||
'from': from,
|
||||
'isPending': isPending,
|
||||
};
|
||||
|
||||
@override
|
||||
DateTime get date => blockTime;
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
String amountFormatted() {
|
||||
String formattedAmount = _rawAmountAsString(tronAmount);
|
||||
|
||||
return '$formattedAmount $tokenSymbol';
|
||||
}
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
@override
|
||||
String feeFormatted() {
|
||||
final formattedFee = onchain.TronHelper.fromSun(BigInt.from(txFee ?? 0));
|
||||
|
||||
return '$formattedFee TRX';
|
||||
}
|
||||
|
||||
String _rawAmountAsString(BigInt amount) {
|
||||
String formattedAmount = TronHelper.fromSun(amount);
|
||||
|
||||
if (formattedAmount.length >= 8) {
|
||||
formattedAmount = formattedAmount.substring(0, 8);
|
||||
}
|
||||
|
||||
return formattedAmount;
|
||||
}
|
||||
|
||||
String rawTronAmount() => _rawAmountAsString(tronAmount);
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import 'package:blockchain_utils/hex/hex.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronTRC20TransactionModel extends TronTransactionModel {
|
||||
String? transactionId;
|
||||
|
||||
String? tokenSymbol;
|
||||
|
||||
int? timestamp;
|
||||
|
||||
@override
|
||||
String? from;
|
||||
|
||||
@override
|
||||
String? to;
|
||||
|
||||
String? value;
|
||||
|
||||
@override
|
||||
String get hash => transactionId!;
|
||||
|
||||
@override
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0);
|
||||
|
||||
@override
|
||||
BigInt? get amount => BigInt.parse(value ?? '0');
|
||||
|
||||
@override
|
||||
int? get fee => 0;
|
||||
|
||||
TronTRC20TransactionModel({
|
||||
this.transactionId,
|
||||
this.tokenSymbol,
|
||||
this.timestamp,
|
||||
this.from,
|
||||
this.to,
|
||||
this.value,
|
||||
});
|
||||
|
||||
TronTRC20TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
transactionId = json['transaction_id'];
|
||||
tokenSymbol = json['token_info'] != null ? json['token_info']['symbol'] : null;
|
||||
timestamp = json['block_timestamp'];
|
||||
from = json['from'];
|
||||
to = json['to'];
|
||||
value = json['value'];
|
||||
}
|
||||
}
|
||||
|
||||
class TronTransactionModel {
|
||||
List<Ret>? ret;
|
||||
String? txID;
|
||||
int? blockTimestamp;
|
||||
List<Contract>? contracts;
|
||||
|
||||
/// Getters to extract out the needed/useful information directly from the model params
|
||||
/// Without having to go through extra steps in the methods that use this model.
|
||||
bool get isError {
|
||||
if (ret?.first.contractRet == null) return true;
|
||||
|
||||
return ret?.first.contractRet != "SUCCESS";
|
||||
}
|
||||
|
||||
String get hash => txID!;
|
||||
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(blockTimestamp ?? 0);
|
||||
|
||||
String? get from => contracts?.first.parameter?.value?.ownerAddress;
|
||||
|
||||
String? get to => contracts?.first.parameter?.value?.receiverAddress;
|
||||
|
||||
BigInt? get amount => contracts?.first.parameter?.value?.txAmount;
|
||||
|
||||
int? get fee => ret?.first.fee;
|
||||
|
||||
String? get contractAddress => contracts?.first.parameter?.value?.contractAddress;
|
||||
|
||||
TronTransactionModel({
|
||||
this.ret,
|
||||
this.txID,
|
||||
this.blockTimestamp,
|
||||
this.contracts,
|
||||
});
|
||||
|
||||
TronTransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
if (json['ret'] != null) {
|
||||
ret = <Ret>[];
|
||||
json['ret'].forEach((v) {
|
||||
ret!.add(Ret.fromJson(v));
|
||||
});
|
||||
}
|
||||
txID = json['txID'];
|
||||
blockTimestamp = json['block_timestamp'];
|
||||
contracts = json['raw_data'] != null
|
||||
? (json['raw_data']['contract'] as List)
|
||||
.map((e) => Contract.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class Ret {
|
||||
String? contractRet;
|
||||
int? fee;
|
||||
|
||||
Ret({this.contractRet, this.fee});
|
||||
|
||||
Ret.fromJson(Map<String, dynamic> json) {
|
||||
contractRet = json['contractRet'];
|
||||
fee = json['fee'];
|
||||
}
|
||||
}
|
||||
|
||||
class Contract {
|
||||
Parameter? parameter;
|
||||
String? type;
|
||||
|
||||
Contract({this.parameter, this.type});
|
||||
|
||||
Contract.fromJson(Map<String, dynamic> json) {
|
||||
parameter = json['parameter'] != null ? Parameter.fromJson(json['parameter']) : null;
|
||||
type = json['type'];
|
||||
}
|
||||
}
|
||||
|
||||
class Parameter {
|
||||
Value? value;
|
||||
String? typeUrl;
|
||||
|
||||
Parameter({this.value, this.typeUrl});
|
||||
|
||||
Parameter.fromJson(Map<String, dynamic> json) {
|
||||
value = json['value'] != null ? Value.fromJson(json['value']) : null;
|
||||
typeUrl = json['type_url'];
|
||||
}
|
||||
}
|
||||
|
||||
class Value {
|
||||
String? data;
|
||||
String? ownerAddress;
|
||||
String? contractAddress;
|
||||
int? amount;
|
||||
String? toAddress;
|
||||
String? assetName;
|
||||
|
||||
//Getters to extract address for tron transactions
|
||||
/// If the contract address is null, it returns the toAddress
|
||||
/// If it's not null, it decodes the data field and gets the receiver address.
|
||||
String? get receiverAddress {
|
||||
if (contractAddress == null) return toAddress;
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAddressFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
//Getters to extract amount for tron transactions
|
||||
/// If the contract address is null, it returns the amount
|
||||
/// If it's not null, it decodes the data field and gets the tx amount.
|
||||
BigInt? get txAmount {
|
||||
if (contractAddress == null) return BigInt.from(amount ?? 0);
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAmountInvolvedFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
Value(
|
||||
{this.data,
|
||||
this.ownerAddress,
|
||||
this.contractAddress,
|
||||
this.amount,
|
||||
this.toAddress,
|
||||
this.assetName});
|
||||
|
||||
Value.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'];
|
||||
ownerAddress = json['owner_address'];
|
||||
contractAddress = json['contract_address'];
|
||||
amount = json['amount'];
|
||||
toAddress = json['to_address'];
|
||||
assetName = json['asset_name'];
|
||||
}
|
||||
|
||||
/// To get the address from the encoded data field
|
||||
String _decodeAddressFromEncodedDataField(String output) {
|
||||
// To get the receiver address from the encoded params
|
||||
output = output.replaceFirst('0x', '').substring(8);
|
||||
final abiCoder = ABICoder.fromType('address');
|
||||
final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output));
|
||||
final tronAddress = TronAddress.fromEthAddress((decoded.result as ETHAddress).toBytes());
|
||||
|
||||
return tronAddress.toString();
|
||||
}
|
||||
|
||||
/// To get the amount from the encoded data field
|
||||
BigInt _decodeAmountInvolvedFromEncodedDataField(String output) {
|
||||
output = output.replaceFirst('0x', '').substring(72);
|
||||
final amountAbiCoder = ABICoder.fromType('uint256');
|
||||
final decodedA = amountAbiCoder.decode(AbiParameter.uint256, hex.decode(output));
|
||||
final amount = decodedA.result as BigInt;
|
||||
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -1,676 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/default_tron_tokens.dart';
|
||||
import 'package:cw_tron/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_credentials.dart';
|
||||
import 'package:cw_tron/tron_transaction_history.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:cw_tron/tron_wallet_addresses.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
part 'tron_wallet.g.dart';
|
||||
|
||||
class TronWallet = TronWalletBase with _$TronWallet;
|
||||
|
||||
abstract class TronWalletBase
|
||||
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo>
|
||||
with Store, WalletKeysFile {
|
||||
TronWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
required DerivationInfo derivationInfo,
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
TronBalance? initialBalance,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_hexPrivateKey = privateKey,
|
||||
_client = TronClient(),
|
||||
walletAddresses = TronWalletAddresses(walletInfo),
|
||||
balance = ObservableMap<CryptoCurrency, TronBalance>.of(
|
||||
{CryptoCurrency.trx: initialBalance ?? TronBalance(BigInt.zero)},
|
||||
),
|
||||
super(walletInfo, derivationInfo) {
|
||||
this.walletInfo = walletInfo;
|
||||
transactionHistory = TronTransactionHistory(
|
||||
walletInfo: walletInfo, password: password, encryptionFileUtils: encryptionFileUtils);
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(TronToken.typeId)) {
|
||||
CakeHive.registerAdapter(TronTokenAdapter());
|
||||
}
|
||||
}
|
||||
|
||||
final String? _mnemonic;
|
||||
final String? _hexPrivateKey;
|
||||
final String _password;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
|
||||
late final Box<TronToken> tronTokensBox;
|
||||
|
||||
late final TronPrivateKey _tronPrivateKey;
|
||||
|
||||
late final TronPublicKey _tronPublicKey;
|
||||
|
||||
TronPublicKey get tronPublicKey => _tronPublicKey;
|
||||
|
||||
TronPrivateKey get tronPrivateKey => _tronPrivateKey;
|
||||
|
||||
late String _tronAddress;
|
||||
|
||||
late final TronClient _client;
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
||||
@observable
|
||||
String? nativeTxEstimatedFee;
|
||||
|
||||
@observable
|
||||
String? trc20EstimatedFee;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
late ObservableMap<CryptoCurrency, TronBalance> balance;
|
||||
|
||||
Future<void> init() async {
|
||||
await initTronTokensBox();
|
||||
|
||||
await walletAddresses.init();
|
||||
await transactionHistory.init();
|
||||
_tronPrivateKey = await getPrivateKey(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
password: _password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
_tronPublicKey = _tronPrivateKey.publicKey();
|
||||
|
||||
_tronAddress = _tronPublicKey.toAddress().toString();
|
||||
|
||||
walletAddresses.address = _tronAddress;
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
static Future<TronWallet> open({
|
||||
required String name,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = TronBalance.fromJSON(data?['balance'] as String?) ?? TronBalance(BigInt.zero);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final passphrase = data['passphrase'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(
|
||||
name,
|
||||
walletInfo.type,
|
||||
password,
|
||||
encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
final derivationInfo = await walletInfo.getDerivationInfo();
|
||||
|
||||
return TronWallet(
|
||||
walletInfo: walletInfo,
|
||||
derivationInfo: derivationInfo,
|
||||
password: password,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
passphrase: keysData.passphrase,
|
||||
initialBalance: balance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
void addInitialTokens() {
|
||||
final initialTronTokens = DefaultTronTokens().initialTronTokens;
|
||||
|
||||
for (var token in initialTronTokens) {
|
||||
if (!tronTokensBox.containsKey(token.contractAddress)) {
|
||||
tronTokensBox.put(token.contractAddress, token);
|
||||
} else {
|
||||
// update existing token
|
||||
final existingToken = tronTokensBox.get(token.contractAddress);
|
||||
tronTokensBox.put(
|
||||
token.contractAddress, TronToken.copyWith(token, enabled: existingToken!.enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initTronTokensBox() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${TronToken.boxName}";
|
||||
|
||||
tronTokensBox = await CakeHive.openBox<TronToken>(boxName);
|
||||
}
|
||||
|
||||
String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
|
||||
|
||||
Future<TronPrivateKey> getPrivateKey({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) return TronPrivateKey(privateKey);
|
||||
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!, passphrase: passphrase ?? '');
|
||||
|
||||
// Derive a TRON private key from the seed
|
||||
final bip44 = Bip44.fromSeed(seed, Bip44Coins.tron);
|
||||
|
||||
final childKey = bip44.deriveDefaultPath;
|
||||
|
||||
return TronPrivateKey.fromBytes(childKey.privateKey.raw);
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
Future<void> close({bool shouldCleanup = false}) async => _transactionsUpdateTimer?.cancel();
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
final isConnected = _client.connect(node);
|
||||
|
||||
if (!isConnected) {
|
||||
throw Exception("${walletInfo.type.name.toUpperCase()} Node connection failed");
|
||||
}
|
||||
|
||||
_getEstimatedFees();
|
||||
_setTransactionUpdateTimer();
|
||||
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getEstimatedFees() async {
|
||||
final nativeFee = await _getNativeTxFee();
|
||||
nativeTxEstimatedFee = TronHelper.fromSun(BigInt.from(nativeFee));
|
||||
|
||||
final trc20Fee = await _getTrc20TxFee();
|
||||
trc20EstimatedFee = TronHelper.fromSun(BigInt.from(trc20Fee));
|
||||
|
||||
log('Native Estimated Fee: $nativeTxEstimatedFee');
|
||||
log('TRC20 Estimated Fee: $trc20EstimatedFee');
|
||||
}
|
||||
|
||||
Future<int> _getNativeTxFee() async {
|
||||
try {
|
||||
final fee = await _client.getEstimatedFee(_tronPublicKey.toAddress());
|
||||
return fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _getTrc20TxFee() async {
|
||||
try {
|
||||
final trc20fee = await _client.getTRCEstimatedFee(_tronPublicKey.toAddress());
|
||||
return trc20fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
|
||||
// Verify node health before attempting to sync
|
||||
final isHealthy = await checkNodeHealth();
|
||||
if (!isHealthy) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
return;
|
||||
}
|
||||
await _updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
final tronCredentials = credentials as TronTransactionCredentials;
|
||||
|
||||
final outputs = tronCredentials.outputs;
|
||||
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
final transactionCurrency = balance.keys.firstWhere(
|
||||
(currency) =>
|
||||
currency.title == tronCredentials.currency.title &&
|
||||
currency.tag == tronCredentials.currency.tag,
|
||||
orElse: () => throw Exception(
|
||||
'Currency ${tronCredentials.currency.title} ${tronCredentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
|
||||
|
||||
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||
|
||||
BigInt totalAmount = BigInt.zero;
|
||||
bool shouldSendAll = false;
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
|
||||
final totalAmountFromCredentials =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
totalAmount = BigInt.from(totalAmountFromCredentials);
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
|
||||
shouldSendAll = output.sendAll;
|
||||
|
||||
if (shouldSendAll) {
|
||||
totalAmount = walletBalanceForCurrency;
|
||||
} else {
|
||||
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
|
||||
totalAmount = TronHelper.toSun(totalOriginalAmount.toString());
|
||||
}
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount || totalAmount < BigInt.zero) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
final tronBalance = balance[CryptoCurrency.trx]?.balance ?? BigInt.zero;
|
||||
|
||||
final pendingTransaction = await _client.signTransaction(
|
||||
ownerPrivKey: _tronPrivateKey,
|
||||
toAddress: tronCredentials.outputs.first.isParsedAddress
|
||||
? tronCredentials.outputs.first.extractedAddress!
|
||||
: tronCredentials.outputs.first.address,
|
||||
amount: TronHelper.fromSun(totalAmount),
|
||||
currency: transactionCurrency,
|
||||
tronBalance: tronBalance,
|
||||
sendAll: shouldSendAll,
|
||||
);
|
||||
|
||||
return pendingTransaction;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactionsHistory() async {
|
||||
await Future.wait([
|
||||
fetchTransactions(),
|
||||
fetchTrc20ExcludedTransactions(),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, TronTransactionInfo>> fetchTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final ownerAddress = TronAddress(_tronAddress);
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionModel.isError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter out spam transaactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions
|
||||
if (transactionModel.contracts?.first.type == "TransferAssetContract") {
|
||||
continue;
|
||||
}
|
||||
|
||||
String? tokenSymbol;
|
||||
if (transactionModel.contractAddress != null) {
|
||||
final tokenAddress = TronAddress(transactionModel.contractAddress!);
|
||||
|
||||
tokenSymbol = (await _client.getTokenDetail(
|
||||
contract,
|
||||
"symbol",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as String?) ??
|
||||
'';
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
|
||||
return transactionHistory.transactions;
|
||||
}
|
||||
|
||||
Future<void> fetchTrc20ExcludedTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTrc20ExcludedTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionHistory.transactions.containsKey(transactionModel.hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: transactionModel.from! == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: transactionModel.tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Object get keys => throw UnimplementedError("keys");
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) => throw UnimplementedError("rescan");
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password, encryptionFileUtils);
|
||||
saveKeysFile(_password, encryptionFileUtils, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
String? get seed => _mnemonic;
|
||||
|
||||
@override
|
||||
String get privateKey => _tronPrivateKey.toHex();
|
||||
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: privateKey,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': privateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
balance[currency] = await _fetchTronBalance();
|
||||
|
||||
await _fetchTronTokenBalances();
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<TronBalance> _fetchTronBalance() async {
|
||||
final balance = await _client.getBalance(_tronPublicKey.toAddress());
|
||||
return TronBalance(balance);
|
||||
}
|
||||
|
||||
Future<void> _fetchTronTokenBalances() async {
|
||||
for (var token in tronTokensBox.values) {
|
||||
try {
|
||||
if (token.enabled) {
|
||||
balance[token] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
token.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(token);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
@override
|
||||
Future<bool> checkNodeHealth() async {
|
||||
try {
|
||||
// Check native balance
|
||||
await _client.getBalance(_tronPublicKey.toAddress(), throwOnError: true);
|
||||
|
||||
// Check USDT token balance
|
||||
const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
|
||||
await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, throwOnError: true);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<TronToken> get tronTokenCurrencies => tronTokensBox.values.toList();
|
||||
|
||||
Future<void> addTronToken(TronToken token) async {
|
||||
String? iconPath;
|
||||
if ((token.iconPath == null || token.iconPath!.isEmpty) && !token.isPotentialScam) {
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
} else if (!token.isPotentialScam) {
|
||||
iconPath = token.iconPath;
|
||||
}
|
||||
|
||||
final newToken = TronToken(
|
||||
name: token.name,
|
||||
symbol: token.symbol,
|
||||
contractAddress: token.contractAddress,
|
||||
decimal: token.decimal,
|
||||
enabled: token.enabled,
|
||||
tag: token.tag ?? "TRX",
|
||||
iconPath: iconPath,
|
||||
isPotentialScam: token.isPotentialScam,
|
||||
);
|
||||
|
||||
await tronTokensBox.put(newToken.contractAddress, newToken);
|
||||
|
||||
if (newToken.enabled) {
|
||||
balance[newToken] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
newToken.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(newToken);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteTronToken(TronToken token) async {
|
||||
if (tronTokensBox.isOpen) {
|
||||
await tronTokensBox.delete(token.contractAddress);
|
||||
}
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
_updateBalance();
|
||||
}
|
||||
|
||||
Future<void> _removeTokenTransactionsInHistory(TronToken token) async {
|
||||
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress) async =>
|
||||
await _client.getTronToken(contractAddress, _tronAddress);
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
const transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
final currentTransactionsFile = File('$currentDirPath/$transactionHistoryFileNameForWallet');
|
||||
|
||||
// Copies current wallet files into new wallet name's dir and files
|
||||
if (currentWalletFile.existsSync()) {
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
await currentWalletFile.copy(newWalletPath);
|
||||
}
|
||||
if (currentTransactionsFile.existsSync()) {
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
await currentTransactionsFile.copy('$newDirPath/$transactionHistoryFileNameForWallet');
|
||||
}
|
||||
|
||||
// Delete old name's dir and files
|
||||
await Directory(currentDirPath).delete(recursive: true);
|
||||
}
|
||||
|
||||
void _setTransactionUpdateTimer() {
|
||||
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||
_transactionsUpdateTimer!.cancel();
|
||||
}
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) async {
|
||||
_updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address}) async {
|
||||
return _tronPrivateKey.signPersonalMessage(ascii.encode(message));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address}) async {
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
TronPublicKey pubKey = TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!;
|
||||
return pubKey.toAddress().toString() == address;
|
||||
}
|
||||
|
||||
String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress();
|
||||
|
||||
void updateScanProviderUsageState(bool isEnabled) {
|
||||
if (isEnabled) {
|
||||
fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
_setTransactionUpdateTimer();
|
||||
} else {
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'tron_wallet_addresses.g.dart';
|
||||
|
||||
class TronWalletAddresses = TronWalletAddressesBase with _$TronWalletAddresses;
|
||||
|
||||
abstract class TronWalletAddressesBase extends WalletAddresses with Store {
|
||||
TronWalletAddressesBase(WalletInfo walletInfo)
|
||||
: address = '',
|
||||
super(walletInfo);
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
PaymentURI getPaymentUri(String amount) => TronURI(amount: amount, address: address);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class TronNewWalletCredentials extends WalletCredentials {
|
||||
TronNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
this.mnemonic,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
TronRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||
TronRestoreWalletFromPrivateKey(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.privateKey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String privateKey;
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_tron/tron_wallet.dart';
|
||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class TronWalletService extends WalletService<
|
||||
TronNewWalletCredentials,
|
||||
TronRestoreWalletFromSeedCredentials,
|
||||
TronRestoreWalletFromPrivateKey,
|
||||
TronNewWalletCredentials> {
|
||||
TronWalletService({required this.client, required this.isDirect});
|
||||
|
||||
late TronClient client;
|
||||
|
||||
final bool isDirect;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.tron;
|
||||
|
||||
@override
|
||||
Future<TronWallet> create(TronNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
|
||||
|
||||
final wallet = TronWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = await WalletInfo.get(name, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
|
||||
try {
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromKeys(
|
||||
TronRestoreWalletFromPrivateKey credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
privateKey: credentials.privateKey,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromSeed(
|
||||
TronRestoreWalletFromSeedCredentials credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw TronMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = await WalletInfo.get(currentName, getType());
|
||||
if (currentWalletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final currentWallet = await TronWalletBase.open(
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
|
||||
await newWalletInfo.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = await WalletInfo.get(wallet, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
await WalletInfo.delete(walletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>>
|
||||
restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
|
||||
// TODO: implement restoreFromHardwareWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
name: cw_tron
|
||||
description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
homepage: https://github.com/Such-Software/hash-wallet
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.6 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
cw_evm:
|
||||
path: ../cw_evm
|
||||
on_chain:
|
||||
git:
|
||||
url: https://github.com/cake-tech/on_chain.git
|
||||
ref: 096865a8c6b89c260beadfec04f7e184c40a3273
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v2
|
||||
mobx: ^2.3.0+1
|
||||
bip39: ^1.0.6
|
||||
hive: ^2.2.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
build_runner: ^2.4.15
|
||||
mobx_codegen: ^2.1.1
|
||||
hive_generator: ^2.0.1
|
||||
flutter:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:cw_tron/cw_tron.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
final calculator = Calculator();
|
||||
expect(calculator.addOne(2), 3);
|
||||
expect(calculator.addOne(-7), -6);
|
||||
expect(calculator.addOne(0), 1);
|
||||
});
|
||||
}
|
||||
7
cw_zano/.gitignore
vendored
7
cw_zano/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
.DS_Store
|
||||
.dart_tool/
|
||||
|
||||
.packages
|
||||
.pub/
|
||||
|
||||
build/
|
||||
@@ -1,10 +0,0 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb
|
||||
channel: stable
|
||||
|
||||
project_type: plugin
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,23 +0,0 @@
|
||||
# cw_zano
|
||||
|
||||
Zano wallet module for Cake Wallet. Provides a Dart wrapper around the Zano wallet API with typed models and transaction helpers.
|
||||
|
||||
## Features
|
||||
|
||||
- Wallet lifecycle and status queries via `ZanoWalletApi`.
|
||||
- Typed models for balances, transfers, recent history, and wallet info.
|
||||
- Build and submit transfers; pending transaction modeling.
|
||||
- Address and asset utilities, formatter helpers.
|
||||
|
||||
## Usage
|
||||
|
||||
See `lib/zano_wallet_api.dart` and `lib/zano_wallet.dart` for the high-level API. Typical flow:
|
||||
|
||||
```dart
|
||||
final api = ZanoWalletApi();
|
||||
final info = await api.getWalletInfo();
|
||||
final balance = await api.getBalance();
|
||||
// create transfer params and broadcast
|
||||
```
|
||||
|
||||
Consult `lib/api/model/` for full set of supported request/response models.
|
||||
@@ -1,6 +0,0 @@
|
||||
class Consts {
|
||||
static const errorWrongSeed = 'WRONG_SEED';
|
||||
static const errorAlreadyExists = 'ALREADY_EXISTS';
|
||||
static const errorWalletWrongId = 'WALLET_WRONG_ID';
|
||||
static const errorBusy = 'BUSY';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
class AssetIdParams {
|
||||
final String assetId;
|
||||
|
||||
AssetIdParams({required this.assetId});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'asset_id': assetId,
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import 'package:cw_core/zano_asset.dart';
|
||||
import 'package:cw_zano/model/zano_asset.dart';
|
||||
import 'package:cw_zano/zano_formatter.dart';
|
||||
|
||||
class Balance {
|
||||
final ZanoAsset assetInfo;
|
||||
final BigInt awaitingIn;
|
||||
final BigInt awaitingOut;
|
||||
final BigInt total;
|
||||
final BigInt unlocked;
|
||||
|
||||
Balance(
|
||||
{required this.assetInfo,
|
||||
required this.awaitingIn,
|
||||
required this.awaitingOut,
|
||||
required this.total,
|
||||
required this.unlocked});
|
||||
|
||||
String get assetId => assetInfo.assetId;
|
||||
|
||||
@override
|
||||
String toString() => '$assetInfo: $total/$unlocked';
|
||||
|
||||
factory Balance.fromJson(Map<String, dynamic> json) => Balance(
|
||||
assetInfo:
|
||||
ZanoAsset.fromJson(json['asset_info'] as Map<String, dynamic>? ?? {}),
|
||||
awaitingIn: ZanoFormatter.bigIntFromDynamic(json['awaiting_in']),
|
||||
awaitingOut: ZanoFormatter.bigIntFromDynamic(json['awaiting_out']),
|
||||
total: ZanoFormatter.bigIntFromDynamic(json['total']),
|
||||
unlocked: ZanoFormatter.bigIntFromDynamic(json['unlocked']),
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import 'package:cw_zano/api/model/recent_history.dart';
|
||||
import 'package:cw_zano/api/model/wi.dart';
|
||||
import 'package:cw_zano/zano_wallet.dart';
|
||||
|
||||
class CreateWalletResult {
|
||||
final String name;
|
||||
final String pass;
|
||||
final RecentHistory recentHistory;
|
||||
final bool recovered;
|
||||
final int walletFileSize;
|
||||
final int walletId;
|
||||
final int walletLocalBcSize;
|
||||
final Wi wi;
|
||||
final String privateSpendKey;
|
||||
final String privateViewKey;
|
||||
final String publicSpendKey;
|
||||
final String publicViewKey;
|
||||
|
||||
CreateWalletResult(
|
||||
{required this.name,
|
||||
required this.pass,
|
||||
required this.recentHistory,
|
||||
required this.recovered,
|
||||
required this.walletFileSize,
|
||||
required this.walletId,
|
||||
required this.walletLocalBcSize,
|
||||
required this.wi,
|
||||
required this.privateSpendKey,
|
||||
required this.privateViewKey,
|
||||
required this.publicSpendKey,
|
||||
required this.publicViewKey});
|
||||
|
||||
factory CreateWalletResult.fromJson(Map<String, dynamic> json) =>
|
||||
CreateWalletResult(
|
||||
name: json['name'] as String? ?? '',
|
||||
pass: json['pass'] as String? ?? '',
|
||||
recentHistory: RecentHistory.fromJson(
|
||||
json['recent_history'] as Map<String, dynamic>? ?? {}),
|
||||
recovered: json['recovered'] as bool? ?? false,
|
||||
walletFileSize: json['wallet_file_size'] as int? ?? 0,
|
||||
walletId: json['wallet_id'] as int? ?? 0,
|
||||
walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0,
|
||||
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
|
||||
privateSpendKey: json['private_spend_key'] as String? ?? '',
|
||||
privateViewKey: json['private_view_key'] as String? ?? '',
|
||||
publicSpendKey: json['public_spend_key'] as String? ?? '',
|
||||
publicViewKey: json['public_view_key'] as String? ?? '',
|
||||
);
|
||||
Future<String> seed(ZanoWalletBase api) {
|
||||
return api.getSeed();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user