mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
feat: zkool2 (#3165)
* feat: zkool2 * feat: rescan on zkool fix: sending to extracted addresses fix: labeling shielding txs fix: autoshield endless loop fix: autoshield t addr not firing fix: autoshield destinations chore: remove old routes for rescan with zkool other minor fixes/cleanups * fix: zcash hardfork * fix: update sync progress more reliably * fix: pending transaction loading fix: autoshield txs fix: taddress fix: fee estimation fix: tx send errors * chore: bump zkool2 to latest version fix: reduce amount of taddrs generated fix: derivation path for internal wallets fix: display address in correct hidden/usable spots fix: get wallet db height before it syncs for the first time fix: taddr rotation with passphrase fix: sync progress ui refresh fix: taddress generation not getting capped fix: hidden addresses not being hidden fix: pending transactions not being shown fix: t address cache misses fix: t address rotation not getting refreshed fix: t address high index not being used * fix: refresh t addresses * fix: patch rust side to make t addr rotation work * fix: ui issue in recipient address fix: zcash multiaddress send to show in the ui fix: potential race condition * minor fixes * format * fix: flashing on receive screen * remove unused 'autoShield' * review
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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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()}");
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -222,16 +222,37 @@ class _TransactionDetailsModalState extends State<TransactionDetailsModal> {
|
||||
|
||||
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),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -391,8 +391,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;
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -1071,6 +1071,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",
|
||||
|
||||
Reference in New Issue
Block a user