mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
Merge branch 'cake-tech:dev' into dev
This commit is contained in:
@@ -159,11 +159,6 @@ jobs:
|
||||
- name: Generate localization
|
||||
run: |
|
||||
dart run tool/generate_localization.dart
|
||||
- name: Workaround zcash
|
||||
run: |
|
||||
mkdir -p scripts/zcash_lib/assets/
|
||||
touch scripts/zcash_lib/assets/sapling-spend.params
|
||||
touch scripts/zcash_lib/assets/sapling-output.params
|
||||
- name: Build linux
|
||||
run: |
|
||||
flutter build linux --dart-define=hasDevOptions=true --release
|
||||
|
||||
+2
-1
@@ -226,4 +226,5 @@ scripts/android/yttrium
|
||||
scripts/bitbox_flutter
|
||||
scripts/reown_flutter
|
||||
|
||||
*.salive
|
||||
*.salive
|
||||
android/.kotlin
|
||||
|
||||
@@ -32,7 +32,6 @@ Cake Wallet includes support for several cryptocurrencies, including:
|
||||
* Nano (XNO)
|
||||
* Zano (ZANO)
|
||||
* Decred (DCR)
|
||||
* Wownero (WOW)
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
label: "Cake Wallet"
|
||||
-
|
||||
uri: zec.rocks:443
|
||||
- uri: zec.rocks:443
|
||||
is_default: true
|
||||
useSSL: true
|
||||
isEnabledForAutoSwitching: true
|
||||
label: "Zec.rocks"
|
||||
|
||||
@@ -634,10 +634,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1100,10 +1100,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1322,5 +1322,5 @@ packages:
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
sdks:
|
||||
dart: ">=3.10.0-0 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.29.0"
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
class ZcashHeight {
|
||||
// for i in $(seq 1 331); do echo "_Checkpoint($(date -j -f "%Y-%m-%d %H:%M:%S" "$(curl -s "https://mainnet.zcashexplorer.app/blocks/${i}0000" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}')" "+%s"), ${i}0000),"; done > points.txt
|
||||
static final List<_Checkpoint> _points = [
|
||||
_Checkpoint(1479114667, 10000),
|
||||
_Checkpoint(1480618460, 20000),
|
||||
_Checkpoint(1482129143, 30000),
|
||||
_Checkpoint(1483635610, 40000),
|
||||
_Checkpoint(1485145514, 50000),
|
||||
_Checkpoint(1486655742, 60000),
|
||||
_Checkpoint(1488163642, 70000),
|
||||
_Checkpoint(1489668244, 80000),
|
||||
_Checkpoint(1491175107, 90000),
|
||||
_Checkpoint(1492681094, 100000),
|
||||
_Checkpoint(1494185626, 110000),
|
||||
_Checkpoint(1495691260, 120000),
|
||||
_Checkpoint(1497196465, 130000),
|
||||
_Checkpoint(1498702428, 140000),
|
||||
_Checkpoint(1500209116, 150000),
|
||||
_Checkpoint(1501716823, 160000),
|
||||
_Checkpoint(1503222893, 170000),
|
||||
_Checkpoint(1504730866, 180000),
|
||||
_Checkpoint(1506236963, 190000),
|
||||
_Checkpoint(1507744233, 200000),
|
||||
_Checkpoint(1509254578, 210000),
|
||||
_Checkpoint(1510768449, 220000),
|
||||
_Checkpoint(1512275502, 230000),
|
||||
_Checkpoint(1513783951, 240000),
|
||||
_Checkpoint(1515292290, 250000),
|
||||
_Checkpoint(1516799219, 260000),
|
||||
_Checkpoint(1518306180, 270000),
|
||||
_Checkpoint(1519813817, 280000),
|
||||
_Checkpoint(1521319150, 290000),
|
||||
_Checkpoint(1522828107, 300000),
|
||||
_Checkpoint(1524336223, 310000),
|
||||
_Checkpoint(1525844954, 320000),
|
||||
_Checkpoint(1527354342, 330000),
|
||||
_Checkpoint(1528860301, 340000),
|
||||
_Checkpoint(1530365206, 350000),
|
||||
_Checkpoint(1531873693, 360000),
|
||||
_Checkpoint(1533380392, 370000),
|
||||
_Checkpoint(1534888873, 380000),
|
||||
_Checkpoint(1536393381, 390000),
|
||||
_Checkpoint(1537899117, 400000),
|
||||
_Checkpoint(1539406330, 410000),
|
||||
_Checkpoint(1540912214, 420000),
|
||||
_Checkpoint(1542422891, 430000),
|
||||
_Checkpoint(1543931111, 440000),
|
||||
_Checkpoint(1545437758, 450000),
|
||||
_Checkpoint(1546945529, 460000),
|
||||
_Checkpoint(1548450107, 470000),
|
||||
_Checkpoint(1549957767, 480000),
|
||||
_Checkpoint(1551466960, 490000),
|
||||
_Checkpoint(1552969806, 500000),
|
||||
_Checkpoint(1554477648, 510000),
|
||||
_Checkpoint(1555981451, 520000),
|
||||
_Checkpoint(1557489241, 530000),
|
||||
_Checkpoint(1558997282, 540000),
|
||||
_Checkpoint(1560503840, 550000),
|
||||
_Checkpoint(1562011717, 560000),
|
||||
_Checkpoint(1563518091, 570000),
|
||||
_Checkpoint(1565027136, 580000),
|
||||
_Checkpoint(1566532760, 590000),
|
||||
_Checkpoint(1568039395, 600000),
|
||||
_Checkpoint(1569547911, 610000),
|
||||
_Checkpoint(1571051853, 620000),
|
||||
_Checkpoint(1572559831, 630000),
|
||||
_Checkpoint(1574069743, 640000),
|
||||
_Checkpoint(1575576895, 650000),
|
||||
_Checkpoint(1576603014, 660000),
|
||||
_Checkpoint(1577356346, 670000),
|
||||
_Checkpoint(1578109375, 680000),
|
||||
_Checkpoint(1578863180, 690000),
|
||||
_Checkpoint(1579616443, 700000),
|
||||
_Checkpoint(1580369806, 710000),
|
||||
_Checkpoint(1581122786, 720000),
|
||||
_Checkpoint(1581876634, 730000),
|
||||
_Checkpoint(1582630471, 740000),
|
||||
_Checkpoint(1583383678, 750000),
|
||||
_Checkpoint(1584133944, 760000),
|
||||
_Checkpoint(1584887940, 770000),
|
||||
_Checkpoint(1585641057, 780000),
|
||||
_Checkpoint(1586395543, 790000),
|
||||
_Checkpoint(1587148500, 800000),
|
||||
_Checkpoint(1587901493, 810000),
|
||||
_Checkpoint(1588655183, 820000),
|
||||
_Checkpoint(1589408476, 830000),
|
||||
_Checkpoint(1590163464, 840000),
|
||||
_Checkpoint(1590916379, 850000),
|
||||
_Checkpoint(1591669786, 860000),
|
||||
_Checkpoint(1592423887, 870000),
|
||||
_Checkpoint(1593176771, 880000),
|
||||
_Checkpoint(1593930930, 890000),
|
||||
_Checkpoint(1594684380, 900000),
|
||||
_Checkpoint(1595439209, 910000),
|
||||
_Checkpoint(1596192463, 920000),
|
||||
_Checkpoint(1596946009, 930000),
|
||||
_Checkpoint(1597700256, 940000),
|
||||
_Checkpoint(1598453538, 950000),
|
||||
_Checkpoint(1599207483, 960000),
|
||||
_Checkpoint(1599960598, 970000),
|
||||
_Checkpoint(1600714563, 980000),
|
||||
_Checkpoint(1601467884, 990000),
|
||||
_Checkpoint(1602220941, 1000000),
|
||||
_Checkpoint(1602974722, 1010000),
|
||||
_Checkpoint(1603728848, 1020000),
|
||||
_Checkpoint(1604485637, 1030000),
|
||||
_Checkpoint(1605238381, 1040000),
|
||||
_Checkpoint(1605992807, 1050000),
|
||||
_Checkpoint(1606747750, 1060000),
|
||||
_Checkpoint(1607500606, 1070000),
|
||||
_Checkpoint(1608255278, 1080000),
|
||||
_Checkpoint(1609008684, 1090000),
|
||||
_Checkpoint(1609763782, 1100000),
|
||||
_Checkpoint(1610517871, 1110000),
|
||||
_Checkpoint(1611271575, 1120000),
|
||||
_Checkpoint(1612025546, 1130000),
|
||||
_Checkpoint(1612779940, 1140000),
|
||||
_Checkpoint(1613533993, 1150000),
|
||||
_Checkpoint(1614287669, 1160000),
|
||||
_Checkpoint(1615042297, 1170000),
|
||||
_Checkpoint(1615792993, 1180000),
|
||||
_Checkpoint(1616546145, 1190000),
|
||||
_Checkpoint(1617299867, 1200000),
|
||||
_Checkpoint(1618054038, 1210000),
|
||||
_Checkpoint(1618808513, 1220000),
|
||||
_Checkpoint(1619563243, 1230000),
|
||||
_Checkpoint(1620318159, 1240000),
|
||||
_Checkpoint(1621071298, 1250000),
|
||||
_Checkpoint(1621826156, 1260000),
|
||||
_Checkpoint(1622580237, 1270000),
|
||||
_Checkpoint(1623335553, 1280000),
|
||||
_Checkpoint(1624090141, 1290000),
|
||||
_Checkpoint(1624844712, 1300000),
|
||||
_Checkpoint(1625597720, 1310000),
|
||||
_Checkpoint(1626351652, 1320000),
|
||||
_Checkpoint(1627106054, 1330000),
|
||||
_Checkpoint(1627860648, 1340000),
|
||||
_Checkpoint(1628614015, 1350000),
|
||||
_Checkpoint(1629368398, 1360000),
|
||||
_Checkpoint(1630122679, 1370000),
|
||||
_Checkpoint(1630877190, 1380000),
|
||||
_Checkpoint(1631631884, 1390000),
|
||||
_Checkpoint(1632385851, 1400000),
|
||||
_Checkpoint(1633141006, 1410000),
|
||||
_Checkpoint(1633895592, 1420000),
|
||||
_Checkpoint(1634649550, 1430000),
|
||||
_Checkpoint(1635403121, 1440000),
|
||||
_Checkpoint(1636157669, 1450000),
|
||||
_Checkpoint(1636915133, 1460000),
|
||||
_Checkpoint(1637670036, 1470000),
|
||||
_Checkpoint(1638424121, 1480000),
|
||||
_Checkpoint(1639177413, 1490000),
|
||||
_Checkpoint(1639931234, 1500000),
|
||||
_Checkpoint(1640684476, 1510000),
|
||||
_Checkpoint(1641438827, 1520000),
|
||||
_Checkpoint(1642192402, 1530000),
|
||||
_Checkpoint(1642946383, 1540000),
|
||||
_Checkpoint(1643700901, 1550000),
|
||||
_Checkpoint(1644455522, 1560000),
|
||||
_Checkpoint(1645208504, 1570000),
|
||||
_Checkpoint(1645962081, 1580000),
|
||||
_Checkpoint(1646715150, 1590000),
|
||||
_Checkpoint(1647465727, 1600000),
|
||||
_Checkpoint(1648219045, 1610000),
|
||||
_Checkpoint(1648973131, 1620000),
|
||||
_Checkpoint(1649727422, 1630000),
|
||||
_Checkpoint(1650481408, 1640000),
|
||||
_Checkpoint(1651234287, 1650000),
|
||||
_Checkpoint(1651989865, 1660000),
|
||||
_Checkpoint(1652743327, 1670000),
|
||||
_Checkpoint(1653497455, 1680000),
|
||||
_Checkpoint(1654252094, 1690000),
|
||||
_Checkpoint(1655005944, 1700000),
|
||||
_Checkpoint(1655759708, 1710000),
|
||||
_Checkpoint(1656513765, 1720000),
|
||||
_Checkpoint(1657268358, 1730000),
|
||||
_Checkpoint(1658022453, 1740000),
|
||||
_Checkpoint(1658777019, 1750000),
|
||||
_Checkpoint(1659531133, 1760000),
|
||||
_Checkpoint(1660283907, 1770000),
|
||||
_Checkpoint(1661037645, 1780000),
|
||||
_Checkpoint(1661791977, 1790000),
|
||||
_Checkpoint(1662544394, 1800000),
|
||||
_Checkpoint(1663298018, 1810000),
|
||||
_Checkpoint(1664051450, 1820000),
|
||||
_Checkpoint(1664805547, 1830000),
|
||||
_Checkpoint(1665558994, 1840000),
|
||||
_Checkpoint(1666312456, 1850000),
|
||||
_Checkpoint(1667066473, 1860000),
|
||||
_Checkpoint(1667823393, 1870000),
|
||||
_Checkpoint(1668576623, 1880000),
|
||||
_Checkpoint(1669330657, 1890000),
|
||||
_Checkpoint(1670084156, 1900000),
|
||||
_Checkpoint(1670837831, 1910000),
|
||||
_Checkpoint(1671591094, 1920000),
|
||||
_Checkpoint(1672343975, 1930000),
|
||||
_Checkpoint(1673097703, 1940000),
|
||||
_Checkpoint(1673852016, 1950000),
|
||||
_Checkpoint(1674605810, 1960000),
|
||||
_Checkpoint(1675360050, 1970000),
|
||||
_Checkpoint(1676113710, 1980000),
|
||||
_Checkpoint(1676867174, 1990000),
|
||||
_Checkpoint(1677620242, 2000000),
|
||||
_Checkpoint(1678374852, 2010000),
|
||||
_Checkpoint(1679125063, 2020000),
|
||||
_Checkpoint(1679878121, 2030000),
|
||||
_Checkpoint(1680632254, 2040000),
|
||||
_Checkpoint(1681386518, 2050000),
|
||||
_Checkpoint(1682140381, 2060000),
|
||||
_Checkpoint(1682894564, 2070000),
|
||||
_Checkpoint(1683648178, 2080000),
|
||||
_Checkpoint(1684401932, 2090000),
|
||||
_Checkpoint(1685156464, 2100000),
|
||||
_Checkpoint(1685910040, 2110000),
|
||||
_Checkpoint(1686663406, 2120000),
|
||||
_Checkpoint(1687417875, 2130000),
|
||||
_Checkpoint(1688172069, 2140000),
|
||||
_Checkpoint(1688925610, 2150000),
|
||||
_Checkpoint(1689679500, 2160000),
|
||||
_Checkpoint(1690432691, 2170000),
|
||||
_Checkpoint(1691186349, 2180000),
|
||||
_Checkpoint(1691940901, 2190000),
|
||||
_Checkpoint(1692694780, 2200000),
|
||||
_Checkpoint(1693449743, 2210000),
|
||||
_Checkpoint(1694203665, 2220000),
|
||||
_Checkpoint(1694957643, 2230000),
|
||||
_Checkpoint(1695710712, 2240000),
|
||||
_Checkpoint(1696465787, 2250000),
|
||||
_Checkpoint(1697218701, 2260000),
|
||||
_Checkpoint(1697973031, 2270000),
|
||||
_Checkpoint(1698726864, 2280000),
|
||||
_Checkpoint(1699483348, 2290000),
|
||||
_Checkpoint(1700237650, 2300000),
|
||||
_Checkpoint(1700991210, 2310000),
|
||||
_Checkpoint(1701745742, 2320000),
|
||||
_Checkpoint(1702500723, 2330000),
|
||||
_Checkpoint(1703253121, 2340000),
|
||||
_Checkpoint(1704007238, 2350000),
|
||||
_Checkpoint(1704760935, 2360000),
|
||||
_Checkpoint(1705514669, 2370000),
|
||||
_Checkpoint(1706268087, 2380000),
|
||||
_Checkpoint(1707021423, 2390000),
|
||||
_Checkpoint(1707776257, 2400000),
|
||||
_Checkpoint(1708529542, 2410000),
|
||||
_Checkpoint(1709283065, 2420000),
|
||||
_Checkpoint(1710037165, 2430000),
|
||||
_Checkpoint(1710787780, 2440000),
|
||||
_Checkpoint(1711541488, 2450000),
|
||||
_Checkpoint(1712294996, 2460000),
|
||||
_Checkpoint(1713048843, 2470000),
|
||||
_Checkpoint(1713803486, 2480000),
|
||||
_Checkpoint(1714557127, 2490000),
|
||||
_Checkpoint(1715311181, 2500000),
|
||||
_Checkpoint(1716064271, 2510000),
|
||||
_Checkpoint(1716818792, 2520000),
|
||||
_Checkpoint(1717572765, 2530000),
|
||||
_Checkpoint(1718326333, 2540000),
|
||||
_Checkpoint(1719080275, 2550000),
|
||||
_Checkpoint(1719834297, 2560000),
|
||||
_Checkpoint(1720588042, 2570000),
|
||||
_Checkpoint(1721341832, 2580000),
|
||||
_Checkpoint(1722095122, 2590000),
|
||||
_Checkpoint(1722848604, 2600000),
|
||||
_Checkpoint(1723602357, 2610000),
|
||||
_Checkpoint(1724355843, 2620000),
|
||||
_Checkpoint(1725110085, 2630000),
|
||||
_Checkpoint(1725863681, 2640000),
|
||||
_Checkpoint(1726616892, 2650000),
|
||||
_Checkpoint(1727371443, 2660000),
|
||||
_Checkpoint(1728124893, 2670000),
|
||||
_Checkpoint(1728878141, 2680000),
|
||||
_Checkpoint(1729631572, 2690000),
|
||||
_Checkpoint(1730385888, 2700000),
|
||||
_Checkpoint(1731142437, 2710000),
|
||||
_Checkpoint(1731896266, 2720000),
|
||||
_Checkpoint(1732650193, 2730000),
|
||||
_Checkpoint(1733403626, 2740000),
|
||||
_Checkpoint(1734157390, 2750000),
|
||||
_Checkpoint(1734911442, 2760000),
|
||||
_Checkpoint(1735665720, 2770000),
|
||||
_Checkpoint(1736419845, 2780000),
|
||||
_Checkpoint(1737173511, 2790000),
|
||||
_Checkpoint(1737926795, 2800000),
|
||||
_Checkpoint(1738681511, 2810000),
|
||||
_Checkpoint(1739434236, 2820000),
|
||||
_Checkpoint(1740188214, 2830000),
|
||||
_Checkpoint(1740941352, 2840000),
|
||||
_Checkpoint(1741692338, 2850000),
|
||||
_Checkpoint(1742445247, 2860000),
|
||||
_Checkpoint(1743198927, 2870000),
|
||||
_Checkpoint(1743952947, 2880000),
|
||||
_Checkpoint(1744706370, 2890000),
|
||||
_Checkpoint(1745461377, 2900000),
|
||||
_Checkpoint(1746214735, 2910000),
|
||||
_Checkpoint(1746967908, 2920000),
|
||||
_Checkpoint(1747722271, 2930000),
|
||||
_Checkpoint(1748474582, 2940000),
|
||||
_Checkpoint(1749228582, 2950000),
|
||||
_Checkpoint(1749983372, 2960000),
|
||||
_Checkpoint(1750737278, 2970000),
|
||||
_Checkpoint(1751490762, 2980000),
|
||||
_Checkpoint(1752243931, 2990000),
|
||||
_Checkpoint(1752997873, 3000000),
|
||||
_Checkpoint(1753751756, 3010000),
|
||||
_Checkpoint(1754504935, 3020000),
|
||||
_Checkpoint(1755259449, 3030000),
|
||||
_Checkpoint(1756012904, 3040000),
|
||||
_Checkpoint(1756765335, 3050000),
|
||||
_Checkpoint(1757519206, 3060000),
|
||||
_Checkpoint(1758272484, 3070000),
|
||||
_Checkpoint(1759025597, 3080000),
|
||||
_Checkpoint(1759778957, 3090000),
|
||||
_Checkpoint(1760532780, 3100000),
|
||||
_Checkpoint(1761286210, 3110000),
|
||||
_Checkpoint(1762039783, 3120000),
|
||||
_Checkpoint(1762796627, 3130000),
|
||||
_Checkpoint(1763550532, 3140000),
|
||||
_Checkpoint(1764303860, 3150000),
|
||||
_Checkpoint(1765057381, 3160000),
|
||||
_Checkpoint(1765811222, 3170000),
|
||||
_Checkpoint(1766564356, 3180000),
|
||||
_Checkpoint(1767318326, 3190000),
|
||||
_Checkpoint(1768071956, 3200000),
|
||||
_Checkpoint(1768824957, 3210000),
|
||||
_Checkpoint(1769578781, 3220000),
|
||||
_Checkpoint(1770332206, 3230000),
|
||||
_Checkpoint(1771086265, 3240000),
|
||||
_Checkpoint(1771840180, 3250000),
|
||||
_Checkpoint(1772592783, 3260000),
|
||||
_Checkpoint(1773343947, 3270000),
|
||||
_Checkpoint(1774097085, 3280000),
|
||||
_Checkpoint(1774850161, 3290000),
|
||||
_Checkpoint(1775603525, 3300000),
|
||||
_Checkpoint(1776358117, 3310000),
|
||||
];
|
||||
|
||||
static int getBlockHeightByTime(DateTime time) {
|
||||
final t = time.toUtc().millisecondsSinceEpoch;
|
||||
|
||||
if (_points.isEmpty) return 0;
|
||||
|
||||
if (t <= _points.first.timeMs) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < _points.length - 1; i++) {
|
||||
final p0 = _points[i];
|
||||
final p1 = _points[i + 1];
|
||||
|
||||
if (t >= p0.timeMs && t < p1.timeMs) {
|
||||
return _interpolate(
|
||||
p0.height,
|
||||
p1.height,
|
||||
p0.timeMs,
|
||||
p1.timeMs,
|
||||
t,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final last = _points[_points.length - 1];
|
||||
final prev = _points[_points.length - 2];
|
||||
|
||||
final avgBlockTimeMs = (last.timeMs - prev.timeMs) / (last.height - prev.height);
|
||||
|
||||
final blocksSinceLast = ((t - last.timeMs) / avgBlockTimeMs).floor();
|
||||
|
||||
return last.height + blocksSinceLast;
|
||||
}
|
||||
|
||||
static DateTime getTimeByBlockHeight(int height) {
|
||||
if (_points.isEmpty) return DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
||||
if (height <= 1) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(_points.first.timeMs, isUtc: true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < _points.length - 1; i++) {
|
||||
final p0 = _points[i];
|
||||
final p1 = _points[i + 1];
|
||||
if (height >= p0.height && height < p1.height) {
|
||||
final t = _interpolate(
|
||||
p0.timeMs,
|
||||
p1.timeMs,
|
||||
p0.height,
|
||||
p1.height,
|
||||
height,
|
||||
);
|
||||
return DateTime.fromMillisecondsSinceEpoch(t, isUtc: true);
|
||||
}
|
||||
}
|
||||
|
||||
final last = _points[_points.length - 1];
|
||||
final prev = _points[_points.length - 2];
|
||||
final avgBlockTimeMs = (last.timeMs - prev.timeMs) / (last.height - prev.height);
|
||||
final msSinceLast = ((height - last.height) * avgBlockTimeMs).floor();
|
||||
return DateTime.fromMillisecondsSinceEpoch(last.timeMs + msSinceLast, isUtc: true);
|
||||
}
|
||||
|
||||
static int _interpolate(
|
||||
int h0,
|
||||
int h1,
|
||||
int t0,
|
||||
int t1,
|
||||
int t,
|
||||
) {
|
||||
final ratio = (t - t0) / (t1 - t0);
|
||||
return h0 + ((h1 - h0) * ratio).floor();
|
||||
}
|
||||
}
|
||||
|
||||
class _Checkpoint {
|
||||
final int timeMs;
|
||||
final int height;
|
||||
|
||||
_Checkpoint(int timeSec, this.height) : timeMs = timeSec * 1000;
|
||||
}
|
||||
+140
-37
@@ -1,19 +1,20 @@
|
||||
import 'dart:convert';
|
||||
import "dart:convert";
|
||||
|
||||
import 'package:bech32/bech32.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import "package:bech32/bech32.dart";
|
||||
import "package:cw_core/amount/money.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/utils/proxy_wrapper.dart";
|
||||
|
||||
const _BOLT_PREFIXES = ["lnbcrt", "lntbs", "lnbc", "lntb"];
|
||||
const _LUD17_PREFIXES = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth'];
|
||||
const _LUD17_PREFIXES = ["lnurlw", "lnurlc", "lnurlp", "keyauth"];
|
||||
|
||||
bool isBolt11ZeroInvoice(String invoice) {
|
||||
try {
|
||||
final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
|
||||
final request =
|
||||
const Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
|
||||
|
||||
final prefix =
|
||||
_BOLT_PREFIXES.firstWhere((prefix) => request.hrp.startsWith(prefix), orElse: () => "");
|
||||
_BOLT_PREFIXES.firstWhere(request.hrp.startsWith, orElse: () => "");
|
||||
|
||||
return request.hrp.length == prefix.length;
|
||||
} catch (e) {
|
||||
@@ -23,48 +24,56 @@ bool isBolt11ZeroInvoice(String invoice) {
|
||||
|
||||
/// Get the amount of a Bolt 11 Invoice in
|
||||
int? _getAmountBolt11Msat(String invoice) {
|
||||
final request = Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
|
||||
final request =
|
||||
const Bech32Codec().decode(invoice.replaceFirst("lightning:", ""), invoice.length);
|
||||
|
||||
final prefix = _BOLT_PREFIXES.firstWhere(
|
||||
(prefix) => request.hrp.startsWith(prefix),
|
||||
orElse: () => throw FormatException('Invalid BOLT11 invoice: unknown HRP prefix.'),
|
||||
request.hrp.startsWith,
|
||||
orElse: () => throw const FormatException("Invalid BOLT11 invoice: unknown HRP prefix."),
|
||||
);
|
||||
|
||||
final amountPart = request.hrp.substring(prefix.length);
|
||||
if (amountPart.isEmpty) return null; // zero-amount invoice
|
||||
if (amountPart.isEmpty) {
|
||||
return null; // zero-amount invoice
|
||||
}
|
||||
|
||||
final hasMultiplier = RegExp(r'[munp]$').hasMatch(amountPart);
|
||||
final multiplier = hasMultiplier ? amountPart[amountPart.length - 1] : '';
|
||||
final hasMultiplier = RegExp(r"[munp]$").hasMatch(amountPart);
|
||||
final multiplier = hasMultiplier ? amountPart[amountPart.length - 1] : "";
|
||||
final numberStr = hasMultiplier ? amountPart.substring(0, amountPart.length - 1) : amountPart;
|
||||
|
||||
if (numberStr.isEmpty || !RegExp(r'^\d+$').hasMatch(numberStr)) {
|
||||
throw FormatException('Invalid BOLT11 invoice: invalid amount number in HRP.');
|
||||
if (numberStr.isEmpty || !RegExp(r"^\d+$").hasMatch(numberStr)) {
|
||||
throw const FormatException("Invalid BOLT11 invoice: invalid amount number in HRP.");
|
||||
}
|
||||
|
||||
final amount = int.parse(numberStr);
|
||||
|
||||
switch (multiplier) {
|
||||
case '': // bitcoin
|
||||
case "": // bitcoin
|
||||
return amount * 100000000000;
|
||||
case 'm': // milli-bitcoin
|
||||
case "m": // milli-bitcoin
|
||||
return amount * 100000000;
|
||||
case 'u': // micro-bitcoin
|
||||
case "u": // micro-bitcoin
|
||||
return amount * 100000;
|
||||
case 'n': // nano-bitcoin
|
||||
case "n": // nano-bitcoin
|
||||
return amount * 100;
|
||||
case 'p':
|
||||
case "p":
|
||||
if (amount % 10 != 0) {
|
||||
throw FormatException('Invalid BOLT11 invoice: amount not representable in whole msat.');
|
||||
throw const FormatException(
|
||||
"Invalid BOLT11 invoice: amount not representable in whole msat.",
|
||||
);
|
||||
}
|
||||
return amount ~/ 10;
|
||||
default:
|
||||
throw FormatException('Invalid BOLT11 invoice: unknown amount multiplier.');
|
||||
throw const FormatException("Invalid BOLT11 invoice: unknown amount multiplier.");
|
||||
}
|
||||
}
|
||||
|
||||
Money? getBolt11Amount(String invoice) {
|
||||
final msat = _getAmountBolt11Msat(invoice);
|
||||
if (msat == null || msat % 1000 != 0) return null;
|
||||
if (msat == null || msat % 1000 != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln);
|
||||
}
|
||||
|
||||
@@ -73,30 +82,99 @@ class LNURL {
|
||||
final url = decode(lnurl);
|
||||
final response = await ProxyWrapper().get(clearnetUri: url);
|
||||
|
||||
if (response.statusCode != 200) return null;
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final body = jsonDecode(response.body) as Map;
|
||||
final tag = body["tag"] as String?;
|
||||
|
||||
if (tag != "payRequest") return null;
|
||||
if (tag != "payRequest") {
|
||||
return null;
|
||||
}
|
||||
|
||||
final msat = body["minSendable"] as int?;
|
||||
final maxSendable = body["maxSendable"] as int?;
|
||||
|
||||
// if minSendable and maxSendable are the same we assume a specific payment request
|
||||
if (msat != maxSendable) return null;
|
||||
if (msat != maxSendable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (msat == null || msat % 1000 != 0) return null;
|
||||
if (msat == null || msat % 1000 != 0) {
|
||||
return null;
|
||||
}
|
||||
return Money.fromInt(msat ~/ 1000, CryptoCurrency.btcln);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LNURLWithdrawRequest?> getWithdrawRequest(String lnurl) async {
|
||||
final url = decode(lnurl);
|
||||
final response = await ProxyWrapper().get(clearnetUri: url);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final body = jsonDecode(response.body) as Map;
|
||||
final tag = body["tag"] as String?;
|
||||
|
||||
if (tag != "withdrawRequest") {
|
||||
return null;
|
||||
}
|
||||
|
||||
final minWithdrawable = body["minWithdrawable"] as int?;
|
||||
final maxWithdrawable = body["maxWithdrawable"] as int?;
|
||||
|
||||
if ((minWithdrawable == null || minWithdrawable % 1000 != 0) ||
|
||||
(maxWithdrawable == null || maxWithdrawable % 1000 != 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return LNURLWithdrawRequest(
|
||||
callback: body["callback"] as String,
|
||||
k1: body["k1"] as String,
|
||||
description: body["defaultDescription"] as String,
|
||||
minAmount: Money.fromInt(minWithdrawable ~/ 1000, CryptoCurrency.btcln),
|
||||
maxAmount: Money.fromInt(maxWithdrawable ~/ 1000, CryptoCurrency.btcln),
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> commitWithdrawRequest(LNURLWithdrawRequest request, String invoice) async {
|
||||
try {
|
||||
final url = Uri.parse(request.callback);
|
||||
url.queryParameters["k1"] = request.k1;
|
||||
url.queryParameters["pr"] = invoice;
|
||||
|
||||
final response = await ProxyWrapper().get(clearnetUri: url);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final body = jsonDecode(response.body) as Map;
|
||||
final status = body["status"] as String?;
|
||||
|
||||
if (status == "OK") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static String encode(String url) {
|
||||
final raw = _convert(utf8.encode(url), 8, 5, true);
|
||||
return const Bech32Codec().encode(Bech32('lnurl', raw), 999);
|
||||
return const Bech32Codec().encode(Bech32("lnurl", raw), 999);
|
||||
}
|
||||
|
||||
static Uri decode(String encodedUrl) {
|
||||
@@ -114,9 +192,10 @@ class LNURL {
|
||||
if (_LUD17_PREFIXES.contains(decodedUri.scheme)) {
|
||||
/// If the non-bech32 LNURL is a Tor address, the port has to be http instead of https for the clearnet LNURL so check if the host ends with '.onion' or '.onion.'
|
||||
decodedUri = decodedUri.replace(
|
||||
scheme: decodedUri.host.endsWith('onion') || decodedUri.host.endsWith('onion.')
|
||||
? 'http'
|
||||
: 'https');
|
||||
scheme: decodedUri.host.endsWith("onion") || decodedUri.host.endsWith("onion.")
|
||||
? "http"
|
||||
: "https",
|
||||
);
|
||||
} else {
|
||||
/// Try to parse the input as a lnUrl. Will throw an error if it fails.
|
||||
final lnUrl = _findLnUrl(encodedUrl);
|
||||
@@ -129,12 +208,30 @@ class LNURL {
|
||||
}
|
||||
}
|
||||
|
||||
class LNURLWithdrawRequest {
|
||||
const LNURLWithdrawRequest({
|
||||
required this.callback,
|
||||
required this.k1,
|
||||
required this.description,
|
||||
required this.minAmount,
|
||||
required this.maxAmount,
|
||||
});
|
||||
|
||||
final String callback;
|
||||
final String k1;
|
||||
final String description;
|
||||
final Money minAmount;
|
||||
final Money maxAmount;
|
||||
}
|
||||
|
||||
/// Parse and return a given lnurl string if it's valid. Will remove
|
||||
/// `lightning:` from the beginning of it if present.
|
||||
String _findLnUrl(String input) {
|
||||
final res = RegExp(r',*?((lnurl)([0-9]+[a-z0-9]+))').allMatches(input.toLowerCase());
|
||||
final res = RegExp(r",*?((lnurl)([0-9]+[a-z0-9]+))").allMatches(input.toLowerCase());
|
||||
|
||||
if (res.length != 1) throw ArgumentError('Not a valid lnurl string');
|
||||
if (res.length != 1) {
|
||||
throw ArgumentError("Not a valid lnurl string");
|
||||
}
|
||||
return res.first.group(0)!;
|
||||
}
|
||||
|
||||
@@ -156,10 +253,16 @@ List<int> _convert(List<int> data, int inBits, int outBits, bool pad) {
|
||||
}
|
||||
|
||||
if (pad) {
|
||||
if (bits > 0) result.add((value << (outBits - bits)) & maxV);
|
||||
if (bits > 0) {
|
||||
result.add((value << (outBits - bits)) & maxV);
|
||||
}
|
||||
} else {
|
||||
if (bits >= inBits) throw Exception('[BECH32] Excess padding');
|
||||
if ((value << (outBits - bits)) & maxV > 0) throw Exception('[BECH32] Non-zero padding');
|
||||
if (bits >= inBits) {
|
||||
throw Exception("[BECH32] Excess padding");
|
||||
}
|
||||
if ((value << (outBits - bits)) & maxV > 0) {
|
||||
throw Exception("[BECH32] Non-zero padding");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -394,10 +394,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -738,10 +738,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -838,5 +838,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.10.0-0 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
||||
@@ -417,10 +417,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -769,10 +769,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -257,6 +257,10 @@ void setRecoveringFromSeed({required bool isRecovery}) {
|
||||
|
||||
final storeMutex = Mutex();
|
||||
|
||||
void refresh() {
|
||||
currentWallet!.refresh();
|
||||
}
|
||||
|
||||
int lastStorePointer = 0;
|
||||
int lastStoreHeight = 0;
|
||||
void storeSync({bool force = false}) async {
|
||||
|
||||
@@ -150,6 +150,11 @@ void restoreWalletFromSeedSync(
|
||||
_lastOpenedWallet = path;
|
||||
}
|
||||
|
||||
void storePassphrase({required String path, required String passphrase}) {
|
||||
currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase);
|
||||
currentWallet!.store(path: path);
|
||||
}
|
||||
|
||||
void restoreWalletFromKeys(
|
||||
{required String path,
|
||||
required String password,
|
||||
|
||||
@@ -382,22 +382,30 @@ abstract class MoneroWalletBase
|
||||
}
|
||||
|
||||
MoneroTrezorService? trezorService;
|
||||
|
||||
Future<Trezor> _getTrezor() async {
|
||||
if (trezorService == null) throw Exception("Trezor not connected");
|
||||
|
||||
final trezor = Trezor(trezorService!);
|
||||
await trezor.newPassphraseSession(passphrase);
|
||||
return trezor;
|
||||
}
|
||||
|
||||
Future<void> syncTrezor() async {
|
||||
if (trezorService == null) throw Exception("Trezor not connected");
|
||||
|
||||
final ptr = Pointer<Void>.fromAddress(currentWallet!.ffiAddress());
|
||||
final tdis = monero.Wallet_exportTrezorTdis(ptr);
|
||||
|
||||
final response = await Trezor(trezorService!).keyImageSync(tdis);
|
||||
|
||||
final trezor = await _getTrezor();
|
||||
final response = await trezor.keyImageSync(tdis);
|
||||
final success = monero.Wallet_importTrezorEncryptedKeyImagesJson(ptr, response);
|
||||
|
||||
if (!success) throw Exception(monero.Wallet_errorString(ptr));
|
||||
}
|
||||
|
||||
Future<String> signTrezorTransaction(String json) async {
|
||||
if (trezorService == null) throw Exception("Trezor not connected");
|
||||
return Trezor(trezorService!).signTransaction(json);
|
||||
final trezor = await _getTrezor();
|
||||
return trezor.signTransaction(json);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -44,10 +44,15 @@ class MoneroNewWalletCredentials extends WalletCredentials {
|
||||
}
|
||||
|
||||
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
|
||||
MoneroRestoreWalletFromHardwareCredentials(
|
||||
{required String name, required this.hardwareWalletService, int height = 0, String? password})
|
||||
: super(name: name, password: password, height: height);
|
||||
HardwareWalletService hardwareWalletService;
|
||||
MoneroRestoreWalletFromHardwareCredentials({
|
||||
required String name,
|
||||
required this.hardwareWalletService,
|
||||
int height = 0,
|
||||
String? password,
|
||||
String? passphrase,
|
||||
}) : super(name: name, password: password, height: height, passphrase: passphrase);
|
||||
|
||||
final HardwareWalletService hardwareWalletService;
|
||||
}
|
||||
|
||||
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
@@ -306,7 +311,13 @@ class MoneroWalletService extends WalletService<
|
||||
deviceName: 'Ledger',
|
||||
);
|
||||
} else if (credentials.hardwareWalletService case MoneroTrezorService service) {
|
||||
final watchCredentials = await Trezor(service).getWatchCredentials();
|
||||
final trezor = Trezor(service);
|
||||
|
||||
if (credentials.passphrase != null) {
|
||||
await trezor.newPassphraseSession(credentials.passphrase);
|
||||
}
|
||||
|
||||
final watchCredentials = await trezor.getWatchCredentials();
|
||||
|
||||
monero_wallet_manager.restoreWalletFromKeys(
|
||||
path: path,
|
||||
@@ -317,6 +328,10 @@ class MoneroWalletService extends WalletService<
|
||||
viewKey: watchCredentials.watchKey,
|
||||
spendKey: "",
|
||||
);
|
||||
|
||||
if (credentials.passphrase != null) {
|
||||
monero_wallet_manager.storePassphrase(path: path, passphrase: credentials.passphrase!);
|
||||
}
|
||||
}
|
||||
|
||||
final wallet = MoneroWallet(
|
||||
|
||||
@@ -21,6 +21,15 @@ class Trezor {
|
||||
|
||||
Trezor(this.service);
|
||||
|
||||
String? _sessionPassphrase;
|
||||
Future<void> newPassphraseSession(String? passphrase) async {
|
||||
if (passphrase == null) return;
|
||||
|
||||
if (_sessionPassphrase == passphrase) return;
|
||||
_sessionPassphrase = passphrase;
|
||||
return service.client.createChannel(passphrase: _sessionPassphrase);
|
||||
}
|
||||
|
||||
Future<MoneroTrezorWatchCredentials> getWatchCredentials() async {
|
||||
final credentials = await TrezorMonero(service.client).getWatchCredentials();
|
||||
|
||||
|
||||
@@ -505,10 +505,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -906,10 +906,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -921,8 +921,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: trezor-flutter
|
||||
ref: d1289dfa4247a93ee57342dfd249957b339915ef
|
||||
resolved-ref: d1289dfa4247a93ee57342dfd249957b339915ef
|
||||
ref: "944080d950a62dd48a199b9736237cbe580351de"
|
||||
resolved-ref: "944080d950a62dd48a199b9736237cbe580351de"
|
||||
url: "https://github.com/cake-tech/trezor-flutter.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
@@ -930,8 +930,8 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: trezor_usb_transport
|
||||
ref: d1289dfa4247a93ee57342dfd249957b339915ef
|
||||
resolved-ref: d1289dfa4247a93ee57342dfd249957b339915ef
|
||||
ref: "944080d950a62dd48a199b9736237cbe580351de"
|
||||
resolved-ref: "944080d950a62dd48a199b9736237cbe580351de"
|
||||
url: "https://github.com/cake-tech/trezor-flutter.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
@@ -1048,5 +1048,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.10.0-0 <4.0.0"
|
||||
dart: ">=3.9.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
|
||||
@@ -33,7 +33,7 @@ dependencies:
|
||||
trezor_flutter:
|
||||
git:
|
||||
url: https://github.com/cake-tech/trezor-flutter.git
|
||||
ref: d1289dfa4247a93ee57342dfd249957b339915ef
|
||||
ref: 944080d950a62dd48a199b9736237cbe580351de
|
||||
path: trezor-flutter
|
||||
|
||||
dev_dependencies:
|
||||
|
||||
@@ -462,10 +462,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -887,10 +887,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -417,10 +417,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -786,10 +786,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -422,10 +422,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.17.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -783,10 +783,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.10"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
library;
|
||||
|
||||
export 'src/zcash_balance.dart';
|
||||
export 'src/zcash_mnemonic_is_incorrect_exception.dart';
|
||||
export 'src/zcash_transaction_info.dart';
|
||||
export 'src/zcash_transactions.dart';
|
||||
export 'src/zcash_wallet.dart';
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:zkool/src/rust/api/account.dart' as zkool_account;
|
||||
import 'package:zkool/src/rust/api/coin.dart' as zkool_coin;
|
||||
import 'package:zkool/src/rust/api/sync.dart' as zkool_sync;
|
||||
import 'package:zkool/src/rust/api/pay.dart' as zkool_pay;
|
||||
import 'package:zkool/src/rust/api/key.dart' as zkool_key;
|
||||
import 'package:zkool/src/rust/api/network.dart' as zkool_network;
|
||||
import 'package:zkool/src/rust/pay.dart' as zkool_paydart;
|
||||
import 'package:zkool/src/rust/frb_generated.dart' as zkool_frb;
|
||||
|
||||
class ZkoolSweep {
|
||||
ZkoolSweep({
|
||||
required this.currentBalance,
|
||||
required this.cacheDir,
|
||||
required this.seed,
|
||||
required this.passphrase,
|
||||
required this.address,
|
||||
required this.url,
|
||||
required this.height,
|
||||
});
|
||||
final int currentBalance;
|
||||
final String seed;
|
||||
final String cacheDir;
|
||||
final String passphrase;
|
||||
final String address;
|
||||
final String url;
|
||||
final int height;
|
||||
static SweepUpdate msg = SweepUpdate(message: 'Idling', currentHeight: 0, networkHeight: 0);
|
||||
Future<void> start() async {
|
||||
try {
|
||||
await _init();
|
||||
if (!zkool_key.isValidAddress(address: address)) {
|
||||
msg.message = "$address is not valid";
|
||||
return;
|
||||
}
|
||||
|
||||
msg = SweepUpdate(message: 'Starting import', currentHeight: 0, networkHeight: 0);
|
||||
await _newAccount();
|
||||
msg.message = "Starting sync";
|
||||
await warpSync();
|
||||
msg.message = "Waiting";
|
||||
while (msg.blocksLeft != 0 && msg.networkHeight != 0) {
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
}
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
final balance = await getBalance();
|
||||
if (balance.toInt() == balance) {
|
||||
msg.message = "No need to sweep, balance is the same";
|
||||
return;
|
||||
}
|
||||
msg.message = "Sweeping";
|
||||
await sweep(balance);
|
||||
} catch (e) {
|
||||
msg.message = e.toString();
|
||||
printV("zkool_sweep: $e");
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<BigInt> getBalance() async {
|
||||
final b = await zkool_sync.balance(c: c);
|
||||
return b.field0.reduce((final a, final b) => a + b);
|
||||
}
|
||||
|
||||
Future<void> sweep(final BigInt amount) async {
|
||||
msg.message = "prepare";
|
||||
final tx = await zkool_pay.prepare(
|
||||
recipients: [zkool_paydart.Recipient(address: address, amount: amount)],
|
||||
options: zkool_pay.PaymentOptions(
|
||||
srcPools: 7,
|
||||
recipientPaysFee: true,
|
||||
smartTransparent: false,
|
||||
),
|
||||
c: c,
|
||||
);
|
||||
msg.message = "getLatestHeight";
|
||||
final height = await getLatestHeight();
|
||||
msg.message = "signTransaction";
|
||||
final signTx = await zkool_pay.signTransaction(pczt: tx, c: c);
|
||||
msg.message = "extractTransaction";
|
||||
final txBytes = await zkool_pay.extractTransaction(package: signTx);
|
||||
msg.message = "broadcastTransaction";
|
||||
final result = await zkool_pay.broadcastTransaction(height: height, txBytes: txBytes, c: c);
|
||||
msg.message = "result";
|
||||
if (result.isEmpty) {
|
||||
msg.message = "Unknown error";
|
||||
throw Exception(result);
|
||||
}
|
||||
}
|
||||
|
||||
static var c = zkool_coin.Coin();
|
||||
|
||||
static bool _isInit = false;
|
||||
|
||||
File get dbFile => File(p.join(cacheDir, "zkool-tmp-mirror.db"));
|
||||
|
||||
Future<int> warpSync() async {
|
||||
final currentHeight = await getLatestHeight();
|
||||
msg.networkHeight = currentHeight;
|
||||
final sync = zkool_sync.synchronize(
|
||||
accounts: [currentAccount],
|
||||
currentHeight: currentHeight,
|
||||
actionsPerSync: 10000,
|
||||
transparentLimit: 100,
|
||||
checkpointAge: 200,
|
||||
c: c,
|
||||
);
|
||||
bool success = false;
|
||||
await sync.listen(
|
||||
(final syncProgress) {
|
||||
msg.currentHeight = syncProgress.height;
|
||||
msg.message = "Sync ${syncProgress.height} / ${syncProgress.time}";
|
||||
},
|
||||
onError: (final e) {
|
||||
msg.message = e.toString();
|
||||
},
|
||||
onDone: () {
|
||||
msg.message = "Sync done!";
|
||||
success = true;
|
||||
},
|
||||
);
|
||||
|
||||
if (msg.currentHeight != currentHeight && msg.currentHeight != 0) {
|
||||
msg.message = "Canceling sync";
|
||||
await zkool_sync.cancelSync();
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
return warpSync();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<int> getLatestHeight() async {
|
||||
try {
|
||||
return await zkool_network.getCurrentHeight(c: c);
|
||||
} catch (e) {
|
||||
printV("getLatestHeight: $e");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Future<void> _newAccount() async {
|
||||
final id = await zkool_account.newAccount(
|
||||
na: zkool_account.NewAccount(
|
||||
name: 'restoretmp' + DateTime.now().microsecondsSinceEpoch.toString(),
|
||||
restore: true,
|
||||
passphrase: "",
|
||||
key: seed,
|
||||
aindex: 0,
|
||||
birth: height,
|
||||
folder: '',
|
||||
useInternal: true,
|
||||
internal: false,
|
||||
ledger: false,
|
||||
),
|
||||
c: c,
|
||||
);
|
||||
currentAccount = id;
|
||||
c = await c.setAccount(account: id);
|
||||
}
|
||||
|
||||
int currentAccount = 0;
|
||||
|
||||
Future<void> _init() async {
|
||||
if (_isInit) return;
|
||||
_isInit = true;
|
||||
printV(".init()");
|
||||
await zkool_frb.RustLib.init();
|
||||
await zkool_network.initDatadir(directory: dbFile.parent.path);
|
||||
c = await c.openDatabase(dbFilepath: dbFile.path, password: 'cw_zcash_migration');
|
||||
c = c.setLwd(url: url, serverType: 0);
|
||||
printV(".init(): Done");
|
||||
}
|
||||
}
|
||||
|
||||
class SweepUpdate {
|
||||
SweepUpdate({required this.message, required this.currentHeight, required this.networkHeight});
|
||||
String message;
|
||||
int currentHeight;
|
||||
int networkHeight;
|
||||
double get progress {
|
||||
if (networkHeight == 0) return 0.01;
|
||||
return currentHeight / networkHeight;
|
||||
}
|
||||
|
||||
int get blocksLeft => networkHeight - currentHeight;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"message": message,
|
||||
"currentHeight": currentHeight,
|
||||
"networkHeight": networkHeight,
|
||||
"progress": progress,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/get_height_by_date_zec.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_zcash/cw_zcash.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class ZkoolSeed {
|
||||
ZkoolSeed({required this.seed, required this.passphrase});
|
||||
|
||||
factory ZkoolSeed.fromYwalletSeed({required final String ywalletSeed}) {
|
||||
final splSeed = ywalletSeed.split(" ");
|
||||
String seed = "";
|
||||
;
|
||||
String passphrase = "";
|
||||
if ([13, 25].contains(splSeed.length)) {
|
||||
seed = splSeed.take(splSeed.length - 1).join(" ");
|
||||
passphrase = ywalletSeed.replaceFirst("${seed} ", "");
|
||||
} else {
|
||||
seed = ywalletSeed;
|
||||
}
|
||||
return ZkoolSeed(seed: seed, passphrase: passphrase);
|
||||
}
|
||||
|
||||
String seed;
|
||||
String passphrase;
|
||||
}
|
||||
|
||||
class YwalletAccountInfo {
|
||||
YwalletAccountInfo({
|
||||
required this.idAccount,
|
||||
required this.name,
|
||||
required this.seed,
|
||||
required this.aindex,
|
||||
required this.sk,
|
||||
required this.ivk,
|
||||
required this.address,
|
||||
});
|
||||
final int idAccount;
|
||||
final String name;
|
||||
final String seed;
|
||||
late final zkoolSeed = ZkoolSeed.fromYwalletSeed(ywalletSeed: seed);
|
||||
final int aindex;
|
||||
final String sk;
|
||||
final String ivk;
|
||||
final String address;
|
||||
static YwalletAccountInfo fromJson(final Map<String, dynamic> j) => YwalletAccountInfo(
|
||||
idAccount: j['id_account'],
|
||||
name: j['name'],
|
||||
seed: j['seed'],
|
||||
aindex: j['aindex'],
|
||||
sk: j['sk'],
|
||||
ivk: j['ivk'],
|
||||
address: j['address'],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> migrateOldSqliteToZkool2({required final String walletName}) async {
|
||||
final dbFile = p.join(await pathForWalletTypeDir(type: WalletType.zcash), "zec.db");
|
||||
final migrateDb = await openDatabase(dbFile);
|
||||
final outputRaw = await migrateDb.rawQuery(
|
||||
"SELECT `id_account`, `name`, `seed`, `aindex`, `sk`, `ivk`, `address` FROM `accounts`",
|
||||
);
|
||||
final output = outputRaw.map(YwalletAccountInfo.fromJson);
|
||||
|
||||
final walletId = await ZcashWalletBase.getLegacyZcashAccountIdForName(walletName);
|
||||
for (final ya in output) {
|
||||
if (ya.idAccount != walletId) {
|
||||
continue;
|
||||
}
|
||||
printV("migrating account: ${ya.name}");
|
||||
int birthHeight = await ZcashHeight.getBlockHeightByTime(DateTime(2026, 1, 1));
|
||||
|
||||
final accTxs = await migrateDb.rawQuery(
|
||||
"SELECT height FROM transactions WHERE account = ${ya.idAccount}",
|
||||
);
|
||||
for (final tx in accTxs) {
|
||||
final txHeight = int.tryParse(tx['height'].toString()) ?? birthHeight;
|
||||
birthHeight = min(birthHeight, txHeight);
|
||||
}
|
||||
|
||||
final accountId = await ZcashWalletBase.restoreZcashWalletFromSeed(
|
||||
name: ya.name,
|
||||
seed: ya.zkoolSeed.seed,
|
||||
passphrase: ya.zkoolSeed.passphrase,
|
||||
birthHeight: birthHeight,
|
||||
);
|
||||
await ZcashWalletBase.saveAccountId(ya.name, accountId);
|
||||
return;
|
||||
}
|
||||
|
||||
throw Exception("migration not finished (wallet name: $walletName not found)");
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_zcash/cw_zcash.dart';
|
||||
import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
|
||||
import 'package:warp_api/data_fb_generated.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'package:zkool/src/rust/api/pay.dart' as zkool_pay;
|
||||
import 'package:zkool/src/rust/api/network.dart' as zkool_network;
|
||||
|
||||
class PendingZcashTransaction with PendingTransaction {
|
||||
PendingZcashTransaction({
|
||||
@@ -21,7 +17,7 @@ class PendingZcashTransaction with PendingTransaction {
|
||||
|
||||
final ZcashWallet zcashWallet;
|
||||
final ZcashTransactionCredentials credentials;
|
||||
final String txPlan;
|
||||
final zkool_pay.PcztPackage txPlan;
|
||||
String? _txId;
|
||||
final Money availableBalance;
|
||||
|
||||
@@ -32,56 +28,42 @@ class PendingZcashTransaction with PendingTransaction {
|
||||
String get hex => '';
|
||||
|
||||
@override
|
||||
Money get amount => Money.fromInt(_totalAmount, CryptoCurrency.zec);
|
||||
Money get amount {
|
||||
final isAll = credentials.outputs.fold<bool>(false, (final a, final b) => a || (b.sendAll));
|
||||
if (isAll) {
|
||||
return availableBalance - fee;
|
||||
}
|
||||
return credentials.outputs
|
||||
.map((final output) => output.cryptoAmount)
|
||||
.reduce((final a, final b) => a + b);
|
||||
}
|
||||
|
||||
@override
|
||||
String get amountFormatted => amount.toString();
|
||||
|
||||
int get _totalAmount {
|
||||
final isAll = credentials.outputs.fold<bool>(false, (final a, final b) => a || (b.sendAll));
|
||||
if (isAll) {
|
||||
return availableBalance.amount.toInt();
|
||||
}
|
||||
return credentials.outputs.fold<int>(
|
||||
0,
|
||||
(final a, final b) => a + b.cryptoAmount.amount.toInt(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
final Money fee;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
_txId = await ZcashWalletService.runInDbMutex(
|
||||
() => WarpApi.signAndBroadcast(ZcashWalletBase.coin, zcashWallet.accountId, txPlan),
|
||||
);
|
||||
ZcashWalletBase.temporarySentTx[zcashWallet.accountId] ??= [];
|
||||
ZcashWalletBase.temporarySentTx[zcashWallet.accountId]?.add(
|
||||
ShieldedTx(
|
||||
base64.decode(
|
||||
ZcashTaddressRotation.flatBuffersPack(
|
||||
ShieldedTxT(
|
||||
id: Random().nextInt(pow(2, 32).toInt()),
|
||||
txId: _txId,
|
||||
height: 0,
|
||||
timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
value: -_totalAmount,
|
||||
).pack,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await ZcashTransactionInfo.addCachedDestinationAddress(
|
||||
_txId ?? '',
|
||||
credentials.outputs.reduce((final o1, final o2) {
|
||||
return OutputInfo(
|
||||
cryptoAmount: Money.zero(CryptoCurrency.zec),
|
||||
address: "${o1.address},${o2.address}",
|
||||
sendAll: false,
|
||||
isParsedAddress: false,
|
||||
await ZcashWalletBase.runWithCoin(
|
||||
accountId: zcashWallet.accountId,
|
||||
func: (coin) async {
|
||||
final signTx = await zkool_pay.signTransaction(pczt: txPlan, c: coin);
|
||||
final txBytes = await zkool_pay.extractTransaction(package: signTx);
|
||||
final currentHeight = await zkool_network.getCurrentHeight(c: coin);
|
||||
final result = await zkool_pay.broadcastTransaction(
|
||||
height: currentHeight,
|
||||
txBytes: txBytes,
|
||||
c: coin,
|
||||
);
|
||||
}).address,
|
||||
printV("result: $result");
|
||||
final txId = ZcashWalletService.normalizeTxId(result);
|
||||
if (txId.length != 64) {
|
||||
throw TransactionCommitFailed(errorMessage: result);
|
||||
}
|
||||
_txId = txId;
|
||||
},
|
||||
);
|
||||
await zcashWallet.updateTransactions();
|
||||
await zcashWallet.updateBalance();
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
String uint8ListToHex(final Uint8List bytes) {
|
||||
return bytes.map((final b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_zcash/src/zcash_wallet_service.dart';
|
||||
import 'package:zkool/src/rust/api/coin.dart' as zkool_coin;
|
||||
import 'package:zkool/src/rust/api/mempool.dart' as zkool_mempool;
|
||||
|
||||
typedef ZcashMempoolRefreshCallback = void Function(Set<int> accountIds);
|
||||
|
||||
class ZcashMempoolService {
|
||||
ZcashMempoolService._();
|
||||
|
||||
static final ZcashMempoolService instance = ZcashMempoolService._();
|
||||
|
||||
zkool_mempool.Mempool? _mempool;
|
||||
final Map<String, zkool_mempool.MempoolTx> _txsById = {};
|
||||
|
||||
zkool_mempool.Mempool get _mempoolInstance => _mempool ??= zkool_mempool.Mempool();
|
||||
|
||||
ZcashMempoolRefreshCallback? onAccountsUpdated;
|
||||
|
||||
bool _loopRunning = false;
|
||||
String? _nodeUrl;
|
||||
StreamSubscription<zkool_mempool.MempoolMsg>? _subscription;
|
||||
|
||||
List<zkool_mempool.MempoolTx> txsForAccount(final int accountId) => _txsById.values
|
||||
.where(
|
||||
(final tx) =>
|
||||
tx.notes.any((final n) => n.account == accountId) ||
|
||||
tx.amounts.any((final a) => a.account == accountId),
|
||||
)
|
||||
.toList();
|
||||
|
||||
Future<void> ensureRunning(final zkool_coin.Coin c) async {
|
||||
if (c.url.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (_loopRunning && _nodeUrl == c.url) {
|
||||
return;
|
||||
}
|
||||
await stop();
|
||||
_nodeUrl = c.url;
|
||||
_loopRunning = true;
|
||||
unawaited(_runLoop(c));
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
_loopRunning = false;
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
_txsById.clear();
|
||||
try {
|
||||
await _mempool?.cancel();
|
||||
} catch (e) {
|
||||
printV("mempool cancel: $e");
|
||||
}
|
||||
_mempool = null;
|
||||
}
|
||||
|
||||
Future<void> _runLoop(final zkool_coin.Coin c) async {
|
||||
while (_loopRunning && _nodeUrl == c.url) {
|
||||
try {
|
||||
final completer = Completer<void>();
|
||||
_subscription = _mempoolInstance
|
||||
.run(c: c)
|
||||
.listen(
|
||||
(final msg) {
|
||||
if (!_loopRunning) {
|
||||
return;
|
||||
}
|
||||
switch (msg) {
|
||||
case zkool_mempool.MempoolMsg_BlockHeight():
|
||||
_onBlockMined();
|
||||
case zkool_mempool.MempoolMsg_TxId(:final field0):
|
||||
_onMempoolTx(field0);
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
onError: (final e) {
|
||||
printV("mempool stream error: $e");
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
);
|
||||
await completer.future;
|
||||
} catch (e) {
|
||||
printV("mempool loop error: $e");
|
||||
}
|
||||
if (_loopRunning) {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _onBlockMined() {
|
||||
final affected = _allAffectedAccounts();
|
||||
_txsById.clear();
|
||||
onAccountsUpdated?.call(affected);
|
||||
}
|
||||
|
||||
void _onMempoolTx(final zkool_mempool.MempoolTx tx) {
|
||||
final normalized = ZcashWalletService.normalizeTxId(tx.txid);
|
||||
_txsById[normalized] = tx;
|
||||
onAccountsUpdated?.call(_affectedAccountsForTx(tx));
|
||||
}
|
||||
|
||||
Set<int> _allAffectedAccounts() => _txsById.values.expand(_affectedAccountsForTx).toSet();
|
||||
|
||||
Set<int> _affectedAccountsForTx(final zkool_mempool.MempoolTx tx) => {
|
||||
...tx.notes.map((final n) => n.account),
|
||||
...tx.amounts.map((final a) => a.account),
|
||||
};
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
class ZcashMnemonicIsIncorrectException implements Exception {
|
||||
@override
|
||||
String toString() =>
|
||||
'Zcash mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||
}
|
||||
@@ -12,368 +12,484 @@
|
||||
// turned off and replaced with proper solution.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show max;
|
||||
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.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:cw_zcash/src/util/crc32.dart';
|
||||
import 'package:cw_zcash/src/zcash_wallet.dart';
|
||||
import 'package:cw_zcash/src/zcash_wallet_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:warp_api/data_fb_generated.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:cw_zcash/src/zkool_compat.dart';
|
||||
import 'package:cw_zcash/src/zkooltx.dart';
|
||||
import 'package:zkool/src/rust/api/account.dart' as zkool_account;
|
||||
import 'package:zkool/src/rust/api/coin.dart' as zkool_coin;
|
||||
import 'package:zkool/src/rust/api/sync.dart' as zkool_sync;
|
||||
import 'package:zkool/src/rust/api/pay.dart' as zkool_pay;
|
||||
import 'package:zkool/src/rust/api/network.dart' as zkool_network;
|
||||
import 'package:zkool/src/rust/api/sweep.dart' as zkool_sweep;
|
||||
import 'package:zkool/src/rust/pay.dart' as zkool_paydart;
|
||||
|
||||
class ZcashTaddressRotation {
|
||||
ZcashTaddressRotation(this.accountIndex);
|
||||
final int accountIndex;
|
||||
|
||||
static bool _isStarted = false;
|
||||
static zkool_coin.Coin get c => ZcashWalletBase.c;
|
||||
static const int _sweepThreshold = 30000;
|
||||
static const int _lookahead = 5;
|
||||
// Matches transparentLimit in ZcashWalletBase._oneshotSync.
|
||||
static const int _transparentSyncLimit = 100;
|
||||
|
||||
static const coin = ZcashWalletBase.coin;
|
||||
static String seedForOffset(final String seed) {
|
||||
final seedWords = seed.split(" ");
|
||||
static zkool_account.Seed seedForOffset(final zkool_account.Seed seed) {
|
||||
final seedStr = "${seed.mnemonic} ${seed.phrase.replaceAll(" ", "_")}".trim();
|
||||
final seedWords = seedStr.split(" ");
|
||||
if ([12, 24].contains(seedWords.length)) {
|
||||
seedWords.add("");
|
||||
}
|
||||
final lastI = seedWords.length - 1;
|
||||
final crc = CRC32.compute(seed);
|
||||
seedWords[lastI] += ":tgen:${crc}";
|
||||
return seedWords.join(" ");
|
||||
final crc = CRC32.compute(seedStr);
|
||||
seedWords[lastI] += ":tgen:$crc";
|
||||
final phrase = seedWords.removeLast();
|
||||
return zkool_account.Seed(mnemonic: seedWords.join(" "), phrase: phrase, aindex: 0);
|
||||
}
|
||||
|
||||
static bool isSeedForWallet(final String? mainWallet, final String? subWallet) {
|
||||
static bool zkoolAccountSeedIsEqual(final zkool_account.Seed a, final zkool_account.Seed b) {
|
||||
return a.mnemonic == b.mnemonic && a.phrase == b.phrase;
|
||||
}
|
||||
|
||||
static bool isSeedForWallet(
|
||||
final zkool_account.Seed? mainWallet,
|
||||
final zkool_account.Seed? subWallet,
|
||||
) {
|
||||
if (mainWallet == null || subWallet == null) return false;
|
||||
return seedForOffset(mainWallet.trim()) == subWallet;
|
||||
return zkoolAccountSeedIsEqual(seedForOffset(mainWallet), subWallet);
|
||||
}
|
||||
|
||||
static Map<int, List<ZkoolTx>> shieldedAccountsTx = {};
|
||||
static final Map<int, int> _rotationAccountByMain = {};
|
||||
static final Set<int> _resyncedRotationAccounts = {};
|
||||
static final Set<String> _poolRescanAttempted = {};
|
||||
static bool _sweepJobRunning = false;
|
||||
static final Map<int, DateTime> _lastSweepBroadcastAt = {};
|
||||
|
||||
static bool _poolNeedsRescan(final List<zkool_account.TAddressTxCount> slots) {
|
||||
if (slots.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final unused = slots.where((final e) => e.txCount == 0);
|
||||
if (unused.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
final head = unused.first;
|
||||
final tail = slots.last.dindex;
|
||||
if (slots.any((final e) => e.dindex > head.dindex && e.txCount > 0)) {
|
||||
return true;
|
||||
}
|
||||
return head.dindex + _transparentSyncLimit <= tail;
|
||||
}
|
||||
|
||||
static Future<void> _scanTransparentChain(final int rotationAccountId) async {
|
||||
await ZcashWalletBase.runWithCoin(
|
||||
accountId: rotationAccountId,
|
||||
func: (final coin) async {
|
||||
final all = await zkool_account.fetchAddressTxCount(
|
||||
c: coin,
|
||||
aggregate: false,
|
||||
poolFilter: 1,
|
||||
);
|
||||
final external = all.where((final e) => e.scope == 0).toList();
|
||||
if (external.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final maxD = external.map((final e) => e.dindex).reduce(max);
|
||||
final scanner = await zkool_sweep.TransparentScanner.newInstance();
|
||||
final height = await zkool_network.getCurrentHeight(c: coin);
|
||||
final completer = Completer<void>();
|
||||
final sub = scanner
|
||||
.run(endHeight: height, gapLimit: maxD + _lookahead + 1, c: coin)
|
||||
.listen(
|
||||
(_) {},
|
||||
onError: (final e) {
|
||||
printV("transparent chain scan: $e");
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
);
|
||||
await Future.any([
|
||||
completer.future,
|
||||
Future.delayed(Duration(seconds: (maxD + 2).clamp(8, 45))),
|
||||
]);
|
||||
await sub.cancel();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _resyncRotationPool(
|
||||
final int rotationAccountId,
|
||||
final int transparentLimit,
|
||||
) async {
|
||||
await ZcashWalletBase.runWithCoin(
|
||||
accountId: rotationAccountId,
|
||||
func: (final coin) async {
|
||||
await zkool_sync.cancelSync();
|
||||
await zkool_account.resetSync(id: rotationAccountId, c: coin);
|
||||
final height = await zkool_network.getCurrentHeight(c: coin);
|
||||
final sync = zkool_sync.synchronize(
|
||||
accounts: [rotationAccountId],
|
||||
currentHeight: height,
|
||||
actionsPerSync: 10000,
|
||||
transparentLimit: transparentLimit,
|
||||
checkpointAge: 200,
|
||||
c: coin,
|
||||
fast: false,
|
||||
);
|
||||
final completer = Completer<void>();
|
||||
sync.listen(
|
||||
(_) {},
|
||||
onError: (final e) {
|
||||
printV("rotation pool resync error: $e");
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
onDone: () {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
);
|
||||
await completer.future;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _ensurePoolScanned(final int rotationAccountId) async {
|
||||
var slots = await _transparentSlots(rotationAccountId);
|
||||
final firstSession = _resyncedRotationAccounts.add(rotationAccountId);
|
||||
if (!firstSession && !_poolNeedsRescan(slots)) {
|
||||
return;
|
||||
}
|
||||
if (!firstSession) {
|
||||
final head = slots.where((final e) => e.txCount == 0).firstOrNull?.dindex;
|
||||
final key = '$rotationAccountId:$head';
|
||||
if (_poolRescanAttempted.contains(key)) {
|
||||
return;
|
||||
}
|
||||
_poolRescanAttempted.add(key);
|
||||
}
|
||||
await _ensureLookahead(rotationAccountId);
|
||||
await _scanTransparentChain(rotationAccountId);
|
||||
slots = await _transparentSlots(rotationAccountId);
|
||||
final limit = slots.isEmpty ? _transparentSyncLimit : slots.last.dindex + 1;
|
||||
printV("rotation pool resync account $rotationAccountId limit $limit");
|
||||
await _resyncRotationPool(rotationAccountId, limit);
|
||||
}
|
||||
|
||||
static Map<int, List<Account>> rotationAccounts = {};
|
||||
static Map<int, List<Account>> rotationAccountsUsable = {};
|
||||
static Map<int, List<ShieldedTx>> shieldedAccountsTx = {};
|
||||
static Future<void> init() async {
|
||||
printV("Deserializing previous state");
|
||||
if (_isStarted) {
|
||||
return;
|
||||
}
|
||||
_isStarted = true;
|
||||
final pfw = await pathForWalletTypeDir(type: WalletType.zcash);
|
||||
try {
|
||||
final f = File(p.join(pfw, "zec-taddr.json"));
|
||||
if (!f.existsSync()) {
|
||||
f.writeAsBytesSync(serialize());
|
||||
}
|
||||
deserialize(f.readAsBytesSync());
|
||||
} catch (e) {
|
||||
printV("Failed to deserialize T address DB for zcash, it doesn't really matter: $e");
|
||||
}
|
||||
|
||||
unawaited(
|
||||
(() async {
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
return _jobRunner();
|
||||
})(),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> serializeToFile() async {
|
||||
final pfw = await pathForWalletTypeDir(type: WalletType.zcash);
|
||||
final f = File(p.join(pfw, "zec-taddr.json"));
|
||||
f.writeAsBytesSync(serialize());
|
||||
}
|
||||
|
||||
static Uint8List serialize() {
|
||||
final data = {
|
||||
"rotationAccounts": rotationAccounts.map(
|
||||
(final k, final v) => MapEntry(
|
||||
k.toRadixString(16),
|
||||
v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
|
||||
),
|
||||
),
|
||||
if (kDebugMode)
|
||||
"_rotationAccounts": rotationAccounts.map(
|
||||
(final k, final v) =>
|
||||
MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
|
||||
),
|
||||
"rotationAccountsUsable": rotationAccountsUsable.map(
|
||||
(final k, final v) => MapEntry(
|
||||
k.toRadixString(16),
|
||||
v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
|
||||
),
|
||||
),
|
||||
if (kDebugMode)
|
||||
"_rotationAccountsUsable": rotationAccountsUsable.map(
|
||||
(final k, final v) =>
|
||||
MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
|
||||
),
|
||||
"shieldedAccountsTx": shieldedAccountsTx.map(
|
||||
(final k, final v) => MapEntry(
|
||||
k.toRadixString(16),
|
||||
v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
|
||||
),
|
||||
),
|
||||
if (kDebugMode)
|
||||
"_shieldedAccountsTx": shieldedAccountsTx.map(
|
||||
(final k, final v) =>
|
||||
MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
|
||||
),
|
||||
};
|
||||
|
||||
if (kDebugMode) {
|
||||
return utf8.encode(JsonEncoder.withIndent(" ").convert(data));
|
||||
static Future<int?> getRotationAccountForMainAccount(final int mainAccountId) async {
|
||||
final cached = _rotationAccountByMain[mainAccountId];
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
return utf8.encode(jsonEncode(data));
|
||||
}
|
||||
|
||||
static String flatBuffersPack(final int Function(fb.Builder fbBuilder) pack) {
|
||||
final fbBuilder = fb.Builder();
|
||||
final offset = pack(fbBuilder);
|
||||
fbBuilder.finish(offset);
|
||||
return base64.encode(fbBuilder.buffer);
|
||||
}
|
||||
|
||||
static void deserialize(final Uint8List bytes) {
|
||||
// todo: replace keys with sums and reverse in serialize
|
||||
// final List<String> seeds = [];
|
||||
// for (int i = 0; i < accounts.length; i++) {
|
||||
// final acc = accounts[i];
|
||||
// final backup = WarpApi.getBackup(coin, acc.id);
|
||||
// await WarpApi.transparentSync(coin, acc.id, syncHeight);
|
||||
// seeds.add(backup.seed!);
|
||||
// }
|
||||
try {
|
||||
final Map<dynamic, dynamic> data = jsonDecode(utf8.decode(bytes));
|
||||
rotationAccounts = (data["rotationAccounts"] as Map<String, dynamic>).map(
|
||||
(final k, final v) => MapEntry(
|
||||
int.parse(k, radix: 16),
|
||||
(v as List).map((final a) => Account(atob(a))).toList(),
|
||||
),
|
||||
await ZcashWalletBase.runWithCoinMutex.acquire();
|
||||
final wSeed = await zkool_account.getAccountSeed(c: c, account: mainAccountId);
|
||||
if (wSeed == null) {
|
||||
printV("Not running Taddr rotation - seed not found");
|
||||
return null;
|
||||
}
|
||||
final seed = seedForOffset(wSeed);
|
||||
final accs = await zkool_account.listAccounts(c: c);
|
||||
for (int i = 0; i < accs.length; i++) {
|
||||
final accSeed = await zkool_account.getAccountSeed(c: c, account: accs[i].id);
|
||||
if (accSeed?.mnemonic == seed.mnemonic &&
|
||||
accSeed?.phrase == seed.phrase &&
|
||||
accSeed?.aindex == seed.aindex) {
|
||||
_rotationAccountByMain[mainAccountId] = accs[i].id;
|
||||
return accs[i].id;
|
||||
}
|
||||
}
|
||||
|
||||
zkool_account.Account? acc;
|
||||
for (final a in accs) {
|
||||
if (a.id == mainAccountId) {
|
||||
acc = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (acc == null) {
|
||||
printV("Main account $mainAccountId not found for Taddr rotation");
|
||||
return null;
|
||||
}
|
||||
|
||||
final name = CRC32.compute(wSeed.mnemonic).toString();
|
||||
final id = await newAccount(
|
||||
name: name,
|
||||
seed: seed.mnemonic,
|
||||
passphrase: seed.phrase,
|
||||
height: acc.birth,
|
||||
aindex: seed.aindex,
|
||||
);
|
||||
rotationAccountsUsable = (data["rotationAccountsUsable"] as Map<String, dynamic>).map(
|
||||
(final k, final v) => MapEntry(
|
||||
int.parse(k, radix: 16),
|
||||
(v as List).map((final a) => Account(atob(a))).toList(),
|
||||
),
|
||||
);
|
||||
shieldedAccountsTx = (data["shieldedAccountsTx"] as Map<String, dynamic>).map(
|
||||
(final k, final v) => MapEntry(
|
||||
int.parse(k, radix: 16),
|
||||
(v as List).map((final a) => ShieldedTx(atob(a))).toList(),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
rethrow;
|
||||
_rotationAccountByMain[mainAccountId] = id;
|
||||
return id;
|
||||
} finally {
|
||||
ZcashWalletBase.runWithCoinMutex.release();
|
||||
}
|
||||
}
|
||||
|
||||
static Uint8List atob(final String value) =>
|
||||
Uint8List.fromList(List<int>.from(base64.decode(value)));
|
||||
// External (scope 0) transparent slots for the rotation account, ordered by
|
||||
// dindex. zkool derives every slot in 0..=dindex, so unused ones (txCount==0)
|
||||
// form the disposable look-ahead pool.
|
||||
static Future<List<zkool_account.TAddressTxCount>> _transparentSlots(
|
||||
final int rotationAccountId,
|
||||
) async {
|
||||
final all = await ZcashWalletBase.runWithCoin(
|
||||
accountId: rotationAccountId,
|
||||
func: (final coin) =>
|
||||
zkool_account.fetchAddressTxCount(c: coin, aggregate: false, poolFilter: 1),
|
||||
);
|
||||
return all.where((final e) => e.scope == 0).toList()
|
||||
..sort((final a, final b) => a.dindex.compareTo(b.dindex));
|
||||
}
|
||||
|
||||
// Keep _lookahead unused disposable addresses ready, generating more whenever
|
||||
// the pool runs low. Newly generated addresses are scanned by ongoing sync
|
||||
// (the latest addresses are always in the scan window), so no resync needed.
|
||||
static Future<void> _ensureLookahead(final int rotationAccountId) async {
|
||||
final slots = await _transparentSlots(rotationAccountId);
|
||||
final unused = slots.where((final e) => e.txCount == 0).length;
|
||||
final toGenerate = _lookahead - unused;
|
||||
for (var i = 0; i < toGenerate; i++) {
|
||||
await ZcashWalletBase.runWithCoin(
|
||||
accountId: rotationAccountId,
|
||||
func: (final coin) => zkool_account.generateNextDindex(c: coin),
|
||||
);
|
||||
}
|
||||
if (toGenerate > 0) {
|
||||
printV("topped up disposable taddr pool by $toGenerate (had $unused)");
|
||||
}
|
||||
}
|
||||
|
||||
static bool _isWalletSynced(final int mainAccountId) {
|
||||
final wallet = ZcashWalletBase.walletsByAccountId[mainAccountId];
|
||||
return wallet != null && wallet.syncStatus is SyncedSyncStatus;
|
||||
}
|
||||
|
||||
static Future<BigInt> _rotationTransparentBalance(final int rotationAccount) async {
|
||||
return ZcashWalletBase.runWithCoin(
|
||||
accountId: rotationAccount,
|
||||
func: (final coin) async {
|
||||
final b = await zkool_sync.balance(c: coin);
|
||||
return b.field0.elementAt(0);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<ZkoolTx>> _loadTxHistory(final int accountId) async {
|
||||
return ZcashWalletBase.runWithCoin(
|
||||
accountId: accountId,
|
||||
func: (final coin) async {
|
||||
final txsI = await zkool_account.listTxHistory(c: coin);
|
||||
final txs = <ZkoolTx>[];
|
||||
for (int i = 0; i < txsI.length; i++) {
|
||||
final tx = txsI[i];
|
||||
try {
|
||||
final details = await zkool_account.getTxDetails(idTx: tx.id, c: coin);
|
||||
txs.add(ZkoolTx(tx, details));
|
||||
} catch (e) {
|
||||
printV("getTxDetails skipped for tx ${tx.id}: $e");
|
||||
}
|
||||
}
|
||||
return txs;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static bool _canBroadcastSweep(final int mainAccountId) {
|
||||
final lastSweep = _lastSweepBroadcastAt[mainAccountId];
|
||||
if (lastSweep == null) {
|
||||
return true;
|
||||
}
|
||||
return !lastSweep.isAfter(DateTime.now().subtract(const Duration(seconds: 120)));
|
||||
}
|
||||
|
||||
static Future<void> createAndSweepTAddresses() async {
|
||||
int chainHeight = 0;
|
||||
try {
|
||||
chainHeight = await WarpApi.getLatestHeight(coin);
|
||||
} catch (e) {
|
||||
printV("Error getting latest height: $e");
|
||||
}
|
||||
final dbHeight = WarpApi.getDbHeight(coin);
|
||||
final height = dbHeight.unpack();
|
||||
final syncHeight = height.height;
|
||||
if (chainHeight != syncHeight) {
|
||||
printV(
|
||||
"Waiting for sync to finish: chainHeight(${chainHeight}) != syncHeight(${syncHeight})",
|
||||
);
|
||||
if (_sweepJobRunning) {
|
||||
return;
|
||||
}
|
||||
final Map<int, List<ShieldedTx>> newShieldedAccountsTx = {};
|
||||
final accounts = WarpApi.getAccountList(coin);
|
||||
if (accounts.isEmpty) return;
|
||||
final Map<int, String> seeds = {};
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
final acc = accounts[i];
|
||||
final backup = WarpApi.getBackup(coin, acc.id);
|
||||
await WarpApi.transparentSync(coin, acc.id, syncHeight);
|
||||
if (backup.seed == null) continue;
|
||||
seeds[acc.id] = backup.seed!;
|
||||
}
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
final seed = seeds[accounts[i].id]!;
|
||||
if ([12, 13, 24, 25].contains(seed.split(" ").length)) {
|
||||
if (seed.split(" ").last.contains(":tgen:")) continue;
|
||||
rotationAccounts[accounts[i].id] = [];
|
||||
_sweepJobRunning = true;
|
||||
try {
|
||||
final mainAccountIds = ZcashWalletBase.walletsByAccountId.keys.toList();
|
||||
if (mainAccountIds.isEmpty) {
|
||||
await createAndSweepTAddressesForAccount(c.account);
|
||||
return;
|
||||
}
|
||||
}
|
||||
final raKeys = rotationAccounts.keys.toList();
|
||||
seeds.removeWhere((_, final val) => raKeys.contains(val));
|
||||
printV("raKeys: ${raKeys.length}");
|
||||
for (int i = 0; i < raKeys.length; i++) {
|
||||
final accs = accounts.where((final a) {
|
||||
final seed = WarpApi.getBackup(coin, a.id).seed;
|
||||
return isSeedForWallet(seeds[raKeys[i]], seed);
|
||||
}).toList();
|
||||
rotationAccounts[raKeys[i]]!.addAll(accs);
|
||||
final acc = accountForSeed(seeds[raKeys[i]]!)!;
|
||||
final txs = WarpApi.getTxsSync(coin, acc.id);
|
||||
newShieldedAccountsTx[acc.id] ??= [];
|
||||
newShieldedAccountsTx[acc.id]!.addAll(txs);
|
||||
}
|
||||
rotationAccountsUsable = rotationAccounts.map((final k, final v) => MapEntry(k, v.toList()));
|
||||
printV("rotationAccounts: ${rotationAccounts.length}");
|
||||
|
||||
for (int i = 0; i < raKeys.length; i++) {
|
||||
final acc = accountForSeed(seeds[raKeys[i]]!)!;
|
||||
newShieldedAccountsTx[acc.id] = [];
|
||||
for (int j = 0; j < (rotationAccounts[raKeys[i]]?.length ?? 0); j++) {
|
||||
final txs = WarpApi.getTxsSync(coin, rotationAccounts[raKeys[i]]![j].id);
|
||||
rotationAccounts[raKeys[i]]![j];
|
||||
newShieldedAccountsTx[acc.id]!.addAll(txs);
|
||||
}
|
||||
rotationAccountsUsable[raKeys[i]]!.removeWhere((final a) {
|
||||
final txs = WarpApi.getTxsSync(coin, a.id);
|
||||
return txs.isNotEmpty;
|
||||
});
|
||||
|
||||
// remove hidden addresses
|
||||
final wis = await WalletInfo.selectList('type = ?', [WalletType.zcash.index]);
|
||||
|
||||
final List<String> hiddenAddresses_ = [];
|
||||
for (int k = 0; k < wis.length; k++) {
|
||||
final addrs = await wis[k].getHiddenAddresses();
|
||||
final addr2 = await wis[k].getUsedAddresses();
|
||||
hiddenAddresses_.addAll(addrs);
|
||||
hiddenAddresses_.addAll(addr2);
|
||||
}
|
||||
final Set<String> hiddenAddresses = hiddenAddresses_.toSet();
|
||||
rotationAccountsUsable[raKeys[i]]!.removeWhere((final a) {
|
||||
return hiddenAddresses.contains(WarpApi.getTAddr(coin, a.id));
|
||||
});
|
||||
}
|
||||
printV("rotationAccountsUsable: ${rotationAccountsUsable.length}");
|
||||
|
||||
bool didAddNewAccount = false;
|
||||
for (int i = 0; i < raKeys.length; i++) {
|
||||
if (rotationAccountsUsable[raKeys[i]]!.length < 5) {
|
||||
final seed = seedForOffset(seeds[raKeys[i]]!);
|
||||
final name = CRC32.compute(raKeys[i].toString()).toString();
|
||||
final id = await ZcashWalletService.runInDbMutex(
|
||||
() => WarpApi.newAccount(coin, name, seed, rotationAccounts[raKeys[i]]!.length),
|
||||
);
|
||||
printV("new id: $id / $seed");
|
||||
printV("${rotationAccounts[raKeys[i]]}");
|
||||
printV(raKeys[i]);
|
||||
rotationAccounts[raKeys[i]]!.forEach((final a) {
|
||||
final b = WarpApi.getBackup(coin, a.id);
|
||||
printV("${a.id}");
|
||||
});
|
||||
didAddNewAccount = true;
|
||||
}
|
||||
}
|
||||
printV("didAddNewAccount: ${didAddNewAccount}");
|
||||
if (didAddNewAccount) {
|
||||
return createAndSweepTAddresses();
|
||||
}
|
||||
|
||||
for (var i = 0; i < raKeys.length; i++) {
|
||||
final accs = rotationAccounts[raKeys[i]]!;
|
||||
inner:
|
||||
for (var j = 0; j < accs.length; j++) {
|
||||
int bal = 0;
|
||||
for (final mainAccountId in mainAccountIds) {
|
||||
try {
|
||||
bal += await WarpApi.getTBalance(coin, accs[j].id);
|
||||
} catch (e) {
|
||||
printV("getTBalance: $e");
|
||||
return;
|
||||
await createAndSweepTAddressesForAccount(mainAccountId);
|
||||
} catch (e, s) {
|
||||
printV("createAndSweepTAddresses for $mainAccountId error: $e");
|
||||
s.toString().split("\n").forEach(printV);
|
||||
}
|
||||
if (bal < 30000) continue inner;
|
||||
final to = accountForSeed(seeds[raKeys[i]]!)!;
|
||||
// final toAddress = WarpApi.getTAddr(coin, to.id);
|
||||
// final fromSeed = WarpApi.getBackup(coin, to.id);
|
||||
// final txId = await ZcashWalletService.runInDbMutex(
|
||||
// () => WarpApi.sweepTransparentSeed(
|
||||
// to.coin,
|
||||
// to.id,
|
||||
// chainHeight,
|
||||
// fromSeed.seed!,
|
||||
// 7,
|
||||
// toAddress,
|
||||
// 0,
|
||||
// 1,
|
||||
// FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0),
|
||||
// ),
|
||||
// );
|
||||
final recipientBuilder = RecipientObjectBuilder(
|
||||
address: WarpApi.getAddress(coin, to.id, 4),
|
||||
pools: 4,
|
||||
feeIncluded: true,
|
||||
amount: bal,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_sweepJobRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
final recipient = Recipient(recipientBuilder.toBytes());
|
||||
final fee = FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0);
|
||||
await ZcashWalletBase.loadProver();
|
||||
final txPlan = await ZcashWalletService.runInDbMutex(
|
||||
() => WarpApi.prepareTx(
|
||||
coin,
|
||||
accs[j].id,
|
||||
[recipient],
|
||||
3, // pools: (Transparent + Sapling)
|
||||
1,
|
||||
0, // anchorOffset
|
||||
fee,
|
||||
static Future<void> createAndSweepTAddressesForAccount(final int cId) async {
|
||||
if (!_isWalletSynced(cId)) {
|
||||
printV("rotation sweep waiting for wallet sync (account $cId)");
|
||||
return;
|
||||
}
|
||||
|
||||
printV("createAndSweepTAddresses for account $cId");
|
||||
|
||||
final rotationAccount = await getRotationAccountForMainAccount(cId);
|
||||
if (rotationAccount == null) {
|
||||
printV("rotation account is null, bailing out");
|
||||
return;
|
||||
}
|
||||
|
||||
await _ensureLookahead(rotationAccount);
|
||||
await _ensurePoolScanned(rotationAccount);
|
||||
await _refreshWalletAddresses(cId);
|
||||
|
||||
BigInt transparentBal = BigInt.zero;
|
||||
try {
|
||||
transparentBal = await _rotationTransparentBalance(rotationAccount);
|
||||
printV("rotation account $rotationAccount transparent balance: $transparentBal");
|
||||
} catch (e) {
|
||||
printV("getTBalance: $e");
|
||||
return;
|
||||
}
|
||||
|
||||
if (transparentBal < BigInt.from(_sweepThreshold)) {
|
||||
await updateCache(mainAccountId: cId);
|
||||
await _refreshWalletAfterRotationChange(cId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_canBroadcastSweep(cId)) {
|
||||
printV("rotation sweep rate-limited for account $cId");
|
||||
await updateCache(mainAccountId: cId);
|
||||
await _refreshWalletAfterRotationChange(cId);
|
||||
return;
|
||||
}
|
||||
|
||||
final toAddress = await ZcashWalletBase.runWithCoin(
|
||||
accountId: cId,
|
||||
func: (final coin) async {
|
||||
final addrs = await zkool_account.getAddresses(c: coin, uaPools: 7);
|
||||
final orchard = addrs.oaddr;
|
||||
if (orchard == null || orchard.isEmpty) {
|
||||
throw Exception('Orchard address unavailable for rotation sweep');
|
||||
}
|
||||
return orchard;
|
||||
},
|
||||
);
|
||||
final result = await ZcashWalletBase.runWithCoin(
|
||||
accountId: rotationAccount,
|
||||
func: (final coin) async {
|
||||
final tx = await zkool_pay.prepare(
|
||||
recipients: [
|
||||
zkool_paydart.Recipient(assetBase: zecBase, address: toAddress, amount: transparentBal),
|
||||
],
|
||||
options: zkool_pay.PaymentOptions(
|
||||
srcPools: 1,
|
||||
recipientPaysFee: true,
|
||||
smartTransparent: false,
|
||||
),
|
||||
c: coin,
|
||||
);
|
||||
final _txId = await ZcashWalletService.runInDbMutex(
|
||||
() => WarpApi.signAndBroadcast(ZcashWalletBase.coin, accs[j].id, txPlan),
|
||||
);
|
||||
await ZcashWalletService.addShieldedTx(_txId);
|
||||
printV("shielded: $_txId");
|
||||
final height = await zkool_network.getCurrentHeight(c: coin);
|
||||
final signTx = await zkool_pay.signTransaction(pczt: tx, c: coin);
|
||||
final txBytes = await zkool_pay.extractTransaction(package: signTx);
|
||||
return zkool_pay.broadcastTransaction(height: height, txBytes: txBytes, c: coin);
|
||||
},
|
||||
);
|
||||
printV("rotation sweep broadcast: $result");
|
||||
if (result.isEmpty) {
|
||||
throw Exception("rotation sweep broadcast failed");
|
||||
}
|
||||
|
||||
await Future.delayed(Duration(seconds: 120)); // let it be
|
||||
return createAndSweepTAddresses();
|
||||
}
|
||||
}
|
||||
final nsatKeys = newShieldedAccountsTx.keys.toList();
|
||||
for (int i = 0; i < nsatKeys.length; i++) {
|
||||
inner:
|
||||
for (int j = 0; j < newShieldedAccountsTx[nsatKeys[i]]!.length; j++) {
|
||||
final tx = newShieldedAccountsTx[nsatKeys[i]]?[j];
|
||||
if (tx == null) {
|
||||
continue inner;
|
||||
}
|
||||
final _txId = tx.txId;
|
||||
if (_txId == null) {
|
||||
continue inner;
|
||||
}
|
||||
if (tx.value < 0) {
|
||||
await ZcashWalletService.addShieldedTx(_txId);
|
||||
// continue inner;
|
||||
}
|
||||
if (ZcashWalletService.autoshieldTx.contains(_txId)) {
|
||||
continue inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
shieldedAccountsTx = newShieldedAccountsTx.map((final k, final v) => MapEntry(k, v.toList()));
|
||||
await serializeToFile();
|
||||
return;
|
||||
await ZcashWalletService.addShieldedTx(result);
|
||||
_lastSweepBroadcastAt[cId] = DateTime.now();
|
||||
await updateCache(mainAccountId: cId);
|
||||
await _refreshWalletAfterRotationChange(cId);
|
||||
}
|
||||
|
||||
static ShieldedTx reverseAmount(final ShieldedTx t) {
|
||||
final tt = t.unpack();
|
||||
tt.value = -tt.value;
|
||||
return ShieldedTx(base64.decode(flatBuffersPack(tt.pack)));
|
||||
static Future<void> _refreshWalletAfterRotationChange(final int mainAccountId) async {
|
||||
final wallet = ZcashWalletBase.walletsByAccountId[mainAccountId];
|
||||
if (wallet == null) {
|
||||
return;
|
||||
}
|
||||
unawaited(wallet.updateTransactions());
|
||||
unawaited(wallet.updateBalance());
|
||||
}
|
||||
|
||||
static Account? accountForSeed(final String seed) {
|
||||
final accounts = WarpApi.getAccountList(coin);
|
||||
static Future<void> updateCache({required final int mainAccountId}) async {
|
||||
final rotationAccount = await getRotationAccountForMainAccount(mainAccountId);
|
||||
if (rotationAccount == null) {
|
||||
printV("rotationAccount is null");
|
||||
return;
|
||||
}
|
||||
await _ensureLookahead(rotationAccount);
|
||||
await _ensurePoolScanned(rotationAccount);
|
||||
final txs = await _loadTxHistory(rotationAccount);
|
||||
shieldedAccountsTx[mainAccountId] = txs;
|
||||
if (mainAccountId != rotationAccount && shieldedAccountsTx.containsKey(rotationAccount)) {
|
||||
shieldedAccountsTx.remove(rotationAccount);
|
||||
}
|
||||
|
||||
for (final tx in shieldedAccountsTx[mainAccountId]!) {
|
||||
if (tx.direction == TransactionDirection.outgoing) {
|
||||
await ZcashWalletService.addShieldedTx(tx.txHash);
|
||||
}
|
||||
}
|
||||
await _refreshWalletAddresses(mainAccountId);
|
||||
await _refreshWalletAfterRotationChange(mainAccountId);
|
||||
}
|
||||
|
||||
static Future<void> _refreshWalletAddresses(final int mainAccountId) async {
|
||||
final wallet = ZcashWalletBase.walletsByAccountId[mainAccountId];
|
||||
if (wallet == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await wallet.walletAddresses.refreshRotationAddresses();
|
||||
} catch (e) {
|
||||
printV("rotation address refresh: $e");
|
||||
}
|
||||
}
|
||||
|
||||
static List<ZkoolTx> rotationTxsForMainAccount(final int mainAccountId) {
|
||||
return shieldedAccountsTx[mainAccountId] ?? const [];
|
||||
}
|
||||
|
||||
static Future<zkool_account.Account?> accountForSeed(final zkool_account.Seed seed) async {
|
||||
final accounts = await zkool_account.listAccounts(c: c);
|
||||
if (accounts.isEmpty) return null;
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
final acc = accounts[i];
|
||||
final backup = WarpApi.getBackup(coin, acc.id);
|
||||
if (backup.seed == seed) {
|
||||
final backup = await zkool_account.getAccountSeed(c: c, account: acc.id);
|
||||
if (backup?.mnemonic == seed.mnemonic && backup?.phrase == seed.phrase) {
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
@@ -383,63 +499,76 @@ class ZcashTaddressRotation {
|
||||
static Future<void> _jobRunner() async {
|
||||
for (;;) {
|
||||
try {
|
||||
await Future.delayed(Duration(seconds: 5));
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
await createAndSweepTAddresses();
|
||||
} catch (e, s) {
|
||||
printV(e);
|
||||
printV("createAndSweepTAddresses error: $e");
|
||||
s.toString().split("\n").forEach(printV);
|
||||
rethrow;
|
||||
} finally {
|
||||
await Future.delayed(Duration(seconds: 30));
|
||||
await Future.delayed(const Duration(seconds: 30));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String? addressForAccount(final int accountId, final Set<String>? hiddenAddresses) {
|
||||
final accs = rotationAccountsUsable[accountId] ?? [];
|
||||
for (int i = 0; i < accs.length; i++) {
|
||||
final addr = WarpApi.getTAddr(coin, accs[i].id);
|
||||
if (hiddenAddresses?.contains(addr) == true) {
|
||||
continue;
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<String>? allAddressesForAccount(final int accountId) {
|
||||
final acc = rotationAccounts[accountId];
|
||||
if (acc == null) {
|
||||
printV("Nothing found");
|
||||
// Current disposable address = first unused slot (lowest dindex).
|
||||
static Future<String?> addressForAccount(final int accountId) async {
|
||||
final rotationAccount = await getRotationAccountForMainAccount(accountId);
|
||||
if (rotationAccount == null) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < acc.length; i++) {
|
||||
final b = WarpApi.getBackup(coin, acc[i].id);
|
||||
printV("$i. ${b.seed?.split(" ").last}, ${b.index}, ${WarpApi.getTAddr(coin, acc[i].id)}");
|
||||
final slots = await _transparentSlots(rotationAccount);
|
||||
final unused = slots.where((final e) => e.txCount == 0);
|
||||
if (unused.isNotEmpty) {
|
||||
return unused.first.address;
|
||||
}
|
||||
return acc.map((final a) => WarpApi.getTAddr(coin, a.id)).toList();
|
||||
return slots.isNotEmpty ? slots.last.address : null;
|
||||
}
|
||||
|
||||
static List<String>? allUsedAddressesForAccount(final int accountId) {
|
||||
final seed = WarpApi.getBackup(coin, accountId).seed;
|
||||
if (seed == null) return [];
|
||||
final acc = rotationAccounts[seed]?.toList();
|
||||
if (acc == null) {
|
||||
printV("Nothing found");
|
||||
return null;
|
||||
static Future<List<String>> usedAddressesForAccount(final int accountId) async {
|
||||
final rotationAccount = await getRotationAccountForMainAccount(accountId);
|
||||
if (rotationAccount == null) {
|
||||
return const [];
|
||||
}
|
||||
acc.removeWhere((final a1) {
|
||||
for (int i = 0; i < (rotationAccountsUsable[seed]?.length ?? 0); i++) {
|
||||
if (rotationAccountsUsable[seed]?[i].id == a1.id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
for (int i = 0; i < acc.length; i++) {
|
||||
final b = WarpApi.getBackup(coin, acc[i].id);
|
||||
printV("$i. ${b.seed?.split(" ").last}, ${b.index}, ${WarpApi.getTAddr(coin, acc[i].id)}");
|
||||
final slots = await _transparentSlots(rotationAccount);
|
||||
return [
|
||||
for (final entry in slots)
|
||||
if (entry.txCount > 0) entry.address,
|
||||
];
|
||||
}
|
||||
|
||||
// The full disposable pool: used slots plus the unused look-ahead window.
|
||||
static Future<List<String>> allAddressesForAccount(final int accountId) async {
|
||||
final rotationAccount = await getRotationAccountForMainAccount(accountId);
|
||||
if (rotationAccount == null) {
|
||||
return const [];
|
||||
}
|
||||
return acc.map((final a) => WarpApi.getTAddr(coin, a.id)).toList();
|
||||
final slots = await _transparentSlots(rotationAccount);
|
||||
return slots.map((final e) => e.address).toList();
|
||||
}
|
||||
|
||||
static Future<int> newAccount({
|
||||
required final String name,
|
||||
required final String seed,
|
||||
required final String passphrase,
|
||||
required final int height,
|
||||
required final int aindex,
|
||||
}) async {
|
||||
final id = await zkool_account.newAccount(
|
||||
na: zkool_account.NewAccount(
|
||||
name: name,
|
||||
restore: true,
|
||||
passphrase: passphrase,
|
||||
key: seed,
|
||||
aindex: aindex,
|
||||
birth: height,
|
||||
folder: '',
|
||||
pools: 1,
|
||||
useInternal: true,
|
||||
internal: false,
|
||||
ledger: false,
|
||||
),
|
||||
c: c,
|
||||
);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_zcash/cw_zcash.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:cw_zcash/src/zkooltx.dart';
|
||||
|
||||
class ZcashTransactionInfo extends TransactionInfo {
|
||||
ZcashTransactionInfo({
|
||||
@@ -23,6 +17,9 @@ class ZcashTransactionInfo extends TransactionInfo {
|
||||
required final int confirmations,
|
||||
required final String to,
|
||||
final String? memo,
|
||||
final TxType? txType,
|
||||
final bool isRotationReceive = false,
|
||||
final bool isShieldAction = false,
|
||||
}) {
|
||||
this.id = id;
|
||||
this.amount = amount;
|
||||
@@ -32,23 +29,21 @@ class ZcashTransactionInfo extends TransactionInfo {
|
||||
this.date = date;
|
||||
this.isPending = isPending;
|
||||
this.confirmations = confirmations;
|
||||
this.to = getCachedDestinationAddress(id);
|
||||
this.to = to;
|
||||
if (memo != null && memo.isNotEmpty) {
|
||||
additionalInfo['memo'] = memo;
|
||||
}
|
||||
additionalInfo['autoShield'] = false;
|
||||
// note: this won't work yet, fee is not in zcash_lib metadata,
|
||||
// leaving here so it can start working automagically in future
|
||||
if (amount == fee && to.startsWith("u")) {
|
||||
additionalInfo['autoShield'] = true;
|
||||
if (txType != null) {
|
||||
additionalInfo['txType'] = txType.name;
|
||||
}
|
||||
// remove below when above starts working
|
||||
additionalInfo['autoShield'] = ZcashWalletService.autoshieldTx.contains(txHash);
|
||||
// --- == === cut here === == ---
|
||||
if (additionalInfo['autoShield'] == true) {
|
||||
additionalInfo['isRotationReceive'] = isRotationReceive;
|
||||
additionalInfo['isAutoShield'] = isShieldAction || ZcashWalletService.isAutoshieldTx(txHash);
|
||||
|
||||
if (additionalInfo['isAutoShield'] == true) {
|
||||
additionalInfo['memo'] ??= '';
|
||||
additionalInfo['memo'] += '\This is an auto-shielding transaction. Enjoy default privacy!';
|
||||
additionalInfo['memo'] = additionalInfo['memo'].trim();
|
||||
additionalInfo['memo'] =
|
||||
'${additionalInfo['memo']}\nThis is an auto-shielding transaction. Enjoy default privacy!.'
|
||||
.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,35 +56,4 @@ class ZcashTransactionInfo extends TransactionInfo {
|
||||
void changeFiatAmount(final String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
String? get memo => additionalInfo['memo'] as String?;
|
||||
|
||||
static final Map<String, String> _destinationAddressMap = {};
|
||||
|
||||
static String? getCachedDestinationAddress(final String txId) {
|
||||
return _destinationAddressMap[txId] ??
|
||||
_destinationAddressMap['"$txId"'] ??
|
||||
_destinationAddressMap[txId.replaceAll('"', '')];
|
||||
}
|
||||
|
||||
static Future<void> addCachedDestinationAddress(final String txId, final String address) async {
|
||||
_destinationAddressMap[txId] = address;
|
||||
final pfwt = await pathForWalletTypeDir(type: WalletType.zcash);
|
||||
final f = File(p.join(pfwt, "sent-tx-map.json"));
|
||||
f.writeAsStringSync(json.encode(_destinationAddressMap));
|
||||
}
|
||||
|
||||
static Future<void> init() async {
|
||||
try {
|
||||
final pfwt = await pathForWalletTypeDir(type: WalletType.zcash);
|
||||
final f = File(p.join(pfwt, "sent-tx-map.json"));
|
||||
if (!f.existsSync()) {
|
||||
f.writeAsStringSync('{}');
|
||||
}
|
||||
final tmpMap = json.decode(f.readAsStringSync());
|
||||
tmpMap.forEach((final k, final v) {
|
||||
_destinationAddressMap[k.toString()] = v.toString();
|
||||
});
|
||||
} catch (e, s) {
|
||||
printV("failed to deserialize: $e, $s");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1082
-764
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_zcash/cw_zcash.dart';
|
||||
import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:warp_api/warp_api.dart';
|
||||
import 'package:zkool/src/rust/api/account.dart' as zkool_account;
|
||||
|
||||
part 'zcash_wallet_addresses.g.dart';
|
||||
|
||||
@@ -17,24 +17,34 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
|
||||
addressPageType = ZcashReceivePageOption.typeFromString(walletInfo.addressPageType ?? "");
|
||||
}
|
||||
|
||||
final int accountId;
|
||||
int accountId;
|
||||
|
||||
static const int transparentType = 0;
|
||||
static const int shieldedSaplingType = 1;
|
||||
static const int shieldedOrchardType = 2;
|
||||
static const int unifiedType = 3;
|
||||
|
||||
String? _transparentAddress;
|
||||
String? _saplingAddress;
|
||||
String? _orchardAddress;
|
||||
String? _unifiedAddress;
|
||||
@observable
|
||||
String? transparentAddress;
|
||||
|
||||
@observable
|
||||
String? _transparentObservableAddress;
|
||||
|
||||
@observable
|
||||
String? saplingAddress;
|
||||
|
||||
@observable
|
||||
String? orchardAddress;
|
||||
|
||||
@observable
|
||||
String? unifiedAddress;
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get latestAddress {
|
||||
switch (addressPageType) {
|
||||
case ZcashAddressType.transparent:
|
||||
return transparentAddress;
|
||||
return transparentAddress ?? "unknown transparentAddress";
|
||||
case ZcashAddressType.transparentRotated:
|
||||
// just to display something, after the wallet is synced the address
|
||||
// will start rotating, and since we can't rotate address until the wallet syncs
|
||||
@@ -46,16 +56,15 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
|
||||
// 3) display first address from rotated pool which is also static until synced
|
||||
// 2 seems like an obvious winner simply because it offers little to no benefit over 3
|
||||
// and is noticably less complex in implementation
|
||||
return transparentAddressRotated ?? transparentAddress;
|
||||
return _transparentObservableAddress ??
|
||||
transparentAddress ??
|
||||
"unknown transparentAddressRotated";
|
||||
case ZcashAddressType.shieldedSapling:
|
||||
return saplingAddress;
|
||||
return saplingAddress ?? "unknown saplingAddress";
|
||||
case ZcashAddressType.shieldedOrchard:
|
||||
return orchardAddress;
|
||||
return orchardAddress ?? "unknown orchardAddress";
|
||||
case ZcashAddressType.unifiedType:
|
||||
if (_unifiedAddress == null) {
|
||||
_unifiedAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 6);
|
||||
}
|
||||
return _unifiedAddress ?? "";
|
||||
return unifiedAddress ?? "unknown unifiedAddress";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,63 +88,21 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
|
||||
await walletInfo.save();
|
||||
}
|
||||
|
||||
String get transparentAddress {
|
||||
try {
|
||||
if (_transparentAddress == null) {
|
||||
_transparentAddress = WarpApi.getTAddr(ZcashWalletBase.coin, accountId);
|
||||
}
|
||||
return _transparentAddress ?? "";
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@computed
|
||||
String? get transparentAddressRotated {
|
||||
try {
|
||||
return ZcashTaddressRotation.addressForAccount(accountId, hiddenAddresses);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String get saplingAddress {
|
||||
try {
|
||||
if (_saplingAddress == null) {
|
||||
_saplingAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 2);
|
||||
}
|
||||
return _saplingAddress ?? "";
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
String get orchardAddress {
|
||||
try {
|
||||
if (_orchardAddress == null) {
|
||||
_orchardAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 4);
|
||||
}
|
||||
return _orchardAddress ?? "";
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
String get addressForExchange =>
|
||||
_transparentObservableAddress ?? transparentAddress ?? "unknown addressForExchange";
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get addressForExchange => transparentAddressRotated ?? transparentAddress;
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get addressForBuy => transparentAddressRotated ?? transparentAddress;
|
||||
String get addressForBuy =>
|
||||
_transparentObservableAddress ?? transparentAddress ?? "unknown addressForBuy";
|
||||
|
||||
@override
|
||||
bool containsAddress(final String address) {
|
||||
return this.address == address || addressesMap.values.contains(address);
|
||||
}
|
||||
|
||||
static int get coin => ZcashWalletBase.coin;
|
||||
|
||||
@override
|
||||
@observable
|
||||
Set<String> hiddenAddresses = {};
|
||||
@@ -149,37 +116,105 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
|
||||
address = latestAddress;
|
||||
}
|
||||
|
||||
Future<void> _syncRotationHiddenAddresses() async {
|
||||
final all = (await ZcashTaddressRotation.allAddressesForAccount(accountId)).toSet();
|
||||
final used = (await ZcashTaddressRotation.usedAddressesForAccount(accountId)).toSet();
|
||||
hiddenAddresses.removeWhere(all.contains);
|
||||
hiddenAddresses.addAll(used);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
try {
|
||||
await _init();
|
||||
} catch (e) {
|
||||
printV("init failed, retrying in 2 seconds");
|
||||
printV(e);
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
await init();
|
||||
}
|
||||
}
|
||||
|
||||
static bool isPlaceholderAddress(final String? address) =>
|
||||
address == null || address.isEmpty || address.startsWith('unknown ');
|
||||
|
||||
String _resolvedWalletInfoAddress() {
|
||||
for (final candidate in [unifiedAddress, orchardAddress, saplingAddress, transparentAddress]) {
|
||||
if (!isPlaceholderAddress(candidate)) {
|
||||
return candidate!;
|
||||
}
|
||||
}
|
||||
return latestAddress;
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
await _initAddresses();
|
||||
|
||||
await ZcashTaddressRotation.init();
|
||||
int accountIndex = 0;
|
||||
addressInfos = {
|
||||
0:
|
||||
ZcashTaddressRotation.allAddressesForAccount(accountId)?.map((final v) {
|
||||
return WalletInfoAddressInfo(
|
||||
walletInfoId: walletInfo.internalId,
|
||||
mapKey: ++accountIndex,
|
||||
accountIndex: 0,
|
||||
address: v,
|
||||
label: "",
|
||||
);
|
||||
}).toList() ??
|
||||
[],
|
||||
};
|
||||
hiddenAddresses.addAll(
|
||||
ZcashTaddressRotation.allUsedAddressesForAccount(accountId)?.toSet() ?? {},
|
||||
final addr = await ZcashWalletBase.runWithCoin(
|
||||
accountId: accountId,
|
||||
func: (final coin) => zkool_account.getAddresses(c: coin, uaPools: 7),
|
||||
);
|
||||
|
||||
// addressInfos[0]?.removeWhere((final test) => hiddenAddresses.contains(test.address));
|
||||
transparentAddress = addr.taddr ?? 'unknown addr.taddr';
|
||||
saplingAddress = addr.saddr ?? 'unknown addr.saddr';
|
||||
orchardAddress = addr.oaddr ?? 'unknown addr.oaddr';
|
||||
unifiedAddress = addr.ua ?? 'unknown addr.ua';
|
||||
_transparentObservableAddress = await ZcashTaddressRotation.addressForAccount(accountId);
|
||||
final rotationAddrs = await ZcashTaddressRotation.allAddressesForAccount(accountId);
|
||||
addressInfos = {
|
||||
0: [
|
||||
for (int i = 0; i < rotationAddrs.length; i++)
|
||||
WalletInfoAddressInfo(
|
||||
walletInfoId: walletInfo.internalId,
|
||||
mapKey: i + 1,
|
||||
accountIndex: 0,
|
||||
address: rotationAddrs[i],
|
||||
label: "",
|
||||
),
|
||||
],
|
||||
};
|
||||
await _syncRotationHiddenAddresses();
|
||||
await _applyAddressForCurrentType();
|
||||
|
||||
if (isPlaceholderAddress(walletInfo.address)) {
|
||||
final resolved = _resolvedWalletInfoAddress();
|
||||
if (!isPlaceholderAddress(resolved)) {
|
||||
walletInfo.address = resolved;
|
||||
await walletInfo.save();
|
||||
}
|
||||
}
|
||||
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
Future<void> refreshRotationAddresses() async {
|
||||
_transparentObservableAddress = await ZcashTaddressRotation.addressForAccount(accountId);
|
||||
final rotationAddrs = await ZcashTaddressRotation.allAddressesForAccount(accountId);
|
||||
addressInfos = {
|
||||
0: [
|
||||
for (int i = 0; i < rotationAddrs.length; i++)
|
||||
WalletInfoAddressInfo(
|
||||
walletInfoId: walletInfo.internalId,
|
||||
mapKey: i + 1,
|
||||
accountIndex: 0,
|
||||
address: rotationAddrs[i],
|
||||
label: "",
|
||||
),
|
||||
],
|
||||
};
|
||||
await _syncRotationHiddenAddresses();
|
||||
await _applyAddressForCurrentType();
|
||||
}
|
||||
|
||||
Future<void> _applyAddressForCurrentType() async {
|
||||
if (_addressPageType == ZcashAddressType.transparentRotated) {
|
||||
final addr = ZcashTaddressRotation.addressForAccount(accountId, hiddenAddresses);
|
||||
final addr = await ZcashTaddressRotation.addressForAccount(accountId);
|
||||
if (addr != null) {
|
||||
address = addr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
await saveAddressesInBox();
|
||||
address = latestAddress;
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -198,12 +233,17 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
|
||||
@override
|
||||
Future<void> saveAddressesInBox() async {
|
||||
try {
|
||||
if (!isPlaceholderAddress(address)) {
|
||||
walletInfo.address = address;
|
||||
}
|
||||
await walletInfo.setAddresses(addressesMap);
|
||||
await walletInfo.setAddressInfos(addressInfos);
|
||||
await walletInfo.setUsedAddresses(usedAddresses.toList());
|
||||
await walletInfo.setHiddenAddresses(hiddenAddresses.toList());
|
||||
await walletInfo.setManualAddresses(manualAddresses.toList());
|
||||
await walletInfo.save();
|
||||
await _initAddresses();
|
||||
await _syncRotationHiddenAddresses();
|
||||
} catch (e) {
|
||||
printV("Error saving addresses: $e");
|
||||
}
|
||||
@@ -211,10 +251,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
_transparentAddress = null;
|
||||
_saplingAddress = null;
|
||||
_orchardAddress = null;
|
||||
_unifiedAddress = null;
|
||||
await _initAddresses();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -227,11 +264,7 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
|
||||
if (addressPageType != ZcashAddressType.transparentRotated) {
|
||||
return [];
|
||||
}
|
||||
final List<WalletInfoAddressInfo> allInfos = [];
|
||||
for (final entry in addressInfos.entries) {
|
||||
allInfos.addAll(entry.value);
|
||||
}
|
||||
return allInfos;
|
||||
return addressInfos[0] ?? [];
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_zcash/cw_zcash.dart';
|
||||
import 'package:cw_zcash/src/migrate.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
@@ -25,9 +26,9 @@ class ZcashWalletService
|
||||
static Mutex dbMutex = Mutex();
|
||||
static int dbMutexQueue = 0;
|
||||
static Future<T> runInDbMutex<T>(final Future<T> Function() call) async {
|
||||
dbMutexQueue++;
|
||||
await dbMutex.acquire();
|
||||
try {
|
||||
printV("dbMutexQueue: ${dbMutexQueue++}");
|
||||
await dbMutex.acquire();
|
||||
return await call();
|
||||
} finally {
|
||||
dbMutex.release();
|
||||
@@ -37,10 +38,15 @@ class ZcashWalletService
|
||||
|
||||
static Set<String> autoshieldTx = {};
|
||||
|
||||
static String normalizeTxId(final String txId) =>
|
||||
txId.replaceAll(RegExp(r'[^a-fA-F0-9]'), '').toLowerCase();
|
||||
|
||||
static bool isAutoshieldTx(final String txHash) => autoshieldTx.contains(normalizeTxId(txHash));
|
||||
|
||||
static Future<void> addShieldedTx(final String txId) async {
|
||||
final pathForWalletType = await pathForWalletTypeDir(type: type);
|
||||
final shieldedListFile = p.join(pathForWalletType, "autoshield_list.txt");
|
||||
autoshieldTx.add(txId.replaceAll(RegExp(r'[^a-zA-Z0-9 ]'), ''));
|
||||
final shieldedListFile = p.join(pathForWalletType, "autoshield_list.v2.txt");
|
||||
autoshieldTx.add(normalizeTxId(txId));
|
||||
final shieldedList = File(shieldedListFile);
|
||||
shieldedList.writeAsStringSync(autoshieldTx.join("\n"));
|
||||
}
|
||||
@@ -48,12 +54,16 @@ class ZcashWalletService
|
||||
static Future<void> loadShieldTxs() async {
|
||||
try {
|
||||
final pathForWalletType = await pathForWalletTypeDir(type: type);
|
||||
final shieldedListFile = p.join(pathForWalletType, "autoshield_list.txt");
|
||||
final shieldedListFile = p.join(pathForWalletType, "autoshield_list.v2.txt");
|
||||
final shieldedList = File(shieldedListFile);
|
||||
if (!shieldedList.existsSync()) {
|
||||
return;
|
||||
}
|
||||
autoshieldTx = shieldedList.readAsLinesSync().toSet();
|
||||
autoshieldTx = shieldedList
|
||||
.readAsLinesSync()
|
||||
.map(normalizeTxId)
|
||||
.where((final line) => line.isNotEmpty)
|
||||
.toSet();
|
||||
} catch (e) {
|
||||
printV("loadShieldTxs failed: $e");
|
||||
}
|
||||
@@ -74,12 +84,21 @@ class ZcashWalletService
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(final String name) async {
|
||||
final path = await pathForWallet(name: name, type: getType());
|
||||
return File(path).existsSync();
|
||||
final oldPath = (await pathForWallet(name: name, type: getType()));
|
||||
final path = (await pathForWallet(name: name, type: getType())) + ".v2";
|
||||
return File(path).existsSync() || File(oldPath).existsSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ZcashWallet> openWallet(final String name, final String password) async {
|
||||
await ZcashWalletBase.$init();
|
||||
if (await isWalletExit(name)) {
|
||||
final path = (await pathForWallet(name: name, type: getType())) + ".v2";
|
||||
if (!File(path).existsSync()) {
|
||||
await migrateOldSqliteToZkool2(walletName: name);
|
||||
}
|
||||
}
|
||||
|
||||
await loadShieldTxs();
|
||||
final walletInfo = await WalletInfo.get(name, getType());
|
||||
if (walletInfo == null) {
|
||||
@@ -108,7 +127,7 @@ class ZcashWalletService
|
||||
|
||||
@override
|
||||
Future<void> remove(final String wallet) async {
|
||||
final path = await pathForWalletDir(name: wallet, type: getType());
|
||||
final path = (await pathForWalletDir(name: wallet, type: getType()));
|
||||
final file = Directory(path);
|
||||
final isExist = file.existsSync();
|
||||
|
||||
@@ -129,17 +148,11 @@ class ZcashWalletService
|
||||
if (currentWalletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final accountId = await ZcashWalletBase.getZcashAccountIdForName(currentName);
|
||||
if (accountId == null) {
|
||||
throw Exception('Wallet account not found for name: $currentName');
|
||||
if (!await isWalletExit(currentName)) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final currentWallet = ZcashWallet(
|
||||
currentWalletInfo,
|
||||
await currentWalletInfo.getDerivationInfo(),
|
||||
accountId: accountId,
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await ZcashWalletBase.renameWalletFilesForName(fromName: currentName, toName: newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
@@ -162,11 +175,11 @@ class ZcashWalletService
|
||||
final bool? isTestnet,
|
||||
}) async {
|
||||
if (credentials.seed == null || credentials.seed!.isEmpty) {
|
||||
throw ZcashMnemonicIsIncorrectException();
|
||||
throw Exception("Seed is missing");
|
||||
}
|
||||
|
||||
if (!bip39.validateMnemonic(credentials.seed!)) {
|
||||
throw ZcashMnemonicIsIncorrectException();
|
||||
throw Exception("Seed is not valid bip39");
|
||||
}
|
||||
|
||||
return ZcashWalletBase.restore(credentials);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
final zecBase = Uint8List(32);
|
||||
@@ -0,0 +1,312 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:cw_core/get_height_by_date_zec.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_zcash/src/util/hex.dart';
|
||||
import 'package:zkool/src/rust/api/account.dart' as zkool_account;
|
||||
|
||||
enum TxType {
|
||||
unknown,
|
||||
selfTransfer, // 0
|
||||
receive, // 1
|
||||
sent, // 2
|
||||
unshield, // 4
|
||||
shield, // 8
|
||||
transparentSelfTransfer, // 12
|
||||
}
|
||||
|
||||
enum NotePool { transparent, sapling, orchard, unknown }
|
||||
|
||||
class ZkoolTx {
|
||||
ZkoolTx(final zkool_account.Tx tx, final zkool_account.TxAccount txAccount)
|
||||
: this._tx = tx,
|
||||
this._txAccount = txAccount;
|
||||
final zkool_account.Tx _tx;
|
||||
final zkool_account.TxAccount _txAccount;
|
||||
|
||||
int get height => max(
|
||||
_tx.height,
|
||||
_txAccount.height,
|
||||
); // sometimes returns 0 - higher number would be correct in this case
|
||||
|
||||
DateTime get time {
|
||||
final ts = max(_tx.time, _txAccount.time) * 1000;
|
||||
if (ts == 0) {
|
||||
return ZcashHeight.getTimeByBlockHeight(height);
|
||||
}
|
||||
return DateTime.fromMillisecondsSinceEpoch(ts);
|
||||
}
|
||||
|
||||
String? get to => _txAccount.outputs.firstOrNull?.address;
|
||||
Iterable<String> get outputAddresses =>
|
||||
_txAccount.outputs.map((final o) => o.address).whereType<String>();
|
||||
Iterable<({String address, int pool, BigInt value})> get outputsWithAddress => _txAccount.outputs
|
||||
.where((final o) => o.address.isNotEmpty)
|
||||
.map((final o) => (address: o.address, pool: o.pool, value: o.value));
|
||||
Iterable<int> get spendPools => _txAccount.spends.map((final s) => s.pool);
|
||||
Iterable<int> get notePools => _txAccount.notes.map((final n) => n.pool);
|
||||
String? get memo => _txAccount.memos.firstOrNull?.memo;
|
||||
|
||||
BigInt get transparentOrSaplingSpent {
|
||||
if (_txAccount.spends.isEmpty) {
|
||||
return BigInt.zero;
|
||||
}
|
||||
return _txAccount.spends
|
||||
.where(
|
||||
(final s) => s.pool == NotePool.transparent.index || s.pool == NotePool.sapling.index,
|
||||
)
|
||||
.fold(BigInt.zero, (final a, final s) => a + s.value);
|
||||
}
|
||||
|
||||
BigInt get orchardReceived {
|
||||
final fromNotes = _txAccount.notes
|
||||
.where((final n) => n.pool == NotePool.orchard.index)
|
||||
.fold(BigInt.zero, (final a, final n) => a + n.value);
|
||||
if (fromNotes > BigInt.zero) {
|
||||
return fromNotes;
|
||||
}
|
||||
return _txAccount.outputs
|
||||
.where((final o) => o.pool == NotePool.orchard.index)
|
||||
.fold(BigInt.zero, (final a, final o) => a + o.value);
|
||||
}
|
||||
|
||||
String get txHash {
|
||||
final reversed = Uint8List.fromList(_txAccount.txid.reversed.toList());
|
||||
final txId = uint8ListToHex(reversed);
|
||||
return txId;
|
||||
}
|
||||
|
||||
BigInt get value => _value.abs();
|
||||
|
||||
BigInt get fee => value - _calcValue;
|
||||
|
||||
BigInt get _value {
|
||||
if (_tx.value != 0) {
|
||||
return BigInt.from(_tx.value);
|
||||
}
|
||||
|
||||
return _calcValue;
|
||||
}
|
||||
|
||||
/////////// calc is short for calculator (calculated in this case)
|
||||
BigInt get _calcValue {
|
||||
final noteSum = _txAccount.notes.isEmpty
|
||||
? BigInt.from(0)
|
||||
: _txAccount.notes.map((final note) => note.value).reduce((final a, final b) => a + b);
|
||||
final outputSum = _txAccount.outputs.isEmpty
|
||||
? BigInt.from(0)
|
||||
: _txAccount.outputs
|
||||
.map((final output) => output.value)
|
||||
.reduce((final a, final b) => a + b);
|
||||
final spentSum = _txAccount.spends.isEmpty
|
||||
? BigInt.from(0)
|
||||
: _txAccount.spends.map((final spent) => spent.value).reduce((final a, final b) => a + b);
|
||||
|
||||
return noteSum - outputSum - spentSum;
|
||||
}
|
||||
|
||||
TransactionDirection get direction {
|
||||
switch (type) {
|
||||
case TxType.selfTransfer:
|
||||
case TxType.receive:
|
||||
case TxType.unshield:
|
||||
case TxType.shield:
|
||||
case TxType.transparentSelfTransfer:
|
||||
return TransactionDirection.incoming;
|
||||
case TxType.sent:
|
||||
return TransactionDirection.outgoing;
|
||||
case TxType.unknown:
|
||||
return _value > BigInt.from(0)
|
||||
? TransactionDirection.incoming
|
||||
: TransactionDirection.outgoing;
|
||||
}
|
||||
}
|
||||
|
||||
TxType get type {
|
||||
switch (_tx.tpe) {
|
||||
case 0:
|
||||
return TxType.selfTransfer;
|
||||
case 1:
|
||||
return TxType.receive;
|
||||
case 2:
|
||||
return TxType.sent;
|
||||
case 4:
|
||||
return TxType.unshield;
|
||||
case 8:
|
||||
return TxType.shield;
|
||||
case 12:
|
||||
return TxType.transparentSelfTransfer;
|
||||
default:
|
||||
return TxType.unknown;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"tx": {
|
||||
"id": _tx.id,
|
||||
"txid": base64.encode(_tx.txid),
|
||||
"height": _tx.height,
|
||||
"time": _tx.time,
|
||||
"value": value.toInt(),
|
||||
"tpe": _tx.tpe,
|
||||
"zsaValue": _tx.zsaValue,
|
||||
"assetDisplay": _tx.assetDisplay,
|
||||
},
|
||||
"txaccount": {
|
||||
"id": _txAccount.id,
|
||||
"account": _txAccount.account,
|
||||
"txid": base64.encode(_txAccount.txid),
|
||||
"height": _txAccount.height,
|
||||
"time": _txAccount.time,
|
||||
"notes": _txAccount.notes
|
||||
.map(
|
||||
(final note) => {
|
||||
"id": note.id,
|
||||
"pool": note.pool,
|
||||
"height": note.height,
|
||||
"tx": note.tx,
|
||||
"scope": note.scope,
|
||||
"value": note.value.toString(),
|
||||
"locked": note.locked,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
"spends": _txAccount.spends
|
||||
.map(
|
||||
(final spend) => {
|
||||
"id": spend.id,
|
||||
"pool": spend.pool,
|
||||
"height": spend.height,
|
||||
"value": spend.value.toString(),
|
||||
"assetDisplay": spend.assetDisplay,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
"outputs": _txAccount.outputs
|
||||
.map(
|
||||
(final output) => {
|
||||
"id": output.id,
|
||||
"pool": output.pool,
|
||||
"height": output.height,
|
||||
"value": output.value.toString(),
|
||||
"address": output.address,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
"memos": _txAccount.memos
|
||||
.map(
|
||||
(final memo) => {
|
||||
"note": memo.note,
|
||||
"pool": memo.pool,
|
||||
"output": memo.output,
|
||||
"memo": memo.memo,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
},
|
||||
};
|
||||
|
||||
static int _asInt(final dynamic value, {final int fallback = 0}) {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
if (value is int) {
|
||||
return value;
|
||||
}
|
||||
if (value is num) {
|
||||
return value.toInt();
|
||||
}
|
||||
return int.parse(value.toString());
|
||||
}
|
||||
|
||||
static Uint8List _txidFromJson(final dynamic raw) {
|
||||
if (raw is Uint8List) {
|
||||
return raw;
|
||||
}
|
||||
if (raw is List) {
|
||||
return Uint8List.fromList(List<int>.from(raw));
|
||||
}
|
||||
if (raw is String) {
|
||||
return base64.decode(raw);
|
||||
}
|
||||
throw FormatException("Invalid txid in ZkoolTx json: $raw");
|
||||
}
|
||||
|
||||
static List<T> _listFromJson<T>(final dynamic raw, final T Function(Map<String, dynamic>) parse) {
|
||||
if (raw is! List) {
|
||||
return const [];
|
||||
}
|
||||
return raw.map((final entry) => parse(Map<String, dynamic>.from(entry as Map))).toList();
|
||||
}
|
||||
|
||||
static ZkoolTx fromJson(final Map<String, dynamic> json) {
|
||||
final txJson = Map<String, dynamic>.from((json["tx"] as Map?)?.cast<String, dynamic>() ?? json);
|
||||
final txAccountJson = Map<String, dynamic>.from(
|
||||
(json["txaccount"] as Map?)?.cast<String, dynamic>() ?? json,
|
||||
);
|
||||
|
||||
return ZkoolTx(
|
||||
zkool_account.Tx(
|
||||
id: _asInt(txJson["id"]),
|
||||
txid: _txidFromJson(txJson["txid"]),
|
||||
height: _asInt(txJson["height"]),
|
||||
time: _asInt(txJson["time"]),
|
||||
value: _asInt(txJson["value"]),
|
||||
tpe: txJson["tpe"] == null ? null : _asInt(txJson["tpe"]),
|
||||
zsaValue: _asInt(txJson["zsaValue"]),
|
||||
assetDisplay: txJson["assetDisplay"] as String? ?? "",
|
||||
),
|
||||
zkool_account.TxAccount(
|
||||
id: _asInt(txAccountJson["id"]),
|
||||
account: _asInt(txAccountJson["account"]),
|
||||
txid: _txidFromJson(txAccountJson["txid"]),
|
||||
height: _asInt(txAccountJson["height"]),
|
||||
time: _asInt(txAccountJson["time"]),
|
||||
notes: _listFromJson(
|
||||
txAccountJson["notes"],
|
||||
(final a) => zkool_account.TxNote(
|
||||
id: _asInt(a["id"]),
|
||||
pool: _asInt(a["pool"]),
|
||||
height: _asInt(a["height"]),
|
||||
tx: _asInt(a["tx"]),
|
||||
value: BigInt.parse(a["value"].toString()),
|
||||
scope: _asInt(a["scope"]),
|
||||
locked: a["locked"] as bool? ?? false,
|
||||
assetDisplay: a["assetDisplay"] as String? ?? "",
|
||||
),
|
||||
),
|
||||
spends: _listFromJson(
|
||||
txAccountJson["spends"],
|
||||
(final a) => zkool_account.TxSpend(
|
||||
id: _asInt(a["id"]),
|
||||
pool: _asInt(a["pool"]),
|
||||
height: _asInt(a["height"]),
|
||||
value: BigInt.parse(a["value"].toString()),
|
||||
assetDisplay: a["assetDisplay"] as String? ?? "",
|
||||
),
|
||||
),
|
||||
outputs: _listFromJson(
|
||||
txAccountJson["outputs"],
|
||||
(final a) => zkool_account.TxOutput(
|
||||
id: _asInt(a["id"]),
|
||||
pool: _asInt(a["pool"]),
|
||||
height: _asInt(a["height"]),
|
||||
value: BigInt.parse(a["value"].toString()),
|
||||
address: a["address"] as String? ?? "",
|
||||
),
|
||||
),
|
||||
memos: _listFromJson(
|
||||
txAccountJson["memos"],
|
||||
(final a) => zkool_account.TxMemo(
|
||||
note: a["note"] == null ? null : _asInt(a["note"]),
|
||||
pool: _asInt(a["pool"]),
|
||||
output: a["output"] == null ? null : _asInt(a["output"]),
|
||||
memo: a["memo"] as String?,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ environment:
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
warp_api:
|
||||
path: ../scripts/zcash_lib/packages/warp_api_ffi
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
mobx: ^2.6.0
|
||||
@@ -20,11 +18,13 @@ dependencies:
|
||||
path: any
|
||||
flat_buffers: any
|
||||
zkool:
|
||||
# path: ../../zkool2
|
||||
git:
|
||||
url: https://github.com/cake-tech/zkool2
|
||||
ref: b1642f260da4fa00dd2f8d4dd689073d0b56960a
|
||||
url: https://github.com/cake-tech/zkool2.git
|
||||
ref: dbd3dde8d8485428326a58e55660b7404666dd7c
|
||||
|
||||
path_provider: any
|
||||
sqflite_common_ffi: any
|
||||
dependency_overrides:
|
||||
decimal: ^2.3.3
|
||||
|
||||
|
||||
+71
-15
@@ -1,4 +1,14 @@
|
||||
PODS:
|
||||
- AppAuth (1.7.6):
|
||||
- AppAuth/Core (= 1.7.6)
|
||||
- AppAuth/ExternalUserAgent (= 1.7.6)
|
||||
- AppAuth/Core (1.7.6)
|
||||
- AppAuth/ExternalUserAgent (1.7.6):
|
||||
- AppAuth/Core
|
||||
- AppCheckCore (11.2.0):
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- bitbox_flutter (0.0.1):
|
||||
- Flutter
|
||||
- breez_sdk_spark_flutter (0.14.0):
|
||||
@@ -73,6 +83,34 @@ PODS:
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- google_sign_in_ios (0.0.1):
|
||||
- AppAuth (>= 1.7.4)
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- GoogleSignIn (~> 8.0)
|
||||
- GTMSessionFetcher (>= 3.4.0)
|
||||
- GoogleSignIn (8.0.0):
|
||||
- AppAuth (< 2.0, >= 1.7.3)
|
||||
- AppCheckCore (~> 11.0)
|
||||
- GTMAppAuth (< 5.0, >= 4.1.1)
|
||||
- GTMSessionFetcher/Core (~> 3.3)
|
||||
- GoogleUtilities/Environment (8.1.0):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.1.0):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.1.0)
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GTMAppAuth (4.1.1):
|
||||
- AppAuth/Core (~> 1.7)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 3.3)
|
||||
- GTMSessionFetcher (3.5.0):
|
||||
- GTMSessionFetcher/Full (= 3.5.0)
|
||||
- GTMSessionFetcher/Core (3.5.0)
|
||||
- GTMSessionFetcher/Full (3.5.0):
|
||||
- GTMSessionFetcher/Core
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- in_app_review (2.0.0):
|
||||
@@ -88,6 +126,7 @@ PODS:
|
||||
- payjoin_flutter (0.23.0)
|
||||
- permission_handler_apple (9.4.8):
|
||||
- Flutter
|
||||
- PromisesObjC (2.4.0)
|
||||
- quick_actions_ios (0.0.1):
|
||||
- Flutter
|
||||
- reown_yttrium (0.0.1):
|
||||
@@ -174,6 +213,7 @@ DEPENDENCIES:
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
|
||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
@@ -200,10 +240,17 @@ DEPENDENCIES:
|
||||
|
||||
SPEC REPOS:
|
||||
https://github.com/CocoaPods/Specs.git:
|
||||
- AppAuth
|
||||
- AppCheckCore
|
||||
- CryptoSwift
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- GoogleSignIn
|
||||
- GoogleUtilities
|
||||
- GTMAppAuth
|
||||
- GTMSessionFetcher
|
||||
- OrderedSet
|
||||
- PromisesObjC
|
||||
- SDWebImage
|
||||
- sqlite3
|
||||
- SwiftyGif
|
||||
@@ -248,6 +295,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
google_sign_in_ios:
|
||||
:path: ".symlinks/plugins/google_sign_in_ios/darwin"
|
||||
image_picker_ios:
|
||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||
in_app_review:
|
||||
@@ -296,6 +345,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/warp_api/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
|
||||
AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
|
||||
bitbox_flutter: 9505732798041c413152669751beeaecc5fe400f
|
||||
breez_sdk_spark_flutter: ed3b6709b8ce9b40309d8728640eade5b19e8a0d
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
@@ -308,24 +359,30 @@ SPEC CHECKSUMS:
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
dnssec_proof: d461cac7bd3301eb7447f87936745a0c1ae0a67e
|
||||
fast_scanner: 44c00940355a51258cd6c2085734193cd23d95bc
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
|
||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
|
||||
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||
in_app_review: 436034b18594851a7328d7f1c2ed5ec235b79cfc
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100
|
||||
fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3
|
||||
file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f
|
||||
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
|
||||
flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
|
||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||
google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
|
||||
GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
|
||||
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
|
||||
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
|
||||
local_auth_darwin: d2e8c53ef0c4f43c646462e3415432c4dab3ae19
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
|
||||
permission_handler_apple: 92d754bbaa7361d436db2d6c3c1c2a0fdcec462e
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
quick_actions_ios: 4b07fb49d8d8f3518d7565fbb7a91014067a7d82
|
||||
reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58
|
||||
rive_common: dd421daaf9ae69f0125aa761dd96abd278399952
|
||||
@@ -344,7 +401,6 @@ SPEC CHECKSUMS:
|
||||
universal_ble: 45519b2aeafe62761e2c6309f8927edb5288b914
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
warp_api: 997d8fb3e777669bc6260d0c12e56cd40b9db1d1
|
||||
YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
|
||||
|
||||
PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
A1B4A70C9CFA13AB71662216 /* LnurlPay.swift in Resources */ = {isa = PBXBuildFile; fileRef = 7D3364C03978A8A74B6D586E /* LnurlPay.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
A3D5E17CC53DF13FA740DEFA /* RedeemSwap.swift in Resources */ = {isa = PBXBuildFile; fileRef = 9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
B6C6E59403ACDE44724C12F4 /* ServiceConfig.swift in Resources */ = {isa = PBXBuildFile; fileRef = B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
CE3D5B792F142305004D767D /* WarpApiFFI.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
CE918BF82D533ECE007F186E /* MoneroWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
CE918BFA2D533ED4007F186E /* WowneroWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
CE918BFC2D533ED8007F186E /* ZanoWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@@ -48,7 +47,6 @@
|
||||
CE918BF82D533ECE007F186E /* MoneroWallet.xcframework in CopyFiles */,
|
||||
CE918BFA2D533ED4007F186E /* WowneroWallet.xcframework in CopyFiles */,
|
||||
CE918BFC2D533ED8007F186E /* ZanoWallet.xcframework in CopyFiles */,
|
||||
CE3D5B792F142305004D767D /* WarpApiFFI.xcframework in CopyFiles */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -89,7 +87,6 @@
|
||||
B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceConfig.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceConfig.swift"; sourceTree = "<group>"; };
|
||||
B478EFCC3C0760ED97FFE561 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WarpApiFFI.xcframework; path = ../scripts/zcash_lib/WarpApiFFI.xcframework; sourceTree = "<group>"; };
|
||||
CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = MoneroWallet.xcframework; sourceTree = "<group>"; };
|
||||
CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = WowneroWallet.xcframework; sourceTree = "<group>"; };
|
||||
CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = ZanoWallet.xcframework; sourceTree = "<group>"; };
|
||||
@@ -170,7 +167,6 @@
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */,
|
||||
0C44A7182518EF4A00B570ED /* CakeWallet */,
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
|
||||
@@ -15,14 +15,14 @@ class RoundedOverlayCards extends StatelessWidget {
|
||||
|
||||
return ClipRRect(
|
||||
borderRadius:
|
||||
BorderRadius.only(bottomLeft: Radius.circular(25.0), bottomRight: Radius.circular(25.0)),
|
||||
BorderRadius.only(bottomLeft: Radius.circular(25), bottomRight: Radius.circular(25)),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(24),
|
||||
bottomRight: Radius.circular(24),
|
||||
),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -30,8 +30,8 @@ class RoundedOverlayCards extends StatelessWidget {
|
||||
Flexible(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(25.0),
|
||||
bottomRight: Radius.circular(25.0),
|
||||
bottomLeft: Radius.circular(25),
|
||||
bottomRight: Radius.circular(25),
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@@ -210,7 +210,11 @@ class BackgroundSync {
|
||||
}
|
||||
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name),
|
||||
tx.date.add(Duration(minutes: 1)).toIso8601String());
|
||||
final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent";
|
||||
final action = tx.additionalInfo['isAutoShield'] == true
|
||||
? "Autoshield"
|
||||
: tx.direction == TransactionDirection.incoming
|
||||
? "Received"
|
||||
: "Sent";
|
||||
if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) {
|
||||
await showNotification(
|
||||
"$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amount.toString()}");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import "dart:convert";
|
||||
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import "package:cw_core/utils/proxy_wrapper.dart";
|
||||
|
||||
Future<String?> getBolt11FromLightingAddress(String lightningAddress, {int amount = 0}) async {
|
||||
try {
|
||||
@@ -31,28 +31,34 @@ Uri getURlOfLightningAddress(String lightningAddress) {
|
||||
}
|
||||
|
||||
class _LNURLPResponseDTO {
|
||||
const _LNURLPResponseDTO(
|
||||
this.callback,
|
||||
this.maxSendable,
|
||||
this.minSendable,
|
||||
this.tag,
|
||||
this.metadata,
|
||||
);
|
||||
|
||||
factory _LNURLPResponseDTO.fromJson(Map<String, dynamic> map) => _LNURLPResponseDTO(
|
||||
map["callback"] as String,
|
||||
map["maxSendable"] as int,
|
||||
map["minSendable"] as int,
|
||||
map["tag"] as String,
|
||||
map["metadata"] as String,
|
||||
);
|
||||
|
||||
final String callback;
|
||||
final int maxSendable;
|
||||
final int minSendable;
|
||||
final String tag;
|
||||
final String metadata;
|
||||
|
||||
const _LNURLPResponseDTO(
|
||||
this.callback, this.maxSendable, this.minSendable, this.tag, this.metadata);
|
||||
|
||||
static _LNURLPResponseDTO fromJson(Map<String, dynamic> map) => _LNURLPResponseDTO(
|
||||
map["callback"] as String,
|
||||
map["maxSendable"] as int,
|
||||
map["minSendable"] as int,
|
||||
map["tag"] as String,
|
||||
map["metadata"] as String);
|
||||
}
|
||||
|
||||
class _LNURLPCallbackResponseDTO {
|
||||
final String pr;
|
||||
|
||||
const _LNURLPCallbackResponseDTO(this.pr);
|
||||
|
||||
static _LNURLPCallbackResponseDTO fromJson(Map<String, dynamic> map) =>
|
||||
factory _LNURLPCallbackResponseDTO.fromJson(Map<String, dynamic> map) =>
|
||||
_LNURLPCallbackResponseDTO(map["pr"] as String);
|
||||
|
||||
final String pr;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
|
||||
const baseDefaultNodeUri = 'base-rpc.publicnode.com';
|
||||
const arbitrumDefaultNodeUri = 'arbitrum.nownodes.io';
|
||||
const bscDefaultNodeUri = 'bsc-dataseed.bnbchain.org';
|
||||
const zcashDefaultNodeUri = 'zec-node.cakewallet.com:443';
|
||||
const zcashDefaultNodeUri = 'zec.rocks:443';
|
||||
|
||||
Future<void> defaultSettingsMigration(
|
||||
{required int version,
|
||||
|
||||
@@ -236,12 +236,14 @@ class CWMonero extends Monero {
|
||||
required String password,
|
||||
required int height,
|
||||
required HardwareWalletService hardwareWalletService,
|
||||
required String? passphrase,
|
||||
}) =>
|
||||
MoneroRestoreWalletFromHardwareCredentials(
|
||||
name: name,
|
||||
password: password,
|
||||
height: height,
|
||||
hardwareWalletService: hardwareWalletService,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
+121
-115
@@ -1,54 +1,54 @@
|
||||
import 'package:cake_wallet/core/utilities.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/modern_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import "package:cake_wallet/anonpay/anonpay_donation_link_info.dart";
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/utilities.dart";
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/entities/auto_generate_subaddress_status.dart";
|
||||
import "package:cake_wallet/entities/preferences_key.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/modern_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/payjoin_copy_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_type.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_address_widget.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_display.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_amount_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_bottom_buttons.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_info_box.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_label_widget.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_large_amount_preview.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_qr_code.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_token_display.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/routes.dart";
|
||||
import "package:cake_wallet/src/screens/receive/anonpay_receive_page.dart";
|
||||
import "package:cake_wallet/utils/share_util.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/receive_option_view_model.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart";
|
||||
import "package:cake_wallet/zcash/zcash.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/payment_uris.dart";
|
||||
import "package:cw_core/receive_page_option.dart";
|
||||
import "package:cw_core/utils/print_verbose.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
import "package:shared_preferences/shared_preferences.dart";
|
||||
|
||||
class NewReceivePage extends StatefulWidget {
|
||||
NewReceivePage(
|
||||
{super.key,
|
||||
required this.addressListViewModel,
|
||||
required this.receiveOptionViewModel,
|
||||
required this.dashboardViewModel,
|
||||
required this.lightningMode,
|
||||
CryptoCurrency? initialCurrency}) {
|
||||
NewReceivePage({
|
||||
required this.addressListViewModel,
|
||||
required this.receiveOptionViewModel,
|
||||
required this.dashboardViewModel,
|
||||
required this.lightningMode,
|
||||
super.key,
|
||||
CryptoCurrency? initialCurrency,
|
||||
}) {
|
||||
if (initialCurrency != null && initialCurrency != addressListViewModel.selectedCurrency) {
|
||||
addressListViewModel.setTokenCurrency(initialCurrency);
|
||||
}
|
||||
@@ -78,31 +78,33 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
.firstWhereOrNull((item) => item.value.contains("Lightning")) ??
|
||||
ReceivePageOption.mainnet,
|
||||
);
|
||||
widget.addressListViewModel.setTokenCurrency(CryptoCurrency.btcln);
|
||||
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btcln;
|
||||
} else if (widget.addressListViewModel.wallet.type == WalletType.bitcoin) {
|
||||
widget.receiveOptionViewModel.selectReceiveOption(
|
||||
widget.receiveOptionViewModel.options
|
||||
.firstWhereOrNull((item) => item.value.contains("Standard")) ??
|
||||
ReceivePageOption.mainnet,
|
||||
);
|
||||
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btc;
|
||||
}
|
||||
});
|
||||
|
||||
reaction((_) => widget.addressListViewModel.uri, (newAddress) {
|
||||
_reloadAddressWithLabel(newAddress);
|
||||
});
|
||||
reaction((_) => widget.addressListViewModel.uri, _reloadAddressWithLabel);
|
||||
|
||||
_addressItemWithLabel =
|
||||
widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull((item) {
|
||||
return (item is WalletAddressListItem &&
|
||||
item.address == widget.addressListViewModel.uri.address);
|
||||
}) as WalletAddressListItem?;
|
||||
_addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull(
|
||||
(item) =>
|
||||
item is WalletAddressListItem && item.address == widget.addressListViewModel.uri.address,
|
||||
) as WalletAddressListItem?;
|
||||
|
||||
reaction((_) => widget.receiveOptionViewModel.selectedReceiveOption,
|
||||
(ReceivePageOption option) {
|
||||
reaction((_) => widget.receiveOptionViewModel.selectedReceiveOption, (option) {
|
||||
if (widget.dashboardViewModel.type == WalletType.bitcoin &&
|
||||
bitcoin!.isBitcoinReceivePageOption(option)) {
|
||||
widget.addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
|
||||
if (option.value.contains("Lightning")) {
|
||||
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btcln;
|
||||
} else {
|
||||
widget.addressListViewModel.selectedCurrency = CryptoCurrency.btc;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (widget.dashboardViewModel.type == WalletType.zcash) {
|
||||
@@ -166,16 +168,18 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
final hasAddressTypeSelector = widget.receiveOptionViewModel.options.length > 1;
|
||||
final hasLabel = _addressItemWithLabel?.name != null && _addressItemWithLabel!.name!.isNotEmpty;
|
||||
final infoboxDismissed = widget.addressListViewModel.wallet.walletInfo.receiveInfoboxDismissed;
|
||||
final infobox = ReceiveInfoBox.forWalletType(widget.addressListViewModel.type,
|
||||
supportedCurrencies: widget.addressListViewModel.tokenCurrencies
|
||||
.whereType<CryptoCurrency>()
|
||||
.toList(), onDismissed: () {
|
||||
widget.addressListViewModel.dismissInfobox();
|
||||
setState(() {});
|
||||
},
|
||||
autoGenerateSubaddressStatus: widget.lightningMode
|
||||
? AutoGenerateSubaddressStatus.disabled
|
||||
: widget.dashboardViewModel.settingsStore.autoGenerateSubaddressStatus);
|
||||
final infobox = ReceiveInfoBox.forWalletType(
|
||||
widget.addressListViewModel.type,
|
||||
supportedCurrencies:
|
||||
widget.addressListViewModel.tokenCurrencies.whereType<CryptoCurrency>().toList(),
|
||||
onDismissed: () {
|
||||
widget.addressListViewModel.dismissInfobox();
|
||||
setState(() {});
|
||||
},
|
||||
autoGenerateSubaddressStatus: widget.lightningMode
|
||||
? AutoGenerateSubaddressStatus.disabled
|
||||
: widget.dashboardViewModel.settingsStore.autoGenerateSubaddressStatus,
|
||||
);
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
@@ -187,9 +191,7 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(24),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
@@ -198,13 +200,13 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: _largeQrMode ? "" : S.of(context).receive,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
leadingIcon: const Icon(Icons.close),
|
||||
onLeadingPressed: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
},
|
||||
trailingWidget: Observer(
|
||||
builder: (_) => AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: _largeQrMode ||
|
||||
widget.addressListViewModel.hasAddressRotation
|
||||
/* TODO rotating is broken on mweb, disabling for now, fix after mvp*/
|
||||
@@ -217,10 +219,10 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
key: ValueKey(_largeQrMode),
|
||||
size: 36,
|
||||
icon: _largeQrMode
|
||||
? Icon(Icons.share)
|
||||
? const Icon(Icons.share)
|
||||
: widget.addressListViewModel.isRotatingAddress
|
||||
? CupertinoActivityIndicator()
|
||||
: Icon(Icons.refresh),
|
||||
? const CupertinoActivityIndicator()
|
||||
: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
if (_largeQrMode) {
|
||||
ShareUtil.share(
|
||||
@@ -232,8 +234,9 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
widget.addressListViewModel.rotateAddress();
|
||||
}
|
||||
}
|
||||
})
|
||||
: SizedBox.shrink(),
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -269,11 +272,12 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
addressListViewModel: widget.addressListViewModel,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _showLabelModal,
|
||||
child: ReceiveLabelWidget(
|
||||
name: _addressItemWithLabel?.name ?? "",
|
||||
largeQrMode: _largeQrMode,
|
||||
)),
|
||||
onTap: _showLabelModal,
|
||||
child: ReceiveLabelWidget(
|
||||
name: _addressItemWithLabel?.name ?? "",
|
||||
largeQrMode: _largeQrMode,
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => ReceiveBottomButtons(
|
||||
key: const ValueKey(0),
|
||||
@@ -285,14 +289,16 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
: ClipboardData(
|
||||
text: widget.addressListViewModel.displayAmount.isEmpty
|
||||
? widget.addressListViewModel.uri.address
|
||||
: widget.addressListViewModel.uri.toString()),
|
||||
: widget.addressListViewModel.uri.toString(),
|
||||
),
|
||||
onCopyButtonPressed: () {
|
||||
if (widget.addressListViewModel.hasPayjoin) {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
PayjoinCopyModal(uri: widget.addressListViewModel.uri));
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
PayjoinCopyModal(uri: widget.addressListViewModel.uri),
|
||||
);
|
||||
}
|
||||
},
|
||||
onAmountButtonPressed: () {
|
||||
@@ -300,12 +306,10 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: Colors.black.withAlpha(80),
|
||||
builder: (context) {
|
||||
return ReceiveAmountModal(
|
||||
walletAddressListViewModel: widget.addressListViewModel,
|
||||
onSubmitted: (amount) {},
|
||||
);
|
||||
},
|
||||
builder: (context) => ReceiveAmountModal(
|
||||
walletAddressListViewModel: widget.addressListViewModel,
|
||||
onSubmitted: (amount) {},
|
||||
),
|
||||
);
|
||||
},
|
||||
onLabelButtonPressed: _showLabelModal,
|
||||
@@ -318,22 +322,25 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
),
|
||||
),
|
||||
ReceiveLargeAmountPreview(
|
||||
amount: widget.addressListViewModel.displayAmount,
|
||||
currency: widget.addressListViewModel.cryptoCurrencySymbol,
|
||||
largeQrMode: _largeQrMode),
|
||||
amount: widget.addressListViewModel.displayAmount,
|
||||
currency: widget.addressListViewModel.cryptoCurrencySymbol,
|
||||
largeQrMode: _largeQrMode,
|
||||
),
|
||||
if (infobox != null && !widget.addressListViewModel.isLightning)
|
||||
ClipRect(
|
||||
child: AnimatedAlign(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOutCubic,
|
||||
heightFactor: infoboxDismissed ? 0 : 1,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedOpacity(
|
||||
child: AnimatedAlign(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOutCubic,
|
||||
heightFactor: infoboxDismissed ? 0 : 1,
|
||||
alignment: Alignment.center,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
opacity: infoboxDismissed ? 0 : 1,
|
||||
curve: Curves.easeOutCubic,
|
||||
child: infobox),
|
||||
))
|
||||
child: infobox,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -345,12 +352,11 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
|
||||
void _showLabelModal() {
|
||||
showMaterialModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: Colors.black.withAlpha(80),
|
||||
builder: (context) {
|
||||
return getIt.get<ReceiveLabelModal>(param1: _addressItemWithLabel);
|
||||
}).then((value) {
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
barrierColor: Colors.black.withAlpha(80),
|
||||
builder: (_) => getIt.get<ReceiveLabelModal>(param1: _addressItemWithLabel),
|
||||
).then((value) {
|
||||
_reloadAddressWithLabel(widget.addressListViewModel.uri);
|
||||
});
|
||||
}
|
||||
@@ -359,8 +365,8 @@ class _NewReceivePageState extends State<NewReceivePage> {
|
||||
// FIXME: viewmodel doesn't want to load address name here, so we make it. investigate why later
|
||||
setState(() {
|
||||
_addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull(
|
||||
(item) => (item is WalletAddressListItem && item.address == newAddress.address))
|
||||
as WalletAddressListItem?;
|
||||
(item) => item is WalletAddressListItem && item.address == newAddress.address,
|
||||
) as WalletAddressListItem?;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +594,7 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
builder: (context) => Material(
|
||||
child: L2ActionWalletSelector(
|
||||
showOtherWallets: false,
|
||||
action: l2actions.deposit,
|
||||
action: L2Actions.deposit,
|
||||
sendViewModel: widget.sendViewModel,
|
||||
contactListViewModel:
|
||||
widget.contactListViewModel,
|
||||
@@ -609,7 +609,7 @@ class _NewSendPageState extends State<NewSendPage> {
|
||||
builder: (context) => Material(
|
||||
child: L2ActionWalletSelector(
|
||||
showOtherWallets: false,
|
||||
action: l2actions.withdraw,
|
||||
action: L2Actions.withdraw,
|
||||
sendViewModel: widget.sendViewModel,
|
||||
contactListViewModel:
|
||||
widget.contactListViewModel,
|
||||
|
||||
@@ -59,14 +59,13 @@ class _AssetsHistorySectionState extends State<AssetsHistorySection> {
|
||||
AssetsSection(
|
||||
dashboardViewModel: widget.dashboardViewModel,
|
||||
),
|
||||
hasAssetsButton ?
|
||||
AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg",
|
||||
() {
|
||||
Navigator.of(context).pushNamed(
|
||||
hasAssetsButton ? AssetsHistorySectionActionButton(S.current.tokens, "assets/new-ui/options_slider.svg",
|
||||
|
||||
() {Navigator.of(context).pushNamed(
|
||||
Routes.homeSettings,
|
||||
arguments: widget.dashboardViewModel.balanceViewModel,
|
||||
);
|
||||
}) : null),
|
||||
}): null),
|
||||
AssetsHistorySectionTab(
|
||||
S.current.history,
|
||||
HistorySection(
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart';
|
||||
import 'package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/copy_wrapper.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_list_section.dart';
|
||||
import 'package:cake_wallet/utils/address_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item.dart";
|
||||
import "package:cake_wallet/entities/new_ui_entities/list_item/list_item_regular_row.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/copy_wrapper.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/routes.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_list_section.dart";
|
||||
import "package:cake_wallet/utils/address_formatter.dart";
|
||||
import "package:cake_wallet/view_model/transaction_details_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
class TransactionDetailsModal extends StatefulWidget {
|
||||
const TransactionDetailsModal(
|
||||
{super.key, required this.transactionDetailsViewModel, this.highlightNoteField = false});
|
||||
{
|
||||
required this.transactionDetailsViewModel,
|
||||
this.highlightNoteField = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final TransactionDetailsViewModel transactionDetailsViewModel;
|
||||
final bool highlightNoteField;
|
||||
@@ -48,190 +52,229 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final transactionInfoAmount = widget.transactionDetailsViewModel.transactionInfo.amount;
|
||||
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: GestureDetector(
|
||||
onTap: FocusScope.of(context).unfocus,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
Widget build(BuildContext context) => SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: GestureDetector(
|
||||
onTap: FocusScope.of(context).unfocus,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(25))),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).transaction,
|
||||
leadingIcon: Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Column(
|
||||
children: [
|
||||
TokenImageWidget(
|
||||
imageUrl:
|
||||
widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "",
|
||||
size: 64,
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Text(
|
||||
widget.transactionDetailsViewModel.formattedTitle +
|
||||
widget.transactionDetailsViewModel.formattedStatus,
|
||||
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
CopyWrapper(
|
||||
requireLongPress: true,
|
||||
data: ClipboardData(text: transactionInfoAmount.toString()),
|
||||
builder: (context, copied) => AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
key: ValueKey(copied),
|
||||
copied
|
||||
? S.of(context).copied
|
||||
: transactionInfoAmount.toStringWithSymbol(),
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: copied
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface),
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(25)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).transaction,
|
||||
leadingIcon: const Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: ModalScrollController.of(context),
|
||||
child: Column(
|
||||
children: [
|
||||
TokenImageWidget(
|
||||
imageUrl:
|
||||
widget.transactionDetailsViewModel.transactionAsset.iconPath ?? "",
|
||||
size: 64,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
widget.transactionDetailsViewModel.formattedTitle +
|
||||
widget.transactionDetailsViewModel.formattedStatus,
|
||||
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => CopyWrapper(
|
||||
requireLongPress: true,
|
||||
data: ClipboardData(
|
||||
text: widget.transactionDetailsViewModel.transactionCopyAmount,
|
||||
),
|
||||
builder: (context, copied) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Text(
|
||||
key: ValueKey(copied),
|
||||
copied
|
||||
? S.of(context).copied
|
||||
: widget.transactionDetailsViewModel.transactionAmount,
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
color: copied
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
NewListSections(sections: {
|
||||
"": widget.transactionDetailsViewModel.items
|
||||
.map((item) {
|
||||
if (item.value.isEmpty) return null;
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
NewListSections(
|
||||
sections: {
|
||||
"": widget.transactionDetailsViewModel.items
|
||||
.map((item) {
|
||||
if (item.value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final shouldBuildBottomWidget = item.value.length > 25;
|
||||
final shouldBuildBottomWidget = item.value.length > 25;
|
||||
|
||||
return ListItemRegularRow(
|
||||
copyableText: item.value,
|
||||
showArrow: false,
|
||||
keyValue: ((item.key as ValueKey?)?.value as String?) ??
|
||||
item.title,
|
||||
label: item.title,
|
||||
trailingWidget: shouldBuildBottomWidget
|
||||
? null
|
||||
: _buildTrailingWIdget(item),
|
||||
bottomWidget: shouldBuildBottomWidget
|
||||
? _buildBottomWidget(item)
|
||||
: null);
|
||||
})
|
||||
.whereType<ListItem>()
|
||||
.toList(),
|
||||
}),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
return ListItemRegularRow(
|
||||
copyableText: item.value,
|
||||
showArrow: false,
|
||||
keyValue: ((item.key as ValueKey?)?.value as String?) ??
|
||||
item.title,
|
||||
label: item.title,
|
||||
trailingWidget: shouldBuildBottomWidget
|
||||
? null
|
||||
: _buildTrailingWidget(item),
|
||||
bottomWidget: shouldBuildBottomWidget
|
||||
? _buildBottomWidget(item)
|
||||
: null,
|
||||
);
|
||||
})
|
||||
.whereType<ListItem>()
|
||||
.toList(),
|
||||
},
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Theme.of(context).colorScheme.surfaceContainer),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).note),
|
||||
TextField(
|
||||
focusNode: noteFocusNode,
|
||||
controller: noteController,
|
||||
decoration: InputDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(S.of(context).note),
|
||||
TextField(
|
||||
focusNode: noteFocusNode,
|
||||
controller: noteController,
|
||||
decoration: InputDecoration(
|
||||
hintText: S.of(context).add_a_note,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
isDense: true),
|
||||
)
|
||||
],
|
||||
isDense: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => NewListSections(sections: {
|
||||
"view tx": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "view tx on",
|
||||
label:
|
||||
widget.transactionDetailsViewModel.explorerDescription,
|
||||
onTap: widget.transactionDetailsViewModel.launchExplorer,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
trailingIconSize: 8)
|
||||
],
|
||||
if (widget.transactionDetailsViewModel.canReplaceByFee)
|
||||
"rbf": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "replace by fee",
|
||||
label: S.of(context).bump_fee,
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.bumpFeePage, arguments: [
|
||||
widget.transactionDetailsViewModel.transactionInfo,
|
||||
widget.transactionDetailsViewModel.rawTransaction
|
||||
]);
|
||||
})
|
||||
]
|
||||
}),
|
||||
)
|
||||
],
|
||||
Observer(
|
||||
builder: (_) => NewListSections(
|
||||
sections: {
|
||||
"view tx": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "view tx on",
|
||||
label: widget
|
||||
.transactionDetailsViewModel.explorerDescription,
|
||||
onTap: widget.transactionDetailsViewModel.launchExplorer,
|
||||
foregroundColor: Theme.of(context).colorScheme.primary,
|
||||
trailingIconPath: "assets/new-ui/link_arrow.svg",
|
||||
trailingIconSize: 8,
|
||||
),
|
||||
],
|
||||
if (widget.transactionDetailsViewModel.canReplaceByFee)
|
||||
"rbf": [
|
||||
ListItemRegularRow(
|
||||
keyValue: "replace by fee",
|
||||
label: S.of(context).bump_fee,
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.bumpFeePage,
|
||||
arguments: [
|
||||
widget
|
||||
.transactionDetailsViewModel.transactionInfo,
|
||||
widget.transactionDetailsViewModel.rawTransaction,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
|
||||
],
|
||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Widget _buildTrailingWIdget(TransactionDetailsListItem item) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: switch (item.runtimeType) {
|
||||
ConfirmationsListItem => Row(
|
||||
children: [
|
||||
Text((item as ConfirmationsListItem).current.toString(),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary)),
|
||||
if (item.needed > 0)
|
||||
Text("/${item.needed}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant))
|
||||
],
|
||||
),
|
||||
_ => Text(
|
||||
item.value,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
Widget _buildTrailingWidget(TransactionDetailsListItem item) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: switch (item.runtimeType) {
|
||||
ConfirmationsListItem => Row(
|
||||
children: [
|
||||
Text(
|
||||
(item as ConfirmationsListItem).current.toString(),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
if (item.needed > 0)
|
||||
Text(
|
||||
"/${item.needed}",
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
),
|
||||
],
|
||||
),
|
||||
_ => Text(
|
||||
item.value,
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildBottomWidget(TransactionDetailsListItem item) {
|
||||
return switch (item.runtimeType) {
|
||||
AddressListItem => AddressFormatter.buildSegmentedAddress(
|
||||
address: item.value,
|
||||
evenTextStyle: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: "IBM Plex Mono",
|
||||
color: Theme.of(context).colorScheme.onSurface)),
|
||||
AddressListItem => _segmentedAddressList(item.value),
|
||||
_ => Text(
|
||||
item.value,
|
||||
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
Widget _segmentedAddressList(String value) {
|
||||
final style = TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: "IBM Plex Mono",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
);
|
||||
final lines = value
|
||||
.split(RegExp(r'[,\n]'))
|
||||
.map((final line) => line.trim())
|
||||
.where((final line) => line.isNotEmpty)
|
||||
.toList();
|
||||
if (lines.length <= 1) {
|
||||
return AddressFormatter.buildSegmentedAddress(
|
||||
address: lines.isNotEmpty ? lines.first : value.trim(),
|
||||
evenTextStyle: style,
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
for (final line in lines)
|
||||
AddressFormatter.buildSegmentedAddress(address: line, evenTextStyle: style),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import "dart:io";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
|
||||
class DigitInputController {
|
||||
String _text = "";
|
||||
@@ -19,38 +21,37 @@ class DigitInputController {
|
||||
}
|
||||
|
||||
class DigitInputPill extends StatelessWidget {
|
||||
const DigitInputPill({super.key, this.digit, required this.highlighted});
|
||||
const DigitInputPill({required this.highlighted, super.key, this.digit});
|
||||
|
||||
final String? digit;
|
||||
final bool highlighted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 51,
|
||||
height: 84,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border:
|
||||
highlighted ? Border.all(color: Theme.of(context).colorScheme.primary, width: 2) : null,
|
||||
),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(digit ?? " ", style: TextStyle(fontSize: 40)),
|
||||
Widget build(BuildContext context) => Container(
|
||||
width: 51,
|
||||
height: 84,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: highlighted
|
||||
? Border.all(color: Theme.of(context).colorScheme.primary, width: 2)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
child: Text(digit ?? " ", style: const TextStyle(fontSize: 40)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class DigitInput extends StatefulWidget {
|
||||
const DigitInput({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.desiredLength,
|
||||
this.breakAt = 3,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final DigitInputController controller;
|
||||
@@ -74,12 +75,9 @@ class _DigitInputState extends State<DigitInput> implements TextInputClient {
|
||||
if (_textInputConnection == null || !_textInputConnection!.attached) {
|
||||
_textInputConnection = TextInput.attach(
|
||||
this,
|
||||
const TextInputConfiguration(
|
||||
inputType: TextInputType.numberWithOptions(
|
||||
signed: false,
|
||||
decimal: false,
|
||||
),
|
||||
inputAction: TextInputAction.none,
|
||||
TextInputConfiguration(
|
||||
inputType: TextInputType.number,
|
||||
inputAction: Platform.isIOS ? TextInputAction.done : TextInputAction.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -102,30 +100,28 @@ class _DigitInputState extends State<DigitInput> implements TextInputClient {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: _openVirtualKeyboard,
|
||||
child: SizedBox(
|
||||
height: 84,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => DigitInputPill(
|
||||
digit: widget.controller.text.length > index ? widget.controller.text[index] : null,
|
||||
highlighted: widget.controller.text.length == index,
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: _openVirtualKeyboard,
|
||||
child: SizedBox(
|
||||
height: 84,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) => DigitInputPill(
|
||||
digit: widget.controller.text.length > index ? widget.controller.text[index] : null,
|
||||
highlighted: widget.controller.text.length == index,
|
||||
),
|
||||
separatorBuilder: (context, index) =>
|
||||
SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4),
|
||||
itemCount: widget.desiredLength,
|
||||
),
|
||||
separatorBuilder: (context, index) =>
|
||||
SizedBox(width: (index + 1) % widget.breakAt == 0 ? 16 : 4),
|
||||
itemCount: widget.desiredLength,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@override
|
||||
void updateEditingValue(TextEditingValue value) {
|
||||
final RegExp numberRegExp = RegExp(r'^\d{0,6}$');
|
||||
final RegExp numberRegExp = RegExp(r"^\d{0,6}$");
|
||||
|
||||
if (numberRegExp.hasMatch(value.text)) {
|
||||
setState(() => widget.controller.text = value.text);
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import 'dart:async';
|
||||
import "dart:async";
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/new_primary_button.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
|
||||
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/main.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/token_image_widget.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/send_page/l2_send_external_modal.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/send_page/send_address_input.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart";
|
||||
import "package:cake_wallet/view_model/contact_list/contact_list_view_model.dart";
|
||||
import "package:cake_wallet/view_model/send/send_view_model.dart";
|
||||
import "package:cake_wallet/view_model/wallet_switcher_view_model.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||
|
||||
enum l2actions { deposit, withdraw }
|
||||
enum L2Actions { deposit, withdraw }
|
||||
|
||||
class L2ActionWalletSelector extends StatefulWidget {
|
||||
const L2ActionWalletSelector({
|
||||
super.key,
|
||||
required this.showOtherWallets,
|
||||
required this.sendViewModel,
|
||||
required this.action,
|
||||
required this.onSendInitiated,
|
||||
required this.contactListViewModel,
|
||||
required this.walletSwitcherViewModel,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final bool showOtherWallets;
|
||||
final SendViewModel sendViewModel;
|
||||
final l2actions action;
|
||||
final L2Actions action;
|
||||
final VoidCallback onSendInitiated;
|
||||
final ContactListViewModel contactListViewModel;
|
||||
final WalletSwitcherViewModel walletSwitcherViewModel;
|
||||
@@ -56,12 +56,18 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
super.initState();
|
||||
if (widget.showOtherWallets) {
|
||||
() async {
|
||||
items.addAll((await WalletInfo.getAll()).where((item) =>
|
||||
item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null));
|
||||
items.addAll(
|
||||
(await WalletInfo.getAll()).where(
|
||||
(item) =>
|
||||
item.type == widget.sendViewModel.walletType && item.hardwareWalletType == null,
|
||||
),
|
||||
);
|
||||
items.sort((a, b) {
|
||||
if (a.name == widget.sendViewModel.wallet.name)
|
||||
if (a.name == widget.sendViewModel.wallet.name) {
|
||||
return -1;
|
||||
else if (b.name == widget.sendViewModel.wallet.name) return 1;
|
||||
} else if (b.name == widget.sendViewModel.wallet.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
setState(() {});
|
||||
@@ -80,16 +86,16 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: widget.action == l2actions.deposit
|
||||
title: widget.action == L2Actions.deposit
|
||||
? "${S.of(context).send_from}..."
|
||||
: "${S.of(context).receive_to}...",
|
||||
leadingIcon: Icon(Icons.arrow_back_ios_new),
|
||||
leadingIcon: const Icon(Icons.arrow_back_ios_new),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Flexible(
|
||||
child: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -123,29 +129,17 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
itemBuilder: (context, index) {
|
||||
final item = items[index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: WalletRow(
|
||||
currencyIconPath: getCryptoCurrencyIconForWalletListItem(item.type),
|
||||
walletName: item.name,
|
||||
isCurrent: item.name == widget.sendViewModel.wallet.name,
|
||||
isSelected: _selectedWalletIndex == index && !textEntered,
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
setState(() {
|
||||
addressController.text = "";
|
||||
_selectedWalletIndex = index;
|
||||
});
|
||||
// if (widget.action == l2actions.withdraw) {
|
||||
// widget.sendViewModel.outputs.first.address = item.address;
|
||||
// widget.onSendInitiated();
|
||||
// } else if (widget.action == l2actions.deposit) {
|
||||
// setState(() {
|
||||
// loadingWalletName = item.name;
|
||||
// });
|
||||
// await _handleChangeWallet(item);
|
||||
// widget.onSendInitiated();
|
||||
// setState(() {
|
||||
// loadingWalletName = null;
|
||||
// });
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -198,7 +192,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
if (widget.action == l2actions.withdraw)
|
||||
if (widget.action == L2Actions.withdraw)
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
@@ -215,12 +209,12 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
if (widget.action == l2actions.deposit && widget.showOtherWallets) ...[
|
||||
if (widget.action == L2Actions.deposit && widget.showOtherWallets) ...[
|
||||
Container(
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant.withAlpha(64),
|
||||
),
|
||||
SizedBox(),
|
||||
const SizedBox(),
|
||||
Container(
|
||||
height: 52,
|
||||
decoration: BoxDecoration(
|
||||
@@ -232,7 +226,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: () async {
|
||||
onTap: () {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
showCupertinoModalBottomSheet(
|
||||
context: navigatorKey.currentContext ?? context,
|
||||
@@ -246,7 +240,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 10,
|
||||
@@ -275,19 +269,22 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
onPressed: () async {
|
||||
if (widget.sendViewModel.wallet.type == WalletType.bitcoin ||
|
||||
widget.sendViewModel.wallet.type == WalletType.litecoin) {
|
||||
if (widget.action == l2actions.withdraw) {
|
||||
if (widget.action == L2Actions.withdraw) {
|
||||
widget.sendViewModel.outputs.first.address =
|
||||
bitcoin!.getUnusedSegwitAddress(widget.sendViewModel.wallet)!;
|
||||
}
|
||||
|
||||
if (widget.showOtherWallets) {
|
||||
if (widget.action == l2actions.deposit) {
|
||||
if (widget.action == L2Actions.deposit) {
|
||||
await _handleChangeWallet(items[_selectedWalletIndex]);
|
||||
} else {
|
||||
widget.sendViewModel.outputs.first.address =
|
||||
addressController.text.isNotEmpty
|
||||
? addressController.text
|
||||
: items[_selectedWalletIndex].address;
|
||||
if (items[_selectedWalletIndex].name !=
|
||||
widget.sendViewModel.wallet.name) {
|
||||
widget.sendViewModel.outputs.first.address =
|
||||
addressController.text.isNotEmpty
|
||||
? addressController.text
|
||||
: items[_selectedWalletIndex].address;
|
||||
}
|
||||
}
|
||||
}
|
||||
widget.onSendInitiated();
|
||||
@@ -298,7 +295,7 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isLoading: _isLoading,
|
||||
),
|
||||
SizedBox()
|
||||
const SizedBox()
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -339,13 +336,13 @@ class _L2ActionWalletSelectorState extends State<L2ActionWalletSelector> {
|
||||
|
||||
class WalletRow extends StatelessWidget {
|
||||
const WalletRow({
|
||||
super.key,
|
||||
required this.currencyIconPath,
|
||||
required this.walletName,
|
||||
required this.onTap,
|
||||
this.isLoading = false,
|
||||
this.isCurrent = false,
|
||||
this.isSelected,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String currencyIconPath;
|
||||
@@ -370,7 +367,7 @@ class WalletRow extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
||||
@@ -513,7 +513,8 @@ class _TransactionCommitedScreenState extends State<TransactionCommitedScreen> {
|
||||
showModalBottomSheet(
|
||||
isScrollControlled: true,
|
||||
context: context,
|
||||
builder: (context) => page);
|
||||
builder: (context) =>
|
||||
FractionallySizedBox(heightFactor: 0.9, child: page));
|
||||
}),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -142,12 +142,22 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
var _isRefreshingUsb = false;
|
||||
Future<void> _refreshUsbDevices() async {
|
||||
final dev = await widget.hardwareWalletVM.getAllUsbDevices();
|
||||
|
||||
if (usbDevices.length != dev.length) {
|
||||
setState(() => usbDevices = dev);
|
||||
if (_isRefreshingUsb) {
|
||||
return;
|
||||
}
|
||||
_isRefreshingUsb = true;
|
||||
try {
|
||||
final dev = await widget.hardwareWalletVM.getAllUsbDevices();
|
||||
|
||||
if (usbDevices.length != dev.length) {
|
||||
setState(() => usbDevices = dev);
|
||||
}
|
||||
} catch(e) {
|
||||
printV(e);
|
||||
}
|
||||
_isRefreshingUsb = false;
|
||||
}
|
||||
|
||||
Future<void> _refreshBleDevices() async {
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import 'package:cake_wallet/core/wallet_name_validator.dart';
|
||||
import 'package:cw_core/generate_name.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import "dart:async";
|
||||
|
||||
import "package:cake_wallet/core/wallet_name_validator.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/src/screens/base_page.dart";
|
||||
import "package:cake_wallet/src/screens/connect_device/monero_hardware_wallet_passphrase_input.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:cake_wallet/src/widgets/base_text_form_field.dart";
|
||||
import "package:cake_wallet/src/widgets/blockchain_height_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/primary_button.dart";
|
||||
import "package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart";
|
||||
import "package:cake_wallet/utils/responsive_layout_util.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart";
|
||||
import "package:cw_core/generate_name.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
|
||||
class MoneroHardwareWalletOptionsPage extends BasePage {
|
||||
MoneroHardwareWalletOptionsPage(this._walletHardwareRestoreVM);
|
||||
@@ -42,13 +45,15 @@ class _MoneroHardwareWalletOptionsFormState extends State<_MoneroHardwareWalletO
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_blockchainHeightKey = GlobalKey<BlockchainHeightState>(),
|
||||
_blockHeightFocusNode = FocusNode(),
|
||||
_controller = TextEditingController();
|
||||
_walletNameController = TextEditingController(),
|
||||
_passphraseController = TextEditingController();
|
||||
|
||||
final GlobalKey<FormState> _formKey;
|
||||
final GlobalKey<BlockchainHeightState> _blockchainHeightKey;
|
||||
final FocusNode _blockHeightFocusNode;
|
||||
final WalletHardwareRestoreViewModel _walletHardwareRestoreVM;
|
||||
final TextEditingController _controller;
|
||||
final TextEditingController _walletNameController;
|
||||
final TextEditingController _passphraseController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -57,131 +62,159 @@ class _MoneroHardwareWalletOptionsFormState extends State<_MoneroHardwareWalletO
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
content: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
onChanged: (value) => _walletHardwareRestoreVM.name = value,
|
||||
controller: _controller,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 20.0,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
hintText: S.of(context).wallet_name,
|
||||
suffixIcon: Semantics(
|
||||
label: S.of(context).generate_name,
|
||||
child: IconButton(
|
||||
onPressed: _onGenerateName,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6.0),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.only(top: 24),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
content: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
onChanged: (value) => _walletHardwareRestoreVM.name = value,
|
||||
controller: _walletNameController,
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: Image.asset(
|
||||
'assets/images/refresh_icon.png',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
placeholderTextStyle: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
hintText: S.of(context).wallet_name,
|
||||
suffixIcon: Semantics(
|
||||
label: S.of(context).generate_name,
|
||||
child: IconButton(
|
||||
onPressed: _onGenerateName,
|
||||
icon: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: Image.asset(
|
||||
"assets/images/refresh_icon.png",
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: WalletNameValidator(),
|
||||
),
|
||||
validator: WalletNameValidator(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20),
|
||||
child: BlockchainHeightWidget(
|
||||
focusNode: _blockHeightFocusNode,
|
||||
key: _blockchainHeightKey,
|
||||
hasDatePicker: true,
|
||||
walletType: WalletType.monero,
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 20),
|
||||
child: BlockchainHeightWidget(
|
||||
focusNode: _blockHeightFocusNode,
|
||||
key: _blockchainHeightKey,
|
||||
hasDatePicker: true,
|
||||
walletType: WalletType.monero,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: const EdgeInsets.all(24),
|
||||
bottomSection: Observer(
|
||||
builder: (_) => Column(
|
||||
children: [
|
||||
if (_walletHardwareRestoreVM.passphraseAvailable) ...[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => MoneroHardwareWalletPassphraseInputModal(
|
||||
controller: _passphraseController,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(S.of(context).add_a_passphrase),
|
||||
),
|
||||
],
|
||||
LoadingPrimaryButton(
|
||||
onPressed: _confirmForm,
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isDisabled: _walletHardwareRestoreVM.name.isEmpty,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.all(24),
|
||||
bottomSection: Observer(
|
||||
builder: (context) => LoadingPrimaryButton(
|
||||
onPressed: _confirmForm,
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
isDisabled: _walletHardwareRestoreVM.name.isEmpty,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Future<void> _onGenerateName() async {
|
||||
final rName = await generateName();
|
||||
FocusManager.instance.primaryFocus?.unfocus();
|
||||
|
||||
setState(() {
|
||||
_controller.text = rName;
|
||||
_walletNameController.text = rName;
|
||||
_walletHardwareRestoreVM.name = rName;
|
||||
_controller.selection =
|
||||
TextSelection.fromPosition(TextPosition(offset: _controller.text.length));
|
||||
_walletNameController.selection =
|
||||
TextSelection.fromPosition(TextPosition(offset: _walletNameController.text.length));
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _confirmForm() async {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).proceed_on_device,
|
||||
alertContent: S.of(context).proceed_on_device_description,
|
||||
buttonText: S.of(context).cancel,
|
||||
alertBarrierDismissible: false,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
unawaited(
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).proceed_on_device,
|
||||
alertContent: S.of(context).proceed_on_device_description,
|
||||
buttonText: S.of(context).cancel,
|
||||
alertBarrierDismissible: false,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final options = {'height': _blockchainHeightKey.currentState?.height ?? -1};
|
||||
final options = <String, dynamic>{"height": _blockchainHeightKey.currentState?.height ?? -1};
|
||||
|
||||
if (_walletHardwareRestoreVM.passphraseAvailable && _passphraseController.text.isNotEmpty) {
|
||||
options["passphrase"] = _passphraseController.text;
|
||||
}
|
||||
await _walletHardwareRestoreVM.create(options: options);
|
||||
}
|
||||
|
||||
bool _effectsInstalled = false;
|
||||
|
||||
void _setEffects(BuildContext context) {
|
||||
if (_effectsInstalled) return;
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => _walletHardwareRestoreVM.error, (String? error) {
|
||||
reaction((_) => _walletHardwareRestoreVM.error, (error) {
|
||||
if (error != null) {
|
||||
if (error == S.current.ledger_connection_error) Navigator.of(context).pop();
|
||||
if (error == S.current.ledger_connection_error) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
builder: (context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).error,
|
||||
alertContent: error,
|
||||
buttonText: S.of(context).ok,
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/new_primary_button.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart";
|
||||
import "package:cake_wallet/src/widgets/base_text_form_field.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
class MoneroHardwareWalletPassphraseInputModal extends StatelessWidget {
|
||||
const MoneroHardwareWalletPassphraseInputModal({required this.controller, super.key});
|
||||
|
||||
final TextEditingController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(18)),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ModalTopBar(
|
||||
title: S.of(context).passphrase_entry,
|
||||
leadingIcon: const Icon(Icons.close),
|
||||
onLeadingPressed: Navigator.of(context).pop,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
BaseTextFormField(
|
||||
controller: controller,
|
||||
hintText: S.of(context).passphrase_raw,
|
||||
),
|
||||
const SizedBox(),
|
||||
NewPrimaryButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
text: S.of(context).restore_next,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
const SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -334,37 +334,6 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
if (dashboardViewModel.showZcashMissingFundsCard) ...[
|
||||
SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: InfoCard(
|
||||
title: S.of(context).zcash_card_missing_funds,
|
||||
description: S.of(context).zcash_card_description,
|
||||
leftButtonTitle: S.of(context).zcash_card_dismiss,
|
||||
rightButtonTitle: S.of(context).zcash_card_scan,
|
||||
image: 'assets/new-ui/crypto_full_icons/zcash.svg',
|
||||
leftButtonAction: () => _dismissZcash(context),
|
||||
rightButtonAction: () => _rescanInternalChangeZcash(context),
|
||||
hintWidget: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse(
|
||||
"https://docs.cakewallet.com/tutorials/zashi/#creating-a-new-zcash-wallet-on-cake"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).learn_more,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
height: 1,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (dashboardViewModel.showDecredInfoCard) ...[
|
||||
SizedBox(height: 10),
|
||||
Padding(
|
||||
@@ -494,21 +463,6 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||
dashboardViewModel.setMwebEnabled();
|
||||
}
|
||||
|
||||
Future<void> _rescanInternalChangeZcash(BuildContext context) async {
|
||||
dashboardViewModel.rescanInternalChangeZcash();
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).alert_notice,
|
||||
alertContent: S.of(context).zcash_card_warning,
|
||||
buttonText: S.of(context).understand,
|
||||
buttonAction: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _dismissMweb(BuildContext context) async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
@@ -522,18 +476,4 @@ class CryptoBalanceWidget extends StatelessWidget {
|
||||
));
|
||||
dashboardViewModel.dismissMweb();
|
||||
}
|
||||
|
||||
Future<void> _dismissZcash(BuildContext context) async {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).alert_notice,
|
||||
alertContent: S.of(context).zcash_card_enable_later,
|
||||
buttonText: S.of(context).understand,
|
||||
buttonAction: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
));
|
||||
dashboardViewModel.dismissZcash();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ class TransactionsPage extends StatelessWidget {
|
||||
key: item.key,
|
||||
onTap: () => Navigator.of(context)
|
||||
.pushNamed(Routes.transactionDetails, arguments: transaction),
|
||||
isShield: transaction.additionalInfo['autoShield'] == true,
|
||||
isShield: transaction.additionalInfo['isAutoShield'] == true,
|
||||
direction: transaction.direction,
|
||||
formattedDate: DateFormat('HH:mm').format(transaction.date),
|
||||
formattedAmount: item.formattedCryptoAmount,
|
||||
|
||||
@@ -99,7 +99,7 @@ class WalletRestorePage extends BasePage {
|
||||
children: [
|
||||
Observer(
|
||||
builder: (context) {
|
||||
return walletRestoreViewModel.mode == WalletRestoreMode.seed
|
||||
return walletRestoreViewModel.passphraseAvailable
|
||||
? StandardCheckbox(
|
||||
captionColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
value: walletRestoreViewModel.hasPassphrase,
|
||||
|
||||
@@ -115,21 +115,12 @@ class DisplaySettingsPage extends StatelessWidget {
|
||||
onChanged: (val) {
|
||||
_displaySettingsViewModel.setDisableTradeOption(val);
|
||||
}),
|
||||
if (_displaySettingsViewModel.showZcashCardSetting)
|
||||
ListItemToggle(
|
||||
keyValue: "display_settings_show_zcashcard",
|
||||
label: S.of(context).show_zcash_card,
|
||||
value: _displaySettingsViewModel.showZcashCard,
|
||||
onChanged: (val) {
|
||||
_displaySettingsViewModel.setShowZcashCard(val);
|
||||
}),
|
||||
ListItemSelector(
|
||||
keyValue: "display_settings_sync_status_display",
|
||||
label: S.of(context).sync_status_display_mode,
|
||||
options: [_displaySettingsViewModel.syncStatusDisplayMode.title],
|
||||
onTap: () async {
|
||||
final items = SyncStatusDisplayMode.values.toList();
|
||||
|
||||
final selectedAtIndex =
|
||||
items.indexOf(_displaySettingsViewModel.syncStatusDisplayMode);
|
||||
|
||||
|
||||
@@ -1,34 +1,36 @@
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/list_row.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_list.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_picker_list.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
|
||||
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import "package:cake_wallet/core/execution_state.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/src/screens/base_page.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_one_action.dart";
|
||||
import "package:cake_wallet/src/widgets/alert_with_two_actions.dart";
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/bottom_sheet/info_bottom_sheet_widget.dart";
|
||||
import "package:cake_wallet/src/widgets/list_row.dart";
|
||||
import "package:cake_wallet/src/widgets/primary_button.dart";
|
||||
import "package:cake_wallet/src/widgets/standard_expandable_list.dart";
|
||||
import "package:cake_wallet/src/widgets/standard_list.dart";
|
||||
import "package:cake_wallet/src/widgets/standard_picker_list.dart";
|
||||
import "package:cake_wallet/utils/show_bar.dart";
|
||||
import "package:cake_wallet/utils/show_pop_up.dart";
|
||||
import "package:cake_wallet/view_model/send/send_view_model_state.dart";
|
||||
import "package:cake_wallet/view_model/transaction_details_view_model.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_mobx/flutter_mobx.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
|
||||
class RBFDetailsPage extends BasePage {
|
||||
RBFDetailsPage({required this.transactionDetailsViewModel, required this.rawTransaction}) {
|
||||
transactionDetailsViewModel.addBumpFeesListItems(
|
||||
transactionDetailsViewModel.transactionInfo, rawTransaction);
|
||||
transactionDetailsViewModel.transactionInfo,
|
||||
rawTransaction,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -46,70 +48,70 @@ class RBFDetailsPage extends BasePage {
|
||||
children: [
|
||||
Expanded(
|
||||
child: SectionStandardList(
|
||||
sectionCount: 1,
|
||||
itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.RBFListItems[index];
|
||||
sectionCount: 1,
|
||||
itemCounter: (_) => transactionDetailsViewModel.rbfListItems.length,
|
||||
itemBuilder: (__, index) {
|
||||
final item = transactionDetailsViewModel.rbfListItems[index];
|
||||
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: '${item.title}:', value: item.value),
|
||||
);
|
||||
}
|
||||
if (item is StandartListItem) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: item.value));
|
||||
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
|
||||
},
|
||||
child: ListRow(title: "${item.title}:", value: item.value),
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardExpandableListItem) {
|
||||
return StandardExpandableList(
|
||||
title: '${item.title}: ${item.expandableItems.length}',
|
||||
expandableItems: item.expandableItems,
|
||||
);
|
||||
}
|
||||
if (item is StandardExpandableListItem) {
|
||||
return StandardExpandableList(
|
||||
title: "${item.title}: ${item.expandableItems.length}",
|
||||
expandableItems: item.expandableItems,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is StandardPickerListItem) {
|
||||
return StandardPickerList(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
items: item.items,
|
||||
displayItem: item.displayItem,
|
||||
onSliderChanged: item.onSliderChanged,
|
||||
onItemSelected: item.onItemSelected,
|
||||
selectedIdx: item.selectedIdx,
|
||||
customItemIndex: item.customItemIndex,
|
||||
customValue: item.customValue,
|
||||
maxValue: item.maxValue,
|
||||
);
|
||||
}
|
||||
if (item is StandardPickerListItem) {
|
||||
return StandardPickerList(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
items: item.items,
|
||||
displayItem: item.displayItem,
|
||||
onSliderChanged: item.onSliderChanged,
|
||||
onItemSelected: item.onItemSelected,
|
||||
selectedIdx: item.selectedIdx,
|
||||
customItemIndex: item.customItemIndex,
|
||||
customValue: item.customValue,
|
||||
maxValue: item.maxValue,
|
||||
);
|
||||
}
|
||||
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
if (item is TextFieldListItem) {
|
||||
return TextFieldListRow(
|
||||
title: item.title,
|
||||
value: item.value,
|
||||
onSubmitted: item.onSubmitted,
|
||||
);
|
||||
}
|
||||
|
||||
return Container();
|
||||
}),
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () async {
|
||||
transactionDetailsViewModel
|
||||
.replaceByFee(transactionDetailsViewModel.newFee.toString());
|
||||
},
|
||||
text: S.of(context).send,
|
||||
isLoading:
|
||||
transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
|
||||
isDisabled: transactionDetailsViewModel.sendViewModel.state
|
||||
is ExecutedSuccessfullyState,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
))),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
onPressed: () => transactionDetailsViewModel
|
||||
.replaceByFee(transactionDetailsViewModel.newFee.toString()),
|
||||
text: S.of(context).send,
|
||||
isLoading: transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
|
||||
isDisabled:
|
||||
transactionDetailsViewModel.sendViewModel.state is ExecutedSuccessfullyState,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -121,7 +123,7 @@ class RBFDetailsPage extends BasePage {
|
||||
return;
|
||||
}
|
||||
|
||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
|
||||
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (state) {
|
||||
if (state is! IsExecutingState &&
|
||||
loadingBottomSheetContext != null &&
|
||||
loadingBottomSheetContext!.mounted) {
|
||||
@@ -131,35 +133,35 @@ class RBFDetailsPage extends BasePage {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
context: context,
|
||||
builder: (popupContext) => AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).error,
|
||||
alertContent: state.error,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop(),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
if (state is AwaitingConfirmationState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: state.title ?? '',
|
||||
alertContent: state.message ?? '',
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
state.onConfirm?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
state.onCancel?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
});
|
||||
});
|
||||
context: context,
|
||||
builder: (popupContext) => AlertWithTwoActions(
|
||||
alertTitle: state.title ?? "",
|
||||
alertContent: state.message ?? "",
|
||||
rightButtonText: S.of(context).ok,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
actionRightButton: () {
|
||||
state.onConfirm?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
actionLeftButton: () {
|
||||
state.onCancel?.call();
|
||||
Navigator.of(popupContext).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -169,7 +171,7 @@ class RBFDetailsPage extends BasePage {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
builder: (context) {
|
||||
loadingBottomSheetContext = context;
|
||||
return LoadingBottomSheet(
|
||||
titleText: S.of(context).generating_transaction,
|
||||
@@ -187,42 +189,42 @@ class RBFDetailsPage extends BasePage {
|
||||
context: context,
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext bottomSheetContext) {
|
||||
return ConfirmSendingBottomSheet(
|
||||
key: ValueKey('rbf_confirm_sending_bottom_sheet'),
|
||||
titleText: S.of(bottomSheetContext).confirm_transaction,
|
||||
isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend,
|
||||
walletType: transactionDetailsViewModel.sendViewModel.walletType,
|
||||
titleIconPath:
|
||||
transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(bottomSheetContext).send_fee,
|
||||
feeValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs,
|
||||
footerType: FooterType.slideActionButton,
|
||||
accessibleNavigationModeSlideActionButtonText: S.of(context).send,
|
||||
onSlideActionComplete: () async {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
|
||||
try {
|
||||
builder: (bottomSheetContext) => ConfirmSendingBottomSheet(
|
||||
key: const ValueKey("rbf_confirm_sending_bottom_sheet"),
|
||||
titleText: S.of(bottomSheetContext).confirm_transaction,
|
||||
isSlideActionEnabled: transactionDetailsViewModel.sendViewModel.isReadyForSend,
|
||||
walletType: transactionDetailsViewModel.sendViewModel.walletType,
|
||||
titleIconPath:
|
||||
transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
|
||||
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
|
||||
amount: S.of(bottomSheetContext).send_amount,
|
||||
amountValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.amountFormatted,
|
||||
fiatAmountValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
|
||||
fee: S.of(bottomSheetContext).send_fee,
|
||||
feeValue:
|
||||
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
|
||||
feeFiatAmount: transactionDetailsViewModel
|
||||
.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
|
||||
outputs: transactionDetailsViewModel.sendViewModel.outputs,
|
||||
footerType: FooterType.slideActionButton,
|
||||
accessibleNavigationModeSlideActionButtonText: S.of(context).send,
|
||||
onSlideActionComplete: () async {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
await transactionDetailsViewModel.sendViewModel.commitTransaction(context);
|
||||
try {
|
||||
if (bottomSheetContext.mounted) {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
} catch (_) {}
|
||||
},
|
||||
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
|
||||
amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy,
|
||||
);
|
||||
},
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
change: transactionDetailsViewModel.sendViewModel.pendingTransaction!.change,
|
||||
amountParsingProxy: transactionDetailsViewModel.sendViewModel.amountParsingProxy,
|
||||
),
|
||||
);
|
||||
if (result == null) {
|
||||
transactionDetailsViewModel.sendViewModel.dismissTransaction();
|
||||
await transactionDetailsViewModel.sendViewModel.dismissTransaction();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -232,14 +234,14 @@ class RBFDetailsPage extends BasePage {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (context.mounted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext popupContext) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).sending,
|
||||
alertContent: S.of(popupContext).transaction_sent,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop());
|
||||
});
|
||||
context: context,
|
||||
builder: (popupContext) => AlertWithOneAction(
|
||||
alertTitle: S.of(popupContext).sending,
|
||||
alertContent: S.of(popupContext).transaction_sent,
|
||||
buttonText: S.of(popupContext).ok,
|
||||
buttonAction: () => Navigator.of(popupContext).pop(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,8 +60,13 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
return 'Transaction has missing data';
|
||||
}
|
||||
|
||||
if (transaction.additionalInfo['autoShield'] == true) {
|
||||
return "Autoshield";
|
||||
if (transaction.additionalInfo['isAutoShield'] == true) {
|
||||
if (transaction.isPending) {
|
||||
final status = formattedStatus;
|
||||
final baseString = S.current.shielding;
|
||||
return status.isNotEmpty ? "$baseString $status" : "$baseString...";
|
||||
}
|
||||
return S.current.shielding;
|
||||
}
|
||||
if (transaction.isPending) {
|
||||
final status = formattedStatus;
|
||||
|
||||
@@ -21,6 +21,7 @@ import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -50,8 +51,9 @@ abstract class OutputBase with Store {
|
||||
parsedAddress = ParsedAddress(parsedAddressByCurrencyMap: {}) {
|
||||
autorun((_) {
|
||||
final status = _wallet.syncStatus;
|
||||
printV("Sync status changed to $status. Recalculating fees");
|
||||
|
||||
if (status is! SyncedSyncStatus) {
|
||||
return;
|
||||
}
|
||||
calculateEstimatedFee();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/address_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/core/address_validator.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
|
||||
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
|
||||
import 'package:cake_wallet/utils/date_formatter.dart';
|
||||
import 'package:cake_wallet/view_model/send/send_view_model.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/address_validator.dart";
|
||||
import "package:cake_wallet/entities/priority_for_wallet_type.dart";
|
||||
import "package:cake_wallet/entities/transaction_description.dart";
|
||||
import "package:cake_wallet/evm/evm.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/monero/monero.dart";
|
||||
import "package:cake_wallet/reactions/wallet_connect.dart";
|
||||
import "package:cake_wallet/solana/solana.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/address_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/confirmations_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/standart_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart";
|
||||
import "package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart";
|
||||
import "package:cake_wallet/store/app_store.dart";
|
||||
import "package:cake_wallet/tron/tron.dart";
|
||||
import "package:cake_wallet/view_model/send/send_view_model.dart";
|
||||
import "package:cake_wallet/zano/zano.dart";
|
||||
import "package:collection/collection.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/transaction_direction.dart";
|
||||
import "package:cw_core/transaction_info.dart";
|
||||
import "package:cw_core/transaction_priority.dart";
|
||||
import "package:cw_core/wallet_base.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:hive/hive.dart";
|
||||
import "package:intl/intl.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
import "package:url_launcher/url_launcher.dart";
|
||||
|
||||
part 'transaction_details_view_model.g.dart';
|
||||
part "transaction_details_view_model.g.dart";
|
||||
|
||||
bool _trueFunc(_) => true;
|
||||
|
||||
/// We're adding a regex here so we can remove any already saved address that has the account in it.
|
||||
/// In the refactor, we will make another separate variable for accounts and the UI would handle it as needed.
|
||||
String _moneroRecipientAddressForDisplay(String raw, WalletType walletType) {
|
||||
if (walletType != WalletType.monero || raw.isEmpty) return raw;
|
||||
if (walletType != WalletType.monero || raw.isEmpty) {
|
||||
return raw;
|
||||
}
|
||||
|
||||
final compact = raw.replaceAll(RegExp(r'\s'), '');
|
||||
final compact = raw.replaceAll(RegExp(r"\s"), "");
|
||||
final match = RegExp(
|
||||
r'4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}',
|
||||
r"4[0-9a-zA-Z]{94}|8[0-9a-zA-Z]{94}|[0-9a-zA-Z]{106}",
|
||||
caseSensitive: false,
|
||||
).firstMatch(compact);
|
||||
return match?.group(0) ?? raw.trim();
|
||||
@@ -55,6 +55,14 @@ bool isLightning(TransactionInfo tx) => (tx.additionalInfo["isLightning"] as boo
|
||||
bool hasLightningPreimage(TransactionInfo tx) => (tx.additionalInfo["preimage"] as String?) != null;
|
||||
|
||||
class TxDetailRowDefinition {
|
||||
TxDetailRowDefinition({
|
||||
required this.keyString,
|
||||
required this.title,
|
||||
required this.valueGetter,
|
||||
this.applicable = _trueFunc,
|
||||
this.listItemBuilder = StandartListItem.new,
|
||||
});
|
||||
|
||||
final String keyString;
|
||||
final String title;
|
||||
final String Function(TransactionDetailsViewModelBase) valueGetter;
|
||||
@@ -65,20 +73,13 @@ class TxDetailRowDefinition {
|
||||
required Key key,
|
||||
}) listItemBuilder;
|
||||
|
||||
TxDetailRowDefinition(
|
||||
{required this.keyString,
|
||||
required this.title,
|
||||
required this.valueGetter,
|
||||
this.applicable = _trueFunc,
|
||||
this.listItemBuilder = StandartListItem.new});
|
||||
|
||||
static final List<TxDetailRowDefinition> defs = [
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_date_key",
|
||||
title: S.current.transaction_details_date,
|
||||
valueGetter: (vm) =>
|
||||
DateFormat("d MMMM yyyy, HH:mm", vm._appStore.settingsStore.languageCode)
|
||||
.format(vm.transactionInfo.date)),
|
||||
keyString: "standard_list_item_transaction_details_date_key",
|
||||
title: S.current.transaction_details_date,
|
||||
valueGetter: (vm) => DateFormat("d MMMM yyyy, HH:mm", vm._appStore.settingsStore.languageCode)
|
||||
.format(vm.transactionInfo.date),
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_height_key",
|
||||
title: S.current.transaction_details_height,
|
||||
@@ -88,125 +89,137 @@ class TxDetailRowDefinition {
|
||||
!isLightning(vm.transactionInfo),
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_fee_key",
|
||||
title: S.current.transaction_details_fee,
|
||||
valueGetter: (vm) => vm.transactionInfo.fee!.toStringWithSymbol(),
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type != WalletType.nano &&
|
||||
(vm.transactionInfo.fee?.toStringWithSymbol() ?? "").isNotEmpty),
|
||||
keyString: "standard_list_item_transaction_details_fee_key",
|
||||
title: S.current.transaction_details_fee,
|
||||
valueGetter: (vm) => vm.feeAmount,
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type != WalletType.nano &&
|
||||
(vm.transactionInfo.fee?.toStringWithSymbol() ?? "").isNotEmpty,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_confirmations_key",
|
||||
title: S.current.confirmations,
|
||||
valueGetter: (vm) => "${vm.transactionInfo.confirmations}/${vm.neededConfirmations}",
|
||||
applicable: (vm) =>
|
||||
[...electrumWalletTypes, ...evmWalletTypes, WalletType.zcash, WalletType.monero]
|
||||
.contains(vm.wallet.type) &&
|
||||
!isLightning(vm.transactionInfo),
|
||||
listItemBuilder: ConfirmationsListItem.new),
|
||||
keyString: "standard_list_item_transaction_confirmations_key",
|
||||
title: S.current.confirmations,
|
||||
valueGetter: (vm) => "${vm.transactionInfo.confirmations}/${vm.neededConfirmations}",
|
||||
applicable: (vm) =>
|
||||
[...electrumWalletTypes, ...evmWalletTypes, WalletType.zcash, WalletType.monero]
|
||||
.contains(vm.wallet.type) &&
|
||||
!isLightning(vm.transactionInfo),
|
||||
listItemBuilder: ConfirmationsListItem.new,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_recipient_address_key",
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
valueGetter: (vm) {
|
||||
String? ret = null;
|
||||
keyString: "standard_list_item_transaction_details_recipient_address_key",
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
valueGetter: (vm) {
|
||||
String? ret;
|
||||
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.monero:
|
||||
if (vm.transactionInfo.direction == TransactionDirection.incoming) {
|
||||
ret = monero!.getTransactionAddress(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo['accountIndex'] as int,
|
||||
vm.transactionInfo.additionalInfo['addressIndex'] as int);
|
||||
}
|
||||
case WalletType.bitcoin:
|
||||
ret = (bitcoin!.getTransactionAddresses(vm.wallet, vm.transactionInfo) ?? [])
|
||||
.firstOrNull ??
|
||||
"";
|
||||
case WalletType.tron:
|
||||
if (vm.transactionInfo.to != null)
|
||||
ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ret == null) {
|
||||
ret = vm.transactionInfo.to ?? "";
|
||||
}
|
||||
final resolvedAddress = _moneroRecipientAddressForDisplay(ret, vm.wallet.type);
|
||||
vm.isRecipientAddressShown = resolvedAddress.isNotEmpty;
|
||||
return resolvedAddress;
|
||||
},
|
||||
applicable: (vm) =>
|
||||
vm.showRecipientAddress &&
|
||||
(vm.transactionInfo.to != null ||
|
||||
[WalletType.monero, WalletType.tron].contains(vm.wallet.type) ||
|
||||
vm.wallet.type == WalletType.bitcoin &&
|
||||
vm.transactionInfo.direction == TransactionDirection.incoming),
|
||||
listItemBuilder: AddressListItem.new),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_source_address_key",
|
||||
title: S.current.transaction_details_source_address,
|
||||
valueGetter: (vm) {
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.tron:
|
||||
return tron!.getTronBase58Address(vm.transactionInfo.from!, vm.wallet);
|
||||
default:
|
||||
return vm.transactionInfo.from!;
|
||||
}
|
||||
},
|
||||
applicable: (vm) => vm.transactionInfo.from != null,
|
||||
listItemBuilder: AddressListItem.new),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_address_label_key",
|
||||
title: S.current.address_label,
|
||||
valueGetter: (vm) => monero!.getSubaddressLabel(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo['accountIndex'] as int,
|
||||
vm.transactionInfo.additionalInfo['addressIndex'] as int),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_key",
|
||||
title: S.current.transaction_key,
|
||||
valueGetter: (vm) {
|
||||
final descriptionKey =
|
||||
'${vm.transactionInfo.txHash}_${vm.wallet.walletAddresses.primaryAddress}';
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.monero:
|
||||
if (vm.transactionInfo.direction == TransactionDirection.incoming) {
|
||||
ret = monero!.getTransactionAddress(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo["accountIndex"] as int,
|
||||
vm.transactionInfo.additionalInfo["addressIndex"] as int,
|
||||
);
|
||||
}
|
||||
case WalletType.bitcoin:
|
||||
ret = (bitcoin!.getTransactionAddresses(vm.wallet, vm.transactionInfo) ?? [])
|
||||
.firstOrNull ??
|
||||
"";
|
||||
case WalletType.tron:
|
||||
if (vm.transactionInfo.to != null) {
|
||||
ret = tron!.getTronBase58Address(vm.transactionInfo.to!, vm.wallet);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ret ??= vm.transactionInfo.to ?? "";
|
||||
|
||||
final description = vm.transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == vm.transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey));
|
||||
return vm.transactionInfo.additionalInfo['key'] as String? ??
|
||||
description.transactionKey ??
|
||||
"";
|
||||
},
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero),
|
||||
final resolvedAddress = _moneroRecipientAddressForDisplay(ret, vm.wallet.type);
|
||||
vm.isRecipientAddressShown = resolvedAddress.isNotEmpty;
|
||||
return resolvedAddress;
|
||||
},
|
||||
applicable: (vm) =>
|
||||
vm.showRecipientAddress &&
|
||||
(vm.transactionInfo.to != null ||
|
||||
[WalletType.monero, WalletType.tron].contains(vm.wallet.type) ||
|
||||
vm.wallet.type == WalletType.bitcoin &&
|
||||
vm.transactionInfo.direction == TransactionDirection.incoming),
|
||||
listItemBuilder: AddressListItem.new,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_source_address_key",
|
||||
title: S.current.transaction_details_source_address,
|
||||
valueGetter: (vm) {
|
||||
switch (vm.wallet.type) {
|
||||
case WalletType.tron:
|
||||
return tron!.getTronBase58Address(vm.transactionInfo.from!, vm.wallet);
|
||||
default:
|
||||
return vm.transactionInfo.from!;
|
||||
}
|
||||
},
|
||||
applicable: (vm) => vm.transactionInfo.from != null,
|
||||
listItemBuilder: AddressListItem.new,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_address_label_key",
|
||||
title: S.current.address_label,
|
||||
valueGetter: (vm) => monero!.getSubaddressLabel(
|
||||
vm.wallet,
|
||||
vm.transactionInfo.additionalInfo["accountIndex"] as int,
|
||||
vm.transactionInfo.additionalInfo["addressIndex"] as int,
|
||||
),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_key",
|
||||
title: S.current.transaction_key,
|
||||
valueGetter: (vm) {
|
||||
final descriptionKey =
|
||||
"${vm.transactionInfo.txHash}_${vm.wallet.walletAddresses.primaryAddress}";
|
||||
|
||||
final description = vm.transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == vm.transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey),
|
||||
);
|
||||
return vm.transactionInfo.additionalInfo["key"] as String? ??
|
||||
description.transactionKey ??
|
||||
"";
|
||||
},
|
||||
applicable: (vm) => vm.wallet.type == WalletType.monero,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_lightning_preimage",
|
||||
title: S.current.transaction_preimage,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo['preimage'] as String? ?? "",
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo["preimage"] as String? ?? "",
|
||||
applicable: (vm) =>
|
||||
hasLightningPreimage(vm.transactionInfo) && isLightning(vm.transactionInfo),
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_confirmed_key",
|
||||
title: S.current.confirmed_tx,
|
||||
valueGetter: (vm) => (vm.transactionInfo.confirmations > 0).toString(),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.nano),
|
||||
keyString: "standard_list_item_transaction_confirmed_key",
|
||||
title: S.current.confirmed_tx,
|
||||
valueGetter: (vm) => (vm.transactionInfo.confirmations > 0).toString(),
|
||||
applicable: (vm) => vm.wallet.type == WalletType.nano,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_memo_key",
|
||||
title: S.current.memo,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo['memo'] as String,
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type == WalletType.zcash &&
|
||||
vm.transactionInfo.additionalInfo["memo"] != null),
|
||||
keyString: "standard_list_item_transaction_details_memo_key",
|
||||
title: S.current.memo,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo["memo"] as String,
|
||||
applicable: (vm) =>
|
||||
vm.wallet.type == WalletType.zcash && vm.transactionInfo.additionalInfo["memo"] != null,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_asset_id_key",
|
||||
title: "Asset ID",
|
||||
valueGetter: (vm) =>
|
||||
vm.transactionInfo.additionalInfo["assetId"] as String? ?? "Unknown asset id",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano),
|
||||
keyString: "standard_list_item_transaction_details_asset_id_key",
|
||||
title: "Asset ID",
|
||||
valueGetter: (vm) =>
|
||||
vm.transactionInfo.additionalInfo["assetId"] as String? ?? "Unknown asset id",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_comment_key",
|
||||
title: S.current.transaction_details_title,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo['comment'] as String? ?? "",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano),
|
||||
keyString: "standard_list_item_transaction_details_comment_key",
|
||||
title: S.current.transaction_details_title,
|
||||
valueGetter: (vm) => vm.transactionInfo.additionalInfo["comment"] as String? ?? "",
|
||||
applicable: (vm) => vm.wallet.type == WalletType.zano,
|
||||
),
|
||||
TxDetailRowDefinition(
|
||||
keyString: "standard_list_item_transaction_details_id_key",
|
||||
title: S.current.transaction_details_transaction_id,
|
||||
@@ -227,7 +240,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
required this.sendViewModel,
|
||||
this.canReplaceByFee = false,
|
||||
}) : items = [],
|
||||
RBFListItems = [],
|
||||
rbfListItems = [],
|
||||
newFee = 0,
|
||||
isRecipientAddressShown = false,
|
||||
_appStore = appStore,
|
||||
@@ -236,19 +249,23 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
|
||||
for (final def in TxDetailRowDefinition.defs) {
|
||||
if (def.applicable(this)) {
|
||||
items.add(def.listItemBuilder(
|
||||
items.add(
|
||||
def.listItemBuilder(
|
||||
title: def.title,
|
||||
value: def.valueGetter(this),
|
||||
key: ValueKey(def.keyString)) as TransactionDetailsListItem);
|
||||
key: ValueKey(def.keyString),
|
||||
) as TransactionDetailsListItem,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
_checkForRBF(tx);
|
||||
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey));
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey),
|
||||
);
|
||||
|
||||
if (showRecipientAddress && !isRecipientAddressShown) {
|
||||
final recipientAddress = description.recipientAddress;
|
||||
@@ -260,7 +277,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
AddressListItem(
|
||||
title: S.current.transaction_details_recipient_address,
|
||||
value: recipientAddressForDisplay,
|
||||
key: ValueKey('standard_list_item_${recipientAddressForDisplay}_key'),
|
||||
key: ValueKey("standard_list_item_${recipientAddressForDisplay}_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -268,10 +285,11 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
|
||||
void updateNote(String note) {
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values.firstWhere(
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey));
|
||||
(val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
orElse: () => TransactionDescription(id: descriptionKey),
|
||||
);
|
||||
|
||||
description.transactionNote = note;
|
||||
|
||||
@@ -283,9 +301,10 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
|
||||
String get note {
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash);
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
|
||||
);
|
||||
return description?.transactionNote ?? "";
|
||||
}
|
||||
|
||||
@@ -296,7 +315,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final AppStore _appStore;
|
||||
|
||||
final List<TransactionDetailsListItem> items;
|
||||
final List<TransactionDetailsListItem> RBFListItems;
|
||||
final List<TransactionDetailsListItem> rbfListItems;
|
||||
bool showRecipientAddress;
|
||||
bool isRecipientAddressShown;
|
||||
int newFee;
|
||||
@@ -304,9 +323,13 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
TransactionPriority? transactionPriority;
|
||||
|
||||
CryptoCurrency get transactionAsset {
|
||||
if (isEVMCompatibleChain(wallet.type)) return evm!.assetOfTransaction(wallet, transactionInfo);
|
||||
if (isEVMCompatibleChain(wallet.type)) {
|
||||
return evm!.assetOfTransaction(wallet, transactionInfo);
|
||||
}
|
||||
|
||||
if (isLightning(transactionInfo)) return CryptoCurrency.btcln;
|
||||
if (isLightning(transactionInfo)) {
|
||||
return CryptoCurrency.btcln;
|
||||
}
|
||||
|
||||
return switch (wallet.type) {
|
||||
WalletType.solana => solana!.assetOfTransaction(wallet, transactionInfo),
|
||||
@@ -316,26 +339,38 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO integrate these getters with the TransactionInfo object
|
||||
@computed
|
||||
String get transactionAmount =>
|
||||
_appStore.amountParsingProxy.asDisplayStringWithSymbol(transactionInfo.amount);
|
||||
|
||||
@computed
|
||||
String get feeAmount =>
|
||||
_appStore.amountParsingProxy.asDisplayStringWithSymbol(transactionInfo.fee!);
|
||||
|
||||
@computed
|
||||
String get transactionCopyAmount =>
|
||||
_appStore.amountParsingProxy.asDisplayString(transactionInfo.amount);
|
||||
|
||||
// TODO(malik1004x): integrate these getters with the TransactionInfo object
|
||||
String get formattedPendingStatus {
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
case WalletType.haven:
|
||||
case WalletType.zano:
|
||||
if (transactionInfo.confirmations >= 0 && transactionInfo.confirmations < 10) {
|
||||
return ' (${transactionInfo.confirmations}/10)';
|
||||
return " (${transactionInfo.confirmations}/10)";
|
||||
}
|
||||
break;
|
||||
case WalletType.wownero:
|
||||
if (transactionInfo.confirmations >= 0 && transactionInfo.confirmations < 3) {
|
||||
return ' (${transactionInfo.confirmations}/3)';
|
||||
return " (${transactionInfo.confirmations}/3)";
|
||||
}
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
bool isPegIn = (transactionInfo.additionalInfo["isPegIn"] as bool?) ?? false;
|
||||
bool isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
bool fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
String str = '';
|
||||
final isPegIn = (transactionInfo.additionalInfo["isPegIn"] as bool?) ?? false;
|
||||
final isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
final fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
String str = "";
|
||||
if (transactionInfo.confirmations <= 0) {
|
||||
str = S.current.pending;
|
||||
}
|
||||
@@ -352,10 +387,10 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
return str;
|
||||
default:
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
String get formattedStatus {
|
||||
@@ -369,7 +404,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
return formattedPendingStatus;
|
||||
}
|
||||
|
||||
return transactionInfo.isPending ? S.current.pending : '';
|
||||
return transactionInfo.isPending ? S.current.pending : "";
|
||||
}
|
||||
|
||||
int get neededConfirmations {
|
||||
@@ -381,9 +416,11 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
case WalletType.wownero:
|
||||
return 3;
|
||||
case WalletType.litecoin:
|
||||
bool isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
bool fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
if (isPegOut || fromPegOut) return 6;
|
||||
final isPegOut = (transactionInfo.additionalInfo["isPegOut"] as bool?) ?? false;
|
||||
final fromPegOut = (transactionInfo.additionalInfo["fromPegOut"] as bool?) ?? false;
|
||||
if (isPegOut || fromPegOut) {
|
||||
return 6;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -391,8 +428,8 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
|
||||
String get formattedTitle {
|
||||
if (transactionInfo.additionalInfo['autoShield'] == true) {
|
||||
return "Autoshield";
|
||||
if (transactionInfo.additionalInfo['isAutoShield'] == true) {
|
||||
return S.current.shielding;
|
||||
}
|
||||
if (transactionInfo.direction == TransactionDirection.incoming) {
|
||||
return S.current.received;
|
||||
@@ -408,52 +445,56 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final txId = transactionInfo.txHash;
|
||||
if (wallet.chainId != null) {
|
||||
final explorerUrl = evm!.getExplorerUrlForChainId(wallet.chainId!);
|
||||
if (explorerUrl != null) return '$explorerUrl/tx/${txId}';
|
||||
if (explorerUrl != null) {
|
||||
return "$explorerUrl/tx/${txId}";
|
||||
}
|
||||
}
|
||||
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return 'https://monero.com/tx/${txId}';
|
||||
return "https://monero.com/tx/${txId}";
|
||||
case WalletType.bitcoin:
|
||||
return 'https://mempool.cakewallet.com/${wallet.isTestnet ? "testnet/" : ""}tx/${txId}';
|
||||
return isLightning(transactionInfo)
|
||||
? "https://sparkscan.io/tx/${txId}"
|
||||
: 'https://mempool.cakewallet.com/${wallet.isTestnet ? "testnet/" : ""}tx/${txId}';
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.txIsMweb(transactionInfo)
|
||||
? "https://www.mwebexplorer.com/blocks/block/${transactionInfo.height}"
|
||||
: 'https://blockchair.com/litecoin/transaction/${txId}';
|
||||
: "https://blockchair.com/litecoin/transaction/${txId}";
|
||||
case WalletType.bitcoinCash:
|
||||
return 'https://blockchair.com/bitcoin-cash/transaction/${txId}';
|
||||
return "https://blockchair.com/bitcoin-cash/transaction/${txId}";
|
||||
case WalletType.haven:
|
||||
return 'https://explorer.havenprotocol.org/search?value=${txId}';
|
||||
return "https://explorer.havenprotocol.org/search?value=${txId}";
|
||||
case WalletType.ethereum:
|
||||
return 'https://etherscan.io/tx/${txId}';
|
||||
return "https://etherscan.io/tx/${txId}";
|
||||
case WalletType.base:
|
||||
return 'https://basescan.org/tx/${txId}';
|
||||
return "https://basescan.org/tx/${txId}";
|
||||
case WalletType.arbitrum:
|
||||
return 'https://arbiscan.io/tx/${txId}';
|
||||
return "https://arbiscan.io/tx/${txId}";
|
||||
case WalletType.bsc:
|
||||
return 'https://bscscan.com/tx/${txId}';
|
||||
return "https://bscscan.com/tx/${txId}";
|
||||
case WalletType.polygon:
|
||||
return 'https://polygonscan.com/tx/${txId}';
|
||||
return "https://polygonscan.com/tx/${txId}";
|
||||
case WalletType.nano:
|
||||
return 'https://nanexplorer.com/nano/block/${txId}';
|
||||
return "https://nanexplorer.com/nano/block/${txId}";
|
||||
case WalletType.banano:
|
||||
return 'https://nanexplorer.com/banano/block/${txId}';
|
||||
return "https://nanexplorer.com/banano/block/${txId}";
|
||||
case WalletType.solana:
|
||||
return 'https://solscan.io/tx/${txId}';
|
||||
return "https://solscan.io/tx/${txId}";
|
||||
case WalletType.tron:
|
||||
return 'https://tronscan.org/#/transaction/${txId}';
|
||||
return "https://tronscan.org/#/transaction/${txId}";
|
||||
case WalletType.wownero:
|
||||
return 'https://explore.wownero.com/tx/${txId}';
|
||||
return "https://explore.wownero.com/tx/${txId}";
|
||||
case WalletType.zano:
|
||||
return 'https://explorer.zano.org/transaction/${txId}';
|
||||
return "https://explorer.zano.org/transaction/${txId}";
|
||||
case WalletType.decred:
|
||||
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
|
||||
case WalletType.dogecoin:
|
||||
return 'https://blockchair.com/dogecoin/transaction/${txId}';
|
||||
return "https://blockchair.com/dogecoin/transaction/${txId}";
|
||||
case WalletType.zcash:
|
||||
return 'https://blockchair.com/zcash/transaction/${txId}';
|
||||
return "https://blockchair.com/zcash/transaction/${txId}";
|
||||
case WalletType.none:
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,13 +514,17 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
: transactionInfo.outputAddresses!.length;
|
||||
|
||||
newFee = bitcoin!.getFeeAmountForPriority(
|
||||
wallet, bitcoin!.getBitcoinTransactionPriorityMedium(), inputsCount, outputsCount);
|
||||
wallet,
|
||||
bitcoin!.getBitcoinTransactionPriorityMedium(),
|
||||
inputsCount,
|
||||
outputsCount,
|
||||
);
|
||||
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandartListItem(
|
||||
title: S.current.old_fee,
|
||||
value: tx.fee?.toStringWithSymbol() ?? '0.0',
|
||||
key: ValueKey('standard_list_item_rbf_old_fee_key'),
|
||||
value: tx.fee?.toStringWithSymbol() ?? "0.0",
|
||||
key: const ValueKey("standard_list_item_rbf_old_fee_key"),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -488,32 +533,33 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
final recommendedRate = (transactionInfo.fee! / BigInt.from(size)) +
|
||||
transactionInfo.fee!.copyWith(amount: BigInt.one);
|
||||
|
||||
RBFListItems.add(
|
||||
StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte'));
|
||||
rbfListItems.add(
|
||||
StandartListItem(title: "New recommended fee rate", value: "$recommendedRate sat/byte"),
|
||||
);
|
||||
}
|
||||
|
||||
final priorities = priorityForWalletType(wallet.type);
|
||||
final selectedItem = priorities.indexOf(sendViewModel.feesViewModel.transactionPriority);
|
||||
final customItem = priorities.firstWhereOrNull(
|
||||
(element) => element == sendViewModel.feesViewModel.bitcoinTransactionPriorityCustom);
|
||||
(element) => element == sendViewModel.feesViewModel.bitcoinTransactionPriorityCustom,
|
||||
);
|
||||
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
|
||||
final maxCustomFeeRate = sendViewModel.feesViewModel.maxCustomFeeRate?.toDouble();
|
||||
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandardPickerListItem(
|
||||
key: ValueKey('standard_picker_list_item_transaction_priorities_key'),
|
||||
key: const ValueKey("standard_picker_list_item_transaction_priorities_key"),
|
||||
title: S.current.estimated_new_fee,
|
||||
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${wallet.currency}',
|
||||
value: "${bitcoin!.formatterBitcoinAmountToString(amount: newFee)} ${wallet.currency}",
|
||||
items: priorityForWalletType(wallet.type),
|
||||
customValue: _appStore.settingsStore.customBitcoinFeeRate.toDouble(),
|
||||
maxValue: maxCustomFeeRate,
|
||||
selectedIdx: selectedItem,
|
||||
customItemIndex: customItemIndex ?? 0,
|
||||
displayItem: (dynamic priority, double sliderValue) =>
|
||||
displayItem: (dynamic priority, sliderValue) =>
|
||||
sendViewModel.feesViewModel.displayFeeRate(priority, sliderValue.round()),
|
||||
onSliderChanged: (double newValue) =>
|
||||
setNewFee(value: newValue, priority: transactionPriority!),
|
||||
onItemSelected: (dynamic item, double sliderValue) {
|
||||
onSliderChanged: (newValue) => setNewFee(value: newValue, priority: transactionPriority!),
|
||||
onItemSelected: (dynamic item, sliderValue) {
|
||||
transactionPriority = item as TransactionPriority;
|
||||
return setNewFee(value: sliderValue, priority: transactionPriority!);
|
||||
},
|
||||
@@ -521,9 +567,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
);
|
||||
|
||||
if (transactionInfo.inputAddresses != null && transactionInfo.inputAddresses!.isNotEmpty) {
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandardExpandableListItem(
|
||||
key: ValueKey('standard_expandable_list_item_transaction_input_addresses_key'),
|
||||
key: const ValueKey("standard_expandable_list_item_transaction_input_addresses_key"),
|
||||
title: S.current.inputs,
|
||||
expandableItems: transactionInfo.inputAddresses!,
|
||||
),
|
||||
@@ -532,17 +578,17 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
|
||||
if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) {
|
||||
final outputAddresses = transactionInfo.outputAddresses!.map((element) {
|
||||
if (element.contains('OP_RETURN:') && element.length > 40) {
|
||||
return element.substring(0, 40) + '...';
|
||||
if (element.contains("OP_RETURN:") && element.length > 40) {
|
||||
return "${element.substring(0, 40)}...";
|
||||
}
|
||||
return element;
|
||||
}).toList();
|
||||
|
||||
RBFListItems.add(
|
||||
rbfListItems.add(
|
||||
StandardExpandableListItem(
|
||||
title: S.current.outputs,
|
||||
expandableItems: outputAddresses,
|
||||
key: ValueKey('standard_expandable_list_item_transaction_output_addresses_key'),
|
||||
key: const ValueKey("standard_expandable_list_item_transaction_output_addresses_key"),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -552,7 +598,7 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
Future<void> _checkForRBF(TransactionInfo tx) async {
|
||||
if (wallet.type == WalletType.bitcoin &&
|
||||
transactionInfo.direction == TransactionDirection.outgoing) {
|
||||
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
|
||||
final descriptionKey = "${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}";
|
||||
final description = transactionDescriptionBox.values
|
||||
.firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash);
|
||||
|
||||
@@ -569,45 +615,23 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
}
|
||||
|
||||
String setNewFee({double? value, required TransactionPriority priority}) {
|
||||
String setNewFee({required TransactionPriority priority, double? value}) {
|
||||
newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null
|
||||
? bitcoin!.feeAmountWithFeeRate(
|
||||
wallet,
|
||||
value.round(),
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1)
|
||||
transactionInfo.outputAddresses?.length ?? 1,
|
||||
)
|
||||
: bitcoin!.getFeeAmountForPriority(
|
||||
wallet,
|
||||
priority,
|
||||
transactionInfo.inputAddresses?.length ?? 1,
|
||||
transactionInfo.outputAddresses?.length ?? 1);
|
||||
transactionInfo.outputAddresses?.length ?? 1,
|
||||
);
|
||||
|
||||
return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
|
||||
}
|
||||
|
||||
String get formattedCryptoAmount {
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
final crypto = isLightning(transactionInfo) ? CryptoCurrency.btcln : CryptoCurrency.btc;
|
||||
final amount = _appStore.amountParsingProxy
|
||||
.asDisplayString(transactionInfo.amount)
|
||||
.withMaxDecimals(8)
|
||||
.withLocalSeperator(_appStore.settingsStore.languageCode);
|
||||
|
||||
return '$amount ${_appStore.amountParsingProxy.getCryptoSymbol(crypto)}';
|
||||
}
|
||||
|
||||
return transactionInfo.amount.toStringWithSymbol();
|
||||
}
|
||||
|
||||
void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee);
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
|
||||
@computed
|
||||
String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled
|
||||
? ''
|
||||
: sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title;
|
||||
}
|
||||
|
||||
@@ -1,59 +1,58 @@
|
||||
import 'dart:core';
|
||||
import 'dart:developer' as dev;
|
||||
import "dart:core";
|
||||
import "dart:developer" as dev;
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/address_resolver/yat/yat_store.dart';
|
||||
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
|
||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
import 'package:cake_wallet/evm/evm.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_connect.dart';
|
||||
import 'package:cake_wallet/reactions/wallet_utils.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
import 'package:cake_wallet/utils/qr_util.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_util.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/currency.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import "package:cake_wallet/bitcoin/bitcoin.dart";
|
||||
import "package:cake_wallet/core/address_resolver/yat/yat_store.dart";
|
||||
import "package:cake_wallet/core/amount_parsing_proxy.dart";
|
||||
import "package:cake_wallet/core/fiat_conversion_service.dart";
|
||||
import "package:cake_wallet/core/wallet_change_listener_view_model.dart";
|
||||
import "package:cake_wallet/decred/decred.dart";
|
||||
import "package:cake_wallet/entities/auto_generate_subaddress_status.dart";
|
||||
import "package:cake_wallet/entities/fiat_api_mode.dart";
|
||||
import "package:cake_wallet/entities/fiat_currency.dart";
|
||||
import "package:cake_wallet/evm/evm.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/monero/monero.dart";
|
||||
import "package:cake_wallet/reactions/wallet_connect.dart";
|
||||
import "package:cake_wallet/reactions/wallet_utils.dart";
|
||||
import "package:cake_wallet/solana/solana.dart";
|
||||
import "package:cake_wallet/store/app_store.dart";
|
||||
import "package:cake_wallet/store/dashboard/fiat_conversion_store.dart";
|
||||
import "package:cake_wallet/tron/tron.dart";
|
||||
import "package:cake_wallet/utils/list_item.dart";
|
||||
import "package:cake_wallet/utils/qr_util.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart";
|
||||
import "package:cake_wallet/view_model/wallet_address_list/wallet_address_util.dart";
|
||||
import "package:cake_wallet/wownero/wownero.dart";
|
||||
import "package:cake_wallet/zano/zano.dart";
|
||||
import "package:cake_wallet/zcash/zcash.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/currency.dart";
|
||||
import "package:cw_core/currency_for_wallet_type.dart";
|
||||
import "package:cw_core/erc20_token.dart";
|
||||
import "package:cw_core/payment_uris.dart";
|
||||
import "package:cw_core/spl_token.dart";
|
||||
import "package:cw_core/tron_token.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:mobx/mobx.dart";
|
||||
|
||||
part 'wallet_address_list_view_model.g.dart';
|
||||
part "wallet_address_list_view_model.g.dart";
|
||||
|
||||
class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
|
||||
|
||||
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
|
||||
WalletAddressListViewModelBase({
|
||||
required AppStore appStore,
|
||||
required super.appStore,
|
||||
required this.yatStore,
|
||||
required this.fiatConversionStore,
|
||||
}) : _baseItems = <ListItem>[],
|
||||
selectedCurrency = appStore.wallet!.currency,
|
||||
hasAccounts = [WalletType.monero, WalletType.wownero].contains(appStore.wallet!.type),
|
||||
_appStore = appStore,
|
||||
receivePageOption = appStore.wallet!.walletAddresses.walletInfo.addressPageType ?? '',
|
||||
super(appStore: appStore) {
|
||||
receivePageOption = appStore.wallet!.walletAddresses.walletInfo.addressPageType ?? "" {
|
||||
_init();
|
||||
}
|
||||
|
||||
@@ -81,8 +80,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
CryptoCurrency? tokenCurrency;
|
||||
|
||||
@computed
|
||||
String get cryptoCurrencySymbol =>
|
||||
_appStore.amountParsingProxy.getCryptoSymbol(tokenCurrency ?? wallet.currency);
|
||||
String get cryptoCurrencySymbol => _appStore.amountParsingProxy.getCryptoSymbol(
|
||||
tokenCurrency ??
|
||||
(selectedCurrency is CryptoCurrency
|
||||
? selectedCurrency as CryptoCurrency
|
||||
: wallet.currency),
|
||||
);
|
||||
|
||||
void setTokenCurrency(Currency curr) {
|
||||
if (curr == wallet.currency || curr == CryptoCurrency.btcln) {
|
||||
@@ -122,7 +125,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
_appStore.amountParsingProxy.useSatoshi(selectedCurrency as CryptoCurrency);
|
||||
|
||||
@observable
|
||||
String searchText = '';
|
||||
String searchText = "";
|
||||
|
||||
@computed
|
||||
int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency);
|
||||
@@ -131,30 +134,45 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
int get tokenCurrencyIndex => tokenCurrency == null ? 0 : tokenCurrencies.indexOf(tokenCurrency!);
|
||||
|
||||
@observable
|
||||
String _amount = '';
|
||||
String _amount = "";
|
||||
|
||||
@computed
|
||||
String get displayAmount => _appStore.amountParsingProxy
|
||||
.getDisplayCryptoAmount(_amount, tokenCurrency ?? wallet.currency);
|
||||
String get displayAmount => _appStore.amountParsingProxy.getDisplayCryptoAmount(
|
||||
_amount,
|
||||
tokenCurrency ??
|
||||
(selectedCurrency is CryptoCurrency
|
||||
? selectedCurrency as CryptoCurrency
|
||||
: wallet.currency),
|
||||
);
|
||||
|
||||
// NOT PRECISE! just for display purposes.
|
||||
@computed
|
||||
String get fiatAmount {
|
||||
if (_amount.isEmpty) return "";
|
||||
if (_amount.isEmpty) {
|
||||
return "";
|
||||
}
|
||||
|
||||
var cryptoCurrency = tokenCurrency ?? wallet.currency;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) cryptoCurrency = CryptoCurrency.btc;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) {
|
||||
cryptoCurrency = CryptoCurrency.btc;
|
||||
}
|
||||
|
||||
if (selectedCurrency is FiatCurrency && _fiatRate != null) {
|
||||
return selectedCurrencyFiatAmount;
|
||||
}
|
||||
|
||||
if (!fiatConversionStore.prices.containsKey(cryptoCurrency)) return "";
|
||||
if (!fiatConversionStore.prices.containsKey(cryptoCurrency)) {
|
||||
return "";
|
||||
}
|
||||
final amount = double.tryParse(_amount) ?? 0;
|
||||
return (amount * fiatConversionStore.prices[cryptoCurrency]!).toStringAsFixed(2);
|
||||
}
|
||||
|
||||
@computed
|
||||
String get selectedCurrencyFiatAmount {
|
||||
if (_fiatRate == null) return "";
|
||||
if (_fiatRate == null) {
|
||||
return "";
|
||||
}
|
||||
final amount = double.tryParse(_amount) ?? 0;
|
||||
return (amount * _fiatRate!).toStringAsFixed(2);
|
||||
}
|
||||
@@ -201,26 +219,29 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
PaymentURI get uri {
|
||||
if (tokenCurrency != null && isEVMCompatibleChain(wallet.type)) {
|
||||
return ERC681URI(
|
||||
chainId: wallet.chainId ?? 1,
|
||||
address: wallet.walletAddresses.address,
|
||||
amount: _amount,
|
||||
contractAddress: (tokenCurrency as Erc20Token).contractAddress);
|
||||
chainId: wallet.chainId ?? 1,
|
||||
address: wallet.walletAddresses.address,
|
||||
amount: _amount,
|
||||
contractAddress: (tokenCurrency! as Erc20Token).contractAddress,
|
||||
);
|
||||
}
|
||||
if (tokenCurrency is TronToken && wallet.type == WalletType.tron) {
|
||||
return TronURI(
|
||||
amount: _amount,
|
||||
address: wallet.walletAddresses.address,
|
||||
contractAddress: (tokenCurrency as TronToken).contractAddress,
|
||||
contractAddress: (tokenCurrency! as TronToken).contractAddress,
|
||||
);
|
||||
}
|
||||
if (tokenCurrency is SPLToken && wallet.type == WalletType.solana) {
|
||||
return SolanaURI(
|
||||
amount: _amount,
|
||||
address: wallet.walletAddresses.address,
|
||||
contractAddress: (tokenCurrency as SPLToken).mintAddress,
|
||||
contractAddress: (tokenCurrency! as SPLToken).mintAddress,
|
||||
);
|
||||
}
|
||||
if (isLightning && _lnPaymentRequest != null) return _lnPaymentRequest!;
|
||||
if (isLightning && _lnPaymentRequest != null) {
|
||||
return _lnPaymentRequest!;
|
||||
}
|
||||
return wallet.walletAddresses.getPaymentUri(_amount);
|
||||
}
|
||||
|
||||
@@ -257,10 +278,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
final isPrimary = subaddress == primaryAddress;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.label,
|
||||
address: subaddress.address);
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.label,
|
||||
address: subaddress.address,
|
||||
);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
}
|
||||
@@ -285,37 +307,37 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
addressList.addAll(addressItems);
|
||||
addressList.add(WalletAddressListHeader(title: S.current.received));
|
||||
|
||||
final receivedAddressItems =
|
||||
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
|
||||
return WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: false,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: address.isChange,
|
||||
isOneTimeReceiveAddress: true,
|
||||
derivationPath: address.derivationPath,
|
||||
);
|
||||
});
|
||||
final receivedAddressItems = bitcoin!.getSilentPaymentReceivedAddresses(wallet).map(
|
||||
(address) => WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: false,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(address.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: address.isChange,
|
||||
isOneTimeReceiveAddress: true,
|
||||
derivationPath: address.derivationPath,
|
||||
),
|
||||
);
|
||||
addressList.addAll(receivedAddressItems);
|
||||
} else {
|
||||
var addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(subaddress.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: subaddress.isChange,
|
||||
isLegacyDerivation: subaddress.isLegacyDerivation,
|
||||
derivationPath: subaddress.derivationPath);
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
balance: _appStore.amountParsingProxy
|
||||
.getDisplayCryptoString(subaddress.balance, walletTypeToCryptoCurrency(type)),
|
||||
isChange: subaddress.isChange,
|
||||
isLegacyDerivation: subaddress.isLegacyDerivation,
|
||||
derivationPath: subaddress.derivationPath,
|
||||
);
|
||||
});
|
||||
|
||||
// don't show all 1000+ mweb addresses:
|
||||
@@ -346,11 +368,13 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.nano) {
|
||||
addressList.add(WalletAddressListItem(
|
||||
isPrimary: true,
|
||||
name: null,
|
||||
address: wallet.walletAddresses.address,
|
||||
));
|
||||
addressList.add(
|
||||
WalletAddressListItem(
|
||||
isPrimary: true,
|
||||
name: null,
|
||||
address: wallet.walletAddresses.address,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.tron) {
|
||||
@@ -361,29 +385,35 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
|
||||
if (wallet.type == WalletType.decred) {
|
||||
final addrInfos = decred!.getAddressInfos(wallet);
|
||||
addrInfos.forEach((info) {
|
||||
for (final info in addrInfos) {
|
||||
addressList.add(
|
||||
new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label));
|
||||
});
|
||||
WalletAddressListItem(isPrimary: false, address: info.address, name: info.label),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.zcash) {
|
||||
final addrInfos = zcash!.getAddressInfos(wallet);
|
||||
addrInfos.forEach((info) {
|
||||
for (final info in addrInfos) {
|
||||
addressList.add(
|
||||
new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label));
|
||||
});
|
||||
WalletAddressListItem(isPrimary: false, address: info.address, name: info.label),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < addressList.length; i++) {
|
||||
if (!(addressList[i] is WalletAddressListItem)) continue;
|
||||
if (addressList[i] is! WalletAddressListItem) {
|
||||
continue;
|
||||
}
|
||||
final item = addressList[i] as WalletAddressListItem;
|
||||
item.isHidden = wallet.walletAddresses.hiddenAddresses.contains(item.address) ||
|
||||
(isElectrumWallet && item.isLegacyDerivation);
|
||||
}
|
||||
|
||||
for (var i = 0; i < addressList.length; i++) {
|
||||
if (!(addressList[i] is WalletAddressListItem)) continue;
|
||||
if (addressList[i] is! WalletAddressListItem) {
|
||||
continue;
|
||||
}
|
||||
(addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses
|
||||
.contains((addressList[i] as WalletAddressListItem).address);
|
||||
}
|
||||
@@ -395,25 +425,25 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
}
|
||||
|
||||
if (searchText.isNotEmpty) {
|
||||
return ObservableList.of(addressList.where((item) {
|
||||
if (item is WalletAddressListItem) {
|
||||
return item.address.toLowerCase().contains(searchText.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
return ObservableList.of(
|
||||
addressList.where((item) {
|
||||
if (item is WalletAddressListItem) {
|
||||
return item.address.toLowerCase().contains(searchText.toLowerCase());
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return addressList;
|
||||
}
|
||||
|
||||
@computed
|
||||
ObservableList<ListItem> get addressList {
|
||||
return _computeAddressList();
|
||||
}
|
||||
ObservableList<ListItem> get addressList => _computeAddressList();
|
||||
|
||||
List<ListItem> get forceRecomputeItems {
|
||||
// necessary because the addressList contains non-observable items
|
||||
List<ListItem> recomputed = [];
|
||||
final recomputed = <ListItem>[];
|
||||
recomputed.addAll(_baseItems);
|
||||
recomputed.addAll(_computeAddressList());
|
||||
return recomputed;
|
||||
@@ -451,9 +481,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
case WalletType.wownero:
|
||||
wownero!.getCurrentAccount(wallet).label;
|
||||
default:
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
return '';
|
||||
return "";
|
||||
}
|
||||
|
||||
@computed
|
||||
@@ -473,7 +503,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
WalletType.litecoin,
|
||||
WalletType.decred,
|
||||
WalletType.dogecoin,
|
||||
WalletType.zcash
|
||||
WalletType.zcash,
|
||||
].contains(wallet.type) &&
|
||||
!isLightning &&
|
||||
isZCashTransparent;
|
||||
@@ -486,7 +516,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
WalletType.bitcoin,
|
||||
WalletType.litecoin,
|
||||
WalletType.bitcoinCash,
|
||||
WalletType.dogecoin
|
||||
WalletType.dogecoin,
|
||||
].contains(wallet.type);
|
||||
|
||||
List<String> getWalletImages(int? chainId) {
|
||||
@@ -494,44 +524,44 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
switch (chainId) {
|
||||
case 1:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/deuro_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/ethereum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/deuro_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 137:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/polygon.svg',
|
||||
'assets/images/eth_pol_icon.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/polygon.svg",
|
||||
"assets/images/eth_pol_icon.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 8453:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/ethereum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 42161:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/arbitrum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/arbitrum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case 56:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/bnb.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/new-ui/crypto_full_icons/bnb.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
default:
|
||||
return [
|
||||
'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
"assets/new-ui/crypto_full_icons/ethereum.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -539,22 +569,22 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
switch (wallet.type) {
|
||||
case WalletType.solana:
|
||||
return [
|
||||
'assets/images/sol_icon.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/images/sol_icon.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case WalletType.tron:
|
||||
return [
|
||||
'assets/images/trx_icon.svg',
|
||||
'assets/images/usdc_icon.svg',
|
||||
'assets/images/usdt_wallet_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/images/trx_icon.svg",
|
||||
"assets/images/usdc_icon.svg",
|
||||
"assets/images/usdt_wallet_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
case WalletType.zano:
|
||||
return [
|
||||
'assets/images/zano_icon.svg',
|
||||
'assets/images/more_tokens.svg',
|
||||
"assets/images/zano_icon.svg",
|
||||
"assets/images/more_tokens.svg",
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
@@ -563,7 +593,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
|
||||
@computed
|
||||
String get qrImage {
|
||||
if (isLightning) return 'assets/images/btc_chain_qr_lightning.svg';
|
||||
if (isLightning) {
|
||||
return "assets/images/btc_chain_qr_lightning.svg";
|
||||
}
|
||||
return getQrImage(type);
|
||||
}
|
||||
|
||||
@@ -582,8 +614,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
|
||||
@computed
|
||||
bool get isLightning =>
|
||||
wallet.type == WalletType.bitcoin &&
|
||||
(wallet.walletAddresses.getPaymentUri(_amount) is LightningPaymentRequest);
|
||||
wallet.type == WalletType.bitcoin && selectedCurrency == CryptoCurrency.btcln;
|
||||
|
||||
@computed
|
||||
bool get isZCashTransparent {
|
||||
@@ -698,16 +729,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
@action
|
||||
void changeAmount(String amount) {
|
||||
if (selectedCurrency is FiatCurrency) {
|
||||
this._amount = amount;
|
||||
_amount = amount;
|
||||
_convertAmountToCrypto();
|
||||
} else if (selectedCurrency is CryptoCurrency) {
|
||||
this._amount = _appStore.amountParsingProxy
|
||||
_amount = _appStore.amountParsingProxy
|
||||
.getCanonicalCryptoAmount(amount, selectedCurrency as CryptoCurrency);
|
||||
}
|
||||
if (isLightning) {
|
||||
wallet.walletAddresses
|
||||
.getPaymentRequestUri(this._amount)
|
||||
.then((uri) => _lnPaymentRequest = uri);
|
||||
wallet.walletAddresses.getPaymentRequestUri(_amount).then((uri) => _lnPaymentRequest = uri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -719,22 +748,24 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
@action
|
||||
void _convertAmountToCrypto() {
|
||||
var cryptoCurrency = tokenCurrency ?? wallet.currency;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) cryptoCurrency = CryptoCurrency.btc;
|
||||
if (cryptoCurrency == CryptoCurrency.btcln) {
|
||||
cryptoCurrency = CryptoCurrency.btc;
|
||||
}
|
||||
final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
|
||||
|
||||
if (fiatRate <= 0.0) {
|
||||
dev.log("Invalid Fiat Rate $fiatRate");
|
||||
_amount = '';
|
||||
_amount = "";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final crypto = (double.parse(_amount.replaceAll(',', '.')) / fiatRate).toStringAsFixed(8);
|
||||
final crypto = (double.parse(_amount.replaceAll(",", ".")) / fiatRate).toStringAsFixed(8);
|
||||
if (_amount != crypto) {
|
||||
_amount = crypto;
|
||||
}
|
||||
} catch (e) {
|
||||
_amount = '';
|
||||
_amount = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
@@ -43,6 +43,8 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
@observable
|
||||
String? error = null;
|
||||
|
||||
bool get passphraseAvailable => hardwareWalletVM.hardwareWalletType == HardwareWalletType.trezor;
|
||||
|
||||
// @observable
|
||||
ObservableList<HardwareAccountData> availableAccounts = ObservableList();
|
||||
|
||||
@@ -87,6 +89,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
|
||||
hardwareWalletService: hardwareWalletVM.getHardwareWalletService(type),
|
||||
password: password,
|
||||
height: _options['height'] as int? ?? 0,
|
||||
passphrase: _options['passphrase'] as String?,
|
||||
);
|
||||
default:
|
||||
throw Exception('Unexpected type: ${type.toString()}');
|
||||
|
||||
@@ -108,6 +108,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
@observable
|
||||
WalletRestoreMode mode;
|
||||
|
||||
@computed
|
||||
bool get passphraseAvailable =>
|
||||
mode == WalletRestoreMode.seed || hardwareWalletType == HardwareWalletType.trezor;
|
||||
|
||||
@observable
|
||||
bool hasPassphrase;
|
||||
|
||||
|
||||
@@ -209,16 +209,10 @@ class CWZcash extends Zcash {
|
||||
}
|
||||
|
||||
@override
|
||||
bool showMissingFundsCard(WalletBase wallet) {
|
||||
final zcashWallet = wallet as ZcashWallet;
|
||||
return zcashWallet.couldBeZashiWallet();
|
||||
}
|
||||
bool showMissingFundsCard(WalletBase wallet) => false;
|
||||
|
||||
@override
|
||||
Future<void> rescanInternalChange(WalletBase wallet) {
|
||||
final zcashWallet = wallet as ZcashWallet;
|
||||
return zcashWallet.rescanInternalChange();
|
||||
}
|
||||
Future<void> rescanInternalChange(WalletBase wallet) async {}
|
||||
}
|
||||
|
||||
const wordList = [
|
||||
|
||||
@@ -268,8 +268,6 @@ flutter:
|
||||
- assets/text/
|
||||
- assets/faq/
|
||||
- assets/animation/
|
||||
- scripts/zcash_lib/assets/sapling-spend.params
|
||||
- scripts/zcash_lib/assets/sapling-output.params
|
||||
- assets/new-ui/
|
||||
- assets/new-ui/icons/
|
||||
- assets/new-ui/hero/
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"activeConnectionsPrompt": "Active connections will appear here",
|
||||
"add": "Add",
|
||||
"add_a_note": "Add a note",
|
||||
"add_a_passphrase": "Add a passphrase",
|
||||
"add_account": "Add Account",
|
||||
"add_contact": "Add contact",
|
||||
"add_contact_to_address_book": "Would you like to add this contact to your address book?",
|
||||
@@ -402,6 +403,7 @@
|
||||
"enter_code": "Enter code",
|
||||
"enter_greater_than": "Please enter an amount greater than",
|
||||
"enter_less_than": "Please enter an amount less than",
|
||||
"enter_on_device_instead": "Enter on device instead",
|
||||
"enter_recipient_address": "Please enter recipient address",
|
||||
"enter_seed_phrase": "Enter your seed phrase",
|
||||
"enter_totp_code": "Please enter the TOTP Code.",
|
||||
@@ -745,6 +747,8 @@
|
||||
"pairing_error": "Pairing Error",
|
||||
"pairingInvalidEvent": "Pairing Invalid Event",
|
||||
"passphrase": "Passphrase (Optional)",
|
||||
"passphrase_raw": "Passphrase",
|
||||
"passphrase_entry": "Passphrase entry",
|
||||
"passphrase_view_keys": "Passphrase",
|
||||
"passphrases_doesnt_match": "Passphrases do not match, please try again",
|
||||
"password": "Password",
|
||||
@@ -1071,6 +1075,8 @@
|
||||
"share_this_node": "Share this node",
|
||||
"shared_seed_wallet_groups": "Shared Seed Wallet Groups",
|
||||
"shared_seed_wallets": "Shared Seed Wallets",
|
||||
"share_this_node": "Share this node",
|
||||
"shielding": "Autoshield",
|
||||
"show": "Show",
|
||||
"show_address_book_popup": "Show Address Book popup",
|
||||
"show_balance": "Long Press to Show Balance",
|
||||
|
||||
+1
-1
@@ -455,7 +455,7 @@ abstract class Monero {
|
||||
HardwareWalletType? hardwareWalletType,
|
||||
required int height});
|
||||
WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
|
||||
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService});
|
||||
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService, required String? passphrase});
|
||||
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic});
|
||||
Map<String, String> getKeys(Object wallet);
|
||||
int? getRestoreHeight(Object wallet);
|
||||
|
||||
Reference in New Issue
Block a user