Merge branch 'dev' into cw-1440-new-buy-sell-flow

This commit is contained in:
malik1004x
2026-08-20 16:26:13 +02:00
committed by GitHub
220 changed files with 11956 additions and 9280 deletions
@@ -159,11 +159,8 @@ jobs:
- name: Build generated code - name: Build generated code
run: | run: |
flutter --version flutter --version
flutter clean
rm -rf .dart_tool
rm pubspec.lock
flutter pub get flutter pub get
./model_generator.sh async ./model_generator.sh
- name: Generate key properties - name: Generate key properties
run: | run: |
+55
View File
@@ -0,0 +1,55 @@
name: Check for dependency changes
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
compare-overrides:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-go@v5
with:
go-version: stable
- name: Compare pubspec overrides
id: compare
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
git fetch origin "$BASE_REF"
base_sha=$(git rev-parse "origin/$BASE_REF")
head_sha="${{ github.event.pull_request.head.sha }}"
./scripts/compare_overrides.sh "$base_sha" "$head_sha" > /tmp/compare_output.txt
if [[ ! -s /tmp/compare_output.txt ]]; then
echo "has_output=false" >> "$GITHUB_OUTPUT"
exit 0
fi
{
echo '### changes in dependencies'
echo
echo '```'
cat /tmp/compare_output.txt
echo '```'
} > /tmp/comment_body.md
echo "has_output=true" >> "$GITHUB_OUTPUT"
- uses: marocchino/sticky-pull-request-comment@v2
if: steps.compare.outputs.has_output == 'true'
with:
header: pubspec-overrides-compare
path: /tmp/comment_body.md
+20 -6
View File
@@ -1,18 +1,21 @@
name: Cake Wallet Linux name: Cake Wallet Linux
on: [pull_request]
permissions: permissions:
contents: read contents: read
packages: write packages: write
on: [pull_request]
defaults: defaults:
run: run:
shell: bash shell: bash
jobs: jobs:
PR_test_build: PR_test_build:
# Fork PRs don't have access to repository secrets, so the generated
# lib/.secrets.g.dart would be empty and the build fails to compile.
# Skip for forks, mirroring the Android workflow's internal-build guard.
if: github.event.pull_request.head.repo.fork == false
runs-on: [Linux, amd64, forlinux] runs-on: [Linux, amd64, forlinux]
container: container:
image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.41.9-ndkr28-go1.24.1-ruststablenightly
@@ -28,6 +31,7 @@ jobs:
- /opt/cw_cache_linux/root/.pub-cache/:/root/.pub-cache - /opt/cw_cache_linux/root/.pub-cache/:/root/.pub-cache
- /opt/cw_cache_linux/root/go/pkg:/root/go/pkg - /opt/cw_cache_linux/root/go/pkg:/root/go/pkg
- /opt/cw_cache_linux/opt/generic_cache:/opt/generic_cache - /opt/cw_cache_linux/opt/generic_cache:/opt/generic_cache
- /var/run/docker.sock:/var/run/docker.sock
steps: steps:
- name: Fix github actions messing up $HOME... - name: Fix github actions messing up $HOME...
@@ -44,6 +48,14 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }} repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: configure git - name: configure git
run: | run: |
git config --global --add safe.directory '*' git config --global --add safe.directory '*'
@@ -85,7 +97,6 @@ jobs:
echo "${{ secrets.MAIN_SECRETS_FILE }}" | base64 --decode > lib/.secrets.g.dart echo "${{ secrets.MAIN_SECRETS_FILE }}" | base64 --decode > lib/.secrets.g.dart
- name: Build dependencies utilizing ghcr cache - name: Build dependencies utilizing ghcr cache
run: | run: |
env \ env \
@@ -93,7 +104,6 @@ jobs:
CW_DOCKER_USE_CLOUD=true \ CW_DOCKER_USE_CLOUD=true \
./scripts/linux/docker/build.sh ./scripts/linux/docker/build.sh
- name: Execute Build and Setup Commands - name: Execute Build and Setup Commands
run: | run: |
pushd scripts/linux pushd scripts/linux
@@ -107,12 +117,16 @@ jobs:
- name: Build generated code - name: Build generated code
run: | run: |
./model_generator.sh async ./model_generator.sh
- name: Generate localization - name: Generate localization
run: | run: |
dart run tool/generate_localization.dart dart run tool/generate_localization.dart
- name: Compile graphics
run: |
./compile_graphics.sh
- name: Build linux - name: Build linux
run: | run: |
flutter build linux --dart-define=hasDevOptions=true --release flutter build linux --dart-define=hasDevOptions=true --release
+2 -6
View File
@@ -125,12 +125,8 @@ jobs:
- name: Build generated code - name: Build generated code
run: | run: |
flutter --version ./model_generator.sh
flutter clean
rm -rf .dart_tool
rm pubspec.lock
flutter pub get
./model_generator.sh async
- name: Generate key properties - name: Generate key properties
run: | run: |
+2 -1
View File
@@ -160,7 +160,6 @@ assets/images/app_logo.png
assets/new-ui/**/*.svg.vec assets/new-ui/**/*.svg.vec
/pubspec.yaml /pubspec.yaml
/pubspec.lock
/android/app.properties /android/app.properties
/android/app/src/main/AndroidManifest.xml /android/app/src/main/AndroidManifest.xml
@@ -228,3 +227,5 @@ scripts/reown_flutter
*.salive *.salive
android/.kotlin android/.kotlin
scripts/.cache
pubspec.lock
@@ -90,7 +90,9 @@
<data android:scheme="polygon" /> <data android:scheme="polygon" />
<data android:scheme="polygon-wallet" /> <data android:scheme="polygon-wallet" />
<data android:scheme="polygon_wallet" /> <data android:scheme="polygon_wallet" />
<data android:scheme="solana" />
<data android:scheme="solana-wallet" /> <data android:scheme="solana-wallet" />
<data android:scheme="solana_wallet" />
<data android:scheme="tron" /> <data android:scheme="tron" />
<data android:scheme="tron-wallet" /> <data android:scheme="tron-wallet" />
<data android:scheme="tron_wallet" /> <data android:scheme="tron_wallet" />
@@ -1 +1 @@
[{"title":"مونيرو + تريزور","description":"قم بتأمين XMR الخاص بك من خلال دعم محفظة الأجهزة!"},{"title":"واجهة مستخدم WalletConnect الجديدة","description":"أصبح الاتصال بـ Web3 dApps أكثر أمانًا وسهولة"},{"title":"تحسينات واجهة المستخدم","description":"تمت إضافة بعض عناصر واجهة المستخدم الجديدة خصيصًا لك"},{"title":"تحسينات المزامنة","description":"المزامنة الأمثل للبيتكوين وLitecoin"},{"title":"إصلاحات الشوائب","description":"سحق المزيد من الأخطاء"}] [{"title":"تحسينات زي كاش","description":"بعد الترحيل الناجح إلى IronWood الجديد، أضفنا المزيد من التحسينات إلى محفظة zcash"},{"title":"AnyPay على النحو المنشود","description":"الآن، سيؤدي لصق عنوان ما أو مسحه ضوئيًا من أي محفظة إلى توجيهك بشكل صحيح إما للتبديل أو الإرسال من المحفظة الصحيحة، والمزيد من التحسينات"},{"title":"تحسينات واجهة المستخدم","description":"تمت إضافة بعض عناصر واجهة المستخدم الجديدة خصيصًا لك"},{"title":"إصلاحات الشوائب","description":"سحق المزيد من الأخطاء"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Защитете своя XMR с поддръжка на хардуерен портфейл!"},{"title":"НОВ потребителски интерфейс на WalletConnect","description":"свързването към Web3 dApps стана по-безопасно и по-лесно"},{"title":"Подобрения на потребителския интерфейс","description":"Добавени са някои нови елементи на потребителския интерфейс само за вас"},{"title":"Подобрения при синхронизиране","description":"Оптимизирана синхронизация за Bitcoin и Litecoin"},{"title":"Поправки на грешки","description":"Смачкани още грешки"}] [{"title":"Подобрения на Zcash","description":"След успешната миграция към новия IronWood добавихме още подобрения към zcash портфейла"},{"title":"AnyPay според предназначението","description":"Сега поставянето или сканирането на адрес от който и да е портфейл ще ви насочи правилно към размяна или изпращане от правилния портфейл и много повече подобрения"},{"title":"Подобрения на потребителския интерфейс","description":"Добавени са някои нови елементи на потребителския интерфейс само за вас"},{"title":"Поправки на грешки","description":"Смачкани още грешки"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Zabezpečte svůj XMR s podporou hardwarové peněženky!"},{"title":"NOVÉ uživatelské rozhraní WalletConnect","description":"připojení k Web3 dApps je bezpečnější a jednodušší"},{"title":"vylepšení uživatelského rozhraní","description":"Přidali jsme několik nových prvků uživatelského rozhraní jen pro vás"},{"title":"Vylepšení synchronizace","description":"Optimalizovaná synchronizace pro bitcoiny a litecoiny"},{"title":"Opravy chyb","description":"Zmáčkl další brouky"}] [{"title":"Zcash vylepšení","description":"Po úspěšné migraci na nový IronWood jsme přidali další vylepšení do zcash peněženky"},{"title":"AnyPay, jak bylo zamýšleno","description":"Nyní vložení nebo skenování adresy z libovolné peněženky vás správně nasměruje buď na výměnu nebo odeslání ze správné peněženky, a k mnoha dalším vylepšením"},{"title":"vylepšení uživatelského rozhraní","description":"Přidali jsme několik nových prvků uživatelského rozhraní jen pro vás"},{"title":"Opravy chyb","description":"Rozdrtil více brouků"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Sichern Sie Ihr XMR mit Hardware-Wallet-Unterstützung!"},{"title":"NEUE WalletConnect-Benutzeroberfläche","description":"Die Verbindung zu Web3-dApps wird sicherer und einfacher"},{"title":"Verbesserungen der Benutzeroberfläche","description":"Einige neue UI-Elemente wurden speziell für Sie hinzugefügt"},{"title":"Verbesserungen bei der Synchronisierung","description":"Optimierte Synchronisierung für Bitcoin und Litecoin"},{"title":"Fehlerbehebungen","description":"Weitere Bugs beseitigt"}] [{"title":"Zcash-Verbesserungen","description":"Nach der erfolgreichen Migration auf das neue IronWood haben wir weitere Verbesserungen an der Zcash-Wallet vorgenommen"},{"title":"AnyPay wie vorgesehen","description":"Wenn Sie nun eine Adresse aus einem beliebigen Wallet einfügen oder scannen, werden Sie direkt zum Tauschen oder Senden von dem richtigen Wallet weitergeleitet und es gibt noch viele weitere Verbesserungen"},{"title":"Verbesserungen der Benutzeroberfläche","description":"Einige neue UI-Elemente wurden speziell für Sie hinzugefügt"},{"title":"Fehlerbehebungen","description":"Weitere Bugs beseitigt"}]
@@ -1,20 +1,16 @@
[ [
{ {
"title": "MONERO + TREZOR", "title": "Zcash improvements",
"description": "Secure your XMR with hardware wallet support!" "description": "After the successful migration to the new IronWood, we added more improvements to zcash wallet"
}, },
{ {
"title": "NEW WalletConnect UI", "title": "AnyPay as Intended",
"description": "connecting to Web3 dApps made safer and easier" "description": "Now pasting or scanning an address from any wallet will point you correctly to either swapping or sending from the correct wallet, and much more improvements"
}, },
{ {
"title": "UI improvements", "title": "UI improvements",
"description": "Added some fresh UI elements just for you" "description": "Added some fresh UI elements just for you"
}, },
{
"title": "Sync enhancements",
"description": "Optimized sync for Bitcoin and Litecoin"
},
{ {
"title": "Bug fixes", "title": "Bug fixes",
"description": "Squashed more bugs" "description": "Squashed more bugs"
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"¡Asegure su XMR con soporte de billetera de hardware!"},{"title":"NUEVA interfaz de usuario de WalletConnect","description":"conectarse a Web3 dApps es más seguro y sencillo"},{"title":"Mejoras en la interfaz de usuario","description":"Se agregaron algunos elementos nuevos de la interfaz de usuario solo para ti."},{"title":"Mejoras de sincronización","description":"Sincronización optimizada para Bitcoin y Litecoin"},{"title":"Corrección de errores","description":"Aplastado más errores"}] [{"title":"Mejoras en Zcash","description":"Después de la migración exitosa al nuevo IronWood, agregamos más mejoras a la billetera zcash"},{"title":"AnyPay según lo previsto","description":"Ahora, pegar o escanear una dirección desde cualquier billetera le indicará correctamente si desea intercambiar o enviar desde la billetera correcta, y muchas más mejoras."},{"title":"Mejoras en la interfaz de usuario","description":"Se agregaron algunos elementos nuevos de la interfaz de usuario solo para ti."},{"title":"Corrección de errores","description":"Aplastado más errores"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"XMR خود را با پشتیبانی کیف پول سخت افزاری ایمن کنید!"},{"title":"رابط کاربری جدید WalletConnect","description":"اتصال به Web3 dApps ایمن تر و آسان تر شده است"},{"title":"بهبودهای رابط کاربری","description":"برخی از عناصر رابط کاربری جدید را فقط برای شما اضافه کرد"},{"title":"همگام سازی پیشرفت ها","description":"همگام سازی بهینه شده برای بیت کوین و لایت کوین"},{"title":"رفع اشکال","description":"اشکالات بیشتری را له کرد"}] [{"title":"بهبود Zcash","description":"پس از انتقال موفقیت آمیز به IronWood جدید، بهبودهای بیشتری را به کیف پول zcash اضافه کردیم"},{"title":"AnyPay همانطور که در نظر گرفته شده است","description":"اکنون چسباندن یا اسکن یک آدرس از هر کیف پولی، شما را به درستی به تعویض یا ارسال از کیف پول صحیح و پیشرفت های بسیار بیشتر راهنمایی می کند."},{"title":"بهبودهای رابط کاربری","description":"برخی از عناصر رابط کاربری جدید را فقط برای شما اضافه کرد"},{"title":"رفع اشکال","description":"اشکالات بیشتری را له کرد"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Sécurisez votre XMR avec la prise en charge du portefeuille matériel !"},{"title":"NOUVELLE interface utilisateur WalletConnect","description":"la connexion aux dApps Web3 rendue plus sûre et plus facile"},{"title":"Améliorations de l'interface utilisateur","description":"Ajout de nouveaux éléments d'interface utilisateur juste pour vous"},{"title":"Améliorations de la synchronisation","description":"Synchronisation optimisée pour Bitcoin et Litecoin"},{"title":"Corrections de bugs","description":"Écrasé plus de bugs"}] [{"title":"Améliorations de Zcash","description":"Après la migration réussie vers le nouvel IronWood, nous avons ajouté d'autres améliorations au portefeuille zcash."},{"title":"AnyPay comme prévu","description":"Désormais, coller ou scanner une adresse à partir de n'importe quel portefeuille vous indiquera correctement soit l'échange, soit l'envoi à partir du bon portefeuille, et bien d'autres améliorations."},{"title":"Améliorations de l'interface utilisateur","description":"Ajout de nouveaux éléments d'interface utilisateur juste pour vous"},{"title":"Corrections de bugs","description":"Écrasé plus de bugs"}]
@@ -1 +1 @@
[{"title":"MONERO + TEZOR GUARANI","description":"¡Eñangareko nde XMR pytyvõ billetera hardware reheve!"},{"title":"UI WalletConnect PYAHU","description":"oñembojoajúvo Web3 dApps rehe ojejapo seguro ha ndahasýiva"},{"title":"UI ñemyatyrõ","description":"Oñemoĩve peteĩ elemento UI pyahu ndéve g̃uarãnte"},{"title":"Ñembohekorã ñembotuichave","description":"Sincronización optimizada Bitcoin ha Litecoin-pe g̃uarã"},{"title":"Umi mba’e’oka ñemyatyrõ","description":"Oñembopupu hetave mba’e’oka"}] [{"title":"Zcash ñemyatyrõ","description":"Ojejapo rire migración osẽ porãva IronWood pyahúpe, romoĩve mejora zcash billetera-pe"},{"title":"AnyPay Ojeipotaháicha","description":"Ko’áĝa emboja térã eescanea peteĩ dirección oimeraẽva billetera-gui nde señaláta hekopete taha’e intercambio térã emondo haĝua billetera hekopeteguágui, ha hetaiteve ñemyatyrõ"},{"title":"UI ñemyatyrõ","description":"Oñemoĩve peteĩ elemento UI pyahu ndéve g̃uarãnte"},{"title":"Umi mba’e’oka ñemyatyrõ","description":"Oñembopupu hetave mba’e’oka"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Kiyaye XMR ɗin ku tare da tallafin walat ɗin kayan aiki!"},{"title":"NEW WalletConnect UI","description":"haɗi zuwa Web3 dApps ya zama mafi aminci da sauƙi"},{"title":"UI inganta","description":"An ƙara wasu sabbin abubuwan UI don ku kawai"},{"title":"Haɓaka daidaitawa","description":"Ingantaccen aiki tare don Bitcoin da Litecoin"},{"title":"Gyaran kwaro","description":"Squashed karin kwari"}] [{"title":"Zcash ingantawa","description":"Bayan nasarar ƙaura zuwa sabon IronWood, mun ƙara ƙarin haɓakawa zuwa walat ɗin zcash"},{"title":"AnyPay kamar yadda aka nufa","description":"Yanzu liƙa ko duba adireshi daga kowane walat zai nuna maka daidai ga ko dai musanya ko aikawa daga ainihin walat ɗin, da ƙarin haɓakawa."},{"title":"UI inganta","description":"An ƙara wasu sabbin abubuwan UI don ku kawai"},{"title":"Gyaran kwaro","description":"Squashed karin kwari"}]
@@ -1 +1 @@
[{"title":"मोनेरो + ट्रेज़ोर","description":"हार्डवेयर वॉलेट समर्थन के साथ अपने एक्सएमआर को सुरक्षित करें!"},{"title":"नया वॉलेटकनेक्ट यूआई","description":"Web3 dApps से कनेक्ट करना सुरक्षित और आसान बना दिया गया है"},{"title":"यूआई सुधार","description":"केवल आपके लिए कुछ ताज़ा यूआई तत्व जोड़े गए हैं"},{"title":"सिंक संवर्द्धन","description":"बिटकॉइन और लाइटकॉइन के लिए अनुकूलित सिंक"},{"title":"कंप्यूटर प्रोग्राम या प्रणाली में बग को दूर करना","description":"अधिक कीड़ों को कुचल दिया"}] [{"title":"ज़कैश में सुधार","description":"नए आयरनवुड में सफल माइग्रेशन के बाद, हमने zcash वॉलेट में और सुधार जोड़े"},{"title":"इच्छानुसार कोई भी भुगतान करें","description":"अब किसी भी वॉलेट से किसी पते को चिपकाने या स्कैन करने से आपको स्वैपिंग या सही वॉलेट से भेजने का सही संकेत मिलेगा, और भी बहुत कुछ सुधार होगा"},{"title":"UI improvements","description":"केवल आपके लिए कुछ ताज़ा यूआई तत्व जोड़े गए हैं"},{"title":"कंप्यूटर प्रोग्राम या प्रणाली में बग को दूर करना","description":"अधिक कीड़ों को कुचल दिया"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Osigurajte svoj XMR podrškom za hardverski novčanik!"},{"title":"NOVO korisničko sučelje WalletConnect","description":"povezivanje s Web3 dApps postalo sigurnije i lakše"},{"title":"Poboljšanja korisničkog sučelja","description":"Dodani su novi elementi korisničkog sučelja samo za vas"},{"title":"Poboljšanja sinkronizacije","description":"Optimizirana sinkronizacija za Bitcoin i Litecoin"},{"title":"Ispravci grešaka","description":"Zgnječio još kukaca"}] [{"title":"Zcash improvements","description":"Nakon uspješne migracije na novi IronWood, dodali smo još poboljšanja u zcash novčanik"},{"title":"AnyPay kako je predviđeno","description":"Sada će vas lijepljenje ili skeniranje adrese iz bilo kojeg novčanika ispravno usmjeriti na zamjenu ili slanje iz ispravnog novčanika i još mnogo poboljšanja"},{"title":"UI improvements","description":"Dodani su novi elementi korisničkog sučelja samo za vas"},{"title":"Ispravci grešaka","description":"Squashed more bugs"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Ապահովեք ձեր XMR-ը ապարատային դրամապանակի աջակցությամբ:"},{"title":"ՆՈՐ WalletConnect UI","description":"Web3 dApps-ին միանալը դարձել է ավելի անվտանգ և հեշտ"},{"title":"UI բարելավումներ","description":"Ավելացրել է մի քանի թարմ UI տարրեր հենց ձեզ համար"},{"title":"Համաժամեցման բարելավումներ","description":"Օպտիմիզացված համաժամեցում Bitcoin-ի և Litecoin-ի համար"},{"title":"Սխալների շտկում","description":"Ճզմեց ավելի շատ սխալներ"}] [{"title":"Zcash բարելավումներ","description":"Նոր IronWood-ի հաջող տեղափոխումից հետո մենք ավելի շատ բարելավումներ ենք ավելացրել zcash դրամապանակում"},{"title":"AnyPay, ինչպես նախատեսված է","description":"Այժմ ցանկացած դրամապանակից հասցե կպցնելը կամ սկանավորումը ձեզ ճիշտ կուղղորդի՝ կա՛մ փոխանակելու, կա՛մ ճիշտ դրամապանակից ուղարկելու և շատ ավելի բարելավումներ:"},{"title":"UI բարելավումներ","description":"Ավելացրել է մի քանի թարմ UI տարրեր հենց ձեզ համար"},{"title":"Սխալների շտկում","description":"Ճզմեց ավելի շատ սխալներ"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Amankan XMR Anda dengan dukungan dompet perangkat keras!"},{"title":"UI WalletConnect BARU","description":"menghubungkan ke Web3 dApps menjadi lebih aman dan mudah"},{"title":"Peningkatan UI","description":"Menambahkan beberapa elemen UI baru hanya untuk Anda"},{"title":"Peningkatan sinkronisasi","description":"Sinkronisasi yang dioptimalkan untuk Bitcoin dan Litecoin"},{"title":"Perbaikan bug","description":"Membasmi lebih banyak bug"}] [{"title":"Peningkatan Zcash","description":"Setelah migrasi berhasil ke IronWood baru, kami menambahkan lebih banyak perbaikan pada dompet zcash"},{"title":"AnyPay sesuai tujuan","description":"Sekarang menempelkan atau memindai alamat dari dompet mana pun akan mengarahkan Anda dengan benar untuk menukar atau mengirim dari dompet yang benar, dan banyak lagi peningkatan lainnya"},{"title":"Peningkatan UI","description":"Menambahkan beberapa elemen UI baru hanya untuk Anda"},{"title":"Perbaikan bug","description":"Membasmi lebih banyak bug"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Proteggi il tuo XMR con il supporto del portafoglio hardware!"},{"title":"NUOVA interfaccia utente di WalletConnect","description":"connettersi alle dApp Web3 diventa più semplice e sicuro"},{"title":"Miglioramenti dell'interfaccia utente","description":"Aggiunti alcuni nuovi elementi dell'interfaccia utente solo per te"},{"title":"Miglioramenti della sincronizzazione","description":"Sincronizzazione ottimizzata per Bitcoin e Litecoin"},{"title":"Correzioni di bug","description":"Eliminati altri bug"}] [{"title":"Miglioramenti di Zcash","description":"Dopo la riuscita migrazione al nuovo IronWood, abbiamo aggiunto ulteriori miglioramenti al portafoglio zcash"},{"title":"AnyPay come previsto","description":"Ora incollare o scansionare un indirizzo da qualsiasi portafoglio ti indicherà correttamente lo scambio o l'invio dal portafoglio corretto e molti altri miglioramenti"},{"title":"Miglioramenti dell'interfaccia utente","description":"Aggiunti alcuni nuovi elementi dell'interfaccia utente solo per te"},{"title":"Correzioni di bug","description":"Eliminati altri bug"}]
@@ -1 +1 @@
[{"title":"モネロ + トレザー","description":"ハードウェアウォレットのサポートで XMR を保護してください!"},{"title":"新しい WalletConnect UI","description":"Web3 dApps への接続がより安全かつ簡単になりました"},{"title":"UIの改善","description":"あなたのためだけにいくつかの新しい UI 要素を追加しました"},{"title":"同期の機能強化","description":"ビットコインとライトコインの最適化された同期"},{"title":"バグ修正","description":"より多くのバグを潰しました"}] [{"title":"Zcashの改善","description":"新しい IronWood への移行が成功した後、zcash ウォレットにさらに改良を加えました。"},{"title":"意図したとおりの AnyPay","description":"任意のウォレットからアドレスを貼り付けるかスキャンすると、正しいウォレットからの交換または送信が正しく示されるようになり、さらに多くの改善が行われました。"},{"title":"UIの改善","description":"あなたのためだけにいくつかの新しい UI 要素を追加しました"},{"title":"バグ修正","description":"より多くのバグを潰しました"}]
@@ -1 +1 @@
[{"title":"모네로 + 트레저","description":"하드웨어 지갑 지원으로 XMR을 보호하세요!"},{"title":"새로운 WalletConnect UI","description":"Web3 dApp에 연결하는 것이 더 안전하고 쉬워졌습니다."},{"title":"UI 개선","description":"당신을 위한 새로운 UI 요소를 추가했습니다."},{"title":"동기화 향상","description":"비트코인과 라이트코인에 최적화된 동기화"},{"title":"버그 수정","description":"더 많은 버그를 해결했습니다."}] [{"title":"Zcash 개선","description":"새로운 IronWood로 성공적으로 마이그레이션한 후 zcash 지갑에 더 많은 개선 사항을 추가했습니다."},{"title":"의도한 대로 AnyPay","description":"이제 지갑에서 주소를 붙여넣거나 스캔하면 올바른 지갑에서 주소를 교환하거나 보내는 것을 올바르게 가리킬 수 있으며 훨씬 더 많은 개선이 가능합니다."},{"title":"UI 개선","description":"당신을 위한 새로운 UI 요소를 추가했습니다."},{"title":"버그 수정","description":"더 많은 버그를 해결했습니다."}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"ဟာ့ဒ်ဝဲပိုက်ဆံအိတ်ပံ့ပိုးမှုဖြင့် သင်၏ XMR ကို လုံခြုံအောင်ထားပါ။"},{"title":"WalletConnect UI အသစ်","description":"Web3 dApps သို့ ချိတ်ဆက်ခြင်းသည် ပိုမိုလုံခြုံပြီး ပိုမိုလွယ်ကူစေသည်။"},{"title":"UI တိုးတက်မှုများ","description":"သင့်အတွက်သာ လတ်ဆတ်သော UI အစိတ်အပိုင်းအချို့ကို ထည့်ထားသည်။"},{"title":"ထပ်တူပြုခြင်း မြှင့်တင်မှုများ","description":"Bitcoin နှင့် Litecoin အတွက် အကောင်းဆုံး စင့်ခ်လုပ်ထားသည်။"},{"title":"ချွတ်ယွင်းချက်ပြင်ဆင်မှုများ","description":"နောက်ထပ် ပိုးကောင်များကို နှိမ်နင်းခဲ့သည်။"}] [{"title":"Zcash တိုးတက်မှုများ","description":"IronWood အသစ်သို့ အောင်မြင်စွာ ပြောင်းရွှေ့ပြီးနောက်၊ ကျွန်ုပ်တို့သည် zcash ပိုက်ဆံအိတ်သို့ နောက်ထပ် တိုးတက်မှုများကို ထည့်သွင်းခဲ့သည်။"},{"title":"ရည်ရွယ်ထားသည့်အတိုင်း AnyPay","description":"ယခု မည်သည့်ပိုက်ဆံအိတ်မှ လိပ်စာတစ်ခုကို ကူးထည့်ခြင်း သို့မဟုတ် စကင်န်ဖတ်ခြင်းသည် သင့်အား မှန်ကန်သောပိုက်ဆံအိတ်မှ လဲလှယ်ရန် သို့မဟုတ် ပေးပို့ခြင်းတို့ကို မှန်ကန်စွာ ညွှန်ပြပေးမည်ဖြစ်ပြီး ပိုမိုကောင်းမွန်လာစေရန်၊"},{"title":"UI တိုးတက်မှုများ","description":"သင့်အတွက်သာ လတ်ဆတ်သော UI အစိတ်အပိုင်းအချို့ကို ထည့်ထားသည်။"},{"title":"ချွတ်ယွင်းချက်ပြင်ဆင်မှုများ","description":"နောက်ထပ် ပိုးကောင်များကို နှိမ်နင်းခဲ့သည်။"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Beveilig uw XMR met hardware wallet-ondersteuning!"},{"title":"NIEUWE WalletConnect-gebruikersinterface","description":"verbinding maken met Web3 dApps is veiliger en eenvoudiger geworden"},{"title":"UI-verbeteringen","description":"Speciaal voor jou een aantal nieuwe UI-elementen toegevoegd"},{"title":"Synchronisatieverbeteringen","description":"Geoptimaliseerde synchronisatie voor Bitcoin en Litecoin"},{"title":"Bugfixes","description":"Nog meer bugs geplet"}] [{"title":"Zcash-verbeteringen","description":"Na de succesvolle migratie naar het nieuwe IronWood hebben we meer verbeteringen aan de zcash-portemonnee toegevoegd"},{"title":"AnyPay zoals bedoeld","description":"Als u nu een adres uit een willekeurige portemonnee plakt of scant, wordt u correct verwezen naar het wisselen of verzenden vanuit de juiste portemonnee, en nog veel meer verbeteringen"},{"title":"UI-verbeteringen","description":"Speciaal voor jou een aantal nieuwe UI-elementen toegevoegd"},{"title":"Bugfixes","description":"Nog meer bugs geplet"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Zabezpiecz swój XMR dzięki obsłudze portfela sprzętowego!"},{"title":"NOWY interfejs użytkownika WalletConnect","description":"łączenie się z Web3 dApps stało się bezpieczniejsze i łatwiejsze"},{"title":"Ulepszenia interfejsu użytkownika","description":"Dodano kilka świeżych elementów interfejsu użytkownika specjalnie dla Ciebie"},{"title":"Udoskonalenia synchronizacji","description":"Zoptymalizowana synchronizacja dla Bitcoin i Litecoin"},{"title":"Poprawki błędów","description":"Zmiażdżono więcej błędów"}] [{"title":"Ulepszenia Zcasha","description":"Po udanej migracji do nowego IronWood, dodaliśmy kolejne ulepszenia do portfela Zcash"},{"title":"AnyPay zgodnie z przeznaczeniem","description":"Teraz wklejenie lub zeskanowanie adresu z dowolnego portfela wskaże prawidłowo opcję wymiany lub wysłania z odpowiedniego portfela oraz wiele innych ulepszeń"},{"title":"Ulepszenia interfejsu użytkownika","description":"Dodano kilka świeżych elementów interfejsu użytkownika specjalnie dla Ciebie"},{"title":"Poprawki błędów","description":"Zmiażdżono więcej błędów"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Proteja seu XMR com suporte de carteira de hardware!"},{"title":"NOVA IU do WalletConnect","description":"conectar-se a Web3 dApps tornou-se mais seguro e fácil"},{"title":"Melhorias na interface do usuário","description":"Adicionados alguns novos elementos de UI só para você"},{"title":"Melhorias de sincronização","description":"Sincronização otimizada para Bitcoin e Litecoin"},{"title":"Correções de bugs","description":"Esmagou mais bugs"}] [{"title":"Melhorias no Zcash","description":"Após a migração bem-sucedida para o novo IronWood, adicionamos mais melhorias à carteira zcash"},{"title":"AnyPay conforme pretendido","description":"Agora, colar ou digitalizar um endereço de qualquer carteira indicará corretamente a troca ou o envio da carteira correta e muito mais melhorias"},{"title":"Melhorias na interface do usuário","description":"Adicionados alguns novos elementos de UI só para você"},{"title":"Correções de bugs","description":"Esmagou mais bugs"}]
@@ -1 +1 @@
[{"title":"МОНЕРО + ТРЕЗОР","description":"Защитите свой XMR с помощью аппаратного кошелька!"},{"title":"НОВЫЙ интерфейс WallConnectConnect","description":"подключение к dApps Web3 стало безопаснее и проще"},{"title":"Улучшения пользовательского интерфейса","description":"Специально для вас добавлено несколько свежих элементов пользовательского интерфейса."},{"title":"Улучшения синхронизации","description":"Оптимизированная синхронизация для биткойнов и лайткойнов."},{"title":"Исправления ошибок","description":"Устранено больше ошибок"}] [{"title":"Улучшения Zcash","description":"После успешного перехода на новый IronWood мы добавили дополнительные улучшения в кошелек zcash."},{"title":"AnyPay как задумано","description":"Теперь при вставке или сканировании адреса из любого кошелька вы сможете правильно выбрать обмен или отправку из правильного кошелька, а также множество других улучшений."},{"title":"Улучшения пользовательского интерфейса","description":"Специально для вас добавлено несколько свежих элементов пользовательского интерфейса."},{"title":"Исправления ошибок","description":"Устранено больше ошибок"}]
@@ -1 +1 @@
[{"title":"โมเนโร + เทรเซอร์","description":"รักษาความปลอดภัย XMR ของคุณด้วยการสนับสนุนกระเป๋าเงินฮาร์ดแวร์!"},{"title":"UI WalletConnect ใหม่","description":"การเชื่อมต่อกับ Web3 dApps ทำให้ปลอดภัยและง่ายขึ้น"},{"title":"การปรับปรุง UI","description":"เพิ่มองค์ประกอบ UI ใหม่สำหรับคุณโดยเฉพาะ"},{"title":"การปรับปรุงการซิงค์","description":"การซิงค์ที่ปรับให้เหมาะสมสำหรับ Bitcoin และ Litecoin"},{"title":"แก้ไขข้อบกพร่อง","description":"กำจัดข้อบกพร่องเพิ่มเติม"}] [{"title":"การปรับปรุง Zcash","description":"หลังจากการโยกย้ายไปยัง IronWood ใหม่ได้สำเร็จ เราได้เพิ่มการปรับปรุงเพิ่มเติมให้กับกระเป๋าเงิน zcash"},{"title":"AnyPay ตามที่ตั้งใจไว้","description":"ตอนนี้การวางหรือสแกนที่อยู่จากกระเป๋าเงินใด ๆ จะนำคุณไปสู่การแลกเปลี่ยนหรือส่งจากกระเป๋าเงินที่ถูกต้องอย่างถูกต้อง และการปรับปรุงอื่น ๆ อีกมากมาย"},{"title":"การปรับปรุง UI","description":"เพิ่มองค์ประกอบ UI ใหม่สำหรับคุณโดยเฉพาะ"},{"title":"แก้ไขข้อบกพร่อง","description":"กำจัดข้อบกพร่องเพิ่มเติม"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"I-secure ang iyong XMR gamit ang suporta sa hardware wallet!"},{"title":"BAGONG WalletConnect UI","description":"ang pagkonekta sa Web3 dApps ay ginawang mas ligtas at mas madali"},{"title":"Mga pagpapabuti ng UI","description":"Nagdagdag ng ilang sariwang elemento ng UI para lang sa iyo"},{"title":"I-sync ang mga pagpapahusay","description":"Na-optimize na pag-sync para sa Bitcoin at Litecoin"},{"title":"Mga pag-aayos ng bug","description":"Nilapit ang higit pang mga bug"}] [{"title":"Mga pagpapabuti ng Zcash","description":"Pagkatapos ng matagumpay na paglipat sa bagong IronWood, nagdagdag kami ng higit pang mga pagpapahusay sa zcash wallet"},{"title":"AnyPay gaya ng Nilalayon","description":"Ngayon, ang pag-paste o pag-scan ng isang address mula sa anumang pitaka ay ituturo sa iyo nang tama sa alinman sa pagpapalit o pagpapadala mula sa tamang pitaka, at marami pang mga pagpapabuti"},{"title":"Mga pagpapabuti ng UI","description":"Nagdagdag ng ilang sariwang elemento ng UI para lang sa iyo"},{"title":"Mga pag-aayos ng bug","description":"Nilapit ang higit pang mga bug"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"XMR'nizi donanım cüzdanı desteğiyle güvence altına alın!"},{"title":"YENİ WalletConnect Kullanıcı Arayüzü","description":"Web3 dApp'lerine bağlanmak artık daha güvenli ve daha kolay"},{"title":"Kullanıcı arayüzü iyileştirmeleri","description":"Size özel bazı yeni kullanıcı arayüzü öğeleri eklendi"},{"title":"Senkronizasyon geliştirmeleri","description":"Bitcoin ve Litecoin için optimize edilmiş senkronizasyon"},{"title":"Hata düzeltmeleri","description":"Daha fazla hata giderildi"}] [{"title":"Zcash iyileştirmeleri","description":"Yeni IronWood'a başarılı geçişin ardından zcash cüzdanına daha fazla iyileştirme ekledik"},{"title":"AnyPay İstenildiği Gibi","description":"Artık herhangi bir cüzdandan bir adresi yapıştırmak veya taramak, sizi doğru cüzdandan değiştirmeye veya göndermeye yönlendirecek ve çok daha fazla iyileştirme yapacak"},{"title":"Kullanıcı arayüzü iyileştirmeleri","description":"Size özel bazı yeni kullanıcı arayüzü öğeleri eklendi"},{"title":"Hata düzeltmeleri","description":"Daha fazla hata giderildi"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Захистіть свій XMR за допомогою підтримки апаратного гаманця!"},{"title":"НОВИЙ інтерфейс WalletConnect","description":"підключення до Web3 dApps стало безпечнішим і простішим"},{"title":"Покращення інтерфейсу користувача","description":"Додано кілька свіжих елементів інтерфейсу спеціально для вас"},{"title":"Покращення синхронізації","description":"Оптимізована синхронізація для Bitcoin і Litecoin"},{"title":"Виправлення помилок","description":"Роздавив більше помилок"}] [{"title":"Покращення Zcash","description":"Після успішного переходу на новий IronWood ми додали додаткові покращення до гаманця zcash"},{"title":"AnyPay за призначенням","description":"Тепер вставлення або сканування адреси з будь-якого гаманця правильно вкаже вас на обмін або надсилання з правильного гаманця, а також багато інших покращень"},{"title":"Покращення інтерфейсу користувача","description":"Додано кілька свіжих елементів інтерфейсу спеціально для вас"},{"title":"Виправлення помилок","description":"Роздавив більше помилок"}]
@@ -1 +1 @@
[{"title":"مونیرو + ٹریزور","description":"ہارڈویئر والیٹ سپورٹ کے ساتھ اپنے XMR کو محفوظ بنائیں!"},{"title":"نیا WalletConnect UI","description":"Web3 dApps سے جڑنا محفوظ اور آسان بنا دیا ہے۔"},{"title":"UI میں بہتری","description":"صرف آپ کے لیے کچھ تازہ UI عناصر شامل کیے گئے۔"},{"title":"مطابقت پذیری میں اضافہ","description":"Bitcoin اور Litecoin کے لیے آپٹمائزڈ سنک"},{"title":"بگ کی اصلاحات","description":"مزید کیڑے کو کچل دیا۔"}] [{"title":"Zcash میں بہتری","description":"نئے IronWood میں کامیاب منتقلی کے بعد، ہم نے zcash wallet میں مزید بہتری شامل کی۔"},{"title":"AnyPay جیسا کہ ارادہ ہے۔","description":"اب کسی بھی بٹوے سے ایڈریس پیسٹ یا اسکین کرنا آپ کو صحیح بٹوے سے تبدیل کرنے یا بھیجنے کی طرف صحیح طریقے سے اشارہ کرے گا، اور بہت زیادہ بہتری"},{"title":"UI میں بہتری","description":"صرف آپ کے لیے کچھ تازہ UI عناصر شامل کیے گئے۔"},{"title":"بگ کی اصلاحات","description":"مزید کیڑے کو کچل دیا۔"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Bảo vệ XMR của bạn với sự hỗ trợ của ví phần cứng!"},{"title":"Giao diện người dùng WalletConnect MỚI","description":"kết nối với Web3 dApps trở nên an toàn và dễ dàng hơn"},{"title":"Cải tiến giao diện người dùng","description":"Đã thêm một số thành phần UI mới dành riêng cho bạn"},{"title":"Cải tiến đồng bộ hóa","description":"Đồng bộ hóa được tối ưu hóa cho Bitcoin và Litecoin"},{"title":"Sửa lỗi","description":"Dẹp nhiều lỗi hơn"}] [{"title":"Cải tiến Zcash","description":"Sau khi di chuyển thành công sang IronWood mới, chúng tôi đã bổ sung thêm nhiều cải tiến hơn cho ví zcash"},{"title":"AnyPay như dự định","description":"Giờ đây, việc dán hoặc quét địa chỉ từ bất kỳ ví nào sẽ chỉ cho bạn cách hoán đổi hoặc gửi từ đúng ví và nhiều cải tiến hơn nữa"},{"title":"Cải tiến giao diện người dùng","description":"Đã thêm một số thành phần UI mới dành riêng cho bạn"},{"title":"Sửa lỗi","description":"Dẹp nhiều lỗi hơn"}]
@@ -1 +1 @@
[{"title":"MONERO + TREZOR","description":"Ṣe aabo XMR rẹ pẹlu atilẹyin apamọwọ ohun elo!"},{"title":"Tuntun WalletConnect UI","description":"sisopọ si Web3 dApps jẹ ailewu ati rọrun"},{"title":"UI awọn ilọsiwaju","description":"Ṣafikun diẹ ninu awọn eroja UI tuntun kan fun ọ"},{"title":"Awọn imudara imuṣiṣẹpọ","description":"Iṣapeye amuṣiṣẹpọ fun Bitcoin ati Litecoin"},{"title":"Awọn atunṣe kokoro","description":"Squashed diẹ idun"}] [{"title":"Awọn ilọsiwaju Zcash","description":"Lẹhin ijira aṣeyọri si IronWood tuntun, a ṣafikun awọn ilọsiwaju diẹ sii si apamọwọ zcash"},{"title":"AnyPay bi a ti pinnu","description":"Ni bayi titọ tabi ṣiṣayẹwo adirẹsi kan lati eyikeyi apamọwọ yoo tọka si ni deede si boya paarọ tabi firanṣẹ lati apamọwọ to pe, ati awọn ilọsiwaju pupọ diẹ sii."},{"title":"UI awọn ilọsiwaju","description":"Ṣafikun diẹ ninu awọn eroja UI tuntun kan fun ọ"},{"title":"Awọn atunṣe kokoro","description":"Squashed diẹ idun"}]
@@ -1 +1 @@
[{"title":"门罗+特雷佐","description":"通过硬件钱包支持保护您的 XMR!"},{"title":"新的 WalletConnect 用户界面","description":"连接到 Web3 dApp 变得更安全、更轻松"},{"title":"用户界面改进","description":"为您添加了一些新鲜的 UI 元素"},{"title":"同步增强功能","description":"优化比特币和莱特币的同步"},{"title":"错误修复","description":"消除了更多错误"}] [{"title":"Zcash 的改进","description":"成功迁移到新的 IronWood 后,我们对 zcash 钱包添加了更多改进"},{"title":"AnyPay 符合预期","description":"现在,从任何钱包粘贴或扫描地址都会正确地指示您从正确的钱包进行交换或发送,以及更多改进"},{"title":"用户界面改进","description":"为您添加了一些新鲜的 UI 元素"},{"title":"错误修复","description":"消除了更多错误"}]
+8
View File
@@ -0,0 +1,8 @@
targets:
$default:
sources:
exclude:
- "**/.secrets.g.dart"
- "integration_test/**"
- "test/**"
- "tool/**"
+1
View File
@@ -35,6 +35,7 @@ fi
source ./app_env.sh cakewallet source ./app_env.sh cakewallet
./app_config.sh ./app_config.sh
echo "flutter pub get may take a while, please wait..."
cd ../.. && flutter pub get cd ../.. && flutter pub get
dart run tool/generate_localization.dart dart run tool/generate_localization.dart
./compile_graphics.sh ./compile_graphics.sh
+5
View File
@@ -0,0 +1,5 @@
targets:
$default:
sources:
exclude:
- "**/.secrets.g.dart"
+12 -1
View File
@@ -14,6 +14,7 @@ import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/locktime.dart';
import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart'; import 'package:cw_bitcoin/hardware/bitcoin_hardware_wallet_service.dart';
import 'package:cw_bitcoin/lightning/lightning_wallet.dart'; import 'package:cw_bitcoin/lightning/lightning_wallet.dart';
import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart'; import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart';
@@ -31,6 +32,7 @@ import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/output_info.dart'; import 'package:cw_core/output_info.dart';
import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/payjoin_session.dart';
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
@@ -447,8 +449,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
)); ));
} }
final locktime = antiFeeSnipingLocktime(
chainTip: await getCurrentChainTip(),
synced: syncStatus is SyncedSyncStatus,
);
return PSBTTransactionBuild( return PSBTTransactionBuild(
inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF, cwOutputs: cwOutputs) inputs: psbtReadyInputs,
outputs: outputs,
enableRBF: enableRBF,
cwOutputs: cwOutputs,
locktime: locktime)
.psbt; .psbt;
} }
+88
View File
@@ -0,0 +1,88 @@
import 'dart:math';
int effectiveValue(int value, int inputCost) => value - inputCost;
class SelectedCoins {
final List<int> indices;
final bool hasChange;
const SelectedCoins(this.indices, this.hasChange);
}
SelectedCoins? branchAndBound(List<int> effValues, int target, int costOfChange,
{int maxTries = 100000}) {
final n = effValues.length;
final order = List<int>.generate(n, (i) => i)
..sort((a, b) => effValues[b].compareTo(effValues[a]));
final sorted = [for (final i in order) effValues[i]];
final suffix = List<int>.filled(n + 1, 0);
for (var i = n - 1; i >= 0; i--) suffix[i] = suffix[i + 1] + sorted[i];
final upper = target + costOfChange;
List<int>? best;
final picked = <int>[];
var tries = 0;
void dfs(int i, int sum) {
if (best != null || tries++ >= maxTries) return;
if (sum > upper) return;
if (sum >= target) { best = List.of(picked); return; }
if (i >= n || sum + suffix[i] < target) return;
picked.add(order[i]); dfs(i + 1, sum + sorted[i]); picked.removeLast();
dfs(i + 1, sum);
}
dfs(0, 0);
return best == null ? null : SelectedCoins(best!, false);
}
SelectedCoins? singleRandomDraw(List<int> effValues, int target, int minChange, Random rng) {
final order = List<int>.generate(effValues.length, (i) => i)..shuffle(rng);
final picked = <int>[]; var sum = 0;
for (final i in order) {
picked.add(i); sum += effValues[i];
if (sum >= target + minChange) return SelectedCoins(picked, true);
}
return null;
}
/// Branch-and-bound over effective values (value minus the cost of spending the
/// input at the current fee rate). [inputCosts] is parallel to [values] so each
/// input pays for its own script type's size. A match means the excess over
/// [target] stays within [window], so the remainder can be absorbed into the fee
/// instead of creating a change output. Returns null when no such subset exists.
SelectedCoins? changelessMatch({
required List<int> values,
required int target,
required List<int> inputCosts,
required int window,
int maxTries = 100000,
}) {
assert(values.length == inputCosts.length);
final keep = <int>[];
final eff = <int>[];
for (var i = 0; i < values.length; i++) {
final e = effectiveValue(values[i], inputCosts[i]);
if (e > 0) {
keep.add(i);
eff.add(e);
}
}
final match = branchAndBound(eff, target, window, maxTries: maxTries);
if (match == null) return null;
return SelectedCoins([for (final i in match.indices) keep[i]], false);
}
class InsufficientFundsException implements Exception { const InsufficientFundsException(); }
SelectedCoins selectCoins({required List<int> values, required int target,
required int inputCost, required int costOfChange, required int minChange, Random? rng}) {
final keep = <int>[]; final eff = <int>[];
for (var i = 0; i < values.length; i++) {
final e = effectiveValue(values[i], inputCost);
if (e > 0) { keep.add(i); eff.add(e); }
}
SelectedCoins? map(SelectedCoins? s) =>
s == null ? null : SelectedCoins([for (final i in s.indices) keep[i]], s.hasChange);
final bnb = map(branchAndBound(eff, target, costOfChange));
if (bnb != null) return bnb;
final srd = map(singleRandomDraw(eff, target, minChange, rng ?? Random.secure()));
if (srd != null) return srd;
throw const InsufficientFundsException();
}
+21 -6
View File
@@ -579,14 +579,29 @@ class ElectrumClient {
return []; return [];
}); });
// Floor at 0 so unavailable/-1 estimates never become negative rates;
// cap at 2000 sat/vB per CW-1597.
static const int _maxFeeRate = 2000;
static int _sanitizeFeeRate(double feeRate) {
final rate = (stringDoubleToBitcoinAmount(feeRate.toString()) / 1000).round();
if (rate < 0) {
return 0;
}
if (rate > _maxFeeRate) {
return _maxFeeRate;
}
return rate;
}
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async { Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
try { try {
final topDoubleString = await estimatefee(p: 1); final topDouble = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5); final middleDouble = await estimatefee(p: 5);
final bottomDoubleString = await estimatefee(p: 10); final bottomDouble = await estimatefee(p: 10);
final top = (stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000).round(); final top = _sanitizeFeeRate(topDouble);
final middle = (stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000).round(); final middle = _sanitizeFeeRate(middleDouble);
final bottom = (stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000).round(); final bottom = _sanitizeFeeRate(bottomDouble);
return [bottom, middle, top]; return [bottom, middle, top];
} catch (_) { } catch (_) {
+143 -21
View File
@@ -1,14 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:math' show Random;
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/lightning/lightning_wallet.dart'; import 'package:cw_bitcoin/lightning/lightning_wallet.dart';
import 'package:cw_bitcoin/locktime.dart';
import 'package:cw_core/hardware/hardware_wallet_service.dart'; import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/root_dir.dart'; import 'package:cw_core/root_dir.dart';
import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/coin_selection.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart'; import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
@@ -260,6 +263,29 @@ abstract class ElectrumWalletBase
static int estimatedTransactionSize(int inputsCount, int outputsCounts) => static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10; inputsCount * 68 + outputsCounts * 34 + 10;
// vbytes an input of the given script type adds to a transaction. The generic
// estimate above assumes P2WPKH (68); legacy and taproot inputs differ enough
// to break changeless-match arithmetic if not accounted for.
static int estimatedInputSize(BitcoinAddressType type) {
if (type == P2pkhAddressType.p2pkh) return 148;
if (type == P2shAddressType.p2wpkhInP2sh) return 91;
if (type == SegwitAddresType.p2tr) return 58;
if (type == SegwitAddresType.p2wsh) return 105;
if (type == SilentPaymentsAddresType.p2sp) return 58; // spent via taproot
return 68;
}
// vbytes an output of the given script type adds to a transaction. Silent
// payment outputs are delivered as taproot.
static int estimatedOutputSize(BitcoinAddressType type) {
if (type == P2pkhAddressType.p2pkh) return 34;
if (type == P2shAddressType.p2wpkhInP2sh) return 32;
if (type == SegwitAddresType.p2tr) return 43;
if (type == SegwitAddresType.p2wsh) return 43;
if (type == SilentPaymentsAddresType.p2sp) return 43;
return 31;
}
// Parses the account index from a BIP-44/49/84/86 derivation path. // Parses the account index from a BIP-44/49/84/86 derivation path.
// e.g. "m/84'/0'/1'" → 1. Returns 0 for unrecognised formats. // e.g. "m/84'/0'/1'" → 1. Returns 0 for unrecognised formats.
static int _parseAccountIndex(String? derivationPath) { static int _parseAccountIndex(String? derivationPath) {
@@ -448,6 +474,15 @@ abstract class ElectrumWalletBase
return currentChainTip ?? 0; return currentChainTip ?? 0;
} }
/// Anti-fee-sniping locktime (current tip, exact-tip), LE-encoded for
/// `BitcoinTransactionBuilder`.
Future<List<int>> _antiFeeSnipingLocktime() async {
return locktimeToBytes(antiFeeSnipingLocktime(
chainTip: await getCurrentChainTip(),
synced: syncStatus is SyncedSyncStatus,
));
}
@override @override
BitcoinWalletKeys get keys { BitcoinWalletKeys get keys {
String? wif; String? wif;
@@ -750,6 +785,9 @@ abstract class ElectrumWalletBase
} }
} }
static bool _isValidFeeRates(List<int> feeRates) =>
feeRates.length == 3 && feeRates.every((rate) => rate > 0);
@action @action
Future<void> updateFeeRates() async { Future<void> updateFeeRates() async {
if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) { if (await checkIfMempoolAPIIsEnabled() && type == WalletType.bitcoin) {
@@ -776,7 +814,7 @@ abstract class ElectrumWalletBase
} }
final feeRates = await electrumClient.feeRates(network: network); final feeRates = await electrumClient.feeRates(network: network);
if (feeRates != [0, 0, 0]) { if (_isValidFeeRates(feeRates)) {
_feeRates = feeRates; _feeRates = feeRates;
} else if (isTestnet) { } else if (isTestnet) {
_feeRates = [1, 1, 1]; _feeRates = [1, 1, 1];
@@ -868,11 +906,23 @@ abstract class ElectrumWalletBase
bool _isBelowDust(BigInt amount) => bool _isBelowDust(BigInt amount) =>
amount <= networkDustAmount && network != BitcoinNetwork.testnet; amount <= networkDustAmount && network != BitcoinNetwork.testnet;
// Random draw priority per outpoint. Assigned lazily with a secure RNG and kept
// until the next createTransaction call, so the recursive estimate passes of one
// transaction build all see the same input order (reshuffling between passes made
// fee estimation unstable). Cleared per transaction so each send is a fresh draw.
final Random _coinSelectionRng = Random.secure();
final Map<String, int> _coinSelectionOrder = {};
int _coinSelectionPriority(BitcoinUnspent utx) => _coinSelectionOrder.putIfAbsent(
'${utx.hash}:${utx.vout}', () => _coinSelectionRng.nextInt(1 << 32));
UtxoDetails _createUTXOS({ UtxoDetails _createUTXOS({
required bool sendAll, required bool sendAll,
required bool paysToSilentPayment, required bool paysToSilentPayment,
int credentialsAmount = 0, int credentialsAmount = 0,
int? inputsCount, int? inputsCount,
int feeRate = 0,
int? outputsVBytes,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any, UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) { }) {
List<UtxoWithAddress> utxos = []; List<UtxoWithAddress> utxos = [];
@@ -901,8 +951,52 @@ abstract class ElectrumWalletBase
}).toList(); }).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
// sort the unconfirmed coins so that mweb coins are last: // Single Random Draw: order the pool by each coin's random priority so selection is
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? 1 : -1); // non-deterministic, removing the predictable address/scan order (a fingerprint).
// The priority is stable across the repeated calls of one transaction build.
// MWEB coins are kept last afterwards.
availableInputs.sort((a, b) {
final byPriority = _coinSelectionPriority(a).compareTo(_coinSelectionPriority(b));
if (byPriority != 0) return byPriority;
return '${a.hash}:${a.vout}'.compareTo('${b.hash}:${b.vout}');
});
availableInputs = [
...availableInputs.where((u) => u.bitcoinAddressRecord.type != SegwitAddresType.mweb),
...availableInputs.where((u) => u.bitcoinAddressRecord.type == SegwitAddresType.mweb),
];
// Branch-and-bound: prefer an input set whose excess over the amount plus its own fee
// stays below dust, so the caller drops the change output and the send is changeless.
// When no such set exists, the shuffled pool below acts as a single random draw.
final canTryChangeless = !sendAll &&
inputsCount == null &&
credentialsAmount > 0 &&
feeRate > 0 &&
outputsVBytes != null &&
outputsVBytes > 0 &&
!availableInputs.any((u) => u.bitcoinAddressRecord.type == SegwitAddresType.mweb);
if (canTryChangeless) {
final match = changelessMatch(
values: [for (final u in availableInputs) u.value],
// estimatedTransactionSize(0, 0) is the fixed tx overhead (version,
// counters, locktime); the outputs' own vbytes come pre-computed per type.
target: credentialsAmount + (estimatedTransactionSize(0, 0) + outputsVBytes!) * feeRate,
inputCosts: [
for (final u in availableInputs)
estimatedInputSize(u.bitcoinAddressRecord.type) * feeRate
],
window: networkDustAmount.toInt(),
);
if (match != null) {
final chosen = match.indices.toSet();
availableInputs = [
for (final i in match.indices) availableInputs[i],
for (var i = 0; i < availableInputs.length; i++)
if (!chosen.contains(i)) availableInputs[i],
];
inputsCount = match.indices.length;
}
}
for (int i = 0; i < availableInputs.length; i++) { for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i]; final utx = availableInputs[i];
@@ -1024,7 +1118,7 @@ abstract class ElectrumWalletBase
vinOutpoints: utxoDetails.vinOutpoints, vinOutpoints: utxoDetails.vinOutpoints,
); );
if (fee == 0) { if (fee <= 0) {
throw BitcoinTransactionNoFeeException(); throw BitcoinTransactionNoFeeException();
} }
@@ -1118,10 +1212,25 @@ abstract class ElectrumWalletBase
} }
} }
// Per-type output sizes for the changeless target. MWEB outputs follow a
// different size model entirely, so they disable the changeless path (null).
int? outputsVBytes = 0;
for (final out in outputs) {
final type =
out.isSilentPayment == true ? SilentPaymentsAddresType.p2sp : _getScriptType(out.address);
if (type == SegwitAddresType.mweb) {
outputsVBytes = null;
break;
}
outputsVBytes = outputsVBytes! + estimatedOutputSize(type);
}
final utxoDetails = _createUTXOS( final utxoDetails = _createUTXOS(
sendAll: false, sendAll: false,
credentialsAmount: credentialsAmount.amount.toInt(), credentialsAmount: credentialsAmount.amount.toInt(),
inputsCount: inputsCount, inputsCount: inputsCount,
feeRate: feeRate,
outputsVBytes: outputsVBytes,
paysToSilentPayment: hasSilentPayment, paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom, coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
@@ -1205,7 +1314,7 @@ abstract class ElectrumWalletBase
)); ));
} }
if (fee == 0) { if (fee <= 0) {
throw BitcoinTransactionNoFeeException(); throw BitcoinTransactionNoFeeException();
} }
@@ -1384,6 +1493,10 @@ abstract class ElectrumWalletBase
@override @override
Future<PendingTransaction> createTransaction(Object credentials) async { Future<PendingTransaction> createTransaction(Object credentials) async {
try { try {
// New transaction, new random draw: drop the previous input ordering so this
// build gets fresh priorities, then keep them fixed for all estimate passes.
_coinSelectionOrder.clear();
// start by updating unspent coins // start by updating unspent coins
await updateAllUnspents(); await updateAllUnspents();
@@ -1513,6 +1626,8 @@ abstract class ElectrumWalletBase
}); });
} }
final locktime = await _antiFeeSnipingLocktime();
BasedBitcoinTransacationBuilder txb; BasedBitcoinTransacationBuilder txb;
if (network is BitcoinCashNetwork) { if (network is BitcoinCashNetwork) {
txb = ForkedTransactionBuilder( txb = ForkedTransactionBuilder(
@@ -1537,6 +1652,7 @@ abstract class ElectrumWalletBase
inputOrdering: BitcoinOrdering.shuffle, inputOrdering: BitcoinOrdering.shuffle,
outputOrdering: BitcoinOrdering.shuffle, outputOrdering: BitcoinOrdering.shuffle,
enableRBF: !estimatedTx.spendsUnconfirmedTX, enableRBF: !estimatedTx.spendsUnconfirmedTX,
locktime: locktime,
); );
} }
@@ -2204,11 +2320,13 @@ abstract class ElectrumWalletBase
} }
} }
// If still not enough, add UTXOs until the fee is covered // If still not enough, add UTXOs until the fee is covered, drawing them at
// random instead of in the predictable wallet scan order (address, then age).
if (remainingFee > BigInt.zero) { if (remainingFee > BigInt.zero) {
final unusedUtxos = unspentCoins final unusedUtxos = unspentCoins
.where((utxo) => utxo.isSending && !utxo.isFrozen && utxo.confirmations! > 0) .where((utxo) => utxo.isSending && !utxo.isFrozen && utxo.confirmations! > 0)
.toList(); .toList()
..shuffle(Random.secure());
for (final utxo in unusedUtxos) { for (final utxo in unusedUtxos) {
final address = RegexUtils.addressTypeFromStr(utxo.address, network); final address = RegexUtils.addressTypeFromStr(utxo.address, network);
@@ -2303,6 +2421,7 @@ abstract class ElectrumWalletBase
inputOrdering: BitcoinOrdering.shuffle, inputOrdering: BitcoinOrdering.shuffle,
outputOrdering: BitcoinOrdering.shuffle, outputOrdering: BitcoinOrdering.shuffle,
enableRBF: true, enableRBF: true,
locktime: await _antiFeeSnipingLocktime(),
); );
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
@@ -2513,16 +2632,16 @@ abstract class ElectrumWalletBase
Map<String, ElectrumTransactionInfo> historiesWithDetails, Map<String, ElectrumTransactionInfo> historiesWithDetails,
BitcoinAddressType type, BitcoinAddressType type,
) async { ) async {
final addressesByType = final addressesByType =
walletAddresses.allAddresses.where((addr) => addr.type == type).toList(); walletAddresses.allAddresses.where((addr) => addr.type == type).toList();
final receiveStandard = getAddressBranchByType(hidden: false, legacy: false, type: type); final receiveStandard = getAddressBranchByType(hidden: false, legacy: false, type: type);
final changeStandard = getAddressBranchByType(hidden: true, legacy: false, type: type); final changeStandard = getAddressBranchByType(hidden: true, legacy: false, type: type);
final receiveLegacy = getAddressBranchByType(hidden: false, legacy: true, type: type); final receiveLegacy = getAddressBranchByType(hidden: false, legacy: true, type: type);
final changeLegacy = getAddressBranchByType(hidden: true, legacy: true, type: type); final changeLegacy = getAddressBranchByType(hidden: true, legacy: true, type: type);
walletAddresses.hiddenAddresses.addAll([...changeStandard, ...changeLegacy].map((e) => e.address)); walletAddresses.hiddenAddresses
.addAll([...changeStandard, ...changeLegacy].map((e) => e.address));
await walletAddresses.saveAddressesInBox(); await walletAddresses.saveAddressesInBox();
await Future.wait(addressesByType.map((addressRecord) async { await Future.wait(addressesByType.map((addressRecord) async {
final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip()); final history = await _fetchAddressHistory(addressRecord, await getCurrentChainTip());
@@ -2637,13 +2756,13 @@ abstract class ElectrumWalletBase
Future<void> fetchTransactionsForAddressTypeBatch( Future<void> fetchTransactionsForAddressTypeBatch(
Map<String, ElectrumTransactionInfo> historiesWithDetails, BitcoinAddressType type) async { Map<String, ElectrumTransactionInfo> historiesWithDetails, BitcoinAddressType type) async {
final receiveStandard = getAddressBranchByType(hidden: false, legacy: false, type: type); final receiveStandard = getAddressBranchByType(hidden: false, legacy: false, type: type);
final changeStandard = getAddressBranchByType(hidden: true, legacy: false, type: type); final changeStandard = getAddressBranchByType(hidden: true, legacy: false, type: type);
final receiveLegacy = getAddressBranchByType(hidden: false, legacy: true, type: type); final receiveLegacy = getAddressBranchByType(hidden: false, legacy: true, type: type);
final changeLegacy = getAddressBranchByType(hidden: true, legacy: true, type: type); final changeLegacy = getAddressBranchByType(hidden: true, legacy: true, type: type);
walletAddresses.hiddenAddresses.addAll([...changeStandard, ...changeLegacy].map((e) => e.address)); walletAddresses.hiddenAddresses
.addAll([...changeStandard, ...changeLegacy].map((e) => e.address));
await walletAddresses.saveAddressesInBox(); await walletAddresses.saveAddressesInBox();
await fetchTransactionsForAddressesBranchBatch( await fetchTransactionsForAddressesBranchBatch(
@@ -2665,7 +2784,7 @@ abstract class ElectrumWalletBase
await fetchTransactionsForAddressesBranchBatch( await fetchTransactionsForAddressesBranchBatch(
historiesWithDetails, historiesWithDetails,
type, type,
receiveLegacy, receiveLegacy,
isHidden: false, isHidden: false,
isLegacyDerivation: true, isLegacyDerivation: true,
); );
@@ -2749,10 +2868,13 @@ abstract class ElectrumWalletBase
} }
} }
List<BitcoinAddressRecord> getAddressBranchByType({required bool hidden, required bool legacy, required BitcoinAddressType List<BitcoinAddressRecord> getAddressBranchByType(
type}) => walletAddresses.allAddresses.where((addr) => addr.type == type && addr.isHidden == hidden && addr.isLegacyDerivation == legacy) {required bool hidden, required bool legacy, required BitcoinAddressType type}) =>
.toList() walletAddresses.allAddresses
..sort((a, b) => a.index.compareTo(b.index)); .where((addr) =>
addr.type == type && addr.isHidden == hidden && addr.isLegacyDerivation == legacy)
.toList()
..sort((a, b) => a.index.compareTo(b.index));
int _highestUsedIndex(List<BitcoinAddressRecord> addresses) { int _highestUsedIndex(List<BitcoinAddressRecord> addresses) {
for (int i = addresses.length - 1; i >= 0; i--) { for (int i = addresses.length - 1; i >= 0; i--) {
+20
View File
@@ -0,0 +1,20 @@
import "dart:typed_data";
/// Anti-fee-sniping locktime, set to the current chain tip (exact-tip).
///
/// Matches the exact-tip cluster (payjoin-cli, ldk-node, Bull Bitcoin) and Core's
/// default for externally-constructed transactions, and takes Cake off its unique
/// `nLockTime = 0` fingerprint. It intentionally skips the ~10% backdate
/// Core/Electrum apply for delayed-broadcast privacy, which does not apply here.
/// Returns 0 when not synced or the tip is unknown, to avoid emitting a stale
/// height.
int antiFeeSnipingLocktime({
required int chainTip,
required bool synced,
}) {
if (!synced || chainTip <= 0) return 0;
return chainTip;
}
/// Little-endian 4-byte encoding for `BtcTransaction.locktime`.
List<int> locktimeToBytes(int locktime) => (ByteData(4)..setUint32(0, locktime, Endian.little)).buffer.asUint8List();
+3
View File
@@ -295,6 +295,9 @@ class PayjoinManager {
await _wallet.updateAllUnspents(); await _wallet.updateAllUnspents();
utxos = _wallet.getUtxoWithPrivateKeys(confirmedOnly: true); utxos = _wallet.getUtxoWithPrivateKeys(confirmedOnly: true);
} }
// Candidates arrive in wallet scan order (address, then age), which is
// predictable; shuffle so the receiver's input choice can't mirror it.
utxos.shuffle(Random.secure());
mainToIsolateSendPort?.send({ mainToIsolateSendPort?.send({
'requestId': message['requestId'], 'requestId': message['requestId'],
'result': utxos, 'result': utxos,
+3 -1
View File
@@ -14,8 +14,10 @@ class PSBTTransactionBuild {
{required List<PSBTReadyUtxoWithAddress> inputs, {required List<PSBTReadyUtxoWithAddress> inputs,
required List<BitcoinBaseOutput> outputs, required List<BitcoinBaseOutput> outputs,
required List<OutputInfo> cwOutputs, required List<OutputInfo> cwOutputs,
bool enableRBF = true}) { bool enableRBF = true,
int locktime = 0}) {
psbt.setGlobalTxVersion(2); psbt.setGlobalTxVersion(2);
psbt.setGlobalFallbackLocktime(locktime);
psbt.setGlobalInputCount(inputs.length); psbt.setGlobalInputCount(inputs.length);
psbt.setGlobalOutputCount(outputs.length); psbt.setGlobalOutputCount(outputs.length);
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -29,7 +29,7 @@ dependencies:
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2 ref: 59fdf29d72068e0522a96a8953ed7272833a9f57
cw_mweb: cw_mweb:
path: ../cw_mweb path: ../cw_mweb
grpc: 4.0.1 grpc: 4.0.1
@@ -88,7 +88,7 @@ dependency_overrides:
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v11 ref: bda0e2904effa799ec835b4578cc3fc5d31caa78
pointycastle: 3.7.4 pointycastle: 3.7.4
ffi: 2.1.0 ffi: 2.1.0
intl: any intl: any
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
+100
View File
@@ -0,0 +1,100 @@
import 'dart:math';
import "package:cw_bitcoin/coin_selection.dart";
import "package:flutter_test/flutter_test.dart";
void main() {
test('effectiveValue', () {
expect(effectiveValue(10000, 136), 9864);
expect(effectiveValue(100, 136), -36);
});
test('BnB finds changeless match in window', () {
final r = branchAndBound([200,100,90,80], 300, 50);
expect(r, isNotNull); expect(r!.hasChange, isFalse);
final vals=[200,100,90,80]; final sum=r.indices.map((i)=>vals[i]).reduce((a,b)=>a+b);
expect(sum>=300 && sum<=350, isTrue);
});
test('BnB null when no subset in window', () {
expect(branchAndBound([1000,900], 300, 5), isNull);
});
test('SRD with change', () {
final r = singleRandomDraw([500,500,500,500], 700, 50, Random(1));
expect(r, isNotNull); expect(r!.hasChange, isTrue);
});
test('SRD randomizes across seeds', () {
final a = (singleRandomDraw([100,101,102,103,104,105],150,10,Random(1))!.indices..sort());
final b = (singleRandomDraw([100,101,102,103,104,105],150,10,Random(9))!.indices..sort());
expect(a, isNot(equals(b)));
});
test('SRD null on insufficient funds', () {
expect(singleRandomDraw([100,100], 500, 10, Random(1)), isNull);
});
test('selectCoins prefers changeless BnB, else SRD', () {
expect(selectCoins(values:[200,100,90],target:300,inputCost:0,costOfChange:20,minChange:10).hasChange, isFalse);
expect(selectCoins(values:[500,500,500],target:700,inputCost:0,costOfChange:5,minChange:10,rng:Random(1)).hasChange, isTrue);
});
test('selectCoins drops non-positive effective values', () {
// coin of value 50 with inputCost 136 -> effective -86 -> dropped; only the 1000 usable
final r = selectCoins(values:[50,1000],target:500,inputCost:136,costOfChange:5,minChange:10,rng:Random(1));
expect(r.indices, equals([1]));
});
test('changelessMatch finds a set whose effective sum lands in the window', () {
// inputCost 100: values [50, 400, 300, 200] -> eff [dropped, 300, 200, 100]
// target 500, window 46: eff {300, 200} = 500, exact
final r = changelessMatch(
values: [50, 400, 300, 200], target: 500, inputCosts: [100, 100, 100, 100], window: 46);
expect(r, isNotNull);
expect(r!.hasChange, isFalse);
final effSum = r.indices.map((i) => [50, 400, 300, 200][i] - 100).reduce((a, b) => a + b);
expect(effSum >= 500 && effSum <= 546, isTrue);
expect(r.indices.contains(0), isFalse); // negative-eff coin never selected
});
test('changelessMatch returns null when no subset lands in the window', () {
expect(
changelessMatch(values: [10000, 9000], target: 500, inputCosts: [100, 100], window: 46),
isNull);
});
test('selectCoins throws when insufficient', () {
expect(() => selectCoins(values:[100,100],target:500,inputCost:0,costOfChange:5,minChange:10),
throwsA(isA<InsufficientFundsException>()));
});
test('changeless pipeline: leftover absorbed into fee is non-negative and below dust', () {
// Mirrors the _createUTXOS / estimateTxForAmount arithmetic with the wallet's
// 68*inputs + 34*outputs + 10 vBytes model, at 10 sat/vB with 1 recipient output.
const feeRate = 10;
const amount = 50000;
final values = [60700, 30000, 21800, 9000, 5000];
const target = amount + (34 * 1 + 10) * feeRate;
final r = changelessMatch(
values: values,
target: target,
inputCosts: List.filled(values.length, 68 * feeRate),
window: 546);
expect(r, isNotNull);
final inAmount = r!.indices.map((i) => values[i]).reduce((a, b) => a + b);
final feeNoChange = (68 * r.indices.length + 34 * 1 + 10) * feeRate;
final leftover = inAmount - amount - feeNoChange;
expect(leftover >= 0, isTrue); // the caller never recurses for more inputs
expect(leftover <= 546, isTrue); // fee overpay is bounded by the dust limit
});
test('BnB terminates and returns null on large pools with no possible match', () {
// 300 even effective values, odd target, zero window: no subset can ever match,
// so the search must stop at maxTries instead of exploring 2^300 branches.
final values = List<int>.generate(300, (i) => 1000000 + i * 2);
final r = changelessMatch(
values: values, target: 1500001, inputCosts: List.filled(300, 10), window: 0);
expect(r, isNull);
});
test('changelessMatch charges each input its own script-type cost', () {
// Legacy input (148 vB) vs segwit input (68 vB) at 10 sat/vB: same value, but
// the legacy coin's effective value is 800 lower. Target only reachable when
// the cheaper segwit coin is chosen: eff segwit = 10000-680 = 9320.
const feeRate = 10;
final r = changelessMatch(
values: [10000, 10000],
target: 9320,
inputCosts: [148 * feeRate, 68 * feeRate],
window: 0,
);
expect(r, isNotNull);
expect(r!.indices, equals([1]));
});
}
+32
View File
@@ -0,0 +1,32 @@
import 'package:cw_bitcoin/locktime.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('antiFeeSnipingLocktime', () {
test('returns 0 when not synced', () {
expect(antiFeeSnipingLocktime(chainTip: 800000, synced: false), 0);
});
test('returns 0 when chainTip <= 0', () {
expect(antiFeeSnipingLocktime(chainTip: 0, synced: true), 0);
});
test('returns the current tip when synced', () {
expect(antiFeeSnipingLocktime(chainTip: 800000, synced: true), 800000);
});
});
group('locktimeToBytes', () {
test('encodes 0 as four zero bytes', () {
expect(locktimeToBytes(0), [0, 0, 0, 0]);
});
test('encodes a block height little-endian', () {
expect(locktimeToBytes(800000), [0x00, 0x35, 0x0C, 0x00]);
});
test('encodes max 32-bit value', () {
expect(locktimeToBytes(0xFFFFFFFF), [0xFF, 0xFF, 0xFF, 0xFF]);
});
});
}
-2
View File
@@ -22,8 +22,6 @@ migrate_working_dir/
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/ **/doc/api/
.dart_tool/ .dart_tool/
.packages .packages
+2 -2
View File
@@ -28,7 +28,7 @@ dependencies:
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2 ref: 59fdf29d72068e0522a96a8953ed7272833a9f57
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -43,7 +43,7 @@ dependency_overrides:
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v11 ref: bda0e2904effa799ec835b4578cc3fc5d31caa78
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
+5
View File
@@ -0,0 +1,5 @@
targets:
$default:
sources:
exclude:
- "**/.secrets.g.dart"
-4
View File
@@ -202,10 +202,6 @@ class Money implements Comparable<Money> {
@override @override
int get hashCode => amount.hashCode ^ currency.hashCode; int get hashCode => amount.hashCode ^ currency.hashCode;
// Added this to reduce the hops we do to convert Money to double
// for fiat conversion and display
double toDouble() => amount / multiplierOf(currency.decimals);
@override @override
String toString() => formatFixed(amount, currency.decimals); String toString() => formatFixed(amount, currency.decimals);
+1
View File
@@ -1007,6 +1007,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
'bitcoin': btc, 'bitcoin': btc,
'bitcoincash': bch, 'bitcoincash': bch,
'polygon': maticpoly, 'polygon': maticpoly,
'solana': sol,
'nano-gpt': nano, 'nano-gpt': nano,
'secret': scrt, 'secret': scrt,
'stellar': xlm, 'stellar': xlm,
+74 -1
View File
@@ -63,7 +63,7 @@ Future<void> _initDb({String? pathOverride}) async {
} }
} }
await db?.close(); await db?.close();
db = await openDatabase(dbFile.path, version: 9, db = await openDatabase(dbFile.path, version: 10,
onUpgrade: (Database db, int oldVersion, int newVersion) async { onUpgrade: (Database db, int oldVersion, int newVersion) async {
printV("migrating: $oldVersion, $newVersion"); printV("migrating: $oldVersion, $newVersion");
if (oldVersion <= 1) { if (oldVersion <= 1) {
@@ -147,6 +147,11 @@ CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings (
definition: 'BOOLEAN DEFAULT FALSE', definition: 'BOOLEAN DEFAULT FALSE',
); );
} }
if (oldVersion <= 9) {
await _createErc20TokenTable(db);
await _createSplTokenTable(db);
await _createTronTokenTable(db);
}
}, onCreate: (Database db, int version) async { }, onCreate: (Database db, int version) async {
await db.execute(''' await db.execute('''
CREATE TABLE WalletInfo ( CREATE TABLE WalletInfo (
@@ -242,6 +247,9 @@ CREATE TABLE BalanceCardStyleSettings (
await _createBridgeTransferTable(db); await _createBridgeTransferTable(db);
await _createNodeTable(db); await _createNodeTable(db);
await _createTradeTable(db); await _createTradeTable(db);
await _createErc20TokenTable(db);
await _createSplTokenTable(db);
await _createTronTokenTable(db);
}); });
} }
@@ -370,6 +378,71 @@ ON BridgeTransfer(wallet_id);
'''); ''');
} }
Future<void> _createErc20TokenTable(Database db) async {
await db.execute("""
CREATE TABLE IF NOT EXISTS Erc20Token (
Erc20TokenId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
walletName TEXT NOT NULL,
chainId INTEGER NOT NULL,
name TEXT NOT NULL DEFAULT '',
symbol TEXT NOT NULL DEFAULT '',
contractAddress TEXT NOT NULL,
decimal INTEGER NOT NULL DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
iconPath TEXT,
tag TEXT,
isPotentialScam INTEGER NOT NULL DEFAULT 0
);
""");
await db.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_erc20token_wallet_chain_contract
ON Erc20Token (walletName, chainId, contractAddress);
""");
}
Future<void> _createSplTokenTable(Database db) async {
await db.execute("""
CREATE TABLE IF NOT EXISTS SPLToken (
SPLTokenId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
walletName TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
symbol TEXT NOT NULL DEFAULT '',
mintAddress TEXT NOT NULL,
decimal INTEGER NOT NULL DEFAULT 0,
mint TEXT NOT NULL DEFAULT '',
enabled INTEGER NOT NULL DEFAULT 1,
iconPath TEXT,
tag TEXT,
isPotentialScam INTEGER NOT NULL DEFAULT 0
);
""");
await db.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_spltoken_wallet_mint
ON SPLToken (walletName, mintAddress);
""");
}
Future<void> _createTronTokenTable(Database db) async {
await db.execute("""
CREATE TABLE IF NOT EXISTS TronToken (
TronTokenId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
walletName TEXT NOT NULL,
name TEXT NOT NULL DEFAULT '',
symbol TEXT NOT NULL DEFAULT '',
contractAddress TEXT NOT NULL,
decimal INTEGER NOT NULL DEFAULT 0,
enabled INTEGER NOT NULL DEFAULT 1,
iconPath TEXT,
tag TEXT,
isPotentialScam INTEGER NOT NULL DEFAULT 0
);
""");
await db.execute("""
CREATE UNIQUE INDEX IF NOT EXISTS idx_trontoken_wallet_contract
ON TronToken (walletName, contractAddress);
""");
}
Future<void> _createNodeTable(Database db) async { Future<void> _createNodeTable(Database db) async {
db.execute(""" db.execute("""
CREATE TABLE Node ( CREATE TABLE Node (
+153 -45
View File
@@ -1,32 +1,8 @@
import 'package:cw_core/crypto_currency.dart'; import "package:cw_core/crypto_currency.dart";
import 'package:cw_core/hive_type_ids.dart'; import "package:cw_core/db/sqlite.dart";
import 'package:hive/hive.dart'; import "package:sqflite/sqflite.dart";
part 'erc20_token.part.dart';
// @HiveType(typeId: Erc20Token.typeId)
class Erc20Token extends CryptoCurrency with HiveObjectMixin {
// @HiveField(0)
final String name;
// @HiveField(1)
final String symbol;
// @HiveField(2)
final String contractAddress;
// @HiveField(3)
final int decimal;
// @HiveField(4, defaultValue: true)
bool _enabled;
// @HiveField(5)
String? iconPath;
// @HiveField(6)
final String? tag;
// @HiveField(7, defaultValue: false)
bool isPotentialScam;
bool get enabled => _enabled;
set enabled(bool value) => _enabled = value;
class Erc20Token extends CryptoCurrency {
Erc20Token({ Erc20Token({
required this.name, required this.name,
required this.symbol, required this.symbol,
@@ -36,6 +12,9 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
this.iconPath, this.iconPath,
this.tag, this.tag,
this.isPotentialScam = false, this.isPotentialScam = false,
this.id = 0,
this.walletName,
this.chainId,
}) : _enabled = enabled, }) : _enabled = enabled,
super( super(
name: symbol.toLowerCase(), name: symbol.toLowerCase(),
@@ -47,32 +26,161 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
isPotentialScam: isPotentialScam, isPotentialScam: isPotentialScam,
); );
Erc20Token.copyWith(Erc20Token other, {String? icon, String? tag, bool? enabled}) Erc20Token.copyWith(
: this.name = other.name, Erc20Token other, {
this.symbol = other.symbol, String? icon,
this.contractAddress = other.contractAddress, super.tag,
this.decimal = other.decimal, bool? enabled,
this._enabled = enabled ?? other.enabled, String? walletName,
this.tag = tag ?? other.tag, int? chainId,
this.iconPath = icon ?? other.iconPath, }) : name = other.name,
this.isPotentialScam = other.isPotentialScam, symbol = other.symbol,
contractAddress = other.contractAddress,
decimal = other.decimal,
_enabled = enabled ?? other.enabled,
tag = tag ?? other.tag,
iconPath = icon ?? other.iconPath,
isPotentialScam = other.isPotentialScam,
id = 0,
walletName = walletName ?? other.walletName,
chainId = chainId ?? other.chainId,
super( super(
name: other.name, name: other.name,
title: other.symbol.toUpperCase(), title: other.symbol.toUpperCase(),
fullName: other.name, fullName: other.name,
tag: tag,
iconPath: icon, iconPath: icon,
decimals: other.decimal, decimals: other.decimal,
isPotentialScam: other.isPotentialScam, isPotentialScam: other.isPotentialScam,
); );
static const typeId = ERC20_TOKEN_TYPE_ID; Erc20Token.fromMap(Map<String, Object?> map)
static const boxName = 'Erc20Tokens'; : this(
static const ethereumBoxName = 'EthereumErc20Tokens'; name: map["name"] as String? ?? "",
static const polygonBoxName = 'PolygonErc20Tokens'; symbol: map["symbol"] as String? ?? "",
static const baseBoxName = 'BaseErc20Tokens'; contractAddress: map["contractAddress"] as String? ?? "",
static const arbitrumBoxName = 'ArbitrumErc20Tokens'; decimal: (map["decimal"] ?? 0) as int,
static const bscBoxName = 'BscErc20Tokens'; enabled: _getBoolFromDB(map["enabled"], defaultValue: true),
iconPath: map["iconPath"] as String?,
tag: map["tag"] as String?,
isPotentialScam: _getBoolFromDB(map["isPotentialScam"]),
id: (map[selfIdColumn] ?? 0) as int,
walletName: map["walletName"] as String?,
chainId: map["chainId"] as int?,
);
@override
final String name;
@override
final String symbol;
@override
String? iconPath;
@override
final String? tag;
@override
bool isPotentialScam;
@override
bool get enabled => _enabled;
@override
set enabled(bool value) => _enabled = value;
int id;
int? chainId;
bool _enabled;
final int decimal;
String? walletName;
final String contractAddress;
static bool _getBoolFromDB(value, {bool? defaultValue}) {
if (value is bool) {
return value;
} else if (value is int) {
return value == 1;
} else {
return defaultValue ?? false;
}
}
Map<String, dynamic> toMap() => {
selfIdColumn: id,
"walletName": walletName,
"chainId": chainId,
"name": name,
"symbol": symbol,
"contractAddress": contractAddress.toLowerCase(),
"decimal": decimal,
"enabled": _enabled ? 1 : 0,
"iconPath": iconPath,
"tag": tag,
"isPotentialScam": isPotentialScam ? 1 : 0,
};
static String get tableName => "Erc20Token";
static String get selfIdColumn => "${tableName}Id";
Future<int> save() async {
if (walletName == null || chainId == null) {
throw StateError("Erc20Token.save() requires walletName and chainId to be set");
}
final json = toMap();
if (json[selfIdColumn] == 0) {
json[selfIdColumn] = null;
}
id = await db!.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace);
return id;
}
static Future<List<Erc20Token>> selectList(
String where,
List<dynamic> whereArgs, {
String? orderBy,
}) async {
orderBy ??= selfIdColumn;
final list = await db!.query(
tableName,
where: where.isNotEmpty ? where : "1 = 1",
whereArgs: whereArgs.isNotEmpty ? whereArgs : null,
orderBy: orderBy,
);
return List.generate(list.length, (index) => Erc20Token.fromMap(list[index]));
}
static Future<List<Erc20Token>> getAllForWallet(String walletName, int chainId) =>
selectList("walletName = ? AND chainId = ?", [walletName, chainId]);
static Future<Erc20Token?> getByContract(
String walletName,
int chainId,
String contractAddress,
) async {
final list = await selectList(
"walletName = ? AND chainId = ? AND contractAddress = ?",
[walletName, chainId, contractAddress.toLowerCase()],
);
return list.isEmpty ? null : list.first;
}
static Future<int> deleteForWallet(String walletName, int chainId, String contractAddress) =>
db!.delete(
tableName,
where: "walletName = ? AND chainId = ? AND contractAddress = ?",
whereArgs: [walletName, chainId, contractAddress.toLowerCase()],
);
static Future<int> deleteAllForWallet(String walletName) =>
db!.delete(tableName, where: "walletName = ?", whereArgs: [walletName]);
static Future<void> renameWallet(String oldName, String newName) async {
await db!.delete(tableName, where: "walletName = ?", whereArgs: [newName]);
await db!
.update(tableName, {"walletName": newName}, where: "walletName = ?", whereArgs: [oldName]);
}
@override @override
bool operator ==(Object other) => other is Erc20Token && other.contractAddress == contractAddress; bool operator ==(Object other) => other is Erc20Token && other.contractAddress == contractAddress;
+213
View File
@@ -0,0 +1,213 @@
import "package:cw_core/cake_hive.dart";
import "package:cw_core/db/sqlite.dart";
import "package:cw_core/erc20_token.dart" as erc20_new;
import "package:cw_core/hive_type_ids.dart";
import "package:cw_core/utils/print_verbose.dart";
import "package:cw_core/wallet_info.dart";
import "package:cw_core/wallet_type.dart";
import "package:hive/hive.dart";
import "package:sqflite/sqflite.dart";
part "erc20_token_legacy.part.dart";
Future<void> performErc20TokenHiveMigration() async {
try {
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
CakeHive.registerAdapter(Erc20TokenAdapter());
}
final wallets = await WalletInfo.getAll();
await Erc20Token.migrateAllToSqlite(wallets);
} catch (e) {
printV("Error performing Erc20Token Hive migration: $e, continuing anyway");
}
}
// @HiveType(typeId: Erc20Token.typeId)
class Erc20Token extends HiveObject {
Erc20Token({
required this.name,
required this.symbol,
required this.contractAddress,
required this.decimal,
bool enabled = true,
this.iconPath,
this.tag,
this.isPotentialScam = false,
}) : _enabled = enabled;
// @HiveField(0)
final String name;
// @HiveField(1)
final String symbol;
// @HiveField(2)
final String contractAddress;
// @HiveField(3)
final int decimal;
// @HiveField(4, defaultValue: true)
bool _enabled;
// @HiveField(5)
String? iconPath;
// @HiveField(6)
final String? tag;
// @HiveField(7, defaultValue: false)
bool isPotentialScam;
bool get enabled => _enabled;
set enabled(bool value) => _enabled = value;
static const typeId = ERC20_TOKEN_TYPE_ID;
static const boxName = "Erc20Tokens";
static const ethereumBoxName = "EthereumErc20Tokens";
static const polygonBoxName = "PolygonErc20Tokens";
static const baseBoxName = "BaseErc20Tokens";
static const arbitrumBoxName = "ArbitrumErc20Tokens";
static const bscBoxName = "BscErc20Tokens";
static const chainIdToBoxSuffix = {
1: ethereumBoxName,
137: polygonBoxName,
8453: baseBoxName,
42161: arbitrumBoxName,
56: bscBoxName,
};
static const evmWalletTypes = [
WalletType.ethereum,
WalletType.polygon,
WalletType.base,
WalletType.arbitrum,
WalletType.bsc,
];
static Future<void> migrateAllToSqlite(List<WalletInfo> wallets) async {
await _migrateLegacyGlobalBox(wallets);
final sanitizedToRawNames = <String, Set<String>>{};
for (final wallet in wallets) {
if (!evmWalletTypes.contains(wallet.type)) {
continue;
}
sanitizedToRawNames
.putIfAbsent(wallet.name.replaceAll(" ", "_"), () => <String>{})
.add(wallet.name);
}
for (final entry in sanitizedToRawNames.entries) {
for (final chainEntry in chainIdToBoxSuffix.entries) {
final tokenBoxName = "${entry.key}_${chainEntry.value}";
try {
if (!await CakeHive.boxExists(tokenBoxName)) {
continue;
}
final box = await CakeHive.openBox<Erc20Token>(tokenBoxName);
for (final group in _mergeByLowercaseContract(box)) {
for (final rawName in entry.value) {
await group.token.migrateToSqlite(walletName: rawName, chainId: chainEntry.key);
}
await box.deleteAll(group.sourceKeys);
}
await box.deleteFromDisk();
} catch (e) {
printV("Error migrating erc20 token box $tokenBoxName: $e, continuing anyway");
}
}
}
}
static Future<void> _migrateLegacyGlobalBox(List<WalletInfo> wallets) async {
try {
if (!await CakeHive.boxExists(boxName)) {
return;
}
if (!wallets.any((wallet) => wallet.type == WalletType.ethereum)) {
return;
}
final box = await CakeHive.openBox<Erc20Token>(boxName);
final ethereumWallets =
wallets.where((wallet) => wallet.type == WalletType.ethereum).toList();
for (final group in _mergeByLowercaseContract(box)) {
for (final wallet in ethereumWallets) {
await group.token.migrateToSqlite(walletName: wallet.name, chainId: 1);
}
await box.deleteAll(group.sourceKeys);
}
await box.deleteFromDisk();
} catch (e) {
printV("Error migrating legacy global erc20 token box: $e, continuing anyway");
}
}
static List<_MergedTokenGroup> _mergeByLowercaseContract(Box<Erc20Token> box) {
final groups = <String, _MergedTokenGroup>{};
for (final key in box.keys) {
final token = box.get(key);
if (token == null) {
continue;
}
final lowerKey = token.contractAddress.toLowerCase();
final existing = groups[lowerKey];
if (existing == null) {
groups[lowerKey] = _MergedTokenGroup(token: token, sourceKeys: [key]);
continue;
}
groups[lowerKey] = _MergedTokenGroup(
token: Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: lowerKey,
decimal: token.decimal,
enabled: token.enabled || existing.token.enabled,
iconPath:
(token.iconPath?.isNotEmpty ?? false) ? token.iconPath : existing.token.iconPath,
tag: token.tag ?? existing.token.tag,
isPotentialScam: token.isPotentialScam || existing.token.isPotentialScam,
),
sourceKeys: [...existing.sourceKeys, key],
);
}
return groups.values.toList();
}
Future<void> migrateToSqlite({required String walletName, required int chainId}) async {
final row = erc20_new.Erc20Token(
name: name,
symbol: symbol,
contractAddress: contractAddress,
decimal: decimal,
enabled: _enabled,
iconPath: iconPath,
tag: tag,
isPotentialScam: isPotentialScam,
walletName: walletName,
chainId: chainId,
).toMap();
row[erc20_new.Erc20Token.selfIdColumn] = null;
await db!.insert(
erc20_new.Erc20Token.tableName,
row,
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
class _MergedTokenGroup {
_MergedTokenGroup({required this.token, required this.sourceKeys});
final Erc20Token token;
final List<dynamic> sourceKeys;
}
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'erc20_token.dart'; part of "erc20_token_legacy.dart";
// ************************************************************************** // **************************************************************************
// TypeAdapterGenerator // TypeAdapterGenerator
+9
View File
@@ -70,3 +70,12 @@ class WalletDeprecationException implements Exception {
WalletDeprecationException({required this.seed, required this.curr}); WalletDeprecationException({required this.seed, required this.curr});
} }
class WalletSwitchException implements Exception {
WalletSwitchException(this.message);
final String message;
@override
String toString() => message;
}
+22 -9
View File
@@ -4,31 +4,44 @@ String formatFixed(BigInt value, int? decimals, {int? fractionalDigits, bool tri
decimals ??= 0; decimals ??= 0;
fractionalDigits ??= decimals; fractionalDigits ??= decimals;
final multiplier = multiplierOf(decimals); final multiplier = getMultiplier(decimals);
var negative = value.isNegative; // Make sure wei is a big number (convert as necessary)
if (negative) value = -value; final negative = value.isNegative;
if (negative) {
value = -value;
}
var fraction = (value % multiplier).toString().padLeft(decimals, "0"); var fraction =
value.modPow(BigInt.one, BigInt.parse(multiplier)).toString().padLeft(decimals, "0");
if (fractionalDigits < 0) fractionalDigits = 0; if (fractionalDigits < 0) {
if (fractionalDigits > decimals) fractionalDigits = decimals; fractionalDigits = 0;
}
if (fractionalDigits > decimals) {
fractionalDigits = decimals;
}
fraction = fraction.substring(0, fractionalDigits); fraction = fraction.substring(0, fractionalDigits);
if (trimZeros) { if (trimZeros) {
fraction = removeTrailing("0", fraction); fraction = removeTrailing("0", fraction);
} }
final whole = value ~/ multiplier; final whole = value ~/ BigInt.parse(multiplier);
final valString = fraction.isEmpty ? "$whole" : "$whole.$fraction"; final valString = fraction.isEmpty ? "$whole" : "$whole.$fraction";
if (negative) return "-$valString"; if (negative) {
return "-$valString";
}
return valString; return valString;
} }
String removeTrailing(String pattern, String from) { String removeTrailing(String pattern, String from) {
if (pattern.isEmpty) return from; if (pattern.isEmpty) {
return from;
}
var i = from.length; var i = from.length;
while (i > 0 && from.startsWith(pattern, i - pattern.length)) { while (i > 0 && from.startsWith(pattern, i - pattern.length)) {
i -= pattern.length; i -= pattern.length;
+17 -13
View File
@@ -37,40 +37,44 @@ BigInt parseFixed(String value, int decimals) {
} }
final negative = value.startsWith("-"); final negative = value.startsWith("-");
if (negative) value = value.substring(1); if (negative) {
value = value.substring(1);
}
if (value == ".") throw FormatException("missing value, value, $value"); if (value == ".") {
throw FormatException("missing value, value, $value");
}
if (value.startsWith(".")) value = "0$value"; if (value.startsWith(".")) {
value = "0$value";
}
final comps = value.split("."); final comps = value.split(".");
if (comps.length > 2) { if (comps.length > 2) {
throw FormatException("too many decimal points, value, $value"); throw FormatException("too many decimal points, value, $value");
} }
var whole = comps.isNotEmpty ? comps[0] : "0"; final whole = comps.isNotEmpty ? comps[0] : "0";
var fraction = (comps.length == 2 ? comps[1] : "0").padRight(decimals, "0"); final fraction = (comps.length == 2 ? comps[1] : "0").padRight(decimals, "0");
if (fraction.length > multiplier.length - 1) { if (fraction.length > multiplier.length - 1) {
throw FormatException( throw FormatException(
"fractional component(${fraction.length}) exceeds decimals(${decimals}), underflow, parseFixed"); "fractional component(${fraction.length}) exceeds decimals(${decimals}), underflow, parseFixed",
);
} }
final wholeValue = BigInt.parse(whole); final wholeValue = BigInt.parse(whole);
final fractionValue = BigInt.parse(fraction); final fractionValue = BigInt.parse(fraction);
final multiplierValue = multiplierOf(decimals); final multiplierValue = BigInt.parse(multiplier);
var wei = (wholeValue * multiplierValue) + fractionValue; var wei = (wholeValue * multiplierValue) + fractionValue;
if (negative) wei *= BigInt.from(-1); if (negative) {
wei *= BigInt.from(-1);
}
return wei; return wei;
} }
// Returns a string "1" followed by decimal "0"s // Returns a string "1" followed by decimal "0"s
String getMultiplier(int decimals) => "1".padRight(decimals + 1, "0"); String getMultiplier(int decimals) => "1".padRight(decimals + 1, "0");
final _multipliers = <int, BigInt>{};
// this is more direct and faster than having it as string then parsing everytime to get the number
BigInt multiplierOf(int decimals) => _multipliers[decimals] ??= BigInt.from(10).pow(decimals);
+12 -10
View File
@@ -283,8 +283,8 @@ class ERC681URI extends PaymentURI {
} else { } else {
final valueParam = uri.queryParameters["value"]; final valueParam = uri.queryParameters["value"];
if (valueParam != null) { if (valueParam != null) {
final normalized = _normalizeToIntegerWei(valueParam); final normalized = BigInt.tryParse(_normalizeToIntegerWei(valueParam));
formatedAmount = formatFixed(BigInt.parse(normalized), 18); formatedAmount = normalized != null ? formatFixed(normalized, 18) : "";
} else { } else {
formatedAmount = uri.queryParameters["amount"] ?? ""; formatedAmount = uri.queryParameters["amount"] ?? "";
} }
@@ -313,9 +313,7 @@ class ERC681URI extends PaymentURI {
final targetAddress = contractAddress ?? address; final targetAddress = contractAddress ?? address;
uri += targetAddress; uri += targetAddress;
if (chainId != 1) { uri += "@$chainId";
uri += "@$chainId";
}
if (contractAddress != null) { if (contractAddress != null) {
uri += "/transfer"; uri += "/transfer";
@@ -364,14 +362,18 @@ class ERC681URI extends PaymentURI {
} }
} }
static int _getChainID(String path) => int.parse( static int _getChainID(String path) =>
int.tryParse(
RegExp(r"@\d*").firstMatch(path)?.group(0)?.replaceAll("@", "") ?? "1", RegExp(r"@\d*").firstMatch(path)?.group(0)?.replaceAll("@", "") ?? "1",
); ) ??
1;
static (bool, String) _getTargetAddress(String path) { static (bool, String) _getTargetAddress(String path) {
final targetAddress = // I saw in the schema (thanks Konsti) that EIP-681 allows an optional "pay-" prefix before the target address, so adding a check for it here
RegExp(r"^(0x)?[0-9a-f]{40}", caseSensitive: false).firstMatch(path)!.group(0)!; final cleaned = path.startsWith("pay-") ? path.substring(4) : path;
return (path.contains("/"), targetAddress); final match = RegExp(r"^(0x)?[0-9a-f]{40}", caseSensitive: false).firstMatch(cleaned);
final targetAddress = match?.group(0) ?? cleaned.split("@").first.split("/").first;
return (cleaned.contains("/"), targetAddress);
} }
/// Normalize an input amount into an integer wei string. /// Normalize an input amount into an integer wei string.
+130 -52
View File
@@ -1,41 +1,8 @@
import 'package:cw_core/crypto_currency.dart'; import "package:cw_core/crypto_currency.dart";
import 'package:cw_core/hive_type_ids.dart'; import "package:cw_core/db/sqlite.dart";
import 'package:hive/hive.dart'; import "package:sqflite/sqflite.dart";
part 'spl_token.part.dart'; class SPLToken extends CryptoCurrency {
// @HiveType(typeId: SPLToken.typeId)
class SPLToken extends CryptoCurrency with HiveObjectMixin {
@override
// @HiveField(0)
final String name;
// @HiveField(1)
final String symbol;
// @HiveField(2)
final String mintAddress;
// @HiveField(3)
final int decimal;
// @HiveField(4, defaultValue: true)
bool _enabled;
// @HiveField(5)
final String mint;
@override
// @HiveField(6)
final String? iconPath;
@override
// @HiveField(7)
final String? tag;
@override
// @HiveField(8, defaultValue: false)
bool isPotentialScam;
SPLToken({ SPLToken({
required this.name, required this.name,
@@ -44,10 +11,12 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
required this.decimal, required this.decimal,
required this.mint, required this.mint,
this.iconPath, this.iconPath,
this.tag = 'SOL', this.tag = "SOL",
bool enabled = true, bool enabled = true,
this.isPotentialScam = false, this.isPotentialScam = false,
Set<String> groups = const {}, super.groups,
this.id = 0,
this.walletName,
}) : _enabled = enabled, }) : _enabled = enabled,
super( super(
name: mint.toLowerCase(), name: mint.toLowerCase(),
@@ -57,7 +26,6 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
iconPath: iconPath, iconPath: iconPath,
decimals: decimal, decimals: decimal,
isPotentialScam: isPotentialScam, isPotentialScam: isPotentialScam,
groups: groups,
); );
factory SPLToken.fromMetadata({ factory SPLToken.fromMetadata({
@@ -67,8 +35,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
required String mintAddress, required String mintAddress,
String? iconPath, String? iconPath,
bool isPotentialScam = false, bool isPotentialScam = false,
}) { }) => SPLToken(
return SPLToken(
name: name, name: name,
symbol: symbol, symbol: symbol,
mintAddress: mintAddress, mintAddress: mintAddress,
@@ -77,15 +44,8 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
iconPath: iconPath, iconPath: iconPath,
isPotentialScam: isPotentialScam, isPotentialScam: isPotentialScam,
); );
}
@override SPLToken.copyWith(SPLToken other, {String? icon, String? tag, bool? enabled, String? walletName})
bool get enabled => _enabled;
@override
set enabled(bool value) => _enabled = value;
SPLToken.copyWith(SPLToken other, {String? icon, String? tag, bool? enabled})
: name = other.name, : name = other.name,
symbol = other.symbol, symbol = other.symbol,
mintAddress = other.mintAddress, mintAddress = other.mintAddress,
@@ -95,6 +55,8 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
tag = tag ?? other.tag, tag = tag ?? other.tag,
iconPath = icon ?? other.iconPath, iconPath = icon ?? other.iconPath,
isPotentialScam = other.isPotentialScam, isPotentialScam = other.isPotentialScam,
id = 0,
walletName = walletName ?? other.walletName,
super( super(
title: other.symbol.toUpperCase(), title: other.symbol.toUpperCase(),
name: other.symbol.toLowerCase(), name: other.symbol.toLowerCase(),
@@ -106,8 +68,124 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
groups: other.groups, groups: other.groups,
); );
static const typeId = SPL_TOKEN_TYPE_ID; SPLToken.fromMap(Map<String, Object?> map)
static const boxName = 'SPLTokens'; : this(
name: map["name"] as String? ?? "",
symbol: map["symbol"] as String? ?? "",
mintAddress: map["mintAddress"] as String? ?? "",
decimal: (map["decimal"] ?? 0) as int,
mint: map["mint"] as String? ?? "",
enabled: _getBoolFromDB(map["enabled"], defaultValue: true),
iconPath: map["iconPath"] as String?,
tag: map["tag"] as String?,
isPotentialScam: _getBoolFromDB(map["isPotentialScam"]),
id: (map[selfIdColumn] ?? 0) as int,
walletName: map["walletName"] as String?,
);
@override
final String name;
@override
final String symbol;
final String mintAddress;
final int decimal;
bool _enabled;
final String mint;
@override
final String? iconPath;
@override
final String? tag;
@override
bool isPotentialScam;
int id;
String? walletName;
@override
bool get enabled => _enabled;
@override
set enabled(bool value) => _enabled = value;
static bool _getBoolFromDB(value, {bool? defaultValue}) {
if (value is bool) {
return value;
} else if (value is int) {
return value == 1;
} else {
return defaultValue ?? false;
}
}
Map<String, dynamic> toMap() => {
selfIdColumn: id,
"walletName": walletName,
"name": name,
"symbol": symbol,
"mintAddress": mintAddress,
"decimal": decimal,
"mint": mint,
"enabled": _enabled ? 1 : 0,
"iconPath": iconPath,
"tag": tag,
"isPotentialScam": isPotentialScam ? 1 : 0,
};
static String get tableName => "SPLToken";
static String get selfIdColumn => "${tableName}Id";
Future<int> save() async {
if (walletName == null) {
throw StateError("SPLToken.save() requires walletName to be set");
}
final json = toMap();
if (json[selfIdColumn] == 0) {
json[selfIdColumn] = null;
}
id = await db!.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace);
return id;
}
static Future<List<SPLToken>> selectList(String where, List<dynamic> whereArgs,
{String? orderBy}) async {
orderBy ??= selfIdColumn;
final list = await db!.query(
tableName,
where: where.isNotEmpty ? where : "1 = 1",
whereArgs: whereArgs.isNotEmpty ? whereArgs : null,
orderBy: orderBy,
);
return List.generate(list.length, (index) => SPLToken.fromMap(list[index]));
}
static Future<List<SPLToken>> getAllForWallet(String walletName) async => selectList("walletName = ?", [walletName]);
static Future<SPLToken?> getByMint(String walletName, String mintAddress) async {
final list = await selectList("walletName = ? AND mintAddress = ?", [walletName, mintAddress]);
return list.isEmpty ? null : list.first;
}
static Future<int> deleteForWallet(String walletName, String mintAddress) => db!.delete(
tableName,
where: "walletName = ? AND mintAddress = ?",
whereArgs: [walletName, mintAddress],
);
static Future<int> deleteAllForWallet(String walletName) => db!.delete(tableName, where: "walletName = ?", whereArgs: [walletName]);
static Future<void> renameWallet(String oldName, String newName) async {
await db!.delete(tableName, where: "walletName = ?", whereArgs: [newName]);
await db!
.update(tableName, {"walletName": newName}, where: "walletName = ?", whereArgs: [oldName]);
}
@override @override
bool operator ==(other) => bool operator ==(other) =>
+134
View File
@@ -0,0 +1,134 @@
import "package:cw_core/cake_hive.dart";
import "package:cw_core/db/sqlite.dart";
import "package:cw_core/hive_type_ids.dart";
import "package:cw_core/spl_token.dart" as spl_new;
import "package:cw_core/utils/print_verbose.dart";
import "package:cw_core/wallet_info.dart";
import "package:cw_core/wallet_type.dart";
import "package:hive/hive.dart";
import "package:sqflite/sqflite.dart";
part "spl_token_legacy.part.dart";
Future<void> performSplTokenHiveMigration() async {
try {
if (!CakeHive.isAdapterRegistered(SPLToken.typeId)) {
CakeHive.registerAdapter(SPLTokenAdapter());
}
final wallets = await WalletInfo.getAll();
await SPLToken.migrateAllToSqlite(wallets);
} catch (e) {
printV("Error performing SPLToken Hive migration: $e, continuing anyway");
}
}
// @HiveType(typeId: SPLToken.typeId)
class SPLToken extends HiveObject {
SPLToken({
required this.name,
required this.symbol,
required this.mintAddress,
required this.decimal,
required this.mint,
this.iconPath,
this.tag = "SOL",
bool enabled = true,
this.isPotentialScam = false,
}) : _enabled = enabled;
// @HiveField(0)
final String name;
// @HiveField(1)
final String symbol;
// @HiveField(2)
final String mintAddress;
// @HiveField(3)
final int decimal;
// @HiveField(4, defaultValue: true)
bool _enabled;
// @HiveField(5)
final String mint;
// @HiveField(6)
final String? iconPath;
// @HiveField(7)
final String? tag;
// @HiveField(8, defaultValue: false)
bool isPotentialScam;
bool get enabled => _enabled;
set enabled(bool value) => _enabled = value;
static const typeId = SPL_TOKEN_TYPE_ID;
static const boxName = "SPLTokens";
static Future<void> migrateAllToSqlite(List<WalletInfo> wallets) async {
final sanitizedToRawNames = <String, Set<String>>{};
for (final wallet in wallets) {
if (wallet.type != WalletType.solana) {
continue;
}
sanitizedToRawNames
.putIfAbsent(wallet.name.replaceAll(" ", "_"), () => <String>{})
.add(wallet.name);
}
for (final entry in sanitizedToRawNames.entries) {
final tokenBoxName = "${entry.key}_$boxName";
try {
if (!await CakeHive.boxExists(tokenBoxName)) {
continue;
}
final box = await CakeHive.openBox<SPLToken>(tokenBoxName);
for (final key in box.keys.toList()) {
final token = box.get(key);
if (token == null) {
continue;
}
for (final rawName in entry.value) {
await token.migrateToSqlite(walletName: rawName);
}
await box.delete(key);
}
await box.deleteFromDisk();
} catch (e) {
printV("Error migrating spl token box $tokenBoxName: $e, continuing anyway");
}
}
}
Future<void> migrateToSqlite({required String walletName}) async {
final row = spl_new.SPLToken(
name: name,
symbol: symbol,
mintAddress: mintAddress,
decimal: decimal,
mint: mint,
enabled: _enabled,
iconPath: iconPath,
tag: tag,
isPotentialScam: isPotentialScam,
walletName: walletName,
).toMap();
row[spl_new.SPLToken.selfIdColumn] = null;
await db!.insert(
spl_new.SPLToken.tableName,
row,
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'spl_token.dart'; part of "spl_token_legacy.dart";
// ************************************************************************** // **************************************************************************
// TypeAdapterGenerator // TypeAdapterGenerator
+132 -42
View File
@@ -1,41 +1,8 @@
// ignore_for_file: annotate_overrides, overridden_fields import "package:cw_core/crypto_currency.dart";
import "package:cw_core/db/sqlite.dart";
import 'package:cw_core/crypto_currency.dart'; import "package:sqflite/sqflite.dart";
import 'package:cw_core/hive_type_ids.dart';
import 'package:hive/hive.dart';
part 'tron_token.part.dart';
// @HiveType(typeId: TronToken.typeId)
class TronToken extends CryptoCurrency with HiveObjectMixin {
// @HiveField(0)
final String name;
// @HiveField(1)
final String symbol;
// @HiveField(2)
final String contractAddress;
// @HiveField(3)
final int decimal;
// @HiveField(4, defaultValue: true)
bool _enabled;
// @HiveField(5)
final String? iconPath;
// @HiveField(6)
final String? tag;
// @HiveField(7, defaultValue: false)
final bool isPotentialScam;
bool get enabled => _enabled;
set enabled(bool value) => _enabled = value;
class TronToken extends CryptoCurrency {
TronToken({ TronToken({
required this.name, required this.name,
required this.symbol, required this.symbol,
@@ -43,8 +10,10 @@ class TronToken extends CryptoCurrency with HiveObjectMixin {
required this.decimal, required this.decimal,
bool enabled = true, bool enabled = true,
this.iconPath, this.iconPath,
this.tag = 'TRX', this.tag = "TRX",
this.isPotentialScam = false, this.isPotentialScam = false,
this.id = 0,
this.walletName,
}) : _enabled = enabled, }) : _enabled = enabled,
super( super(
name: symbol.toLowerCase(), name: symbol.toLowerCase(),
@@ -55,9 +24,27 @@ class TronToken extends CryptoCurrency with HiveObjectMixin {
decimals: decimal, decimals: decimal,
isPotentialScam: isPotentialScam, isPotentialScam: isPotentialScam,
); );
TronToken.fromMap(Map<String, Object?> map)
: this(
name: map["name"] as String? ?? "",
symbol: map["symbol"] as String? ?? "",
contractAddress: map["contractAddress"] as String? ?? "",
decimal: (map["decimal"] ?? 0) as int,
enabled: _getBoolFromDB(map["enabled"], defaultValue: true),
iconPath: map["iconPath"] as String?,
tag: map["tag"] as String?,
isPotentialScam: _getBoolFromDB(map["isPotentialScam"]),
id: (map[selfIdColumn] ?? 0) as int,
walletName: map["walletName"] as String?,
);
TronToken.copyWith(TronToken other, {String? icon, String? tag, bool? enabled}) TronToken.copyWith(
: name = other.name, TronToken other, {
String? icon,
String? tag,
bool? enabled,
String? walletName,
}) : name = other.name,
symbol = other.symbol, symbol = other.symbol,
contractAddress = other.contractAddress, contractAddress = other.contractAddress,
decimal = other.decimal, decimal = other.decimal,
@@ -65,6 +52,8 @@ class TronToken extends CryptoCurrency with HiveObjectMixin {
tag = tag ?? other.tag, tag = tag ?? other.tag,
iconPath = icon ?? other.iconPath, iconPath = icon ?? other.iconPath,
isPotentialScam = other.isPotentialScam, isPotentialScam = other.isPotentialScam,
id = 0,
walletName = walletName ?? other.walletName,
super( super(
name: other.name, name: other.name,
title: other.symbol.toUpperCase(), title: other.symbol.toUpperCase(),
@@ -74,9 +63,110 @@ class TronToken extends CryptoCurrency with HiveObjectMixin {
decimals: other.decimal, decimals: other.decimal,
isPotentialScam: other.isPotentialScam, isPotentialScam: other.isPotentialScam,
); );
@override
final String name;
static const typeId = TRON_TOKEN_TYPE_ID; @override
static const boxName = 'TronTokens'; final String symbol;
@override
final String? iconPath;
@override
final String? tag;
@override
final bool isPotentialScam;
@override
bool get enabled => _enabled;
@override
set enabled(bool value) => _enabled = value;
int id;
bool _enabled;
final int decimal;
String? walletName;
final String contractAddress;
static bool _getBoolFromDB(value, {bool? defaultValue}) {
if (value is bool) {
return value;
} else if (value is int) {
return value == 1;
} else {
return defaultValue ?? false;
}
}
Map<String, dynamic> toMap() => {
selfIdColumn: id,
"walletName": walletName,
"name": name,
"symbol": symbol,
"contractAddress": contractAddress,
"decimal": decimal,
"enabled": _enabled ? 1 : 0,
"iconPath": iconPath,
"tag": tag,
"isPotentialScam": isPotentialScam ? 1 : 0,
};
static String get tableName => "TronToken";
static String get selfIdColumn => "${tableName}Id";
Future<int> save() async {
if (walletName == null) {
throw StateError("TronToken.save() requires walletName to be set");
}
final json = toMap();
if (json[selfIdColumn] == 0) {
json[selfIdColumn] = null;
}
id = await db!.insert(tableName, json, conflictAlgorithm: ConflictAlgorithm.replace);
return id;
}
static Future<List<TronToken>> selectList(
String where,
List<dynamic> whereArgs, {
String? orderBy,
}) async {
orderBy ??= selfIdColumn;
final list = await db!.query(
tableName,
where: where.isNotEmpty ? where : "1 = 1",
whereArgs: whereArgs.isNotEmpty ? whereArgs : null,
orderBy: orderBy,
);
return List.generate(list.length, (index) => TronToken.fromMap(list[index]));
}
static Future<List<TronToken>> getAllForWallet(String walletName) =>
selectList("walletName = ?", [walletName]);
static Future<TronToken?> getByContract(String walletName, String contractAddress) async {
final list =
await selectList("walletName = ? AND contractAddress = ?", [walletName, contractAddress]);
return list.isEmpty ? null : list.first;
}
static Future<int> deleteForWallet(String walletName, String contractAddress) => db!.delete(
tableName,
where: "walletName = ? AND contractAddress = ?",
whereArgs: [walletName, contractAddress],
);
static Future<int> deleteAllForWallet(String walletName) =>
db!.delete(tableName, where: "walletName = ?", whereArgs: [walletName]);
static Future<void> renameWallet(String oldName, String newName) async {
await db!.delete(tableName, where: "walletName = ?", whereArgs: [newName]);
await db!
.update(tableName, {"walletName": newName}, where: "walletName = ?", whereArgs: [oldName]);
}
@override @override
bool operator ==(other) => bool operator ==(other) =>
+129
View File
@@ -0,0 +1,129 @@
import "package:cw_core/cake_hive.dart";
import "package:cw_core/db/sqlite.dart";
import "package:cw_core/hive_type_ids.dart";
import "package:cw_core/tron_token.dart" as tron_new;
import "package:cw_core/utils/print_verbose.dart";
import "package:cw_core/wallet_info.dart";
import "package:cw_core/wallet_type.dart";
import "package:hive/hive.dart";
import "package:sqflite/sqflite.dart";
part "tron_token_legacy.part.dart";
Future<void> performTronTokenHiveMigration() async {
try {
if (!CakeHive.isAdapterRegistered(TronToken.typeId)) {
CakeHive.registerAdapter(TronTokenAdapter());
}
final wallets = await WalletInfo.getAll();
await TronToken.migrateAllToSqlite(wallets);
} catch (e) {
printV("Error performing TronToken Hive migration: $e, continuing anyway");
}
}
// @HiveType(typeId: TronToken.typeId)
class TronToken extends HiveObject {
TronToken({
required this.name,
required this.symbol,
required this.contractAddress,
required this.decimal,
bool enabled = true,
this.iconPath,
this.tag = "TRX",
this.isPotentialScam = false,
}) : _enabled = enabled;
// @HiveField(0)
final String name;
// @HiveField(1)
final String symbol;
// @HiveField(2)
final String contractAddress;
// @HiveField(3)
final int decimal;
// @HiveField(4, defaultValue: true)
bool _enabled;
// @HiveField(5)
final String? iconPath;
// @HiveField(6)
final String? tag;
// @HiveField(7, defaultValue: false)
final bool isPotentialScam;
bool get enabled => _enabled;
set enabled(bool value) => _enabled = value;
static const typeId = TRON_TOKEN_TYPE_ID;
static const boxName = "TronTokens";
static Future<void> migrateAllToSqlite(List<WalletInfo> wallets) async {
final sanitizedToRawNames = <String, Set<String>>{};
for (final wallet in wallets) {
if (wallet.type != WalletType.tron) {
continue;
}
sanitizedToRawNames
.putIfAbsent(wallet.name.replaceAll(" ", "_"), () => <String>{})
.add(wallet.name);
}
for (final entry in sanitizedToRawNames.entries) {
final tokenBoxName = "${entry.key}_$boxName";
try {
if (!await CakeHive.boxExists(tokenBoxName)) {
continue;
}
final box = await CakeHive.openBox<TronToken>(tokenBoxName);
for (final key in box.keys.toList()) {
final token = box.get(key);
if (token == null) {
continue;
}
for (final rawName in entry.value) {
await token.migrateToSqlite(walletName: rawName);
}
await box.delete(key);
}
await box.deleteFromDisk();
} catch (e) {
printV("Error migrating tron token box $tokenBoxName: $e, continuing anyway");
}
}
}
Future<void> migrateToSqlite({required String walletName}) async {
final row = tron_new.TronToken(
name: name,
symbol: symbol,
contractAddress: contractAddress,
decimal: decimal,
enabled: _enabled,
iconPath: iconPath,
tag: tag,
isPotentialScam: isPotentialScam,
walletName: walletName,
).toMap();
row[tron_new.TronToken.selfIdColumn] = null;
await db!.insert(
tron_new.TronToken.tableName,
row,
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
part of 'tron_token.dart'; part of "tron_token_legacy.dart";
// ************************************************************************** // **************************************************************************
// TypeAdapterGenerator // TypeAdapterGenerator
+30 -14
View File
@@ -1,14 +1,16 @@
import 'dart:convert'; import "dart:convert";
import 'dart:io'; import "dart:io";
import 'package:cw_core/pathForWallet.dart'; import "package:cw_core/pathForWallet.dart";
import 'package:cw_core/utils/file.dart'; import "package:cw_core/spl_token.dart";
import 'package:cw_core/utils/print_verbose.dart'; import "package:cw_core/tron_token.dart";
import 'package:cw_core/wallet_base.dart'; import "package:cw_core/utils/file.dart";
import 'package:cw_core/wallet_credentials.dart'; import "package:cw_core/utils/print_verbose.dart";
import 'package:cw_core/wallet_info.dart'; import "package:cw_core/wallet_base.dart";
import 'package:cw_core/wallet_type.dart'; import "package:cw_core/wallet_credentials.dart";
import 'package:path/path.dart' as p; import "package:cw_core/wallet_info.dart";
import "package:cw_core/wallet_type.dart";
import "package:path/path.dart" as p;
abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials, abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
RFK extends WalletCredentials, RFH extends WalletCredentials> { RFK extends WalletCredentials, RFH extends WalletCredentials> {
@@ -29,11 +31,13 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
Future<void> remove(String wallet); Future<void> remove(String wallet);
Future<void> rename(String currentName, String password, String newName) async { Future<void> rename(String currentName, String password, String newName) async {
if (currentName == newName) return; if (currentName == newName) {
return;
}
final currentWalletInfo = await WalletInfo.get(currentName, getType()); final currentWalletInfo = await WalletInfo.get(currentName, getType());
if (currentWalletInfo == null) { if (currentWalletInfo == null) {
throw Exception('Wallet not found'); throw Exception("Wallet not found");
} }
await copyWalletFilesTo(fromName: currentName, toName: newName, type: getType()); await copyWalletFilesTo(fromName: currentName, toName: newName, type: getType());
@@ -43,6 +47,8 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
currentWalletInfo.name = newName; currentWalletInfo.name = newName;
await currentWalletInfo.save(); await currentWalletInfo.save();
await _renameTokenRows(currentName, newName);
final oldDir = Directory(p.join(await pathForWalletTypeDir(type: getType()), currentName)); final oldDir = Directory(p.join(await pathForWalletTypeDir(type: getType()), currentName));
if (oldDir.existsSync()) { if (oldDir.existsSync()) {
try { try {
@@ -53,6 +59,16 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
} }
} }
Future<void> _renameTokenRows(String currentName, String newName) async {
if (getType() == WalletType.solana) {
await SPLToken.renameWallet(currentName, newName);
}
if (getType() == WalletType.tron) {
await TronToken.renameWallet(currentName, newName);
}
}
Future<void> restoreWalletFilesFromBackup(String name) async { Future<void> restoreWalletFilesFromBackup(String name) async {
final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: getType()); final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: getType());
final walletDirPath = await pathForWalletDir(name: name, type: getType()); final walletDirPath = await pathForWalletDir(name: name, type: getType());
@@ -77,14 +93,14 @@ abstract class WalletService<N extends WalletCredentials, RFS extends WalletCred
final jsonSource = await read(path: path, password: password); final jsonSource = await read(path: path, password: password);
try { try {
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
return data['mnemonic'] as String? ?? ''; return data["mnemonic"] as String? ?? "";
} catch (_) { } catch (_) {
// if not a valid json // if not a valid json
return jsonSource.substring(0, 200); return jsonSource.substring(0, 200);
} }
} catch (_) { } catch (_) {
// if the file couldn't be opened or read // if the file couldn't be opened or read
return ''; return "";
} }
} }
-842
View File
@@ -1,842 +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: "5b7468c326d2f8a4f630056404ca0d291ade42918f4a3c6233618e724f39da8e"
url: "https://pub.dev"
source: hosted
version: "92.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "70e4b1ef8003c64793a9e268a551a82869a8a96f39deb73dea28084b0e8bf75e"
url: "https://pub.dev"
source: hosted
version: "9.0.0"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: "1c296cd268f486cabcc3930e9b93a8133169305f18d722916e675959a88f6d2c"
url: "https://pub.dev"
source: hosted
version: "1.5.9"
async:
dependency: transitive
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
bech32:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
url: "https://github.com/cake-tech/bech32.git"
source: git
version: "0.2.2"
blockchain_utils:
dependency: "direct main"
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: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
url: "https://pub.dev"
source: hosted
version: "4.0.6"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
url: "https://pub.dev"
source: hosted
version: "2.15.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: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
url: "https://pub.dev"
source: hosted
version: "8.10.1"
cake_backup:
dependency: "direct main"
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: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
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"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b
url: "https://pub.dev"
source: hosted
version: "3.1.3"
decimal:
dependency: "direct main"
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt:
dependency: "direct main"
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"
file:
dependency: "direct main"
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: "direct main"
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"
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: "direct dev"
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
http:
dependency: "direct main"
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.4.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: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: "direct main"
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: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mobx:
dependency: "direct main"
description:
name: mobx
sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
mobx_codegen:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f"
url: "https://pub.dev"
source: hosted
version: "2.7.7"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
on_chain:
dependency: "direct main"
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: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
source: hosted
version: "2.2.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider:
dependency: "direct main"
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: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.17"
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: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
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: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
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: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev"
source: hosted
version: "1.4.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
socks5_proxy:
dependency: "direct main"
description:
path: "."
ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
url: "https://github.com/LacticWhale/socks_dart"
source: git
version: "2.1.0"
socks_socket:
dependency: "direct main"
description:
path: "."
ref: e6232c53c1595469931ababa878759a067c02e94
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
url: "https://github.com/sneurlax/socks_socket"
source: git
version: "1.1.1"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
url: "https://pub.dev"
source: hosted
version: "4.2.3"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
sqflite:
dependency: "direct main"
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: "direct main"
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: "direct main"
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: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
torch_dart:
dependency: "direct main"
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: "direct main"
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: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
watcher:
dependency: "direct overridden"
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: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
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"
sdks:
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.27.0"
+1 -1
View File
@@ -42,7 +42,7 @@ dependencies:
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2 ref: 59fdf29d72068e0522a96a8953ed7272833a9f57
sqflite: ^2.4.1 sqflite: ^2.4.1
sqflite_common_ffi: ^2.3.4+4 sqflite_common_ffi: ^2.3.4+4
bech32: bech32:
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
+20 -5
View File
@@ -78,13 +78,13 @@ void main() {
const recipient = "0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6"; const recipient = "0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6";
const contract = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; const contract = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
test("builds a plain native transfer on mainnet", () { test("includes the mainnet chainId on a plain native transfer", () {
final uri = ERC681URI(chainId: 1, address: recipient, amount: "", contractAddress: null); final uri = ERC681URI(chainId: 1, address: recipient, amount: "", contractAddress: null);
expect(uri.toString(), "ethereum:$recipient"); expect(uri.toString(), "ethereum:$recipient@1");
}); });
test("appends the chainId for non-mainnet native transfers", () { test("appends the chainId for native transfers", () {
final uri = ERC681URI(chainId: 137, address: recipient, amount: "", contractAddress: null); final uri = ERC681URI(chainId: 137, address: recipient, amount: "", contractAddress: null);
expect(uri.toString(), "ethereum:$recipient@137"); expect(uri.toString(), "ethereum:$recipient@137");
@@ -93,7 +93,22 @@ void main() {
test("emits the native amount in ERC-681 scientific notation", () { test("emits the native amount in ERC-681 scientific notation", () {
final uri = ERC681URI(chainId: 1, address: recipient, amount: "1", contractAddress: null); final uri = ERC681URI(chainId: 1, address: recipient, amount: "1", contractAddress: null);
expect(uri.toString(), "ethereum:$recipient?value=1.0e18"); expect(uri.toString(), "ethereum:$recipient@1?value=1.0e18");
});
test("includes the mainnet chainId in the transfer form", () {
final uri = ERC681URI(
chainId: 1,
address: recipient,
amount: "200.172148",
contractAddress: contract,
tokenDecimals: 6,
);
expect(
uri.toString(),
"ethereum:$contract@1/transfer?address=$recipient&amount=200.172148",
);
}); });
test("builds the transfer form for a token", () { test("builds the transfer form for a token", () {
@@ -189,7 +204,7 @@ void main() {
rawTokenAmount: "1000000", rawTokenAmount: "1000000",
); );
expect(uri.toString(), "ethereum:$contract/transfer?address=$recipient&uint256=1000000"); expect(uri.toString(), "ethereum:$contract@1/transfer?address=$recipient&uint256=1000000");
}); });
test("parses a scientific notation value", () { test("parses a scientific notation value", () {
@@ -0,0 +1,365 @@
import "dart:io";
import "package:cw_core/cake_hive.dart";
import "package:cw_core/db/sqlite.dart";
import "package:cw_core/erc20_token.dart" as erc20_sql;
import "package:cw_core/erc20_token_legacy.dart" as erc20_legacy;
import "package:cw_core/root_dir.dart";
import "package:cw_core/spl_token.dart" as spl_sql;
import "package:cw_core/spl_token_legacy.dart" as spl_legacy;
import "package:cw_core/tron_token.dart" as tron_sql;
import "package:cw_core/tron_token_legacy.dart" as tron_legacy;
import "package:cw_core/wallet_type.dart";
import "package:flutter_test/flutter_test.dart";
import "package:path_provider_platform_interface/path_provider_platform_interface.dart";
import "package:sqflite_common_ffi/sqflite_ffi.dart";
// Faking the documents dir keeps getAppDir() off the platform channel, so the
// test runs with a plain `flutter test` on any host and in CI.
class _FakePathProviderPlatform extends PathProviderPlatform {
_FakePathProviderPlatform(this.root);
final String root;
@override
Future<String?> getApplicationDocumentsPath() async => root;
@override
Future<String?> getApplicationSupportPath() async => root;
}
Future<void> main() async {
final dataRoot = Directory("./test/data/token_migration");
Future<void> insertWalletInfoRow(String name, WalletType type) async {
await db!.insert("WalletInfo", {
"id": "${walletTypeToString(type).toLowerCase()}_$name",
"name": name,
"type": type.index,
"isRecovery": 0,
"restoreHeight": 0,
"timestamp": 0,
"dirPath": "",
"path": "",
"address": "",
"showIntroCakePayCard": 0,
"walletInfoDerivationInfoId": 0,
"isNonSeedWallet": 0,
"sortOrder": 0,
"receiveInfoboxDismissed": 0,
"showCombinedBalance": 1,
});
}
group(
"token sqlite migration",
() {
setUpAll(() async {
if (dataRoot.existsSync()) {
dataRoot.deleteSync(recursive: true);
}
dataRoot.createSync(recursive: true);
PathProviderPlatform.instance = _FakePathProviderPlatform(dataRoot.absolute.path);
// On linux getAppDir() appends /cake_wallet to the documents dir and picks the
// first existing candidate, so create it up front to keep CI on the faked path.
Directory("${dataRoot.path}/cake_wallet").createSync(recursive: true);
sqfliteFfiInit();
databaseFactory = databaseFactoryFfi;
await initDb();
// Everything must share the dir initDb resolved so boxExists finds the boxes
final appDir = await getAppDir();
CakeHive.init(appDir.path);
if (!CakeHive.isAdapterRegistered(erc20_legacy.Erc20Token.typeId)) {
CakeHive.registerAdapter(erc20_legacy.Erc20TokenAdapter());
}
if (!CakeHive.isAdapterRegistered(spl_legacy.SPLToken.typeId)) {
CakeHive.registerAdapter(spl_legacy.SPLTokenAdapter());
}
if (!CakeHive.isAdapterRegistered(tron_legacy.TronToken.typeId)) {
CakeHive.registerAdapter(tron_legacy.TronTokenAdapter());
}
// Two ethereum wallets whose names sanitize to the same box name, plus sol and tron
await insertWalletInfoRow("My Wallet", WalletType.ethereum);
await insertWalletInfoRow("My_Wallet", WalletType.ethereum);
await insertWalletInfoRow("sol wallet", WalletType.solana);
await insertWalletInfoRow("tron1", WalletType.tron);
// Legacy global box shared by every ethereum wallet in the pre per-wallet era
final globalBox = await CakeHive.openBox<erc20_legacy.Erc20Token>("Erc20Tokens");
await globalBox.put(
"0xGlobalTokenAAA",
erc20_legacy.Erc20Token(
name: "Global Legacy",
symbol: "GLB",
contractAddress: "0xGlobalTokenAAA",
decimal: 18,
enabled: true,
),
);
// Per-wallet ethereum box with a duplicate contract in two casings and a disabled token
final ethBox =
await CakeHive.openBox<erc20_legacy.Erc20Token>("My_Wallet_EthereumErc20Tokens");
await ethBox.put(
"0xDupCASE01",
erc20_legacy.Erc20Token(
name: "Dup Token",
symbol: "DUP",
contractAddress: "0xDupCASE01",
decimal: 18,
enabled: false,
iconPath: "",
),
);
await ethBox.put(
"0xdupcase01",
erc20_legacy.Erc20Token(
name: "Dup Token",
symbol: "DUP",
contractAddress: "0xdupcase01",
decimal: 18,
enabled: true,
iconPath: "assets/images/dup.png",
),
);
await ethBox.put(
"0xkeepmedisabled",
erc20_legacy.Erc20Token(
name: "Keep Me",
symbol: "KEEP",
contractAddress: "0xkeepmedisabled",
decimal: 6,
enabled: false,
),
);
// A polygon chain box for the same wallet name
final polyBox =
await CakeHive.openBox<erc20_legacy.Erc20Token>("My_Wallet_PolygonErc20Tokens");
await polyBox.put(
"0xpolytoken01",
erc20_legacy.Erc20Token(
name: "Poly Token",
symbol: "PLY",
contractAddress: "0xpolytoken01",
decimal: 18,
enabled: true,
tag: "POL",
),
);
// SPL and Tron boxes, base58 keys must keep their exact casing
final splBox = await CakeHive.openBox<spl_legacy.SPLToken>("sol_wallet_SPLTokens");
await splBox.put(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
spl_legacy.SPLToken(
name: "USD Coin",
symbol: "USDC",
mintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
decimal: 6,
mint: "usdc",
enabled: false,
),
);
final tronBox = await CakeHive.openBox<tron_legacy.TronToken>("tron1_TronTokens");
await tronBox.put(
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
tron_legacy.TronToken(
name: "Tether USD",
symbol: "USDT",
contractAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
decimal: 6,
enabled: true,
),
);
await globalBox.close();
await ethBox.close();
await polyBox.close();
await splBox.close();
await tronBox.close();
});
tearDownAll(() async {
await db?.close();
db = null;
if (dataRoot.existsSync()) {
dataRoot.deleteSync(recursive: true);
}
});
test("migrates every token box into sqlite", () async {
await erc20_legacy.performErc20TokenHiveMigration();
await spl_legacy.performSplTokenHiveMigration();
await tron_legacy.performTronTokenHiveMigration();
// Both ethereum wallets share the sanitized box name, so each gets the rows
for (final walletName in ["My Wallet", "My_Wallet"]) {
final ethTokens = await erc20_sql.Erc20Token.getAllForWallet(walletName, 1);
final addresses = ethTokens.map((t) => t.contractAddress).toSet();
expect(
addresses,
{"0xglobaltokenaaa", "0xdupcase01", "0xkeepmedisabled"},
reason: "wallet $walletName should have the global, merged dup and disabled tokens",
);
final dup = ethTokens.firstWhere((t) => t.contractAddress == "0xdupcase01");
expect(dup.enabled, true, reason: "dup merge ORs the enabled flags");
expect(dup.iconPath, "assets/images/dup.png", reason: "dup merge prefers non-empty icon");
final keep = ethTokens.firstWhere((t) => t.contractAddress == "0xkeepmedisabled");
expect(keep.enabled, false, reason: "disabled toggle must survive the migration");
final polyTokens = await erc20_sql.Erc20Token.getAllForWallet(walletName, 137);
expect(polyTokens.map((t) => t.contractAddress), ["0xpolytoken01"]);
}
final splTokens = await spl_sql.SPLToken.getAllForWallet("sol wallet");
expect(splTokens.length, 1);
expect(
splTokens.first.mintAddress,
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
reason: "mint address casing must be preserved",
);
expect(splTokens.first.enabled, false);
final tronTokens = await tron_sql.TronToken.getAllForWallet("tron1");
expect(tronTokens.length, 1);
expect(
tronTokens.first.contractAddress,
"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
reason: "tron contract address casing must be preserved",
);
// Drained boxes are removed from disk
expect(await CakeHive.boxExists("Erc20Tokens"), false);
expect(await CakeHive.boxExists("My_Wallet_EthereumErc20Tokens"), false);
expect(await CakeHive.boxExists("My_Wallet_PolygonErc20Tokens"), false);
expect(await CakeHive.boxExists("sol_wallet_SPLTokens"), false);
expect(await CakeHive.boxExists("tron1_TronTokens"), false);
});
test("re-running the migrations is a no-op", () async {
final before = (await erc20_sql.Erc20Token.selectList("", [])).length +
(await spl_sql.SPLToken.selectList("", [])).length +
(await tron_sql.TronToken.selectList("", [])).length;
await erc20_legacy.performErc20TokenHiveMigration();
await spl_legacy.performSplTokenHiveMigration();
await tron_legacy.performTronTokenHiveMigration();
final after = (await erc20_sql.Erc20Token.selectList("", [])).length +
(await spl_sql.SPLToken.selectList("", [])).length +
(await tron_sql.TronToken.selectList("", [])).length;
expect(after, before);
});
test("an interrupted migration re-drains over existing rows without duplicating", () async {
// Simulates a run that inserted rows into sqlite but died before the box was
// emptied: the box reappears holding a token that already has a row (USDC,
// re-enabled in the box copy) plus one the interrupted run never reached (BONK)
final box = await CakeHive.openBox<spl_legacy.SPLToken>("sol_wallet_SPLTokens");
await box.put(
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
spl_legacy.SPLToken(
name: "USD Coin",
symbol: "USDC",
mintAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
decimal: 6,
mint: "usdc",
enabled: true,
),
);
await box.put(
"DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
spl_legacy.SPLToken(
name: "Bonk",
symbol: "BONK",
mintAddress: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
decimal: 5,
mint: "bonk",
enabled: true,
),
);
await box.close();
await spl_legacy.performSplTokenHiveMigration();
final tokens = await spl_sql.SPLToken.getAllForWallet("sol wallet");
expect(tokens.length, 2, reason: "the re-drain must not duplicate the existing USDC row");
final usdc = tokens.firstWhere((t) => t.symbol == "USDC");
expect(usdc.enabled, true, reason: "the box copy wins over the stale row on re-drain");
expect(await CakeHive.boxExists("sol_wallet_SPLTokens"), false);
});
test("save without wallet context throws", () {
final token = erc20_sql.Erc20Token(
name: "No Context",
symbol: "NOC",
contractAddress: "0xnocontext",
decimal: 18,
);
expect(token.save, throwsStateError);
});
test("seeding preserves the enabled toggle without clobbering", () async {
// Same shape addInitialTokens uses: read existing, copyWith preserving enabled
final existing =
await erc20_sql.Erc20Token.getByContract("My Wallet", 1, "0xKEEPMEDISABLED");
expect(existing, isNotNull);
expect(existing!.enabled, false);
final refreshedDefault = erc20_sql.Erc20Token(
name: "Keep Me Renamed By Defaults",
symbol: "KEEP",
contractAddress: "0xkeepmedisabled",
decimal: 6,
enabled: true,
);
final toSave = erc20_sql.Erc20Token.copyWith(
refreshedDefault,
enabled: existing.enabled,
walletName: "My Wallet",
chainId: 1,
);
await toSave.save();
final reloaded =
await erc20_sql.Erc20Token.getByContract("My Wallet", 1, "0xkeepmedisabled");
expect(reloaded!.enabled, false, reason: "metadata refresh must not re-enable the token");
expect(reloaded.name, "Keep Me Renamed By Defaults");
final rowCount = (await erc20_sql.Erc20Token.getAllForWallet("My Wallet", 1)).length;
expect(rowCount, 3, reason: "upsert must replace, not duplicate");
});
test("rename moves rows and delete removes them", () async {
await erc20_sql.Erc20Token.renameWallet("My Wallet", "Renamed Wallet");
expect(await erc20_sql.Erc20Token.getAllForWallet("My Wallet", 1), isEmpty);
expect((await erc20_sql.Erc20Token.getAllForWallet("Renamed Wallet", 1)).length, 3);
expect((await erc20_sql.Erc20Token.getAllForWallet("Renamed Wallet", 137)).length, 1);
await erc20_sql.Erc20Token.deleteAllForWallet("My_Wallet");
expect(await erc20_sql.Erc20Token.getAllForWallet("My_Wallet", 1), isEmpty);
expect(await erc20_sql.Erc20Token.getAllForWallet("My_Wallet", 137), isEmpty);
// Rename into a name that has orphaned rows must not trip the unique index
await erc20_sql.Erc20Token.renameWallet("Renamed Wallet", "My_Wallet");
expect((await erc20_sql.Erc20Token.getAllForWallet("My_Wallet", 1)).length, 3);
});
},
);
}
+8
View File
@@ -3,6 +3,8 @@ import "package:analysis_server_plugin/registry.dart";
import "package:cw_custom_lints/http_force_proxy/http_force_proxy_rule.dart"; import "package:cw_custom_lints/http_force_proxy/http_force_proxy_rule.dart";
import "package:cw_custom_lints/modal_top_bar_semantics/modal_top_bar_semantics_rule.dart"; import "package:cw_custom_lints/modal_top_bar_semantics/modal_top_bar_semantics_rule.dart";
import "package:cw_custom_lints/modern_button_semantics/modern_button_semantics_rule.dart"; import "package:cw_custom_lints/modern_button_semantics/modern_button_semantics_rule.dart";
import "package:cw_custom_lints/money_amount_to_string/money_amount_to_string_fix.dart";
import "package:cw_custom_lints/money_amount_to_string/money_amount_to_string_rule.dart";
import "package:cw_custom_lints/print_verbose/print_verbose_fix.dart"; import "package:cw_custom_lints/print_verbose/print_verbose_fix.dart";
import "package:cw_custom_lints/print_verbose/print_verbose_rule.dart"; import "package:cw_custom_lints/print_verbose/print_verbose_rule.dart";
import "package:cw_custom_lints/restricted_imports/restricted_imports_rule.dart"; import "package:cw_custom_lints/restricted_imports/restricted_imports_rule.dart";
@@ -20,7 +22,13 @@ class CwCustomLintsPlugin extends Plugin {
registry.registerWarningRule(HttpForceProxyRule()); registry.registerWarningRule(HttpForceProxyRule());
registry.registerWarningRule(ModernButtonSemanticsRule()); registry.registerWarningRule(ModernButtonSemanticsRule());
registry.registerWarningRule(ModalTopBarSemanticsRule()); registry.registerWarningRule(ModalTopBarSemanticsRule());
registry.registerWarningRule(MoneyAmountToStringRule());
registry.registerFixForRule(PrintVerboseRule.code, ReplaceWithPrintV.new); registry.registerFixForRule(PrintVerboseRule.code, ReplaceWithPrintV.new);
registry.registerFixForRule(MoneyAmountToStringRule.code, ReplaceWithMoneyToString.new);
registry.registerFixForRule(
MoneyAmountToStringRule.code,
ReplaceWithMoneyToStringInBaseUnit.new,
);
} }
} }
@@ -0,0 +1,66 @@
import "package:analysis_server_plugin/edit/dart/correction_producer.dart";
import "package:analysis_server_plugin/edit/dart/dart_fix_kind_priority.dart";
import "package:analyzer/dart/ast/ast.dart";
import "package:analyzer_plugin/utilities/change_builder/change_builder_core.dart";
import "package:analyzer_plugin/utilities/fixes/fixes.dart";
import "package:analyzer_plugin/utilities/range_factory.dart";
import "package:cw_custom_lints/money_amount_to_string/money_amount_to_string_rule.dart";
abstract class _ReplaceMoneyAmountToString extends ResolvedCorrectionProducer {
_ReplaceMoneyAmountToString({required super.context});
String get replacement;
@override
CorrectionApplicability get applicability => CorrectionApplicability.singleLocation;
@override
Future<void> compute(ChangeBuilder builder) async {
final invocation = node.thisOrAncestorOfType<MethodInvocation>();
if (invocation == null) {
return;
}
final amount = moneyAmountReference(invocation.realTarget);
if (amount == null) {
return;
}
await builder.addDartFileEdit(
file,
(builder) => builder.addSimpleReplacement(range.startEnd(amount, invocation), replacement),
);
}
}
class ReplaceWithMoneyToString extends _ReplaceMoneyAmountToString {
ReplaceWithMoneyToString({required super.context});
static const _fixKind = FixKind(
"dart.fix.replaceWithMoneyToString",
DartFixKindPriority.standard,
"Replace with toString() for a decimal amount",
);
@override
FixKind get fixKind => _fixKind;
@override
String get replacement => "toString()";
}
class ReplaceWithMoneyToStringInBaseUnit extends _ReplaceMoneyAmountToString {
ReplaceWithMoneyToStringInBaseUnit({required super.context});
static const _fixKind = FixKind(
"dart.fix.replaceWithMoneyToStringInBaseUnit",
DartFixKindPriority.standard,
"Replace with toStringWithPrecision(useBaseUnit: true) for base units",
);
@override
FixKind get fixKind => _fixKind;
@override
String get replacement => "toStringWithPrecision(useBaseUnit: true)";
}
@@ -0,0 +1,85 @@
import "package:analyzer/analysis_rule/analysis_rule.dart";
import "package:analyzer/analysis_rule/rule_context.dart";
import "package:analyzer/analysis_rule/rule_visitor_registry.dart";
import "package:analyzer/dart/ast/ast.dart";
import "package:analyzer/dart/ast/visitor.dart";
import "package:analyzer/dart/element/element.dart";
import "package:analyzer/error/error.dart";
class MoneyAmountToStringRule extends AnalysisRule {
MoneyAmountToStringRule()
: super(
name: "no_money_amount_to_string",
description:
"Money.amount is the internal BigInt, so stringifying it drops the decimal point.",
);
static const LintCode code = LintCode(
"no_money_amount_to_string",
"This stringifies the internal BigInt of Money instead of the Money itself, so the decimal point is dropped and the amount reads far larger than it is.",
correctionMessage:
"Call toString() on the Money for a decimal amount, or toStringWithPrecision(useBaseUnit: true) for base units.",
severity: DiagnosticSeverity.WARNING,
);
@override
LintCode get diagnosticCode => code;
@override
void registerNodeProcessors(RuleVisitorRegistry registry, RuleContext context) =>
registry.addMethodInvocation(this, _Visitor(this));
}
SimpleIdentifier? moneyAmountReference(Expression? expression) {
final identifier = switch (expression) {
PrefixedIdentifier() => expression.identifier,
PropertyAccess() => expression.propertyName,
SimpleIdentifier() => expression,
_ => null,
};
if (identifier == null || identifier.name != "amount") {
return null;
}
return _isDeclaredByMoney(identifier.element) ? identifier : null;
}
bool _isDeclaredByMoney(Element? element) {
if (element == null) {
return false;
}
final enclosing = element.enclosingElement;
if (enclosing is! InterfaceElement || enclosing.name != "Money") {
return false;
}
final libraryUri = element.library?.uri;
if (libraryUri == null || libraryUri.scheme != "package") {
return false;
}
final segments = libraryUri.pathSegments;
return segments.isNotEmpty && segments.first == "cw_core";
}
class _Visitor extends SimpleAstVisitor<void> {
_Visitor(this.rule);
final AnalysisRule rule;
@override
void visitMethodInvocation(MethodInvocation node) {
if (node.methodName.name != "toString" || node.argumentList.arguments.isNotEmpty) {
return;
}
if (moneyAmountReference(node.realTarget) == null) {
return;
}
rule.reportAtNode(node);
}
}
-2
View File
@@ -22,8 +22,6 @@ migrate_working_dir/
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/ **/doc/api/
.dart_tool/ .dart_tool/
.packages .packages
-881
View File
@@ -1,881 +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: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
url: "https://pub.dev"
source: hosted
version: "93.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
url: "https://pub.dev"
source: hosted
version: "10.0.1"
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: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
url: "https://pub.dev"
source: hosted
version: "4.0.6"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
url: "https://pub.dev"
source: hosted
version: "2.15.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: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56"
url: "https://pub.dev"
source: hosted
version: "8.12.6"
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: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
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"
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: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
url: "https://pub.dev"
source: hosted
version: "3.1.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: b7803707faeec4ce3c1b0c2274906504b796e3b70ad573577e72333bd1c9b3ba
url: "https://pub.dev"
source: hosted
version: "20.1.1"
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"
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: "direct dev"
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
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: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.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: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
mobx_codegen:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f"
url: "https://pub.dev"
source: hosted
version: "2.7.7"
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: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
url: "https://pub.dev"
source: hosted
version: "4.2.3"
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: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
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: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
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.10.0 <4.0.0"
flutter: ">=3.24.0"
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
-2
View File
@@ -22,8 +22,6 @@ migrate_working_dir/
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/ **/doc/api/
.dart_tool/ .dart_tool/
.flutter-plugins .flutter-plugins
+2 -2
View File
@@ -25,7 +25,7 @@ dependencies:
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2 ref: 59fdf29d72068e0522a96a8953ed7272833a9f57
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -40,7 +40,7 @@ dependency_overrides:
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v11 ref: bda0e2904effa799ec835b4578cc3fc5d31caa78
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
-2
View File
@@ -22,8 +22,6 @@ migrate_working_dir/
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/ **/doc/api/
.dart_tool/ .dart_tool/
.packages .packages
+5
View File
@@ -0,0 +1,5 @@
targets:
$default:
sources:
exclude:
- "**/.secrets.g.dart"
+6
View File
@@ -1,5 +1,11 @@
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
class EVMChainMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'EVM mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}
class EVMChainTransactionCreationException implements Exception { class EVMChainTransactionCreationException implements Exception {
final String exceptionMessage; final String exceptionMessage;
+48 -209
View File
@@ -5,7 +5,6 @@ import 'dart:typed_data';
import 'package:bip32/bip32.dart' as bip32; import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/amount/money.dart'; import 'package:cw_core/amount/money.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/erc20_token.dart';
@@ -38,7 +37,6 @@ import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart'; import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart'; import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
import 'package:hex/hex.dart'; import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:web3dart/crypto.dart'; import 'package:web3dart/crypto.dart';
@@ -109,10 +107,6 @@ abstract class EVMChainWalletBase
this.walletInfo = walletInfo; this.walletInfo = walletInfo;
transactionHistory = setUpTransactionHistory(walletInfo, password, encryptionFileUtils); transactionHistory = setUpTransactionHistory(walletInfo, password, encryptionFileUtils);
if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
CakeHive.registerAdapter(Erc20TokenAdapter());
}
sharedPrefs.complete(SharedPreferences.getInstance()); sharedPrefs.complete(SharedPreferences.getInstance());
} }
@@ -121,9 +115,7 @@ abstract class EVMChainWalletBase
final String _password; final String _password;
final EncryptionFileUtils encryptionFileUtils; final EncryptionFileUtils encryptionFileUtils;
late final Box<Erc20Token> erc20TokensBox; List<Erc20Token> _erc20Tokens = [];
late Box<Erc20Token> evmChainErc20TokensBox;
late final Credentials _evmChainPrivateKey; late final Credentials _evmChainPrivateKey;
@@ -217,8 +209,8 @@ abstract class EVMChainWalletBase
// Automatically connect to node for the selected chain // Automatically connect to node for the selected chain
await connectToNode(node: node); await connectToNode(node: node);
// Reload ERC20 tokens box for the new chain // Reload ERC20 tokens for the new chain
await initErc20TokensBox(); await initErc20Tokens();
// Reload transaction history from the new chain's file // Reload transaction history from the new chain's file
await transactionHistory.init(); await transactionHistory.init();
@@ -228,179 +220,52 @@ abstract class EVMChainWalletBase
await startSync(); await startSync();
} }
void addInitialTokens() { Future<void> addInitialTokens() async {
final initialErc20Tokens = EVMChainDefaultTokens.getDefaultTokensByChainId(selectedChainId); final initialErc20Tokens = EVMChainDefaultTokens.getDefaultTokensByChainId(selectedChainId);
for (final token in initialErc20Tokens) { for (final token in initialErc20Tokens) {
if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) { final existingToken = _findCachedToken(token.contractAddress);
evmChainErc20TokensBox.put(token.contractAddress, token);
} else { final newToken = Erc20Token.copyWith(
// update existing token token,
final existingToken = evmChainErc20TokensBox.get(token.contractAddress); enabled: existingToken?.enabled ?? token.enabled,
evmChainErc20TokensBox.put( walletName: walletInfo.name,
token.contractAddress, chainId: selectedChainId,
Erc20Token.copyWith(token, enabled: existingToken!.enabled), );
);
} await newToken.save();
_upsertCachedToken(newToken);
} }
} }
List<String> get getDefaultTokenContractAddresses => List<String> get getDefaultTokenContractAddresses =>
EVMChainDefaultTokens.getDefaultTokenAddresses(selectedChainId); EVMChainDefaultTokens.getDefaultTokenAddresses(selectedChainId);
Future<void> initErc20TokensBox() async { Future<void> initErc20Tokens() async {
// Migration for old WalletType.ethereum wallets: _erc20Tokens = await Erc20Token.getAllForWallet(walletInfo.name, selectedChainId);
// Old wallets used a global erc20TokensBox (shared across all wallets).
// New system uses wallet-specific, chain-specific boxes.
// This checks if migration is needed and runs it once.
if (walletInfo.type == WalletType.ethereum) {
try {
// Try to access erc20TokensBox - if it exists, migration already ran
final _ = erc20TokensBox;
// Migration done, proceed with normal chain-specific logic below
} catch (_) {
// erc20TokensBox doesn't exist yet, run migration from global box
await _initEthereumErc20TokensBox();
await _normalizeEvmChainErc20TokensBoxKeys();
return;
}
}
final chainId = selectedChainId; await addInitialTokens();
final boxName = EVMChainUtils.getErc20TokensBoxName(walletInfo.name, chainId);
// Close existing box if it's already open (for chain switching)
try {
if (evmChainErc20TokensBox.isOpen) {
await evmChainErc20TokensBox.close();
}
} catch (_) {
// Box might not be initialized yet, ignore
}
// Check if box is already open, if so use it, otherwise open it
if (CakeHive.isBoxOpen(boxName)) {
evmChainErc20TokensBox = CakeHive.box<Erc20Token>(boxName);
} else {
evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
}
await _normalizeEvmChainErc20TokensBoxKeys();
addInitialTokens();
} }
/// Ethereum-specific initialization with backward compatibility Erc20Token? _findCachedToken(String contractAddress) {
Future<void> _initEthereumErc20TokensBox() async { final lowerAddress = contractAddress.toLowerCase();
// Opens a box specific to this wallet
evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(
"${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}",
);
erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName); for (final token in _erc20Tokens) {
if (token.contractAddress.toLowerCase() == lowerAddress) return token;
if (erc20TokensBox.isEmpty) {
if (evmChainErc20TokensBox.isEmpty) addInitialTokens();
return;
} }
return null;
}
final allValues = erc20TokensBox.values.toList(); void _upsertCachedToken(Erc20Token token) {
final lowerAddress = token.contractAddress.toLowerCase();
// Clear and delete the old token box _erc20Tokens.removeWhere((t) => t.contractAddress.toLowerCase() == lowerAddress);
await erc20TokensBox.clear(); _erc20Tokens.add(token);
await erc20TokensBox.deleteFromDisk();
// Add all the previous tokens with configs to the new box
await evmChainErc20TokensBox.addAll(allValues);
} }
String getTransactionHistoryFileName() => String getTransactionHistoryFileName() =>
EVMChainUtils.getTransactionHistoryFileName(selectedChainId); EVMChainUtils.getTransactionHistoryFileName(selectedChainId);
/// Ensures all ERC20 token entries use lowercase contract addresses as
/// their Hive keys to avoid duplicates caused by case differences.
Future<void> _normalizeEvmChainErc20TokensBoxKeys() async {
if (!evmChainErc20TokensBox.isOpen) return;
final prefs = await sharedPrefs.future;
final migrationKey = 'erc20_box_normalized_${walletInfo.name}_$selectedChainId';
if (prefs.getBool(migrationKey) ?? false) return;
final box = evmChainErc20TokensBox;
final keys = box.keys.toList();
if (keys.isEmpty) {
await prefs.setBool(migrationKey, true);
return;
}
final Map<String, Erc20Token> normalizedTokens = {};
var needsRewrite = false;
for (final key in keys) {
final token = box.get(key);
if (token == null) {
needsRewrite = true;
continue;
}
final lowerKey = key is String ? key.toLowerCase() : token.contractAddress.toLowerCase();
if (key is int) needsRewrite = true;
final Erc20Token normalizedToken =
token.contractAddress == token.contractAddress.toLowerCase()
? token
: Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: token.contractAddress.toLowerCase(),
decimal: token.decimal,
enabled: token.enabled,
iconPath: token.iconPath,
tag: token.tag,
isPotentialScam: token.isPotentialScam,
);
if (!needsRewrite && (lowerKey != key || identical(normalizedToken, token) == false)) {
needsRewrite = true;
}
final existing = normalizedTokens[lowerKey];
if (existing == null) {
normalizedTokens[lowerKey] = normalizedToken;
continue;
}
final merged = Erc20Token(
name: normalizedToken.name,
symbol: normalizedToken.symbol,
contractAddress: lowerKey,
decimal: normalizedToken.decimal,
enabled: normalizedToken.enabled || existing.enabled,
iconPath: (normalizedToken.iconPath?.isNotEmpty ?? false)
? normalizedToken.iconPath
: existing.iconPath,
tag: normalizedToken.tag ?? existing.tag,
isPotentialScam: normalizedToken.isPotentialScam || existing.isPotentialScam,
);
normalizedTokens[lowerKey] = merged;
}
if (needsRewrite) {
await box.clear();
for (final entry in normalizedTokens.entries) {
await box.put(entry.key, entry.value);
}
}
await prefs.setBool(migrationKey, true);
}
Future<bool> checkIfScanProviderIsEnabled() async { Future<bool> checkIfScanProviderIsEnabled() async {
final key = EVMChainUtils.getScanProviderPreferenceKey(selectedChainId); final key = EVMChainUtils.getScanProviderPreferenceKey(selectedChainId);
return (await sharedPrefs.future).getBool(key) ?? true; return (await sharedPrefs.future).getBool(key) ?? true;
@@ -451,6 +316,8 @@ abstract class EVMChainWalletBase
tag: token.tag ?? EVMChainUtils.getDefaultTokenTag(selectedChainId), tag: token.tag ?? EVMChainUtils.getDefaultTokenTag(selectedChainId),
iconPath: iconPath, iconPath: iconPath,
isPotentialScam: token.isPotentialScam, isPotentialScam: token.isPotentialScam,
walletName: walletInfo.name,
chainId: selectedChainId,
); );
} }
@@ -499,7 +366,7 @@ abstract class EVMChainWalletBase
String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name'; String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
Future<void> init() async { Future<void> init() async {
await initErc20TokensBox(); await initErc20Tokens();
await walletAddresses.init(); await walletAddresses.init();
await transactionHistory.init(); await transactionHistory.init();
@@ -669,8 +536,6 @@ abstract class EVMChainWalletBase
Future<MoralisDiscoveryResult> discoverTokensFromMoralis() async { Future<MoralisDiscoveryResult> discoverTokensFromMoralis() async {
try { try {
if (!evmChainErc20TokensBox.isOpen) return MoralisDiscoveryResult.empty;
final address = walletAddresses.address; final address = walletAddresses.address;
if (address.isEmpty) return MoralisDiscoveryResult.empty; if (address.isEmpty) return MoralisDiscoveryResult.empty;
@@ -680,8 +545,7 @@ abstract class EVMChainWalletBase
if (walletTokens.isEmpty) return MoralisDiscoveryResult.empty; if (walletTokens.isEmpty) return MoralisDiscoveryResult.empty;
final existingTokenAddresses = { final existingTokenAddresses = {
for (final token in evmChainErc20TokensBox.values) for (final token in _erc20Tokens) token.contractAddress.toLowerCase(): token,
token.contractAddress.toLowerCase(): token,
}; };
final whitelistedTokenAddresses = final whitelistedTokenAddresses =
@@ -1419,32 +1283,21 @@ abstract class EVMChainWalletBase
} }
Future<void> _fetchErc20Balances() async { Future<void> _fetchErc20Balances() async {
// Check if box is open before accessing it
if (!evmChainErc20TokensBox.isOpen) {
return;
}
// First, clean up any tokens in balance map that don't belong to current chain // First, clean up any tokens in balance map that don't belong to current chain
// This handles tokens from previous chains that might still be in the balance map // This handles tokens from previous chains that might still be in the balance map
final tokensInBalance = balance.keys.whereType<Erc20Token>().toList(); final tokensInBalance = balance.keys.whereType<Erc20Token>().toList();
final tokensInBox = evmChainErc20TokensBox.values.toList(); final tokens = _erc20Tokens.toList();
final boxTokenAddresses = tokensInBox.map((t) => t.contractAddress.toLowerCase()).toSet(); final cachedTokenAddresses = tokens.map((t) => t.contractAddress.toLowerCase()).toSet();
for (var token in tokensInBalance) { for (var token in tokensInBalance) {
// Remove token if it's not in the current box or doesn't match current chain // Remove token if it's not in the current token list or doesn't match current chain
if (!boxTokenAddresses.contains(token.contractAddress.toLowerCase()) || if (!cachedTokenAddresses.contains(token.contractAddress.toLowerCase()) ||
!_isTokenMatchingChain(token)) { !_isTokenMatchingChain(token)) {
balance.remove(token); balance.remove(token);
} }
} }
// Get a snapshot of tokens from current box to avoid issues if box is closed during iteration
final tokens = tokensInBox;
for (var token in tokens) { for (var token in tokens) {
// Check if box is still open before operating on tokens
if (!evmChainErc20TokensBox.isOpen) break;
if (!_isTokenMatchingChain(token)) { if (!_isTokenMatchingChain(token)) {
printV('NOTEE!!!: Token ${token.title} is not matching the currency ${currency.title}'); printV('NOTEE!!!: Token ${token.title} is not matching the currency ${currency.title}');
try { try {
@@ -1532,15 +1385,7 @@ abstract class EVMChainWalletBase
@override @override
Future<void> updateTransactionsHistory() async => await _updateTransactions(); Future<void> updateTransactionsHistory() async => await _updateTransactions();
List<Erc20Token> get erc20Currencies { List<Erc20Token> get erc20Currencies => _erc20Tokens.toList();
try {
if (!evmChainErc20TokensBox.isOpen) return [];
return evmChainErc20TokensBox.values.toList();
} catch (_) {
return [];
}
}
Future<void> addErc20Token(Erc20Token token) async { Future<void> addErc20Token(Erc20Token token) async {
final isSuspicious = isTokenPropertiesSuspicious(token); final isSuspicious = isTokenPropertiesSuspicious(token);
@@ -1561,32 +1406,26 @@ abstract class EVMChainWalletBase
final newToken = createNewErc20TokenObject(token, iconPath); final newToken = createNewErc20TokenObject(token, iconPath);
if (newToken.enabled) { if (newToken.enabled) {
try { balance[newToken] = await _client.fetchERC20Balances(_evmChainPrivateKey.address, newToken);
balance[newToken] = await _client.fetchERC20Balances(_evmChainPrivateKey.address, newToken);
await evmChainErc20TokensBox.put(newToken.contractAddress, newToken); await newToken.save();
} on Exception catch (_) {
rethrow;
}
} else { } else {
await evmChainErc20TokensBox.put(newToken.contractAddress, newToken); await newToken.save();
balance.remove(newToken); balance.remove(newToken);
} }
_upsertCachedToken(newToken);
} }
Future<void> deleteErc20Token(Erc20Token token, {bool shouldUpdateBalance = true}) async { Future<void> deleteErc20Token(Erc20Token token, {bool shouldUpdateBalance = true}) async {
// Check if box is open before trying to delete try {
if (!evmChainErc20TokensBox.isOpen) { await Erc20Token.deleteForWallet(walletInfo.name, selectedChainId, token.contractAddress);
balance.remove(token); } catch (e) {
return; printV('Error deleting token: $e');
} }
try { _erc20Tokens
await token.delete(); .removeWhere((t) => t.contractAddress.toLowerCase() == token.contractAddress.toLowerCase());
} catch (e) {
// Token might be from a closed box, just remove from balance
printV('Error deleting token from box: $e');
}
balance.remove(token); balance.remove(token);
await removeTokenTransactionsInHistory(token); await removeTokenTransactionsInHistory(token);
+39 -19
View File
@@ -1,7 +1,8 @@
import 'dart:io'; import "dart:io";
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import "package:cw_core/erc20_token.dart";
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
@@ -11,6 +12,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:cw_evm/clients/evm_chain_client.dart'; import 'package:cw_evm/clients/evm_chain_client.dart';
import 'package:cw_evm/evm_chain_client_factory.dart'; import 'package:cw_evm/evm_chain_client_factory.dart';
import 'package:cw_evm/evm_chain_exceptions.dart';
import 'package:cw_evm/evm_chain_registry.dart'; import 'package:cw_evm/evm_chain_registry.dart';
import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
@@ -48,8 +50,8 @@ class EVMChainWalletService extends WalletService<
@override @override
WalletType getType() { WalletType getType() {
throw UnsupportedError( throw UnsupportedError(
'EVMChainWalletService is unified and does not have a single type. ' "EVMChainWalletService is unified and does not have a single type. "
'Use walletInfo.type instead.', "Use walletInfo.type instead.",
); );
} }
@@ -59,7 +61,7 @@ class EVMChainWalletService extends WalletService<
Future<void> saveBackup(String name, {WalletInfo? walletInfo}) async { Future<void> saveBackup(String name, {WalletInfo? walletInfo}) async {
final info = walletInfo ?? await _findWalletByName(name); final info = walletInfo ?? await _findWalletByName(name);
if (info == null) { if (info == null) {
throw Exception('Wallet not found: $name'); throw Exception("Wallet not found: $name");
} }
final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: info.type); final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: info.type);
@@ -75,7 +77,7 @@ class EVMChainWalletService extends WalletService<
Future<void> restoreWalletFilesFromBackup(String name) async { Future<void> restoreWalletFilesFromBackup(String name) async {
final walletInfo = await _findWalletByName(name); final walletInfo = await _findWalletByName(name);
if (walletInfo == null) { if (walletInfo == null) {
throw Exception('Wallet not found: $name'); throw Exception("Wallet not found: $name");
} }
final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: walletInfo.type); final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: walletInfo.type);
@@ -96,7 +98,7 @@ class EVMChainWalletService extends WalletService<
// Get chainId from wallet type // Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type); final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) { if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}'); throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
} }
final initialChainId = chainConfig.chainId; final initialChainId = chainConfig.chainId;
@@ -124,7 +126,7 @@ class EVMChainWalletService extends WalletService<
); );
await wallet.init(); await wallet.init();
wallet.addInitialTokens(); await wallet.addInitialTokens();
await wallet.save(); await wallet.save();
return wallet; return wallet;
} }
@@ -133,7 +135,7 @@ class EVMChainWalletService extends WalletService<
Future<EVMChainWallet> openWallet(String name, String password) async { Future<EVMChainWallet> openWallet(String name, String password) async {
final walletInfo = await _findWalletByName(name); final walletInfo = await _findWalletByName(name);
if (walletInfo == null) { if (walletInfo == null) {
throw Exception('Wallet not found'); throw Exception("Wallet not found");
} }
try { try {
@@ -145,7 +147,7 @@ class EVMChainWalletService extends WalletService<
); );
await wallet.init(); await wallet.init();
wallet.addInitialTokens(); await wallet.addInitialTokens();
await wallet.save(); await wallet.save();
await saveBackup(name); await saveBackup(name);
return wallet; return wallet;
@@ -160,7 +162,7 @@ class EVMChainWalletService extends WalletService<
); );
await wallet.init(); await wallet.init();
wallet.addInitialTokens(); await wallet.addInitialTokens();
await wallet.save(); await wallet.save();
return wallet; return wallet;
} }
@@ -172,7 +174,7 @@ class EVMChainWalletService extends WalletService<
final currentWalletInfo = await _findWalletByName(currentName); final currentWalletInfo = await _findWalletByName(currentName);
if (currentWalletInfo == null) { if (currentWalletInfo == null) {
throw Exception('Wallet not found'); throw Exception("Wallet not found");
} }
final type = currentWalletInfo.type; final type = currentWalletInfo.type;
@@ -184,6 +186,16 @@ class EVMChainWalletService extends WalletService<
currentWalletInfo.name = newName; currentWalletInfo.name = newName;
await currentWalletInfo.save(); await currentWalletInfo.save();
final oldNameStillUsed = (await _findWalletByName(currentName)) != null;
if (oldNameStillUsed) {
for (final token in await Erc20Token.selectList("walletName = ?", [currentName])) {
final copiedToken = Erc20Token.copyWith(token, walletName: newName);
await copiedToken.save();
}
} else {
await Erc20Token.renameWallet(currentName, newName);
}
final oldDir = Directory(p.join(await pathForWalletTypeDir(type: type), currentName)); final oldDir = Directory(p.join(await pathForWalletTypeDir(type: type), currentName));
if (oldDir.existsSync()) { if (oldDir.existsSync()) {
try { try {
@@ -199,12 +211,16 @@ class EVMChainWalletService extends WalletService<
EVMChainRestoreWalletFromSeedCredentials credentials, { EVMChainRestoreWalletFromSeedCredentials credentials, {
bool? isTestnet, bool? isTestnet,
}) async { }) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EVMChainMnemonicIsIncorrectException();
}
final walletInfo = credentials.walletInfo!; final walletInfo = credentials.walletInfo!;
// Get chainId from wallet type // Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type); final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) { if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}'); throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
} }
final initialChainId = chainConfig.chainId; final initialChainId = chainConfig.chainId;
@@ -230,7 +246,7 @@ class EVMChainWalletService extends WalletService<
); );
await wallet.init(); await wallet.init();
wallet.addInitialTokens(); await wallet.addInitialTokens();
await wallet.save(); await wallet.save();
return wallet; return wallet;
} }
@@ -245,7 +261,7 @@ class EVMChainWalletService extends WalletService<
// Get chainId from wallet type // Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type); final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) { if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}'); throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
} }
final initialChainId = chainConfig.chainId; final initialChainId = chainConfig.chainId;
@@ -270,7 +286,7 @@ class EVMChainWalletService extends WalletService<
); );
await wallet.init(); await wallet.init();
wallet.addInitialTokens(); await wallet.addInitialTokens();
await wallet.save(); await wallet.save();
return wallet; return wallet;
} }
@@ -284,7 +300,7 @@ class EVMChainWalletService extends WalletService<
// Get chainId from wallet type // Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type); final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) { if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}'); throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
} }
final initialChainId = chainConfig.chainId; final initialChainId = chainConfig.chainId;
@@ -308,7 +324,7 @@ class EVMChainWalletService extends WalletService<
); );
await wallet.init(); await wallet.init();
wallet.addInitialTokens(); await wallet.addInitialTokens();
await wallet.save(); await wallet.save();
return wallet; return wallet;
} }
@@ -327,11 +343,15 @@ class EVMChainWalletService extends WalletService<
Future<void> remove(String wallet) async { Future<void> remove(String wallet) async {
final walletInfo = await _findWalletByName(wallet); final walletInfo = await _findWalletByName(wallet);
if (walletInfo == null) { if (walletInfo == null) {
throw Exception('Wallet not found'); throw Exception("Wallet not found");
} }
File(await pathForWalletDir(name: wallet, type: walletInfo.type)).delete(recursive: true); File(await pathForWalletDir(name: wallet, type: walletInfo.type)).delete(recursive: true);
await WalletInfo.delete(walletInfo); await WalletInfo.delete(walletInfo);
final nameStillUsed = (await _findWalletByName(wallet)) != null;
if (!nameStillUsed) {
await Erc20Token.deleteAllForWallet(wallet);
}
} }
EVMChainWallet _createWalletInstance({ EVMChainWallet _createWalletInstance({
@@ -349,7 +369,7 @@ class EVMChainWalletService extends WalletService<
final chainConfig = _registry.getChainConfigByWalletType(walletType); final chainConfig = _registry.getChainConfigByWalletType(walletType);
if (chainConfig == null) { if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: $walletType'); throw Exception("Chain config not found for wallet type: $walletType");
} }
return EVMChainWallet( return EVMChainWallet(
+41 -73
View File
@@ -1,11 +1,9 @@
import 'package:cw_core/erc20_token.dart'; import "package:cw_evm/evm_chain_transaction_priority.dart";
import 'package:cw_evm/evm_chain_transaction_priority.dart'; import "package:web3dart/web3dart.dart" show EtherAmount, EtherUnit;
import 'package:web3dart/web3dart.dart' show EtherAmount, EtherUnit;
/// Utility class for chain-specific EVM chain operations /// Utility class for chain-specific EVM chain operations
class EVMChainUtils { class EVMChainUtils {
static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) { static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) => switch (chainId) {
return switch (chainId) {
1 => _ethereumPriorityFee(priority), 1 => _ethereumPriorityFee(priority),
137 => _polygonPriorityFee(priority), 137 => _polygonPriorityFee(priority),
8453 => _basePriorityFee(priority), 8453 => _basePriorityFee(priority),
@@ -13,14 +11,11 @@ class EVMChainUtils {
42161 => 0, // Arbitrum doesn't use priority fees 42161 => 0, // Arbitrum doesn't use priority fees
_ => _ethereumPriorityFee(priority), _ => _ethereumPriorityFee(priority),
}; };
}
static bool hasPriorityFee(int chainId) { static bool hasPriorityFee(int chainId) => switch (chainId) {
return switch (chainId) {
42161 => false, // Arbitrum doesn't use priority fees 42161 => false, // Arbitrum doesn't use priority fees
_ => true, _ => true,
}; };
}
static int computeBufferedMaxFeePerGasWei({ static int computeBufferedMaxFeePerGasWei({
required int? gasBaseFee, required int? gasBaseFee,
@@ -38,78 +33,53 @@ class EVMChainUtils {
return gasPrice + priorityFeeWei; return gasPrice + priorityFeeWei;
} }
static String getErc20TokensBoxName(String walletName, int chainId) { static String getTransactionHistoryFileName(int chainId) => switch (chainId) {
final sanitizedName = walletName.replaceAll(" ", "_"); 1 => "transactions.json", // Ethereum
137 => "polygon_transactions.json",
return switch (chainId) { 8453 => "base_transactions.json",
1 => "${sanitizedName}_${Erc20Token.ethereumBoxName}", 42161 => "arbitrum_transactions.json",
137 => "${sanitizedName}_${Erc20Token.polygonBoxName}", 56 => "bsc_transactions.json",
8453 => "${sanitizedName}_${Erc20Token.baseBoxName}", _ => "transactions_$chainId.json", // Generic format for other chains
42161 => "${sanitizedName}_${Erc20Token.arbitrumBoxName}",
56 => "${sanitizedName}_${Erc20Token.bscBoxName}",
_ => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
}; };
}
static String getTransactionHistoryFileName(int chainId) {
return switch (chainId) {
1 => 'transactions.json', // Ethereum
137 => 'polygon_transactions.json',
8453 => 'base_transactions.json',
42161 => 'arbitrum_transactions.json',
56 => 'bsc_transactions.json',
_ => 'transactions_$chainId.json', // Generic format for other chains
};
}
/// Get scan provider preference key for a wallet type /// Get scan provider preference key for a wallet type
static String getScanProviderPreferenceKey(int chainId) { static String getScanProviderPreferenceKey(int chainId) => switch (chainId) {
return switch (chainId) { 1 => "use_etherscan",
1 => 'use_etherscan', 137 => "use_polygonscan",
137 => 'use_polygonscan', 8453 => "use_basescan",
8453 => 'use_basescan', 42161 => "use_arbiscan",
42161 => 'use_arbiscan', 56 => "use_bscscan",
56 => 'use_bscscan', _ => "use_etherscan",
_ => 'use_etherscan',
}; };
}
static String getDefaultTokenTag(int chainId) { static String getDefaultTokenTag(int chainId) => switch (chainId) {
return switch (chainId) { 1 => "ETH",
1 => 'ETH', 137 => "POL",
137 => 'POL', 8453 => "BASE",
8453 => 'BASE', 42161 => "ARB",
42161 => 'ARB', 56 => "BSC",
56 => 'BSC', _ => "ETH",
_ => 'ETH',
}; };
}
static String getFeeCurrency(int chainId) { static String getFeeCurrency(int chainId) => switch (chainId) {
return switch (chainId) { 1 => "ETH",
1 => 'ETH', 137 => "POL",
137 => 'POL', 8453 => "ETH",
8453 => 'ETH', 42161 => "ETH",
42161 => 'ETH', 56 => "BNB",
56 => 'BNB', _ => "ETH",
_ => 'ETH',
}; };
}
static String getDefaultTokenSymbol(int chainId) { static String getDefaultTokenSymbol(int chainId) => switch (chainId) {
return switch (chainId) { 1 => "ETH",
1 => 'ETH', 137 => "POL",
137 => 'POL', 8453 => "BASE",
8453 => 'BASE', 42161 => "ARBITRUM",
42161 => 'ARBITRUM', 56 => "BSC",
56 => 'BSC', _ => "ETH",
_ => 'ETH',
}; };
}
static int _ethereumPriorityFee(EVMChainTransactionPriority priority) { static int _ethereumPriorityFee(EVMChainTransactionPriority priority) => EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
return EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
}
// Polygon priority fee calculation (minimum 25 gwei + additional based on priority) // Polygon priority fee calculation (minimum 25 gwei + additional based on priority)
static int _polygonPriorityFee(EVMChainTransactionPriority priority) { static int _polygonPriorityFee(EVMChainTransactionPriority priority) {
@@ -127,12 +97,10 @@ class EVMChainUtils {
return minPriorityFeeWei + additionalPriorityFee; return minPriorityFeeWei + additionalPriorityFee;
} }
static int _basePriorityFee(EVMChainTransactionPriority priority) { static int _basePriorityFee(EVMChainTransactionPriority priority) => switch (priority) {
return switch (priority) {
EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(), EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(),
EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(), EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(),
EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
_ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), _ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
}; };
}
} }
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
-2
View File
@@ -22,8 +22,6 @@ migrate_working_dir/
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/ **/doc/api/
.dart_tool/ .dart_tool/
.packages .packages
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml
+5
View File
@@ -0,0 +1,5 @@
targets:
$default:
sources:
exclude:
- "**/.secrets.g.dart"
-991
View File
@@ -1,991 +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: "8d7ff3948166b8ec5da0fbb5962000926b8e02f2ed9b3e51d1738905fbd4c98d"
url: "https://pub.dev"
source: hosted
version: "93.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: de7148ed2fcec579b19f122c1800933dfa028f6d9fd38a152b04b1516cec120b
url: "https://pub.dev"
source: hosted
version: "10.0.1"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: "1c296cd268f486cabcc3930e9b93a8133169305f18d722916e675959a88f6d2c"
url: "https://pub.dev"
source: hosted
version: "1.5.9"
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"
bip32:
dependency: "direct main"
description:
name: bip32
sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
bip39:
dependency: "direct main"
description:
name: bip39
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
url: "https://pub.dev"
source: hosted
version: "1.0.6"
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"
bs58check:
dependency: transitive
description:
name: bs58check
sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
url: "https://pub.dev"
source: hosted
version: "1.0.2"
build:
dependency: transitive
description:
name: build
sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10
url: "https://pub.dev"
source: hosted
version: "4.0.6"
build_config:
dependency: transitive
description:
name: build_config
sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71"
url: "https://pub.dev"
source: hosted
version: "1.3.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
url: "https://pub.dev"
source: hosted
version: "4.0.4"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6"
url: "https://pub.dev"
source: hosted
version: "2.15.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: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
url: "https://pub.dev"
source: hosted
version: "8.10.1"
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: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
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: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2"
url: "https://pub.dev"
source: hosted
version: "3.1.7"
decimal:
dependency: "direct main"
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
ed25519_hd_key:
dependency: "direct main"
description:
name: ed25519_hd_key
sha256: "31e191ec97492873067e46dc9cc0c7d55170559c83a478400feffa0627acaccf"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
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"
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"
fixnum_nanodart:
dependency: transitive
description:
name: fixnum_nanodart
sha256: "4b0132d11ecddc0d2ca64b6d7dee6726db432ed02cac1349d7532a08be5c54fc"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
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"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.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"
hex:
dependency: "direct main"
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
hive:
dependency: "direct dev"
description:
name: hive
sha256: "5184b6ea455689754578267c31c3b0b329cccf20a5f2bfdab6941c29c3d3c09a"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
http:
dependency: "direct main"
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
url: "https://pub.dev"
source: hosted
version: "1.4.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: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.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: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
libcrypto:
dependency: "direct main"
description:
name: libcrypto
sha256: "18a97db8d88147b0b60d2755f29b5e4944181c4c1a9f52bd1ecbea1b0a5aab03"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
mime:
dependency: transitive
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
mobx:
dependency: "direct main"
description:
name: mobx
sha256: "25ea9a3ee69243d578fb6288721d8eeb18550d0391c32f896d377d994154bff2"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
mobx_codegen:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: "6529bcae074838004b58ad01fbe4966be182a176aacfb723686985986ebac19f"
url: "https://pub.dev"
source: hosted
version: "2.7.7"
nanodart:
dependency: transitive
description:
name: nanodart
sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
nanoutil:
dependency: "direct main"
description:
path: "."
ref: c01a9c552917008d8fbc6b540db657031625b04f
resolved-ref: c01a9c552917008d8fbc6b540db657031625b04f
url: "https://github.com/perishllc/nanoutil.git"
source: git
version: "1.0.3"
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: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
url: "https://pub.dev"
source: hosted
version: "2.2.0"
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: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.17"
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"
pinenacl:
dependency: transitive
description:
name: pinenacl
sha256: "57e907beaacbc3c024a098910b6240758e899674de07d6949a67b52fd984cbdf"
url: "https://pub.dev"
source: hosted
version: "0.6.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: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
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: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
rational:
dependency: transitive
description:
name: rational
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
name: shelf
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
url: "https://pub.dev"
source: hosted
version: "1.4.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
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: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02
url: "https://pub.dev"
source: hosted
version: "4.2.3"
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: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
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: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "15.0.0"
watcher:
dependency: "direct overridden"
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: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
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"
sdks:
dart: ">=3.10.0 <4.0.0"
flutter: ">=3.27.0"
+1
View File
@@ -0,0 +1 @@
../pubspec_overrides.yaml

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