resolve conflicts and merge
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,8 +1,11 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature or Enhancement Request ✨
|
||||
url: https://github.com/cake-tech/cake_wallet/discussions/new?category=feature-requests
|
||||
about: Suggest an idea for Cake Wallet
|
||||
- name: Not sure where to start?
|
||||
url: https://guides.cakewallet.com
|
||||
about: Start by reading checking out the guides!
|
||||
- name: Need help?
|
||||
url: https://cakewallet.com/#contact
|
||||
about: Use our live chat or send a support email!
|
||||
about: Use our live chat or send a support email!
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature or Enhancement Request ✨
|
||||
about: Suggest an idea for Cake Wallet
|
||||
title: ''
|
||||
labels: Enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
BIN
.github/assets/Logo_CakeWallet.png
vendored
Normal file
After Width: | Height: | Size: 156 KiB |
48
.github/assets/NOTICE.txt
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
Notice for linux-badge.svg:
|
||||
|
||||
1:
|
||||
This is the Linux-penguin again...
|
||||
|
||||
Originally drewn by Larry Ewing (http://www.isc.tamu.edu/~lewing/)
|
||||
(with the GIMP) the Linux Logo has been vectorized by me (Simon Budig,
|
||||
http://www.home.unix-ag.org/simon/).
|
||||
|
||||
This happened quite some time ago with Corel Draw 4. But luckily
|
||||
meanwhile there are tools available to handle vector graphics with
|
||||
Linux. Bernhard Herzog (bernhard@users.sourceforge.net) deserves kudos
|
||||
for creating Sketch (http://sketch.sourceforge.net), a powerful free
|
||||
tool for creating vector graphics. He converted the Corel Draw file to
|
||||
the Sketch native format. Since I am unable to maintain the Corel Draw
|
||||
file any longer, the Sketch version now is the "official" one.
|
||||
|
||||
Anja Gerwinski (anja@gerwinski.de) has created an alternate version of
|
||||
the penguin (penguin-variant.sk) with a thinner mouth line and slightly
|
||||
altered gradients. It also features a nifty drop shadow.
|
||||
|
||||
The third bird (penguin-flat.sk) is a version reduced to three colors
|
||||
(black/white/yellow) for e.g. silk screen printing. I made this version
|
||||
for a mug, available at the friendly folks at
|
||||
http://www.kernelconcepts.de/ - they do good stuff, mail Petra
|
||||
(pinguin@kernelconcepts.de) if you need something special or don't
|
||||
understand the german :-)
|
||||
|
||||
These drawings are copyrighted by Larry Ewing and Simon Budig
|
||||
(penguin-variant.sk also by Anja Gerwinski), redistribution is free but
|
||||
has to include this README/Copyright notice.
|
||||
|
||||
The use of these drawings is free. However I am happy about a sample of
|
||||
your mug/t-shirt/whatever with this penguin on it...
|
||||
|
||||
Have fun
|
||||
Simon Budig
|
||||
|
||||
|
||||
Simon.Budig@unix-ag.org
|
||||
http://www.home.unix-ag.org/simon/
|
||||
|
||||
Simon Budig
|
||||
Am Hardtkoeppel 2
|
||||
D-61279 Graevenwiesbach
|
||||
|
||||
2:
|
||||
Attribution: lewing@isc.tamu.edu Larry Ewing and The GIMP
|
46
.github/assets/app-store-badge.svg
vendored
Executable file
|
@ -0,0 +1,46 @@
|
|||
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="119.66407" height="40" viewBox="0 0 119.66407 40">
|
||||
<title>Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917</title>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M110.13477,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.43779,6.43779,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01515.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27445,6.27445,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H110.13477c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
|
||||
<path d="M8.44483,39.125c-.30468,0-.602-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72186,5.72186,0,0,1-.543-1.6572,12.41351,12.41351,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37039,12.37039,0,0,1,.16553-1.87207,5.7555,5.7555,0,0,1,.54346-1.6621A5.37349,5.37349,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875H111.21387l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73126,12.73126,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
|
||||
</g>
|
||||
<g id="_Group_" data-name="<Group>">
|
||||
<g id="_Group_2" data-name="<Group>">
|
||||
<g id="_Group_3" data-name="<Group>">
|
||||
<path id="_Path_" data-name="<Path>" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
|
||||
<path id="_Path_2" data-name="<Path>" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M42.30227,27.13965h-4.7334l-1.13672,3.35645H34.42727l4.4834-12.418h2.083l4.4834,12.418H43.438ZM38.0591,25.59082h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
|
||||
<path d="M55.15969,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H48.4302v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C53.645,21.34766,55.15969,23.16406,55.15969,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C52.30227,29.01563,53.24953,27.81934,53.24953,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M65.12453,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238H58.395v1.50586h.03418A3.21162,3.21162,0,0,1,61.312,21.34766C63.60988,21.34766,65.12453,23.16406,65.12453,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C62.26711,29.01563,63.21438,27.81934,63.21438,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M71.71047,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52148.75684-2.52148,1.8584c0,.87793.6543,1.39453,2.25488,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
|
||||
<path d="M83.34621,19.2998v2.14258h1.72168v1.47168H83.34621v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406H80.16262V21.44238H81.479V19.2998Z" style="fill: #fff"/>
|
||||
<path d="M86.065,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C87.72609,30.6084,86.065,28.82617,86.065,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40039,1.16211-2.40039,3.10742c0,1.96191.89453,3.10645,2.40039,3.10645S92.76027,27.93164,92.76027,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M96.18606,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.59794,2.59794,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93652,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
|
||||
<path d="M109.3843,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,103.10207,25.13477Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_4" data-name="<Group>">
|
||||
<g>
|
||||
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
|
||||
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57522,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
|
||||
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M61.21779,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M66.4009,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.4009,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29543,13.03955Z" style="fill: #fff"/>
|
||||
<path d="M71.34816,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.0718,14.772,71.34816,13.87061,71.34816,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72121,10.91846,72.26613,11.49707,72.26613,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M79.23,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.12453,13.99463,82.563,13.42432,82.563,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
|
||||
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 11 KiB |
BIN
.github/assets/devices.png
vendored
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
.github/assets/f-droid-badge.png
vendored
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
.github/assets/google-play-badge.png
vendored
Normal file
After Width: | Height: | Size: 22 KiB |
1071
.github/assets/linux-badge.svg
vendored
Executable file
After Width: | Height: | Size: 67 KiB |
51
.github/assets/mac-store-badge.svg
vendored
Executable file
|
@ -0,0 +1,51 @@
|
|||
<svg id="livetype" xmlns="http://www.w3.org/2000/svg" width="156.10054" height="40" viewBox="0 0 156.10054 40">
|
||||
<title>Download_on_the_Mac_App_Store_Badge_US-UK_RGB_blk_092917</title>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M146.57123,0H9.53468c-.3667,0-.729,0-1.09473.002-.30615.002-.60986.00781-.91895.0127A13.21476,13.21476,0,0,0,5.5171.19141a6.66509,6.66509,0,0,0-1.90088.627A6.4378,6.4378,0,0,0,1.99757,1.99707,6.25844,6.25844,0,0,0,.81935,3.61816a6.60119,6.60119,0,0,0-.625,1.90332,12.993,12.993,0,0,0-.1792,2.002C.00587,7.83008.00489,8.1377,0,8.44434V31.5586c.00489.3105.00587.6113.01514.9219a12.99232,12.99232,0,0,0,.1792,2.0019,6.58756,6.58756,0,0,0,.625,1.9043A6.20778,6.20778,0,0,0,1.99757,38.001a6.27446,6.27446,0,0,0,1.61865,1.1787,6.70082,6.70082,0,0,0,1.90088.6308,13.45514,13.45514,0,0,0,2.0039.1768c.30909.0068.6128.0107.91895.0107C8.80567,40,9.168,40,9.53468,40H146.57123c.3594,0,.7246,0,1.084-.002.3047,0,.6172-.0039.9219-.0107a13.279,13.279,0,0,0,2-.1768,6.80432,6.80432,0,0,0,1.9082-.6308,6.27742,6.27742,0,0,0,1.6172-1.1787,6.39482,6.39482,0,0,0,1.1816-1.6143,6.60413,6.60413,0,0,0,.6191-1.9043,13.50643,13.50643,0,0,0,.1856-2.0019c.0039-.3106.0039-.6114.0039-.9219.0078-.3633.0078-.7246.0078-1.0938V9.53613c0-.36621,0-.72949-.0078-1.09179,0-.30664,0-.61426-.0039-.9209a13.5071,13.5071,0,0,0-.1856-2.002,6.6177,6.6177,0,0,0-.6191-1.90332,6.46619,6.46619,0,0,0-2.7988-2.7998,6.76754,6.76754,0,0,0-1.9082-.627,13.04394,13.04394,0,0,0-2-.17676c-.3047-.00488-.6172-.01074-.9219-.01269-.3594-.002-.7246-.002-1.084-.002Z" style="fill: #a6a6a6"/>
|
||||
<path d="M8.44483,39.125c-.30468,0-.60205-.0039-.90429-.0107a12.68714,12.68714,0,0,1-1.86914-.1631,5.88381,5.88381,0,0,1-1.65674-.5479,5.40573,5.40573,0,0,1-1.397-1.0166,5.32082,5.32082,0,0,1-1.02051-1.3965,5.72184,5.72184,0,0,1-.543-1.6572,12.41339,12.41339,0,0,1-.1665-1.875c-.00634-.2109-.01464-.9131-.01464-.9131V8.44434S.88185,7.75293.8877,7.5498a12.37032,12.37032,0,0,1,.16553-1.87207,5.75552,5.75552,0,0,1,.54346-1.6621A5.3735,5.3735,0,0,1,2.61183,2.61768,5.56543,5.56543,0,0,1,4.01417,1.59521a5.82309,5.82309,0,0,1,1.65332-.54394A12.58589,12.58589,0,0,1,7.543.88721L8.44532.875h139.205l.9131.0127a12.38493,12.38493,0,0,1,1.8584.16259,5.93833,5.93833,0,0,1,1.6709.54785,5.59374,5.59374,0,0,1,2.415,2.41993,5.76267,5.76267,0,0,1,.5352,1.64892,12.995,12.995,0,0,1,.1738,1.88721c.0029.2832.0029.5874.0029.89014.0079.375.0079.73193.0079,1.09179V30.4648c0,.3633,0,.7178-.0079,1.0752,0,.3252,0,.6231-.0039.9297a12.73127,12.73127,0,0,1-.1709,1.8535,5.739,5.739,0,0,1-.54,1.67,5.48029,5.48029,0,0,1-1.0156,1.3857,5.4129,5.4129,0,0,1-1.3994,1.0225,5.86168,5.86168,0,0,1-1.668.5498,12.54218,12.54218,0,0,1-1.8692.1631c-.2929.0068-.5996.0107-.8974.0107l-1.084.002Z"/>
|
||||
</g>
|
||||
<g id="_Group_" data-name="<Group>">
|
||||
<g id="_Group_2" data-name="<Group>">
|
||||
<g id="_Group_3" data-name="<Group>">
|
||||
<g id="_Group_4" data-name="<Group>">
|
||||
<path id="_Path_" data-name="<Path>" d="M24.76888,20.30068a4.94881,4.94881,0,0,1,2.35656-4.15206,5.06566,5.06566,0,0,0-3.99116-2.15768c-1.67924-.17626-3.30719,1.00483-4.1629,1.00483-.87227,0-2.18977-.98733-3.6085-.95814a5.31529,5.31529,0,0,0-4.47292,2.72787c-1.934,3.34842-.49141,8.26947,1.3612,10.97608.9269,1.32535,2.01018,2.8058,3.42763,2.7533,1.38706-.05753,1.9051-.88448,3.5794-.88448,1.65876,0,2.14479.88448,3.591.8511,1.48838-.02416,2.42613-1.33124,3.32051-2.66914a10.962,10.962,0,0,0,1.51842-3.09251A4.78205,4.78205,0,0,1,24.76888,20.30068Z" style="fill: #fff"/>
|
||||
<path id="_Path_2" data-name="<Path>" d="M22.03725,12.21089a4.87248,4.87248,0,0,0,1.11452-3.49062,4.95746,4.95746,0,0,0-3.20758,1.65961,4.63634,4.63634,0,0,0-1.14371,3.36139A4.09905,4.09905,0,0,0,22.03725,12.21089Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M46.14895,30.49609V21.35645H46.0884l-3.74316,9.04492H40.91652l-3.75293-9.04492H37.104v9.13965H35.34816v-12.418h2.22949l4.01855,9.80176h.06836l4.01074-9.80176h2.2373v12.418Z" style="fill: #fff"/>
|
||||
<path d="M49.396,27.92285c0-1.583,1.21289-2.53906,3.36523-2.668l2.47852-.1377v-.68848c0-1.00684-.66309-1.5752-1.791-1.5752a1.73035,1.73035,0,0,0-1.90137,1.27441H49.8091c.05176-1.63574,1.5752-2.79687,3.69141-2.79687,2.16016,0,3.58887,1.17871,3.58887,2.96v6.20508H55.30813V29.00684h-.043a3.23683,3.23683,0,0,1-2.85742,1.64453A2.74447,2.74447,0,0,1,49.396,27.92285Zm5.84375-.81738V26.4082l-2.22949.1377c-1.11035.06934-1.73828.55078-1.73828,1.3252,0,.792.6543,1.30859,1.65234,1.30859A2.17046,2.17046,0,0,0,55.23977,27.10547Z" style="fill: #fff"/>
|
||||
<path d="M64.89309,24.55762a1.99909,1.99909,0,0,0-2.13379-1.66895c-1.42871,0-2.375,1.19629-2.375,3.08105,0,1.92773.95508,3.08887,2.3916,3.08887a1.94829,1.94829,0,0,0,2.11719-1.626h1.79A3.61835,3.61835,0,0,1,62.7593,30.6084c-2.582,0-4.26855-1.76465-4.26855-4.63867,0-2.81445,1.68652-4.63867,4.251-4.63867a3.63931,3.63931,0,0,1,3.9248,3.22656Z" style="fill: #fff"/>
|
||||
<path d="M78.7593,27.13965H74.0259l-1.13672,3.35645H70.8843l4.4834-12.418h2.083l4.4834,12.418H79.895Zm-4.24316-1.54883h3.752l-1.84961-5.44727h-.05176Z" style="fill: #fff"/>
|
||||
<path d="M91.61672,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438H83.0884V21.44238h1.79883v1.50586h.03418a3.21161,3.21161,0,0,1,2.88281-1.60059C90.10207,21.34766,91.61672,23.16406,91.61672,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C88.7593,29.01563,89.70656,27.81934,89.70656,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M101.58156,25.96973c0,2.81348-1.50586,4.62109-3.77832,4.62109a3.0693,3.0693,0,0,1-2.84863-1.584h-.043v4.48438h-1.8584V21.44238h1.79883v1.50586h.03418a3.21162,3.21162,0,0,1,2.88281-1.60059C100.06691,21.34766,101.58156,23.16406,101.58156,25.96973Zm-1.91016,0c0-1.833-.94727-3.03809-2.39258-3.03809-1.41992,0-2.375,1.23047-2.375,3.03809,0,1.82422.95508,3.0459,2.375,3.0459C98.72414,29.01563,99.67141,27.81934,99.67141,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M108.1675,27.03613c.1377,1.23145,1.334,2.04,2.96875,2.04,1.56641,0,2.69336-.80859,2.69336-1.91895,0-.96387-.67969-1.541-2.28906-1.93652l-1.60937-.3877c-2.28027-.55078-3.33887-1.61719-3.33887-3.34766,0-2.14258,1.86719-3.61426,4.51855-3.61426,2.624,0,4.42285,1.47168,4.4834,3.61426h-1.876c-.1123-1.23926-1.13672-1.9873-2.63379-1.9873s-2.52149.75684-2.52149,1.8584c0,.87793.65431,1.39453,2.25489,1.79l1.36816.33594c2.54785.60254,3.60645,1.626,3.60645,3.44238,0,2.32324-1.85059,3.77832-4.79395,3.77832-2.75391,0-4.61328-1.4209-4.7334-3.667Z" style="fill: #fff"/>
|
||||
<path d="M119.80324,19.2998v2.14258h1.72168v1.47168h-1.72168v4.99121c0,.77539.34473,1.13672,1.10156,1.13672a5.80752,5.80752,0,0,0,.61133-.043v1.46289a5.10351,5.10351,0,0,1-1.03223.08594c-1.833,0-2.54785-.68848-2.54785-2.44434V22.91406h-1.31641V21.44238h1.31641V19.2998Z" style="fill: #fff"/>
|
||||
<path d="M122.521,25.96973c0-2.84863,1.67773-4.63867,4.29395-4.63867,2.625,0,4.29492,1.79,4.29492,4.63867,0,2.85645-1.66113,4.63867-4.29492,4.63867C124.18215,30.6084,122.521,28.82617,122.521,25.96973Zm6.69531,0c0-1.9541-.89551-3.10742-2.40137-3.10742s-2.40137,1.16211-2.40137,3.10742c0,1.96191.89551,3.10645,2.40137,3.10645S129.21633,27.93164,129.21633,25.96973Z" style="fill: #fff"/>
|
||||
<path d="M132.64309,21.44238h1.77246v1.541h.043a2.1594,2.1594,0,0,1,2.17773-1.63574,2.86616,2.86616,0,0,1,.63672.06934v1.73828a2.598,2.598,0,0,0-.835-.1123,1.87264,1.87264,0,0,0-1.93651,2.083v5.37012h-1.8584Z" style="fill: #fff"/>
|
||||
<path d="M145.84035,27.83691c-.25,1.64355-1.85059,2.77148-3.89844,2.77148-2.63379,0-4.26855-1.76465-4.26855-4.5957,0-2.83984,1.64355-4.68164,4.19043-4.68164,2.50488,0,4.08008,1.7207,4.08008,4.46582v.63672h-6.39453v.1123a2.358,2.358,0,0,0,2.43555,2.56445,2.04834,2.04834,0,0,0,2.09082-1.27344Zm-6.28223-2.70215h4.52637a2.1773,2.1773,0,0,0-2.2207-2.29785A2.292,2.292,0,0,0,139.55813,25.13477Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_Group_5" data-name="<Group>">
|
||||
<g>
|
||||
<path d="M37.82619,8.731a2.63964,2.63964,0,0,1,2.80762,2.96484c0,1.90625-1.03027,3.002-2.80762,3.002H35.67092V8.731Zm-1.22852,5.123h1.125a1.87588,1.87588,0,0,0,1.96777-2.146,1.881,1.881,0,0,0-1.96777-2.13379h-1.125Z" style="fill: #fff"/>
|
||||
<path d="M41.68068,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C44.57521,13.99463,45.01369,13.42432,45.01369,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M51.57326,14.69775h-.92187l-.93066-3.31641h-.07031l-.92676,3.31641h-.91309l-1.24121-4.50293h.90137l.80664,3.436h.06641l.92578-3.436h.85254l.92578,3.436h.07031l.80273-3.436h.88867Z" style="fill: #fff"/>
|
||||
<path d="M53.85354,10.19482H54.709v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915h-.88867V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M59.09377,8.437h.88867v6.26074h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M61.21779,12.44434a2.13323,2.13323,0,1,1,4.24707,0,2.13358,2.13358,0,1,1-4.24707,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C64.11232,13.99463,64.5508,13.42432,64.5508,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M66.40041,13.42432c0-.81055.60352-1.27783,1.6748-1.34424l1.21973-.07031v-.38867c0-.47559-.31445-.74414-.92187-.74414-.49609,0-.83984.18213-.93848.50049h-.86035c.09082-.77344.81836-1.26953,1.83984-1.26953,1.12891,0,1.76563.562,1.76563,1.51318v3.07666h-.85547v-.63281h-.07031a1.515,1.515,0,0,1-1.35254.707A1.36026,1.36026,0,0,1,66.40041,13.42432Zm2.89453-.38477v-.37646l-1.09961.07031c-.62012.0415-.90137.25244-.90137.64941,0,.40527.35156.64111.835.64111A1.0615,1.0615,0,0,0,69.29494,13.03955Z" style="fill: #fff"/>
|
||||
<path d="M71.34768,12.44434c0-1.42285.73145-2.32422,1.86914-2.32422a1.484,1.484,0,0,1,1.38086.79h.06641V8.437h.88867v6.26074h-.85156v-.71143h-.07031a1.56284,1.56284,0,0,1-1.41406.78564C72.07131,14.772,71.34768,13.87061,71.34768,12.44434Zm.918,0c0,.95508.4502,1.52979,1.20313,1.52979.749,0,1.21191-.583,1.21191-1.52588,0-.93848-.46777-1.52979-1.21191-1.52979C72.72072,10.91846,72.26564,11.49707,72.26564,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M79.22951,12.44434a2.13346,2.13346,0,1,1,4.24756,0,2.1338,2.1338,0,1,1-4.24756,0Zm3.333,0c0-.97607-.43848-1.54687-1.208-1.54687-.77246,0-1.207.5708-1.207,1.54688,0,.98389.43457,1.55029,1.207,1.55029C82.124,13.99463,82.56252,13.42432,82.56252,12.44434Z" style="fill: #fff"/>
|
||||
<path d="M84.66945,10.19482h.85547v.71533h.06641a1.348,1.348,0,0,1,1.34375-.80225,1.46456,1.46456,0,0,1,1.55859,1.6748v2.915H87.605V12.00586c0-.72363-.31445-1.0835-.97168-1.0835a1.03294,1.03294,0,0,0-1.0752,1.14111v2.63428h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M93.51516,9.07373v1.1416h.97559v.74854h-.97559V13.2793c0,.47168.19434.67822.63672.67822a2.96657,2.96657,0,0,0,.33887-.02051v.74023a2.9155,2.9155,0,0,1-.4834.04541c-.98828,0-1.38184-.34766-1.38184-1.21582v-2.543h-.71484v-.74854h.71484V9.07373Z" style="fill: #fff"/>
|
||||
<path d="M95.70461,8.437h.88086v2.48145h.07031a1.3856,1.3856,0,0,1,1.373-.80664,1.48339,1.48339,0,0,1,1.55078,1.67871v2.90723H98.69v-2.688c0-.71924-.335-1.0835-.96289-1.0835a1.05194,1.05194,0,0,0-1.13379,1.1416v2.62988h-.88867Z" style="fill: #fff"/>
|
||||
<path d="M104.76125,13.48193a1.828,1.828,0,0,1-1.95117,1.30273A2.04531,2.04531,0,0,1,100.73,12.46045a2.07685,2.07685,0,0,1,2.07617-2.35254c1.25293,0,2.00879.856,2.00879,2.27V12.688h-3.17969v.0498a1.1902,1.1902,0,0,0,1.19922,1.29,1.07934,1.07934,0,0,0,1.07129-.5459Zm-3.126-1.45117h2.27441a1.08647,1.08647,0,0,0-1.1084-1.1665A1.15162,1.15162,0,0,0,101.63527,12.03076Z" style="fill: #fff"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
33
.github/workflows/cache_dependencies.yml
vendored
|
@ -11,15 +11,29 @@ jobs:
|
|||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: insightsengineering/disk-space-reclaimer@v1
|
||||
with:
|
||||
tools-cache: true
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
swap-storage: true
|
||||
docker-images: true
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: '11.x'
|
||||
|
||||
java-version: "11.x"
|
||||
- name: Configure placeholder git details
|
||||
run: |
|
||||
git config --global user.email "CI@cakewallet.com"
|
||||
git config --global user.name "Cake Github Actions"
|
||||
- name: Flutter action
|
||||
uses: subosito/flutter-action@v1
|
||||
with:
|
||||
flutter-version: '3.10.x'
|
||||
flutter-version: "3.19.6"
|
||||
channel: stable
|
||||
|
||||
- name: Install package dependencies
|
||||
|
@ -30,10 +44,13 @@ jobs:
|
|||
sudo mkdir -p /opt/android
|
||||
sudo chown $USER /opt/android
|
||||
cd /opt/android
|
||||
-y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
cargo install cargo-ndk
|
||||
git clone https://github.com/cake-tech/cake_wallet.git --branch main
|
||||
cd cake_wallet/scripts/android/
|
||||
./install_ndk.sh
|
||||
source ./app_env.sh cakewallet
|
||||
chmod +x pubspec_gen.sh
|
||||
./app_config.sh
|
||||
|
||||
- name: Cache Externals
|
||||
|
@ -42,16 +59,12 @@ jobs:
|
|||
with:
|
||||
path: |
|
||||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||
/opt/android/cake_wallet/cw_haven/ios/External
|
||||
/opt/android/cake_wallet/cw_monero/android/.cxx
|
||||
/opt/android/cake_wallet/cw_monero/ios/External
|
||||
/opt/android/cake_wallet/cw_shared_external/ios/External
|
||||
key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }}
|
||||
/opt/android/cake_wallet/scripts/monero_c/release
|
||||
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }}
|
||||
|
||||
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||
name: Generate Externals
|
||||
run: |
|
||||
cd /opt/android/cake_wallet/scripts/android/
|
||||
source ./app_env.sh cakewallet
|
||||
./build_all.sh
|
||||
./copy_monero_deps.sh
|
||||
./build_monero_all.sh
|
||||
|
|
49
.github/workflows/pr_test_build.yml
vendored
|
@ -27,22 +27,29 @@ jobs:
|
|||
if: github.event_name != 'pull_request'
|
||||
run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Free Up GitHub Actions Ubuntu Runner Disk Space
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: insightsengineering/disk-space-reclaimer@v1
|
||||
with:
|
||||
tools-cache: true
|
||||
android: false
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
swap-storage: true
|
||||
docker-images: true
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: "11.x"
|
||||
|
||||
- name: Configure placeholder git details
|
||||
run: |
|
||||
git config --global user.email "CI@cakewallet.com"
|
||||
git config --global user.name "Cake Github Actions"
|
||||
- name: Flutter action
|
||||
uses: subosito/flutter-action@v1
|
||||
with:
|
||||
flutter-version: "3.10.x"
|
||||
flutter-version: "3.19.6"
|
||||
channel: stable
|
||||
|
||||
- name: Install package dependencies
|
||||
|
@ -68,19 +75,15 @@ jobs:
|
|||
with:
|
||||
path: |
|
||||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||
/opt/android/cake_wallet/cw_haven/ios/External
|
||||
/opt/android/cake_wallet/cw_monero/android/.cxx
|
||||
/opt/android/cake_wallet/cw_monero/ios/External
|
||||
/opt/android/cake_wallet/cw_shared_external/ios/External
|
||||
key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }}
|
||||
/opt/android/cake_wallet/scripts/monero_c/release
|
||||
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }}
|
||||
|
||||
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||
name: Generate Externals
|
||||
run: |
|
||||
cd /opt/android/cake_wallet/scripts/android/
|
||||
source ./app_env.sh cakewallet
|
||||
./build_all.sh
|
||||
./copy_monero_deps.sh
|
||||
./build_monero_all.sh
|
||||
|
||||
- name: Install Flutter dependencies
|
||||
run: |
|
||||
|
@ -113,6 +116,9 @@ jobs:
|
|||
touch lib/.secrets.g.dart
|
||||
touch cw_evm/lib/.secrets.g.dart
|
||||
touch cw_solana/lib/.secrets.g.dart
|
||||
touch cw_core/lib/.secrets.g.dart
|
||||
touch cw_nano/lib/.secrets.g.dart
|
||||
touch cw_tron/lib/.secrets.g.dart
|
||||
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
|
||||
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
||||
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
|
||||
|
@ -139,15 +145,26 @@ jobs:
|
|||
echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
|
||||
echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
||||
echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
||||
echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
|
||||
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
|
||||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
|
||||
echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart
|
||||
echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
|
||||
echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
|
||||
- name: Rename app
|
||||
run: |
|
||||
|
|
14
.gitignore
vendored
|
@ -94,9 +94,12 @@ android/app/key.jks
|
|||
**/tool/.evm-secrets-config.json
|
||||
**/tool/.ethereum-secrets-config.json
|
||||
**/tool/.solana-secrets-config.json
|
||||
**/tool/.nano-secrets-config.json
|
||||
**/tool/.tron-secrets-config.json
|
||||
**/lib/.secrets.g.dart
|
||||
**/cw_evm/lib/.secrets.g.dart
|
||||
**/cw_solana/lib/.secrets.g.dart
|
||||
**/cw_tron/lib/.secrets.g.dart
|
||||
|
||||
vendor/
|
||||
|
||||
|
@ -132,6 +135,9 @@ lib/bitcoin_cash/bitcoin_cash.dart
|
|||
lib/nano/nano.dart
|
||||
lib/polygon/polygon.dart
|
||||
lib/solana/solana.dart
|
||||
lib/tron/tron.dart
|
||||
lib/wownero/wownero.dart
|
||||
lib/zano/zano.dart
|
||||
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||
|
@ -152,6 +158,8 @@ assets/images/app_logo.png
|
|||
macos/Runner/Info.plist
|
||||
macos/Runner/DebugProfile.entitlements
|
||||
macos/Runner/Release.entitlements
|
||||
macos/Runner/Runner.entitlements
|
||||
lib/core/secure_storage.dart
|
||||
|
||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
|
||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
|
||||
|
@ -161,3 +169,9 @@ macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
|
|||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
|
||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
|
||||
macos/Runner/Configs/AppInfo.xcconfig
|
||||
|
||||
# Monero.dart (Monero_C)
|
||||
scripts/monero_c
|
||||
# iOS generated framework bin
|
||||
ios/MoneroWallet.framework/MoneroWallet
|
||||
ios/WowneroWallet.framework/WowneroWallet
|
||||
|
|
16
.metadata
|
@ -1,11 +1,11 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
channel: stable
|
||||
revision: "367f9ea16bfae1ca451b9cc27c1366870b187ae2"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
|
@ -13,11 +13,11 @@ project_type: app
|
|||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
- platform: macos
|
||||
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
- platform: windows
|
||||
create_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
base_revision: 367f9ea16bfae1ca451b9cc27c1366870b187ae2
|
||||
|
||||
# User provided section
|
||||
|
||||
|
|
38
README.md
|
@ -1,15 +1,35 @@
|
|||
# Cake Wallet for Mobile and Desktop
|
||||
<div align="center">
|
||||
|
||||
## Open Source Multi-Currency Wallet
|
||||
<img height="100" src=".github/assets/Logo_CakeWallet.png">
|
||||
|
||||
## Links
|
||||
</div>
|
||||
|
||||
* Website: https://cakewallet.com
|
||||
* App Store (iOS / MacOS): https://cakewallet.com/ios
|
||||
* Google Play: https://cakewallet.com/gp
|
||||
* F-Droid: https://fdroid.cakelabs.com
|
||||
* APK: https://github.com/cake-tech/cake_wallet/releases
|
||||
* Linux: https://github.com/cake-tech/cake_wallet/releases
|
||||
![devices](.github/assets/devices.png)
|
||||
|
||||
<div align="center">
|
||||
|
||||
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=iphone)
|
||||
[<img height="42" src=".github/assets/google-play-badge.png">](https://play.google.com/store/apps/details?id=com.cakewallet.cake_wallet)
|
||||
[<img height="42" src=".github/assets/f-droid-badge.png">](https://fdroid.cakelabs.com)
|
||||
[<img height="42" src=".github/assets/mac-store-badge.svg">](https://apps.apple.com/us/app/cake-wallet/id1334702542?platform=mac)
|
||||
[<img height="42" src=".github/assets/linux-badge.svg">](https://github.com/cake-tech/cake_wallet/releases)
|
||||
|
||||
</div>
|
||||
|
||||
# Cake Wallet
|
||||
|
||||
Cake Wallet is an open source, non-custodial, and private multi-currency crypto wallet for Android, iOS, macOS, and Linux.
|
||||
|
||||
Cake Wallet includes support for several cryptocurrencies, including:
|
||||
* Monero (XMR)
|
||||
* Bitcoin (BTC)
|
||||
* Ethereum (ETH)
|
||||
* Litecoin (LTC)
|
||||
* Bitcoin Cash (BCH)
|
||||
* Polygon (MATIC)
|
||||
* Solana (SOL)
|
||||
* Nano (XNO)
|
||||
* Haven (XHV)
|
||||
|
||||
## Features
|
||||
|
||||
|
|
12
SECURITY.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you need to report a vulnerability, please either:
|
||||
|
||||
* Open a security advisory: https://github.com/cake-tech/cake_wallet/security/advisories/new
|
||||
* Send an email to `dev@cakewallet.com` with details on the vulnerability
|
||||
|
||||
## Supported Versions
|
||||
|
||||
As we don't maintain prevoius versions of the app, only the latest release for each platform is supported and any updates will bump the version number.
|
|
@ -46,7 +46,7 @@ android {
|
|||
defaultConfig {
|
||||
applicationId appProperties['id']
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 33
|
||||
targetSdkVersion 34
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
|
@ -9,6 +9,26 @@
|
|||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<!--bibo01 : hardware option-->
|
||||
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/>
|
||||
|
||||
<!-- required for API 18 - 30 -->
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH"
|
||||
android:maxSdkVersion="30" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_ADMIN"
|
||||
android:maxSdkVersion="30" />
|
||||
|
||||
<!-- API 31+ -->
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission
|
||||
android:name="android.permission.BLUETOOTH_SCAN"
|
||||
android:usesPermissionFlags="neverForLocation" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
|
@ -18,7 +38,8 @@
|
|||
android:fullBackupContent="false"
|
||||
android:versionCode="__versionCode__"
|
||||
android:versionName="__versionName__"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:extractNativeLibs="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:launchMode="singleInstance"
|
||||
|
@ -67,6 +88,19 @@
|
|||
<data android:scheme="polygon-wallet" />
|
||||
<data android:scheme="polygon_wallet" />
|
||||
<data android:scheme="solana-wallet" />
|
||||
<data android:scheme="tron" />
|
||||
<data android:scheme="tron-wallet" />
|
||||
<data android:scheme="tron_wallet" />
|
||||
<data android:scheme="wownero" />
|
||||
<data android:scheme="wownero-wallet" />
|
||||
<data android:scheme="wownero_wallet" />
|
||||
</intent-filter>
|
||||
<!-- nano-gpt link scheme -->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="nano-gpt" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<meta-data
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../../../../../scripts/monero_c/release/monero/aarch64-linux-android_libwallet2_api_c.so
|
|
@ -0,0 +1 @@
|
|||
../../../../../../scripts/monero_c/release/wownero/aarch64-linux-android_libwallet2_api_c.so
|
0
android/app/src/main/jniLibs/armeabi-v7a/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
|||
../../../../../../scripts/monero_c/release/monero/armv7a-linux-androideabi_libwallet2_api_c.so
|
|
@ -0,0 +1 @@
|
|||
../../../../../../scripts/monero_c/release/wownero/armv7a-linux-androideabi_libwallet2_api_c.so
|
0
android/app/src/main/jniLibs/x86/.gitkeep
Normal file
0
android/app/src/main/jniLibs/x86_64/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
|||
../../../../../../scripts/monero_c/release/monero/x86_64-linux-android_libwallet2_api_c.so
|
|
@ -0,0 +1 @@
|
|||
../../../../../../scripts/monero_c/release/wownero/x86_64-linux-android_libwallet2_api_c.so
|
|
@ -1,5 +1,5 @@
|
|||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
ext.kotlin_version = '1.8.21'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
|
|
5
assets/banano_node_list.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
-
|
||||
uri: kaliumapi.appditto.com
|
||||
path: /api
|
||||
useSSL: true
|
||||
is_default: true
|
|
@ -1,2 +1,8 @@
|
|||
-
|
||||
uri: electrum.cakewallet.com:50002
|
||||
uri: electrum.cakewallet.com:50002
|
||||
useSSL: true
|
||||
-
|
||||
uri: btc-electrum.cakewallet.com:50002
|
||||
isDefault: true
|
||||
-
|
||||
uri: electrs.cakewallet.com:50001
|
||||
|
|
BIN
assets/images/bluetooth.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
65
assets/images/cards.svg
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
assets/images/ledger_nano.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 471 B |
69
assets/images/notification_icon.svg
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="510px"
|
||||
height="510px"
|
||||
viewBox="0 0 510 510"
|
||||
style="enable-background:new 0 0 510 510;"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="notification_logo_tilt_white.svg"><metadata
|
||||
id="metadata42"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs40" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1090"
|
||||
id="namedview38"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4627451"
|
||||
inkscape:cx="-849.27966"
|
||||
inkscape:cy="255"
|
||||
inkscape:window-x="-12"
|
||||
inkscape:window-y="58"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="notifications"
|
||||
inkscape:object-paths="true" /><g
|
||||
id="g3"
|
||||
transform="matrix(0.87658593,0,0,0.87658593,31.470588,31.470588)"><g
|
||||
id="notifications"
|
||||
transform="rotate(-20,255,255)"><path
|
||||
d="m 233.56017,502.19654 c 28.05,0 51,-22.95 51,-51 h -102 c 0,28.05 22.95,51 51,51 z m 165.75,-153 v -140.25 c 0,-79.05 -53.55,-142.8 -127.5,-160.65 v -17.85 c 0,-20.4 -17.85,-38.2499996 -38.25,-38.2499996 -20.4,0 -38.25,17.8499996 -38.25,38.2499996 v 17.85 c -73.95,17.85 -127.499999,81.6 -127.499999,160.65 v 140.25 l -51,51 v 25.5 H 450.31017 v -25.5 z"
|
||||
id="path6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:0;stroke:#000000;stroke-width:19.39342117;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /></g></g><g
|
||||
id="g8" /><g
|
||||
id="g10" /><g
|
||||
id="g12" /><g
|
||||
id="g14" /><g
|
||||
id="g16" /><g
|
||||
id="g18" /><g
|
||||
id="g20" /><g
|
||||
id="g22" /><g
|
||||
id="g24" /><g
|
||||
id="g26" /><g
|
||||
id="g28" /><g
|
||||
id="g30" /><g
|
||||
id="g32" /><g
|
||||
id="g34" /><g
|
||||
id="g36" /></svg>
|
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/images/quantex.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
assets/images/tbtc.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
assets/images/thorchain.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/images/usb.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/images/wownero_icon.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
assets/images/wownero_menu.png
Normal file
After Width: | Height: | Size: 49 KiB |
|
@ -1,2 +1,4 @@
|
|||
-
|
||||
uri: ltc-electrum.cakewallet.com:50002
|
||||
uri: ltc-electrum.cakewallet.com:50002
|
||||
useSSL: true
|
||||
isDefault: true
|
|
@ -1,6 +1,31 @@
|
|||
-
|
||||
uri: rpc.nano.to
|
||||
uri: nano.nownodes.io
|
||||
useSSL: true
|
||||
is_default: true
|
||||
-
|
||||
uri: node.perish.co:9076
|
||||
uri: rpc.nano.to
|
||||
useSSL: true
|
||||
-
|
||||
uri: node.nautilus.io
|
||||
path: /api
|
||||
useSSL: true
|
||||
-
|
||||
uri: app.natrium.io
|
||||
path: /api
|
||||
useSSL: true
|
||||
-
|
||||
uri: rainstorm.city
|
||||
path: /api
|
||||
useSSL: true
|
||||
-
|
||||
uri: node.somenano.com
|
||||
path: /proxy
|
||||
useSSL: true
|
||||
-
|
||||
uri: nanoslo.0x.no
|
||||
path: /proxy
|
||||
useSSL: true
|
||||
-
|
||||
uri: www.bitrequest.app
|
||||
port: 8020
|
||||
useSSL: true
|
|
@ -6,4 +6,4 @@
|
|||
uri: workers.perish.co
|
||||
-
|
||||
uri: worker.nanoriver.cc
|
||||
useSSL: true
|
||||
useSSL: true
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
New themes
|
||||
Bug fixes and enhancements
|
||||
Monero enhancements
|
||||
Synchronization improvements
|
||||
Bug fixes
|
|
@ -1,6 +1,5 @@
|
|||
Add Solana wallet
|
||||
Support ALL Bitcoin address types (Legacy, Segwit (both variants), Taproot)
|
||||
Enhance Sending/Receiving flow for Bitcoin
|
||||
Improve fee calculations in Bitcoin
|
||||
New themes
|
||||
Bug fixes and enhancements
|
||||
Monero and Ethereum enhancements
|
||||
Synchronization improvements
|
||||
Exchange flow enhancements
|
||||
Ledger improvements
|
||||
Bug fixes
|
12
assets/tron_node_list.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
-
|
||||
uri: tron-rpc.publicnode.com:443
|
||||
is_default: false
|
||||
useSSL: true
|
||||
-
|
||||
uri: api.trongrid.io
|
||||
is_default: false
|
||||
useSSL: true
|
||||
-
|
||||
uri: trx.nownodes.io
|
||||
is_default: true
|
||||
useSSL: true
|
12
assets/wownero_node_list.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
-
|
||||
uri: node3.monerodevs.org:34568
|
||||
is_default: true
|
||||
useSSL: false
|
||||
-
|
||||
uri: node2.monerodevs.org:34568
|
||||
is_default: false
|
||||
useSSL: false
|
||||
-
|
||||
uri: node.monerodevs.org:34568
|
||||
is_default: false
|
||||
useSSL: false
|
4
assets/zano_node_list.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
-
|
||||
uri: zano.org
|
||||
is_default: true
|
||||
useSSL: true
|
51
cakewallet.bat
Normal file
|
@ -0,0 +1,51 @@
|
|||
@echo off
|
||||
set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron
|
||||
set cw_root=%cd%
|
||||
set cw_archive_name=Cake Wallet.zip
|
||||
set cw_archive_path=%cw_root%\%cw_archive_name%
|
||||
set secrets_file_path=lib\.secrets.g.dart
|
||||
set release_dir=build\windows\x64\runner\Release
|
||||
@REM Path could be different
|
||||
if [%~1]==[] (set tools_root=C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.38.33135\x64\Microsoft.VC143.CRT) else (set tools_root=%1)
|
||||
@REM Generate android manifest file
|
||||
cd scripts
|
||||
bash.exe gen_android_manifest.sh
|
||||
cd /d %cw_root%
|
||||
echo === Generating pubspec.yaml ===
|
||||
copy /Y pubspec_description.yaml pubspec.yaml > nul
|
||||
call flutter pub get > nul
|
||||
call dart run tool\generate_pubspec.dart
|
||||
call flutter pub get > nul
|
||||
call dart run tool\configure.dart %cw_win_app_config%
|
||||
|
||||
IF NOT EXIST "%secrets_file_path%" (
|
||||
echo === Generating new secrets file ===
|
||||
call dart run tool\generate_new_secrets.dart
|
||||
) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===)
|
||||
|
||||
echo === Generating mobx models ===
|
||||
for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron .) do (
|
||||
cd %%i
|
||||
call flutter pub get > nul
|
||||
call dart run build_runner build --delete-conflicting-outputs > nul
|
||||
cd /d %cw_root%
|
||||
)
|
||||
|
||||
echo === Generating localization files ===
|
||||
call dart run tool\generate_localization.dart
|
||||
|
||||
echo === Building the application executable file ===
|
||||
call flutter build windows --dart-define-from-file=env.json --release
|
||||
|
||||
echo === Prepare distribution actions. Copy needed files to the application bundle ===
|
||||
copy /Y "%tools_root%\msvcp140.dll" "%release_dir%\" > nul
|
||||
copy /Y "%tools_root%\vcruntime140.dll" "%release_dir%\" > nul
|
||||
copy /Y "%tools_root%\vcruntime140_1.dll" "%release_dir%\" > nul
|
||||
|
||||
echo === Generate the application archive ===
|
||||
xcopy /s /e /v /Y "%release_dir%\*.*" "build\Cake Wallet\" > nul
|
||||
tar acf "%cw_archive_name%" -C build\ "Cake Wallet"
|
||||
|
||||
echo === Open Explorer with the application archive ===
|
||||
echo Cake Wallet created archive at: %cw_archive_path%
|
||||
%SystemRoot%\explorer.exe /select, %cw_archive_path%
|
|
@ -1,11 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
IOS="ios"
|
||||
ANDROID="android"
|
||||
MACOS="macos"
|
||||
|
||||
PLATFORMS=($IOS $ANDROID)
|
||||
PLATFORMS=($IOS $ANDROID $MACOS)
|
||||
PLATFORM=$1
|
||||
|
||||
if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then
|
||||
echo "specify platform: ./configure_cake_wallet.sh ios|android"
|
||||
echo "specify platform: ./configure_cake_wallet.sh ios|android|macos"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
@ -14,6 +17,11 @@ if [ "$PLATFORM" == "$IOS" ]; then
|
|||
cd scripts/ios
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" == "$MACOS" ]; then
|
||||
echo "Configuring for macOS"
|
||||
cd scripts/macos
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" == "$ANDROID" ]; then
|
||||
echo "Configuring for Android"
|
||||
cd scripts/android
|
||||
|
@ -22,15 +30,6 @@ fi
|
|||
source ./app_env.sh cakewallet
|
||||
./app_config.sh
|
||||
cd ../.. && flutter pub get
|
||||
flutter packages pub run tool/generate_localization.dart
|
||||
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_solana && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
|
||||
cd cw_ethereum && flutter pub get && cd ..
|
||||
cd cw_polygon && flutter pub get && cd ..
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
#flutter packages pub run tool/generate_localization.dart
|
||||
./model_generator.sh
|
||||
#cd macos && pod install
|
|
@ -3,6 +3,9 @@ import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
|
|||
|
||||
List<int> addressToOutputScript(String address, bitcoin.BasedUtxoNetwork network) {
|
||||
try {
|
||||
if (network == bitcoin.BitcoinCashNetwork.mainnet) {
|
||||
return bitcoin.BitcoinCashAddress(address).baseAddress.toScriptPubKey().toBytes();
|
||||
}
|
||||
return bitcoin.addressToOutputScript(address: address, network: network);
|
||||
} catch (err) {
|
||||
print(err);
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import 'dart:convert';
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/script_hash.dart' as sh;
|
||||
|
||||
class BitcoinAddressRecord {
|
||||
BitcoinAddressRecord(
|
||||
abstract class BaseBitcoinAddressRecord {
|
||||
BaseBitcoinAddressRecord(
|
||||
this.address, {
|
||||
required this.index,
|
||||
this.isHidden = false,
|
||||
|
@ -14,39 +13,14 @@ class BitcoinAddressRecord {
|
|||
String name = '',
|
||||
bool isUsed = false,
|
||||
required this.type,
|
||||
String? scriptHash,
|
||||
required this.network,
|
||||
}) : _txCount = txCount,
|
||||
_balance = balance,
|
||||
_name = name,
|
||||
_isUsed = isUsed,
|
||||
scriptHash =
|
||||
scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
|
||||
|
||||
factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork? network) {
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinAddressRecord(
|
||||
decoded['address'] as String,
|
||||
index: decoded['index'] as int,
|
||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||
txCount: decoded['txCount'] as int? ?? 0,
|
||||
name: decoded['name'] as String? ?? '',
|
||||
balance: decoded['balance'] as int? ?? 0,
|
||||
type: decoded['type'] != null && decoded['type'] != ''
|
||||
? BitcoinAddressType.values
|
||||
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
||||
: SegwitAddresType.p2wpkh,
|
||||
scriptHash: decoded['scriptHash'] as String?,
|
||||
network: (decoded['network'] as String?) == null
|
||||
? network
|
||||
: BasedUtxoNetwork.fromName(decoded['network'] as String),
|
||||
);
|
||||
}
|
||||
_isUsed = isUsed;
|
||||
|
||||
@override
|
||||
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
|
||||
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
|
||||
|
||||
final String address;
|
||||
bool isHidden;
|
||||
|
@ -55,7 +29,6 @@ class BitcoinAddressRecord {
|
|||
int _balance;
|
||||
String _name;
|
||||
bool _isUsed;
|
||||
String? scriptHash;
|
||||
BasedUtxoNetwork? network;
|
||||
|
||||
int get txCount => _txCount;
|
||||
|
@ -73,18 +46,57 @@ class BitcoinAddressRecord {
|
|||
void setAsUsed() => _isUsed = true;
|
||||
void setNewName(String label) => _name = label;
|
||||
|
||||
@override
|
||||
int get hashCode => address.hashCode;
|
||||
|
||||
String get cashAddr => bitbox.Address.toCashAddress(address);
|
||||
|
||||
BitcoinAddressType type;
|
||||
|
||||
String updateScriptHash(BasedUtxoNetwork network) {
|
||||
String toJSON();
|
||||
}
|
||||
|
||||
class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
||||
BitcoinAddressRecord(
|
||||
super.address, {
|
||||
required super.index,
|
||||
super.isHidden = false,
|
||||
super.txCount = 0,
|
||||
super.balance = 0,
|
||||
super.name = '',
|
||||
super.isUsed = false,
|
||||
required super.type,
|
||||
String? scriptHash,
|
||||
required super.network,
|
||||
}) : scriptHash =
|
||||
scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
|
||||
|
||||
factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinAddressRecord(
|
||||
decoded['address'] as String,
|
||||
index: decoded['index'] as int,
|
||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||
txCount: decoded['txCount'] as int? ?? 0,
|
||||
name: decoded['name'] as String? ?? '',
|
||||
balance: decoded['balance'] as int? ?? 0,
|
||||
type: decoded['type'] != null && decoded['type'] != ''
|
||||
? BitcoinAddressType.values
|
||||
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
||||
: SegwitAddresType.p2wpkh,
|
||||
scriptHash: decoded['scriptHash'] as String?,
|
||||
network: network,
|
||||
);
|
||||
}
|
||||
|
||||
String? scriptHash;
|
||||
|
||||
String getScriptHash(BasedUtxoNetwork network) {
|
||||
if (scriptHash != null) return scriptHash!;
|
||||
scriptHash = sh.scriptHash(address, network: network);
|
||||
return scriptHash!;
|
||||
}
|
||||
|
||||
@override
|
||||
String toJSON() => json.encode({
|
||||
'address': address,
|
||||
'index': index,
|
||||
|
@ -95,6 +107,59 @@ class BitcoinAddressRecord {
|
|||
'balance': balance,
|
||||
'type': type.toString(),
|
||||
'scriptHash': scriptHash,
|
||||
'network': network?.value,
|
||||
});
|
||||
}
|
||||
|
||||
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
|
||||
BitcoinSilentPaymentAddressRecord(
|
||||
super.address, {
|
||||
required super.index,
|
||||
super.isHidden = false,
|
||||
super.txCount = 0,
|
||||
super.balance = 0,
|
||||
super.name = '',
|
||||
super.isUsed = false,
|
||||
required this.silentPaymentTweak,
|
||||
required super.network,
|
||||
required super.type,
|
||||
}) : super();
|
||||
|
||||
factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource,
|
||||
{BasedUtxoNetwork? network}) {
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
return BitcoinSilentPaymentAddressRecord(
|
||||
decoded['address'] as String,
|
||||
index: decoded['index'] as int,
|
||||
isHidden: decoded['isHidden'] as bool? ?? false,
|
||||
isUsed: decoded['isUsed'] as bool? ?? false,
|
||||
txCount: decoded['txCount'] as int? ?? 0,
|
||||
name: decoded['name'] as String? ?? '',
|
||||
balance: decoded['balance'] as int? ?? 0,
|
||||
network: (decoded['network'] as String?) == null
|
||||
? network
|
||||
: BasedUtxoNetwork.fromName(decoded['network'] as String),
|
||||
silentPaymentTweak: decoded['silent_payment_tweak'] as String?,
|
||||
type: decoded['type'] != null && decoded['type'] != ''
|
||||
? BitcoinAddressType.values
|
||||
.firstWhere((type) => type.toString() == decoded['type'] as String)
|
||||
: SilentPaymentsAddresType.p2sp,
|
||||
);
|
||||
}
|
||||
|
||||
final String? silentPaymentTweak;
|
||||
|
||||
@override
|
||||
String toJSON() => json.encode({
|
||||
'address': address,
|
||||
'index': index,
|
||||
'isHidden': isHidden,
|
||||
'isUsed': isUsed,
|
||||
'txCount': txCount,
|
||||
'name': name,
|
||||
'balance': balance,
|
||||
'type': type.toString(),
|
||||
'network': network?.value,
|
||||
'silent_payment_tweak': silentPaymentTweak,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
class BitcoinCommitTransactionException implements Exception {
|
||||
String errorMessage;
|
||||
BitcoinCommitTransactionException(this.errorMessage);
|
||||
|
||||
@override
|
||||
String toString() => 'Transaction commit is failed.';
|
||||
}
|
||||
String toString() => errorMessage;
|
||||
}
|
||||
|
||||
|
|
43
cw_bitcoin/lib/bitcoin_hardware_wallet_service.dart
Normal file
|
@ -0,0 +1,43 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
import 'package:ledger_flutter/ledger_flutter.dart';
|
||||
|
||||
class BitcoinHardwareWalletService {
|
||||
BitcoinHardwareWalletService(this.ledger, this.device);
|
||||
|
||||
final Ledger ledger;
|
||||
final LedgerDevice device;
|
||||
|
||||
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async {
|
||||
final bitcoinLedgerApp = BitcoinLedgerApp(ledger);
|
||||
|
||||
final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device);
|
||||
print(masterFp);
|
||||
|
||||
final accounts = <HardwareAccountData>[];
|
||||
final indexRange = List.generate(limit, (i) => i + index);
|
||||
|
||||
for (final i in indexRange) {
|
||||
final derivationPath = "m/84'/0'/$i'";
|
||||
final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath);
|
||||
HDWallet hd = HDWallet.fromBase58(xpub).derive(0);
|
||||
|
||||
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet);
|
||||
|
||||
accounts.add(HardwareAccountData(
|
||||
address: address,
|
||||
accountIndex: i,
|
||||
derivationPath: derivationPath,
|
||||
masterFingerprint: masterFp,
|
||||
xpub: xpub,
|
||||
));
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
}
|
|
@ -2,9 +2,9 @@ import 'dart:convert';
|
|||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:unorm_dart/unorm_dart.dart' as unorm;
|
||||
import 'package:cryptography/cryptography.dart' as cryptography;
|
||||
import 'package:cw_core/sec_random_native.dart';
|
||||
import 'package:cw_core/utils/text_normalizer.dart';
|
||||
|
||||
const segwit = '100';
|
||||
final wordlist = englishWordlist;
|
||||
|
@ -90,8 +90,7 @@ List<bool> prefixMatches(String source, List<String> prefixes) {
|
|||
return prefixes.map((prefix) => hx.startsWith(prefix.toLowerCase())).toList();
|
||||
}
|
||||
|
||||
Future<String> generateMnemonic(
|
||||
{int strength = 264, String prefix = segwit}) async {
|
||||
Future<String> generateElectrumMnemonic({int strength = 264, String prefix = segwit}) async {
|
||||
final wordBitlen = logBase(wordlist.length, 2).ceil();
|
||||
final wordCount = strength / wordBitlen;
|
||||
final byteCount = ((wordCount * wordBitlen).ceil() / 8).ceil();
|
||||
|
@ -106,22 +105,29 @@ Future<String> generateMnemonic(
|
|||
return result;
|
||||
}
|
||||
|
||||
Future<bool> checkIfMnemonicIsElectrum2(String mnemonic) async {
|
||||
return prefixMatches(mnemonic, [segwit]).first;
|
||||
}
|
||||
|
||||
Future<String> getMnemonicHash(String mnemonic) async {
|
||||
final hmacSha512 = Hmac(sha512, utf8.encode('Seed version'));
|
||||
final digest = hmacSha512.convert(utf8.encode(normalizeText(mnemonic)));
|
||||
final hx = digest.toString();
|
||||
return hx;
|
||||
}
|
||||
|
||||
Future<Uint8List> mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async {
|
||||
final pbkdf2 = cryptography.Pbkdf2(
|
||||
macAlgorithm: cryptography.Hmac.sha512(),
|
||||
iterations: 2048,
|
||||
bits: 512);
|
||||
final pbkdf2 =
|
||||
cryptography.Pbkdf2(macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512);
|
||||
final text = normalizeText(mnemonic);
|
||||
// pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce)
|
||||
final key = await pbkdf2.deriveKey(
|
||||
secretKey: cryptography.SecretKey(text.codeUnits),
|
||||
nonce: 'electrum'.codeUnits);
|
||||
secretKey: cryptography.SecretKey(text.codeUnits), nonce: 'electrum'.codeUnits);
|
||||
final bytes = await key.extractBytes();
|
||||
return Uint8List.fromList(bytes);
|
||||
}
|
||||
|
||||
bool matchesAnyPrefix(String mnemonic) =>
|
||||
prefixMatches(mnemonic, [segwit]).any((el) => el);
|
||||
bool matchesAnyPrefix(String mnemonic) => prefixMatches(mnemonic, [segwit]).any((el) => el);
|
||||
|
||||
bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
|
||||
try {
|
||||
|
@ -131,123 +137,6 @@ bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
|
|||
}
|
||||
}
|
||||
|
||||
final COMBININGCODEPOINTS = combiningcodepoints();
|
||||
|
||||
List<int> combiningcodepoints() {
|
||||
final source = '300:34e|350:36f|483:487|591:5bd|5bf|5c1|5c2|5c4|5c5|5c7|610:61a|64b:65f|670|' +
|
||||
'6d6:6dc|6df:6e4|6e7|6e8|6ea:6ed|711|730:74a|7eb:7f3|816:819|81b:823|825:827|' +
|
||||
'829:82d|859:85b|8d4:8e1|8e3:8ff|93c|94d|951:954|9bc|9cd|a3c|a4d|abc|acd|b3c|' +
|
||||
'b4d|bcd|c4d|c55|c56|cbc|ccd|d4d|dca|e38:e3a|e48:e4b|eb8|eb9|ec8:ecb|f18|f19|' +
|
||||
'f35|f37|f39|f71|f72|f74|f7a:f7d|f80|f82:f84|f86|f87|fc6|1037|1039|103a|108d|' +
|
||||
'135d:135f|1714|1734|17d2|17dd|18a9|1939:193b|1a17|1a18|1a60|1a75:1a7c|1a7f|' +
|
||||
'1ab0:1abd|1b34|1b44|1b6b:1b73|1baa|1bab|1be6|1bf2|1bf3|1c37|1cd0:1cd2|' +
|
||||
'1cd4:1ce0|1ce2:1ce8|1ced|1cf4|1cf8|1cf9|1dc0:1df5|1dfb:1dff|20d0:20dc|20e1|' +
|
||||
'20e5:20f0|2cef:2cf1|2d7f|2de0:2dff|302a:302f|3099|309a|a66f|a674:a67d|a69e|' +
|
||||
'a69f|a6f0|a6f1|a806|a8c4|a8e0:a8f1|a92b:a92d|a953|a9b3|a9c0|aab0|aab2:aab4|' +
|
||||
'aab7|aab8|aabe|aabf|aac1|aaf6|abed|fb1e|fe20:fe2f|101fd|102e0|10376:1037a|' +
|
||||
'10a0d|10a0f|10a38:10a3a|10a3f|10ae5|10ae6|11046|1107f|110b9|110ba|11100:11102|' +
|
||||
'11133|11134|11173|111c0|111ca|11235|11236|112e9|112ea|1133c|1134d|11366:1136c|' +
|
||||
'11370:11374|11442|11446|114c2|114c3|115bf|115c0|1163f|116b6|116b7|1172b|11c3f|' +
|
||||
'16af0:16af4|16b30:16b36|1bc9e|1d165:1d169|1d16d:1d172|1d17b:1d182|1d185:1d18b|' +
|
||||
'1d1aa:1d1ad|1d242:1d244|1e000:1e006|1e008:1e018|1e01b:1e021|1e023|1e024|' +
|
||||
'1e026:1e02a|1e8d0:1e8d6|1e944:1e94a';
|
||||
|
||||
return source.split('|').map((e) {
|
||||
if (e.contains(':')) {
|
||||
return e.split(':').map((hex) => int.parse(hex, radix: 16));
|
||||
}
|
||||
|
||||
return int.parse(e, radix: 16);
|
||||
}).fold(<int>[], (List<int> acc, element) {
|
||||
if (element is List) {
|
||||
for (var i = element[0] as int; i <= (element[1] as int); i++) {}
|
||||
} else if (element is int) {
|
||||
acc.add(element);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
String removeCombiningCharacters(String source) {
|
||||
return source
|
||||
.split('')
|
||||
.where((char) => !COMBININGCODEPOINTS.contains(char.codeUnits.first))
|
||||
.join('');
|
||||
}
|
||||
|
||||
bool isCJK(String char) {
|
||||
final n = char.codeUnitAt(0);
|
||||
|
||||
for (var x in CJKINTERVALS) {
|
||||
final imin = x[0] as num;
|
||||
final imax = x[1] as num;
|
||||
|
||||
if (n >= imin && n <= imax) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
String removeCJKSpaces(String source) {
|
||||
final splitted = source.split('');
|
||||
final filtered = <String>[];
|
||||
|
||||
for (var i = 0; i < splitted.length; i++) {
|
||||
final char = splitted[i];
|
||||
final isSpace = char.trim() == '';
|
||||
final prevIsCJK = i != 0 && isCJK(splitted[i - 1]);
|
||||
final nextIsCJK = i != splitted.length - 1 && isCJK(splitted[i + 1]);
|
||||
|
||||
if (!(isSpace && prevIsCJK && nextIsCJK)) {
|
||||
filtered.add(char);
|
||||
}
|
||||
}
|
||||
|
||||
return filtered.join('');
|
||||
}
|
||||
|
||||
String normalizeText(String source) {
|
||||
final res = removeCombiningCharacters(unorm.nfkd(source).toLowerCase())
|
||||
.trim()
|
||||
.split('/\s+/')
|
||||
.join(' ');
|
||||
|
||||
return removeCJKSpaces(res);
|
||||
}
|
||||
|
||||
const CJKINTERVALS = [
|
||||
[0x4e00, 0x9fff, 'CJK Unified Ideographs'],
|
||||
[0x3400, 0x4dbf, 'CJK Unified Ideographs Extension A'],
|
||||
[0x20000, 0x2a6df, 'CJK Unified Ideographs Extension B'],
|
||||
[0x2a700, 0x2b73f, 'CJK Unified Ideographs Extension C'],
|
||||
[0x2b740, 0x2b81f, 'CJK Unified Ideographs Extension D'],
|
||||
[0xf900, 0xfaff, 'CJK Compatibility Ideographs'],
|
||||
[0x2f800, 0x2fa1d, 'CJK Compatibility Ideographs Supplement'],
|
||||
[0x3190, 0x319f, 'Kanbun'],
|
||||
[0x2e80, 0x2eff, 'CJK Radicals Supplement'],
|
||||
[0x2f00, 0x2fdf, 'CJK Radicals'],
|
||||
[0x31c0, 0x31ef, 'CJK Strokes'],
|
||||
[0x2ff0, 0x2fff, 'Ideographic Description Characters'],
|
||||
[0xe0100, 0xe01ef, 'Variation Selectors Supplement'],
|
||||
[0x3100, 0x312f, 'Bopomofo'],
|
||||
[0x31a0, 0x31bf, 'Bopomofo Extended'],
|
||||
[0xff00, 0xffef, 'Halfwidth and Fullwidth Forms'],
|
||||
[0x3040, 0x309f, 'Hiragana'],
|
||||
[0x30a0, 0x30ff, 'Katakana'],
|
||||
[0x31f0, 0x31ff, 'Katakana Phonetic Extensions'],
|
||||
[0x1b000, 0x1b0ff, 'Kana Supplement'],
|
||||
[0xac00, 0xd7af, 'Hangul Syllables'],
|
||||
[0x1100, 0x11ff, 'Hangul Jamo'],
|
||||
[0xa960, 0xa97f, 'Hangul Jamo Extended A'],
|
||||
[0xd7b0, 0xd7ff, 'Hangul Jamo Extended B'],
|
||||
[0x3130, 0x318f, 'Hangul Compatibility Jamo'],
|
||||
[0xa4d0, 0xa4ff, 'Lisu'],
|
||||
[0x16f00, 0x16f9f, 'Miao'],
|
||||
[0xa000, 0xa48f, 'Yi Syllables'],
|
||||
[0xa490, 0xa4cf, 'Yi Radicals'],
|
||||
];
|
||||
|
||||
final englishWordlist = <String>[
|
||||
'abandon',
|
||||
'ability',
|
||||
|
@ -2297,4 +2186,4 @@ final englishWordlist = <String>[
|
|||
'zero',
|
||||
'zone',
|
||||
'zoo'
|
||||
];
|
||||
];
|
||||
|
|
|
@ -8,6 +8,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
|
||||
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
|
||||
|
||||
static const silent_payments = BitcoinReceivePageOption._('Silent Payments');
|
||||
|
||||
const BitcoinReceivePageOption._(this.value);
|
||||
|
||||
final String value;
|
||||
|
@ -17,6 +19,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
}
|
||||
|
||||
static const all = [
|
||||
BitcoinReceivePageOption.silent_payments,
|
||||
BitcoinReceivePageOption.p2wpkh,
|
||||
BitcoinReceivePageOption.p2tr,
|
||||
BitcoinReceivePageOption.p2wsh,
|
||||
|
@ -24,6 +27,24 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
BitcoinReceivePageOption.p2pkh
|
||||
];
|
||||
|
||||
BitcoinAddressType toType() {
|
||||
switch (this) {
|
||||
case BitcoinReceivePageOption.p2tr:
|
||||
return SegwitAddresType.p2tr;
|
||||
case BitcoinReceivePageOption.p2wsh:
|
||||
return SegwitAddresType.p2wsh;
|
||||
case BitcoinReceivePageOption.p2pkh:
|
||||
return P2pkhAddressType.p2pkh;
|
||||
case BitcoinReceivePageOption.p2sh:
|
||||
return P2shAddressType.p2wpkhInP2sh;
|
||||
case BitcoinReceivePageOption.silent_payments:
|
||||
return SilentPaymentsAddresType.p2sp;
|
||||
case BitcoinReceivePageOption.p2wpkh:
|
||||
default:
|
||||
return SegwitAddresType.p2wpkh;
|
||||
}
|
||||
}
|
||||
|
||||
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
|
||||
switch (type) {
|
||||
case SegwitAddresType.p2tr:
|
||||
|
@ -34,6 +55,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
return BitcoinReceivePageOption.p2pkh;
|
||||
case P2shAddressType.p2wpkhInP2sh:
|
||||
return BitcoinReceivePageOption.p2sh;
|
||||
case SilentPaymentsAddresType.p2sp:
|
||||
return BitcoinReceivePageOption.silent_payments;
|
||||
case SegwitAddresType.p2wpkh:
|
||||
default:
|
||||
return BitcoinReceivePageOption.p2wpkh;
|
||||
|
|
|
@ -2,7 +2,8 @@ import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
|||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class BitcoinTransactionCredentials {
|
||||
BitcoinTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
|
||||
BitcoinTransactionCredentials(this.outputs,
|
||||
{required this.priority, this.feeRate});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final BitcoinTransactionPriority? priority;
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
class BitcoinTransactionNoInputsException implements Exception {
|
||||
@override
|
||||
String toString() => 'Not enough inputs available. Please select more under Coin Control';
|
||||
}
|
|
@ -4,13 +4,15 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
const BitcoinTransactionPriority({required String title, required int raw})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
static const List<BitcoinTransactionPriority> all = [fast, medium, slow];
|
||||
static const List<BitcoinTransactionPriority> all = [fast, medium, slow, custom];
|
||||
static const BitcoinTransactionPriority slow =
|
||||
BitcoinTransactionPriority(title: 'Slow', raw: 0);
|
||||
static const BitcoinTransactionPriority medium =
|
||||
BitcoinTransactionPriority(title: 'Medium', raw: 1);
|
||||
static const BitcoinTransactionPriority fast =
|
||||
BitcoinTransactionPriority(title: 'Fast', raw: 2);
|
||||
static const BitcoinTransactionPriority custom =
|
||||
BitcoinTransactionPriority(title: 'Custom', raw: 3);
|
||||
|
||||
static BitcoinTransactionPriority deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
|
@ -20,6 +22,8 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
return medium;
|
||||
case 2:
|
||||
return fast;
|
||||
case 3:
|
||||
return custom;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw for BitcoinTransactionPriority deserialize');
|
||||
}
|
||||
|
@ -33,13 +37,16 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
|
||||
switch (this) {
|
||||
case BitcoinTransactionPriority.slow:
|
||||
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||
label = 'Slow ~24hrs+'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||
break;
|
||||
case BitcoinTransactionPriority.medium:
|
||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
||||
break;
|
||||
case BitcoinTransactionPriority.fast:
|
||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
||||
label = 'Fast';
|
||||
break; // S.current.transaction_priority_fast;
|
||||
case BitcoinTransactionPriority.custom:
|
||||
label = 'Custom';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -48,7 +55,10 @@ class BitcoinTransactionPriority extends TransactionPriority {
|
|||
return label;
|
||||
}
|
||||
|
||||
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
|
||||
String labelWithRate(int rate, int? customRate) {
|
||||
final rateValue = this == custom ? customRate ??= 0 : rate;
|
||||
return '${toString()} ($rateValue ${units}/byte)';
|
||||
}
|
||||
}
|
||||
|
||||
class LitecoinTransactionPriority extends BitcoinTransactionPriority {
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class BitcoinTransactionWrongBalanceException implements Exception {
|
||||
BitcoinTransactionWrongBalanceException(this.currency);
|
||||
|
||||
final CryptoCurrency currency;
|
||||
|
||||
@override
|
||||
String toString() => 'You do not have enough ${currency.title} to send this amount.';
|
||||
}
|
|
@ -2,13 +2,66 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
|
||||
class BitcoinUnspent extends Unspent {
|
||||
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||
: bitcoinAddressRecord = addressRecord,
|
||||
super(addressRecord.address, hash, value, vout, null);
|
||||
|
||||
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
||||
factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) =>
|
||||
BitcoinUnspent(
|
||||
address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int);
|
||||
address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()),
|
||||
json['tx_hash'] as String,
|
||||
json['value'] as int,
|
||||
json['tx_pos'] as int,
|
||||
);
|
||||
|
||||
final BitcoinAddressRecord bitcoinAddressRecord;
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{
|
||||
'address_record': bitcoinAddressRecord.toJSON(),
|
||||
'tx_hash': hash,
|
||||
'value': value,
|
||||
'tx_pos': vout,
|
||||
};
|
||||
return json;
|
||||
}
|
||||
|
||||
final BaseBitcoinAddressRecord bitcoinAddressRecord;
|
||||
}
|
||||
|
||||
class BitcoinSilentPaymentsUnspent extends BitcoinUnspent {
|
||||
BitcoinSilentPaymentsUnspent(
|
||||
BitcoinSilentPaymentAddressRecord addressRecord,
|
||||
String hash,
|
||||
int value,
|
||||
int vout, {
|
||||
required this.silentPaymentTweak,
|
||||
required this.silentPaymentLabel,
|
||||
}) : super(addressRecord, hash, value, vout);
|
||||
|
||||
@override
|
||||
factory BitcoinSilentPaymentsUnspent.fromJSON(
|
||||
BitcoinSilentPaymentAddressRecord? address, Map<String, dynamic> json) =>
|
||||
BitcoinSilentPaymentsUnspent(
|
||||
address ?? BitcoinSilentPaymentAddressRecord.fromJSON(json['address_record'].toString()),
|
||||
json['tx_hash'] as String,
|
||||
json['value'] as int,
|
||||
json['tx_pos'] as int,
|
||||
silentPaymentTweak: json['silent_payment_tweak'] as String?,
|
||||
silentPaymentLabel: json['silent_payment_label'] as String?,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{
|
||||
'address_record': bitcoinAddressRecord.toJSON(),
|
||||
'tx_hash': hash,
|
||||
'value': value,
|
||||
'tx_pos': vout,
|
||||
'silent_payment_tweak': silentPaymentTweak,
|
||||
'silent_payment_label': silentPaymentLabel,
|
||||
};
|
||||
return json;
|
||||
}
|
||||
|
||||
String? silentPaymentTweak;
|
||||
String? silentPaymentLabel;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||
import 'package:ledger_flutter/ledger_flutter.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'bitcoin_wallet.g.dart';
|
||||
|
||||
|
@ -19,41 +27,60 @@ class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
|||
|
||||
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||
BitcoinWalletBase({
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required Uint8List seedBytes,
|
||||
Uint8List? seedBytes,
|
||||
String? mnemonic,
|
||||
String? xpub,
|
||||
String? addressPageType,
|
||||
BasedUtxoNetwork? networkParam,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
String? passphrase,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
int initialSilentAddressIndex = 0,
|
||||
bool? alwaysScan,
|
||||
}) : super(
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
? bitcoin.bitcoin
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
? bitcoin.bitcoin
|
||||
: bitcoin.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
currency: CryptoCurrency.btc) {
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
xpub: xpub,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
? bitcoin.bitcoin
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
? bitcoin.bitcoin
|
||||
: bitcoin.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
currency:
|
||||
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
|
||||
alwaysScan: alwaysScan,
|
||||
) {
|
||||
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
|
||||
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
|
||||
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
||||
// String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1";
|
||||
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
||||
walletAddresses = BitcoinWalletAddresses(
|
||||
walletInfo,
|
||||
electrumClient: electrumClient,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
|
||||
sideHd: accountHD.derive(1),
|
||||
network: networkParam ?? network,
|
||||
masterHd:
|
||||
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
|
||||
);
|
||||
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
|
@ -64,21 +91,41 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
String? passphrase,
|
||||
String? addressPageType,
|
||||
BasedUtxoNetwork? network,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
int initialSilentAddressIndex = 0,
|
||||
}) async {
|
||||
late Uint8List seedBytes;
|
||||
|
||||
switch (walletInfo.derivationInfo?.derivationType) {
|
||||
case DerivationType.bip39:
|
||||
seedBytes = await bip39.mnemonicToSeed(
|
||||
mnemonic,
|
||||
passphrase: passphrase ?? "",
|
||||
);
|
||||
break;
|
||||
case DerivationType.electrum:
|
||||
default:
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||
break;
|
||||
}
|
||||
return BitcoinWallet(
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase ?? "",
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
addressPageType: addressPageType,
|
||||
|
@ -91,22 +138,122 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required String password,
|
||||
required bool alwaysScan,
|
||||
}) async {
|
||||
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password,
|
||||
walletInfo.network != null ? BasedUtxoNetwork.fromName(walletInfo.network!) : null);
|
||||
final network = walletInfo.network != null
|
||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||
: BitcoinNetwork.mainnet;
|
||||
final snp = await ElectrumWalletSnapshot.load(name, walletInfo.type, password, network);
|
||||
|
||||
walletInfo.derivationInfo ??= DerivationInfo(
|
||||
derivationType: snp.derivationType ?? DerivationType.electrum,
|
||||
derivationPath: snp.derivationPath,
|
||||
);
|
||||
|
||||
// set the default if not present:
|
||||
walletInfo.derivationInfo!.derivationPath = snp.derivationPath ?? electrum_path;
|
||||
walletInfo.derivationInfo!.derivationType = snp.derivationType ?? DerivationType.electrum;
|
||||
|
||||
Uint8List? seedBytes = null;
|
||||
|
||||
if (snp.mnemonic != null) {
|
||||
switch (walletInfo.derivationInfo!.derivationType) {
|
||||
case DerivationType.electrum:
|
||||
seedBytes = await mnemonicToSeedBytes(snp.mnemonic!);
|
||||
break;
|
||||
case DerivationType.bip39:
|
||||
default:
|
||||
seedBytes = await bip39.mnemonicToSeed(
|
||||
snp.mnemonic!,
|
||||
passphrase: snp.passphrase ?? '',
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return BitcoinWallet(
|
||||
mnemonic: snp.mnemonic,
|
||||
xpub: snp.xpub,
|
||||
password: password,
|
||||
passphrase: snp.passphrase,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialSilentAddresses: snp.silentAddresses,
|
||||
initialSilentAddressIndex: snp.silentAddressIndex,
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
networkParam: snp.network,
|
||||
networkParam: network,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
}
|
||||
|
||||
Ledger? _ledger;
|
||||
LedgerDevice? _ledgerDevice;
|
||||
BitcoinLedgerApp? _bitcoinLedgerApp;
|
||||
|
||||
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
|
||||
_ledger = setLedger;
|
||||
_ledgerDevice = setLedgerDevice;
|
||||
_bitcoinLedgerApp =
|
||||
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BtcTransaction> buildHardwareWalletTransaction({
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required BigInt fee,
|
||||
required BasedUtxoNetwork network,
|
||||
required List<UtxoWithAddress> utxos,
|
||||
required Map<String, PublicKeyWithDerivationPath> publicKeys,
|
||||
String? memo,
|
||||
bool enableRBF = false,
|
||||
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
|
||||
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
|
||||
}) async {
|
||||
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!);
|
||||
|
||||
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
|
||||
for (final utxo in utxos) {
|
||||
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
|
||||
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||
|
||||
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
||||
utxo: utxo.utxo,
|
||||
rawTx: rawTx,
|
||||
ownerDetails: utxo.ownerDetails,
|
||||
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
||||
ownerMasterFingerprint: masterFingerprint,
|
||||
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
|
||||
));
|
||||
}
|
||||
|
||||
final psbt =
|
||||
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
|
||||
|
||||
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
|
||||
return BtcTransaction.fromRaw(hex.encode(rawHex));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address = null}) async {
|
||||
if (walletInfo.isHardwareWallet) {
|
||||
final addressEntry = address != null
|
||||
? walletAddresses.allAddresses.firstWhere((element) => element.address == address)
|
||||
: null;
|
||||
final index = addressEntry?.index ?? 0;
|
||||
final isChange = addressEntry?.isHidden == true ? 1 : 0;
|
||||
final accountPath = walletInfo.derivationInfo?.derivationPath;
|
||||
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null;
|
||||
|
||||
final signature = await _bitcoinLedgerApp!
|
||||
.signMessage(_ledgerDevice!, message: ascii.encode(message), signDerivationPath: derivationPath);
|
||||
return base64Encode(signature);
|
||||
}
|
||||
|
||||
return super.signMessage(message, address: address);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,10 +15,12 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
|||
required super.mainHd,
|
||||
required super.sideHd,
|
||||
required super.network,
|
||||
required super.electrumClient,
|
||||
super.initialAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
super.initialSilentAddresses,
|
||||
super.initialSilentAddressIndex = 0,
|
||||
super.masterHd,
|
||||
}) : super(walletInfo);
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,23 +1,58 @@
|
|||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class BitcoinNewWalletCredentials extends WalletCredentials {
|
||||
BitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||
: super(name: name, walletInfo: walletInfo);
|
||||
BitcoinNewWalletCredentials(
|
||||
{required String name,
|
||||
WalletInfo? walletInfo,
|
||||
DerivationType? derivationType,
|
||||
String? derivationPath})
|
||||
: super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
);
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromSeedCredentials(
|
||||
{required String name, required String password, required this.mnemonic, WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
BitcoinRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
required DerivationType derivationType,
|
||||
required String derivationPath,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
walletInfo: walletInfo,
|
||||
derivationInfo: DerivationInfo(
|
||||
derivationType: derivationType,
|
||||
derivationPath: derivationPath,
|
||||
));
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromWIFCredentials(
|
||||
{required String name, required String password, required this.wif, WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
BitcoinRestoreWalletFromWIFCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.wif,
|
||||
WalletInfo? walletInfo,
|
||||
}) : super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String wif;
|
||||
}
|
||||
}
|
||||
|
||||
class BitcoinRestoreWalletFromHardware extends WalletCredentials {
|
||||
BitcoinRestoreWalletFromHardware({
|
||||
required String name,
|
||||
required this.hwAccountData,
|
||||
WalletInfo? walletInfo,
|
||||
}) : super(name: name, walletInfo: walletInfo);
|
||||
|
||||
final HardwareAccountData hwAccountData;
|
||||
}
|
||||
|
|
|
@ -12,13 +12,18 @@ import 'package:cw_core/wallet_info.dart';
|
|||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromWIFCredentials> {
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
class BitcoinWalletService extends WalletService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials,
|
||||
BitcoinRestoreWalletFromHardware> {
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final bool alwaysScan;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.bitcoin;
|
||||
|
@ -29,8 +34,9 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
credentials.walletInfo?.network = network.value;
|
||||
|
||||
final wallet = await BitcoinWalletBase.create(
|
||||
mnemonic: await generateMnemonic(),
|
||||
mnemonic: await generateElectrumMnemonic(),
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
network: network,
|
||||
|
@ -50,20 +56,24 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
try {
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
await wallet.init();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
@ -82,10 +92,12 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||
final currentWallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
@ -97,6 +109,26 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
|
||||
{bool? isTestnet}) async {
|
||||
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||
credentials.walletInfo?.network = network.value;
|
||||
credentials.walletInfo?.derivationInfo?.derivationPath =
|
||||
credentials.hwAccountData.derivationPath;
|
||||
|
||||
final wallet = await BitcoinWallet(
|
||||
password: credentials.password!,
|
||||
xpub: credentials.hwAccountData.xpub,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
networkParam: network,
|
||||
);
|
||||
await wallet.save();
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
|
||||
{bool? isTestnet}) async =>
|
||||
|
@ -105,7 +137,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
@override
|
||||
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
if (!validateMnemonic(credentials.mnemonic)) {
|
||||
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw BitcoinMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
|
@ -114,6 +146,7 @@ class BitcoinWalletService extends WalletService<BitcoinNewWalletCredentials,
|
|||
|
||||
final wallet = await BitcoinWalletBase.create(
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
|
|
@ -7,10 +7,9 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
|||
import 'package:cw_bitcoin/script_hash.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
String jsonrpcparams(List<Object> params) {
|
||||
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
|
||||
final _params = params.map((val) => '"${val.toString()}"').join(',');
|
||||
return '[$_params]';
|
||||
}
|
||||
|
||||
|
@ -34,6 +33,7 @@ class ElectrumClient {
|
|||
: _id = 0,
|
||||
_isConnected = false,
|
||||
_tasks = {},
|
||||
_errors = {},
|
||||
unterminatedString = '';
|
||||
|
||||
static const connectionTimeout = Duration(seconds: 5);
|
||||
|
@ -41,22 +41,35 @@ class ElectrumClient {
|
|||
|
||||
bool get isConnected => _isConnected;
|
||||
Socket? socket;
|
||||
void Function(bool)? onConnectionStatusChange;
|
||||
void Function(bool?)? onConnectionStatusChange;
|
||||
int _id;
|
||||
final Map<String, SocketTask> _tasks;
|
||||
Map<String, SocketTask> get tasks => _tasks;
|
||||
final Map<String, String> _errors;
|
||||
bool _isConnected;
|
||||
Timer? _aliveTimer;
|
||||
String unterminatedString;
|
||||
|
||||
Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port);
|
||||
Uri? uri;
|
||||
bool? useSSL;
|
||||
|
||||
Future<void> connect({required String host, required int port}) async {
|
||||
Future<void> connectToUri(Uri uri, {bool? useSSL}) async {
|
||||
this.uri = uri;
|
||||
this.useSSL = useSSL;
|
||||
await connect(host: uri.host, port: uri.port, useSSL: useSSL);
|
||||
}
|
||||
|
||||
Future<void> connect({required String host, required int port, bool? useSSL}) async {
|
||||
try {
|
||||
await socket?.close();
|
||||
} catch (_) {}
|
||||
|
||||
socket = await SecureSocket.connect(host, port,
|
||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
|
||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||
} else {
|
||||
socket = await SecureSocket.connect(host, port,
|
||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||
}
|
||||
_setIsConnected(true);
|
||||
|
||||
socket!.listen((Uint8List event) {
|
||||
|
@ -78,7 +91,7 @@ class ElectrumClient {
|
|||
_setIsConnected(false);
|
||||
}, onDone: () {
|
||||
unterminatedString = '';
|
||||
_setIsConnected(false);
|
||||
_setIsConnected(null);
|
||||
});
|
||||
keepAlive();
|
||||
}
|
||||
|
@ -133,11 +146,12 @@ class ElectrumClient {
|
|||
await callWithTimeout(method: 'server.ping');
|
||||
_setIsConnected(true);
|
||||
} on RequestFailedTimeoutException catch (_) {
|
||||
_setIsConnected(false);
|
||||
_setIsConnected(null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<String>> version() => call(method: 'server.version').then((dynamic result) {
|
||||
Future<List<String>> version() =>
|
||||
call(method: 'server.version', params: ["", "1.4"]).then((dynamic result) {
|
||||
if (result is List) {
|
||||
return result.map((dynamic val) => val.toString()).toList();
|
||||
}
|
||||
|
@ -243,30 +257,20 @@ class ElectrumClient {
|
|||
});
|
||||
|
||||
Future<String> broadcastTransaction(
|
||||
{required String transactionRaw, BasedUtxoNetwork? network}) async {
|
||||
if (network == BitcoinNetwork.testnet) {
|
||||
return http
|
||||
.post(Uri(scheme: 'https', host: 'blockstream.info', path: '/testnet/api/tx'),
|
||||
headers: <String, String>{'Content-Type': 'application/json; charset=utf-8'},
|
||||
body: transactionRaw)
|
||||
.then((http.Response response) {
|
||||
if (response.statusCode == 200) {
|
||||
return response.body;
|
||||
{required String transactionRaw,
|
||||
BasedUtxoNetwork? network,
|
||||
Function(int)? idCallback}) async =>
|
||||
call(
|
||||
method: 'blockchain.transaction.broadcast',
|
||||
params: [transactionRaw],
|
||||
idCallback: idCallback)
|
||||
.then((dynamic result) {
|
||||
if (result is String) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw Exception('Failed to broadcast transaction: ${response.body}');
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
return call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
||||
.then((dynamic result) {
|
||||
if (result is String) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getMerkle({required String hash, required int height}) async =>
|
||||
await call(method: 'blockchain.transaction.get_merkle', params: [hash, height])
|
||||
|
@ -275,6 +279,18 @@ class ElectrumClient {
|
|||
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
||||
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
|
||||
|
||||
BehaviorSubject<Object>? tweaksSubscribe({required int height, required int count}) {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
id: 'blockchain.tweaks.subscribe:${height + count}',
|
||||
method: 'blockchain.tweaks.subscribe',
|
||||
params: [height, count, false],
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> getTweaks({required int height}) async =>
|
||||
await callWithTimeout(method: 'blockchain.tweaks.subscribe', params: [height, 1, false]);
|
||||
|
||||
Future<double> estimatefee({required int p}) =>
|
||||
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
|
||||
if (result is double) {
|
||||
|
@ -317,9 +333,6 @@ class ElectrumClient {
|
|||
});
|
||||
|
||||
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
||||
if (network == BitcoinNetwork.testnet) {
|
||||
return [1, 1, 1];
|
||||
}
|
||||
try {
|
||||
final topDoubleString = await estimatefee(p: 1);
|
||||
final middleDoubleString = await estimatefee(p: 5);
|
||||
|
@ -341,7 +354,7 @@ class ElectrumClient {
|
|||
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||
// }
|
||||
Future<int?> getCurrentBlockChainTip() =>
|
||||
call(method: 'blockchain.headers.subscribe').then((result) {
|
||||
callWithTimeout(method: 'blockchain.headers.subscribe').then((result) {
|
||||
if (result is Map<String, dynamic>) {
|
||||
return result["height"] as int;
|
||||
}
|
||||
|
@ -349,6 +362,12 @@ class ElectrumClient {
|
|||
return null;
|
||||
});
|
||||
|
||||
BehaviorSubject<Object>? chainTipSubscribe() {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
|
||||
}
|
||||
|
||||
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
|
@ -371,10 +390,12 @@ class ElectrumClient {
|
|||
}
|
||||
}
|
||||
|
||||
Future<dynamic> call({required String method, List<Object> params = const []}) async {
|
||||
Future<dynamic> call(
|
||||
{required String method, List<Object> params = const [], Function(int)? idCallback}) async {
|
||||
final completer = Completer<dynamic>();
|
||||
_id += 1;
|
||||
final id = _id;
|
||||
idCallback?.call(id);
|
||||
_registryTask(id, completer);
|
||||
socket!.write(jsonrpc(method: method, id: id, params: params));
|
||||
|
||||
|
@ -403,7 +424,9 @@ class ElectrumClient {
|
|||
|
||||
Future<void> close() async {
|
||||
_aliveTimer?.cancel();
|
||||
await socket?.close();
|
||||
try {
|
||||
await socket?.close();
|
||||
} catch (_) {}
|
||||
onConnectionStatusChange = null;
|
||||
}
|
||||
|
||||
|
@ -438,17 +461,25 @@ class ElectrumClient {
|
|||
|
||||
_tasks[id]?.subject?.add(params.last);
|
||||
break;
|
||||
case 'blockchain.headers.subscribe':
|
||||
final params = request['params'] as List<dynamic>;
|
||||
_tasks[method]?.subject?.add(params.last);
|
||||
break;
|
||||
case 'blockchain.tweaks.subscribe':
|
||||
final params = request['params'] as List<dynamic>;
|
||||
_tasks[_tasks.keys.first]?.subject?.add(params.last);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _setIsConnected(bool isConnected) {
|
||||
void _setIsConnected(bool? isConnected) {
|
||||
if (_isConnected != isConnected) {
|
||||
onConnectionStatusChange?.call(isConnected);
|
||||
}
|
||||
|
||||
_isConnected = isConnected;
|
||||
_isConnected = isConnected ?? false;
|
||||
}
|
||||
|
||||
void _handleResponse(Map<String, dynamic> response) {
|
||||
|
@ -456,6 +487,23 @@ class ElectrumClient {
|
|||
final id = response['id'] as String?;
|
||||
final result = response['result'];
|
||||
|
||||
try {
|
||||
final error = response['error'] as Map<String, dynamic>?;
|
||||
if (error != null) {
|
||||
final errorMessage = error['message'] as String?;
|
||||
if (errorMessage != null) {
|
||||
_errors[id!] = errorMessage;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
final error = response['error'] as String?;
|
||||
if (error != null) {
|
||||
_errors[id!] = error;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
if (method is String) {
|
||||
_methodHandler(method: method, request: response);
|
||||
return;
|
||||
|
@ -465,6 +513,8 @@ class ElectrumClient {
|
|||
_finish(id, result);
|
||||
}
|
||||
}
|
||||
|
||||
String getErrorMessage(int id) => _errors[id.toString()] ?? '';
|
||||
}
|
||||
|
||||
// FIXME: move me
|
||||
|
|
|
@ -3,8 +3,11 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
|||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class ElectrumBalance extends Balance {
|
||||
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
|
||||
: super(confirmed, unconfirmed);
|
||||
ElectrumBalance({
|
||||
required this.confirmed,
|
||||
required this.unconfirmed,
|
||||
required this.frozen,
|
||||
}) : super(confirmed, unconfirmed);
|
||||
|
||||
static ElectrumBalance? fromJSON(String? jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
|
@ -19,8 +22,8 @@ class ElectrumBalance extends Balance {
|
|||
frozen: decoded['frozen'] as int? ?? 0);
|
||||
}
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
int confirmed;
|
||||
int unconfirmed;
|
||||
final int frozen;
|
||||
|
||||
@override
|
||||
|
|
113
cw_bitcoin/lib/electrum_derivations.dart
Normal file
|
@ -0,0 +1,113 @@
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
|
||||
DerivationType.electrum: [
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.electrum,
|
||||
derivationPath: "m/0'",
|
||||
description: "Electrum",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
],
|
||||
DerivationType.bip39: [
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/44'/0'/0'",
|
||||
description: "Standard BIP44",
|
||||
scriptType: "p2pkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/49'/0'/0'",
|
||||
description: "Standard BIP49 compatibility segwit",
|
||||
scriptType: "p2wpkh-p2sh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/0'/0'",
|
||||
description: "Standard BIP84 native segwit",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/86'/0'/0'",
|
||||
description: "Standard BIP86 Taproot",
|
||||
scriptType: "p2tr",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/0'",
|
||||
description: "Non-standard legacy",
|
||||
scriptType: "p2pkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/0'",
|
||||
description: "Non-standard compatibility segwit",
|
||||
scriptType: "p2wpkh-p2sh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/0'",
|
||||
description: "Non-standard native segwit",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/44'/0'/0'",
|
||||
description: "Samourai Deposit",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/49'/0'/0'",
|
||||
description: "Samourai Deposit",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/0'/2147483644'",
|
||||
description: "Samourai Bad Bank (toxic change)",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/0'/2147483645'",
|
||||
description: "Samourai Whirlpool Pre Mix",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/0'/2147483646'",
|
||||
description: "Samourai Whirlpool Post Mix",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/44'/0'/2147483647'",
|
||||
description: "Samourai Ricochet legacy",
|
||||
scriptType: "p2pkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/49'/0'/2147483647'",
|
||||
description: "Samourai Ricochet compatibility segwit",
|
||||
scriptType: "p2wpkh-p2sh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/0'/2147483647'",
|
||||
description: "Samourai Ricochet native segwit",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
DerivationInfo(
|
||||
derivationType: DerivationType.bip39,
|
||||
derivationPath: "m/84'/2'/0'",
|
||||
description: "Default Litecoin",
|
||||
scriptType: "p2wpkh",
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!;
|
|
@ -11,13 +11,11 @@ part 'electrum_transaction_history.g.dart';
|
|||
|
||||
const transactionsHistoryFileName = 'transactions.json';
|
||||
|
||||
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
|
||||
with _$ElectrumTransactionHistory;
|
||||
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory;
|
||||
|
||||
abstract class ElectrumTransactionHistoryBase
|
||||
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
||||
ElectrumTransactionHistoryBase(
|
||||
{required this.walletInfo, required String password})
|
||||
ElectrumTransactionHistoryBase({required this.walletInfo, required String password})
|
||||
: _password = password,
|
||||
_height = 0 {
|
||||
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
||||
|
@ -30,8 +28,7 @@ abstract class ElectrumTransactionHistoryBase
|
|||
Future<void> init() async => await _load();
|
||||
|
||||
@override
|
||||
void addOne(ElectrumTransactionInfo transaction) =>
|
||||
transactions[transaction.id] = transaction;
|
||||
void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
|
||||
|
@ -40,11 +37,13 @@ abstract class ElectrumTransactionHistoryBase
|
|||
@override
|
||||
Future<void> save() async {
|
||||
try {
|
||||
final dirPath =
|
||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final data =
|
||||
json.encode({'height': _height, 'transactions': transactions});
|
||||
final txjson = {};
|
||||
for (final tx in transactions.entries) {
|
||||
txjson[tx.key] = tx.value.toJson();
|
||||
}
|
||||
final data = json.encode({'height': _height, 'transactions': txjson});
|
||||
await writeData(path: path, password: _password, data: data);
|
||||
} catch (e) {
|
||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
||||
|
@ -57,8 +56,7 @@ abstract class ElectrumTransactionHistoryBase
|
|||
}
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
final dirPath =
|
||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final content = await read(path: path, password: _password);
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
|
@ -84,7 +82,5 @@ abstract class ElectrumTransactionHistoryBase
|
|||
}
|
||||
}
|
||||
|
||||
void _update(ElectrumTransactionInfo transaction) =>
|
||||
transactions[transaction.id] = transaction;
|
||||
|
||||
void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||
import 'package:cw_bitcoin/address_from_output.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
|
@ -11,32 +10,40 @@ import 'package:cw_core/wallet_type.dart';
|
|||
|
||||
class ElectrumTransactionBundle {
|
||||
ElectrumTransactionBundle(this.originalTransaction,
|
||||
{required this.ins, required this.confirmations, this.time, required this.height});
|
||||
{required this.ins, required this.confirmations, this.time});
|
||||
final BtcTransaction originalTransaction;
|
||||
final List<BtcTransaction> ins;
|
||||
final int? time;
|
||||
final int confirmations;
|
||||
final int height;
|
||||
}
|
||||
|
||||
class ElectrumTransactionInfo extends TransactionInfo {
|
||||
List<BitcoinSilentPaymentsUnspent>? unspents;
|
||||
|
||||
ElectrumTransactionInfo(this.type,
|
||||
{required String id,
|
||||
required int height,
|
||||
required int amount,
|
||||
int? fee,
|
||||
List<String>? inputAddresses,
|
||||
List<String>? outputAddresses,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int confirmations}) {
|
||||
required int confirmations,
|
||||
String? to,
|
||||
this.unspents}) {
|
||||
this.id = id;
|
||||
this.height = height;
|
||||
this.amount = amount;
|
||||
this.inputAddresses = inputAddresses;
|
||||
this.outputAddresses = outputAddresses;
|
||||
this.fee = fee;
|
||||
this.direction = direction;
|
||||
this.date = date;
|
||||
this.isPending = isPending;
|
||||
this.confirmations = confirmations;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
|
||||
|
@ -100,6 +107,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
var amount = 0;
|
||||
var inputAmount = 0;
|
||||
var totalOutAmount = 0;
|
||||
List<String> inputAddresses = [];
|
||||
List<String> outputAddresses = [];
|
||||
|
||||
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
|
||||
final input = bundle.originalTransaction.inputs[i];
|
||||
|
@ -108,6 +117,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
inputAmount += outTransaction.amount.toInt();
|
||||
if (addresses.contains(addressFromOutputScript(outTransaction.scriptPubKey, network))) {
|
||||
direction = TransactionDirection.outgoing;
|
||||
inputAddresses.add(addressFromOutputScript(outTransaction.scriptPubKey, network));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,6 +125,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
for (final out in bundle.originalTransaction.outputs) {
|
||||
totalOutAmount += out.amount.toInt();
|
||||
final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
|
||||
outputAddresses.add(addressFromOutputScript(out.scriptPubKey, network));
|
||||
|
||||
if (addressExists) {
|
||||
receivedAmounts.add(out.amount.toInt());
|
||||
|
@ -137,6 +148,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
id: bundle.originalTransaction.txId(),
|
||||
height: height,
|
||||
isPending: bundle.confirmations == 0,
|
||||
inputAddresses: inputAddresses,
|
||||
outputAddresses: outputAddresses,
|
||||
fee: fee,
|
||||
direction: direction,
|
||||
amount: amount,
|
||||
|
@ -144,50 +157,31 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
confirmations: bundle.confirmations);
|
||||
}
|
||||
|
||||
factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
|
||||
{List<String>? addresses, required int height, int? timestamp, required int confirmations}) {
|
||||
final tx = bitcoin.Transaction.fromHex(hex);
|
||||
var exist = false;
|
||||
var amount = 0;
|
||||
|
||||
if (addresses != null) {
|
||||
tx.outs.forEach((out) {
|
||||
try {
|
||||
final p2pkh =
|
||||
bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
|
||||
exist = addresses.contains(p2pkh.data.address);
|
||||
|
||||
if (exist) {
|
||||
amount += out.value!;
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
final date =
|
||||
timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
|
||||
|
||||
return ElectrumTransactionInfo(type,
|
||||
id: tx.getId(),
|
||||
height: height,
|
||||
isPending: false,
|
||||
fee: null,
|
||||
direction: TransactionDirection.incoming,
|
||||
amount: amount,
|
||||
date: date,
|
||||
confirmations: confirmations);
|
||||
}
|
||||
|
||||
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
|
||||
return ElectrumTransactionInfo(type,
|
||||
id: data['id'] as String,
|
||||
height: data['height'] as int,
|
||||
amount: data['amount'] as int,
|
||||
fee: data['fee'] as int,
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
confirmations: data['confirmations'] as int);
|
||||
final inputAddresses = data['inputAddresses'] as List<dynamic>? ?? [];
|
||||
final outputAddresses = data['outputAddresses'] as List<dynamic>? ?? [];
|
||||
final unspents = data['unspents'] as List<dynamic>? ?? [];
|
||||
|
||||
return ElectrumTransactionInfo(
|
||||
type,
|
||||
id: data['id'] as String,
|
||||
height: data['height'] as int,
|
||||
amount: data['amount'] as int,
|
||||
fee: data['fee'] as int,
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
confirmations: data['confirmations'] as int,
|
||||
inputAddresses:
|
||||
inputAddresses.isEmpty ? [] : inputAddresses.map((e) => e.toString()).toList(),
|
||||
outputAddresses:
|
||||
outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(),
|
||||
to: data['to'] as String?,
|
||||
unspents: unspents
|
||||
.map((unspent) =>
|
||||
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
final WalletType type;
|
||||
|
@ -218,6 +212,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
direction: direction,
|
||||
date: date,
|
||||
isPending: isPending,
|
||||
inputAddresses: inputAddresses,
|
||||
outputAddresses: outputAddresses,
|
||||
confirmations: info.confirmations);
|
||||
}
|
||||
|
||||
|
@ -231,6 +227,14 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
m['isPending'] = isPending;
|
||||
m['confirmations'] = confirmations;
|
||||
m['fee'] = fee;
|
||||
m['to'] = to;
|
||||
m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? [];
|
||||
m['inputAddresses'] = inputAddresses;
|
||||
m['outputAddresses'] = outputAddresses;
|
||||
return m;
|
||||
}
|
||||
|
||||
String toString() {
|
||||
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspents)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -25,14 +24,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
WalletInfo walletInfo, {
|
||||
required this.mainHd,
|
||||
required this.sideHd,
|
||||
required this.electrumClient,
|
||||
required this.network,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
int initialSilentAddressIndex = 0,
|
||||
bitcoin.HDWallet? masterHd,
|
||||
BitcoinAddressType? initialAddressPageType,
|
||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||
addressesByReceiveType =
|
||||
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
||||
.toSet()),
|
||||
|
@ -41,10 +43,42 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
.toSet()),
|
||||
currentReceiveAddressIndexByType = initialRegularAddressIndex ?? {},
|
||||
currentChangeAddressIndexByType = initialChangeAddressIndex ?? {},
|
||||
_addressPageType = walletInfo.addressPageType != null
|
||||
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
||||
: SegwitAddresType.p2wpkh,
|
||||
_addressPageType = initialAddressPageType ??
|
||||
(walletInfo.addressPageType != null
|
||||
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
||||
: SegwitAddresType.p2wpkh),
|
||||
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
||||
(initialSilentAddresses ?? []).toSet()),
|
||||
currentSilentAddressIndex = initialSilentAddressIndex,
|
||||
super(walletInfo) {
|
||||
if (masterHd != null) {
|
||||
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
||||
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!),
|
||||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
|
||||
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
|
||||
|
||||
if (silentAddresses.length == 0) {
|
||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||
silentAddress.toString(),
|
||||
index: 0,
|
||||
isHidden: false,
|
||||
name: "",
|
||||
silentPaymentTweak: null,
|
||||
network: network,
|
||||
type: SilentPaymentsAddresType.p2sp,
|
||||
));
|
||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||
silentAddress!.toLabeledSilentPaymentAddress(0).toString(),
|
||||
index: 0,
|
||||
isHidden: true,
|
||||
name: "",
|
||||
silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)),
|
||||
network: network,
|
||||
type: SilentPaymentsAddresType.p2sp,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
updateAddressesByMatch();
|
||||
}
|
||||
|
||||
|
@ -52,35 +86,45 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
static const defaultChangeAddressesCount = 17;
|
||||
static const gap = 20;
|
||||
|
||||
static String toCashAddr(String address) => bitbox.Address.toCashAddress(address);
|
||||
|
||||
static String toLegacy(String address) => bitbox.Address.toLegacyAddress(address);
|
||||
|
||||
final ObservableList<BitcoinAddressRecord> _addresses;
|
||||
// Matched by addressPageType
|
||||
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
|
||||
late ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||
final ElectrumClient electrumClient;
|
||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
final BasedUtxoNetwork network;
|
||||
final bitcoin.HDWallet mainHd;
|
||||
final bitcoin.HDWallet sideHd;
|
||||
|
||||
@observable
|
||||
BitcoinAddressType _addressPageType = SegwitAddresType.p2wpkh;
|
||||
SilentPaymentOwner? silentAddress;
|
||||
|
||||
@observable
|
||||
late BitcoinAddressType _addressPageType;
|
||||
|
||||
@computed
|
||||
BitcoinAddressType get addressPageType => _addressPageType;
|
||||
|
||||
@observable
|
||||
String? activeSilentAddress;
|
||||
|
||||
@computed
|
||||
List<BitcoinAddressRecord> get allAddresses => _addresses;
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get address {
|
||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||
if (activeSilentAddress != null) {
|
||||
return activeSilentAddress!;
|
||||
}
|
||||
|
||||
return silentAddress.toString();
|
||||
}
|
||||
|
||||
String receiveAddress;
|
||||
|
||||
final typeMatchingReceiveAddresses = receiveAddresses.where(_isAddressPageTypeMatch);
|
||||
final typeMatchingReceiveAddresses =
|
||||
receiveAddresses.where(_isAddressPageTypeMatch).where((addr) => !addr.isUsed);
|
||||
|
||||
if ((isEnabledAutoGenerateSubaddress && receiveAddresses.isEmpty) ||
|
||||
typeMatchingReceiveAddresses.isEmpty) {
|
||||
|
@ -97,7 +141,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
}
|
||||
}
|
||||
|
||||
return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress;
|
||||
return receiveAddress;
|
||||
}
|
||||
|
||||
@observable
|
||||
|
@ -105,9 +149,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@override
|
||||
set address(String addr) {
|
||||
if (addr.startsWith('bitcoincash:')) {
|
||||
addr = toLegacy(addr);
|
||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||
final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||
|
||||
if (selected.silentPaymentTweak != null && silentAddress != null) {
|
||||
activeSilentAddress =
|
||||
silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString();
|
||||
} else {
|
||||
activeSilentAddress = silentAddress!.toString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
|
||||
|
||||
previousAddressRecord = addressRecord;
|
||||
|
@ -134,6 +187,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
void set currentChangeAddressIndex(int index) =>
|
||||
currentChangeAddressIndexByType[_addressPageType.toString()] = index;
|
||||
|
||||
int currentSilentAddressIndex;
|
||||
|
||||
@observable
|
||||
BitcoinAddressRecord? previousAddressRecord;
|
||||
|
||||
|
@ -155,14 +210,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
await _generateInitialAddresses();
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
||||
if (walletInfo.type == WalletType.bitcoinCash) {
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
} else if (walletInfo.type == WalletType.litecoin) {
|
||||
await _generateInitialAddresses();
|
||||
} else if (walletInfo.type == WalletType.bitcoin) {
|
||||
await _generateInitialAddresses();
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
||||
}
|
||||
updateAddressesByMatch();
|
||||
updateReceiveAddresses();
|
||||
updateChangeAddresses();
|
||||
_validateAddresses();
|
||||
await updateAddressesInBox();
|
||||
|
||||
if (currentReceiveAddressIndex >= receiveAddresses.length) {
|
||||
|
@ -195,7 +257,50 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
return address;
|
||||
}
|
||||
|
||||
BitcoinAddressRecord generateNewAddress({String label = ''}) {
|
||||
Map<String, String> get labels {
|
||||
final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32));
|
||||
final labels = <String, String>{};
|
||||
for (int i = 0; i < silentAddresses.length; i++) {
|
||||
final silentAddressRecord = silentAddresses[i];
|
||||
final silentPaymentTweak = silentAddressRecord.silentPaymentTweak;
|
||||
|
||||
if (silentPaymentTweak != null &&
|
||||
SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) {
|
||||
labels[G
|
||||
.tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak)))
|
||||
.toHex()] = silentPaymentTweak;
|
||||
}
|
||||
}
|
||||
return labels;
|
||||
}
|
||||
|
||||
@action
|
||||
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
|
||||
if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) {
|
||||
final currentSilentAddressIndex = silentAddresses
|
||||
.where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr)
|
||||
.length -
|
||||
1;
|
||||
|
||||
this.currentSilentAddressIndex = currentSilentAddressIndex;
|
||||
|
||||
final address = BitcoinSilentPaymentAddressRecord(
|
||||
silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(),
|
||||
index: currentSilentAddressIndex,
|
||||
isHidden: false,
|
||||
name: label,
|
||||
silentPaymentTweak:
|
||||
BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)),
|
||||
network: network,
|
||||
type: SilentPaymentsAddresType.p2sp,
|
||||
);
|
||||
|
||||
silentAddresses.add(address);
|
||||
updateAddressesByMatch();
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
final newAddressIndex = addressesByReceiveType.fold(
|
||||
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
|
||||
|
||||
|
@ -220,7 +325,70 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
addressesMap[address] = 'Active';
|
||||
|
||||
allAddressesMap.clear();
|
||||
_addresses.forEach((addressRecord) {
|
||||
allAddressesMap[addressRecord.address] = addressRecord.name;
|
||||
});
|
||||
|
||||
final lastP2wpkh = _addresses
|
||||
.where((addressRecord) =>
|
||||
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
||||
.toList()
|
||||
.last;
|
||||
if (lastP2wpkh.address != address) {
|
||||
addressesMap[lastP2wpkh.address] = 'P2WPKH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2WPKH';
|
||||
}
|
||||
|
||||
final lastP2pkh = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
||||
if (lastP2pkh.address != address) {
|
||||
addressesMap[lastP2pkh.address] = 'P2PKH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2PKH';
|
||||
}
|
||||
|
||||
final lastP2sh = _addresses.firstWhere((addressRecord) =>
|
||||
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
|
||||
if (lastP2sh.address != address) {
|
||||
addressesMap[lastP2sh.address] = 'P2SH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2SH';
|
||||
}
|
||||
|
||||
final lastP2tr = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
|
||||
if (lastP2tr.address != address) {
|
||||
addressesMap[lastP2tr.address] = 'P2TR';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2TR';
|
||||
}
|
||||
|
||||
final lastP2wsh = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
|
||||
if (lastP2wsh.address != address) {
|
||||
addressesMap[lastP2wsh.address] = 'P2WSH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2WSH';
|
||||
}
|
||||
|
||||
silentAddresses.forEach((addressRecord) {
|
||||
if (addressRecord.type != SilentPaymentsAddresType.p2sp || addressRecord.isHidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (addressRecord.address != address) {
|
||||
addressesMap[addressRecord.address] = addressRecord.name.isEmpty
|
||||
? "Silent Payments"
|
||||
: "Silent Payments - " + addressRecord.name;
|
||||
} else {
|
||||
addressesMap[address] = 'Active - Silent Payments';
|
||||
}
|
||||
});
|
||||
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
|
@ -229,19 +397,41 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@action
|
||||
void updateAddress(String address, String label) {
|
||||
if (address.startsWith('bitcoincash:')) {
|
||||
address = toLegacy(address);
|
||||
BaseBitcoinAddressRecord? foundAddress;
|
||||
_addresses.forEach((addressRecord) {
|
||||
if (addressRecord.address == address) {
|
||||
foundAddress = addressRecord;
|
||||
}
|
||||
});
|
||||
silentAddresses.forEach((addressRecord) {
|
||||
if (addressRecord.address == address) {
|
||||
foundAddress = addressRecord;
|
||||
}
|
||||
});
|
||||
|
||||
if (foundAddress != null) {
|
||||
foundAddress!.setNewName(label);
|
||||
|
||||
if (foundAddress is BitcoinAddressRecord) {
|
||||
final index = _addresses.indexOf(foundAddress);
|
||||
_addresses.remove(foundAddress);
|
||||
_addresses.insert(index, foundAddress as BitcoinAddressRecord);
|
||||
} else {
|
||||
final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
|
||||
silentAddresses.remove(foundAddress);
|
||||
silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
|
||||
}
|
||||
}
|
||||
final addressRecord =
|
||||
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
|
||||
addressRecord.setNewName(label);
|
||||
final index = _addresses.indexOf(addressRecord);
|
||||
_addresses.remove(addressRecord);
|
||||
_addresses.insert(index, addressRecord);
|
||||
}
|
||||
|
||||
@action
|
||||
void updateAddressesByMatch() {
|
||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||
addressesByReceiveType.clear();
|
||||
addressesByReceiveType.addAll(silentAddresses);
|
||||
return;
|
||||
}
|
||||
|
||||
addressesByReceiveType.clear();
|
||||
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
|
||||
}
|
||||
|
@ -261,24 +451,19 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
addressRecord.isHidden &&
|
||||
!addressRecord.isUsed &&
|
||||
// TODO: feature to change change address type. For now fixed to p2wpkh, the cheapest type
|
||||
addressRecord.type == SegwitAddresType.p2wpkh);
|
||||
(walletInfo.type != WalletType.bitcoin || addressRecord.type == SegwitAddresType.p2wpkh));
|
||||
changeAddresses.addAll(newAddresses);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
|
||||
Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory,
|
||||
Future<String?> Function(BitcoinAddressRecord) getAddressHistory,
|
||||
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
|
||||
if (!isHidden) {
|
||||
_validateSideHdAddresses(addressList.toList());
|
||||
}
|
||||
|
||||
final newAddresses = await _createNewAddresses(gap,
|
||||
startIndex: addressList.length, isHidden: isHidden, type: type);
|
||||
addAddresses(newAddresses);
|
||||
|
||||
final addressesWithHistory = await Future.wait(newAddresses
|
||||
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
|
||||
final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
|
||||
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
|
||||
|
||||
if (isLastAddressUsed) {
|
||||
|
@ -344,11 +529,24 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
updateAddressesByMatch();
|
||||
}
|
||||
|
||||
void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
|
||||
addrWithTransactions.forEach((element) {
|
||||
if (element.address !=
|
||||
getAddress(index: element.index, hd: mainHd, addressType: element.type))
|
||||
@action
|
||||
void addSilentAddresses(Iterable<BitcoinSilentPaymentAddressRecord> addresses) {
|
||||
final addressesSet = this.silentAddresses.toSet();
|
||||
addressesSet.addAll(addresses);
|
||||
this.silentAddresses.clear();
|
||||
this.silentAddresses.addAll(addressesSet);
|
||||
updateAddressesByMatch();
|
||||
}
|
||||
|
||||
void _validateAddresses() {
|
||||
_addresses.forEach((element) {
|
||||
if (!element.isHidden && element.address !=
|
||||
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
element.isHidden = true;
|
||||
} else if (element.isHidden && element.address !=
|
||||
getAddress(index: element.index, hd: sideHd, addressType: element.type)) {
|
||||
element.isHidden = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -366,4 +564,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
||||
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
|
||||
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
|
||||
!addr.isHidden && !addr.isUsed && addr.type == type;
|
||||
|
||||
@action
|
||||
void deleteSilentPaymentAddress(String address) {
|
||||
final addressRecord = silentAddresses.firstWhere((addressRecord) =>
|
||||
addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address);
|
||||
|
||||
silentAddresses.remove(addressRecord);
|
||||
updateAddressesByMatch();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,9 @@ import 'dart:convert';
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/utils/file.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
|
@ -12,40 +14,65 @@ class ElectrumWalletSnapshot {
|
|||
required this.type,
|
||||
required this.password,
|
||||
required this.mnemonic,
|
||||
required this.xpub,
|
||||
required this.addresses,
|
||||
required this.balance,
|
||||
required this.regularAddressIndex,
|
||||
required this.changeAddressIndex,
|
||||
required this.addressPageType,
|
||||
required this.network,
|
||||
required this.silentAddresses,
|
||||
required this.silentAddressIndex,
|
||||
this.passphrase,
|
||||
this.derivationType,
|
||||
this.derivationPath,
|
||||
});
|
||||
|
||||
final String name;
|
||||
final String password;
|
||||
final WalletType type;
|
||||
final String addressPageType;
|
||||
final BasedUtxoNetwork network;
|
||||
final String? addressPageType;
|
||||
|
||||
String mnemonic;
|
||||
String? mnemonic;
|
||||
String? xpub;
|
||||
List<BitcoinAddressRecord> addresses;
|
||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
Map<String, int> changeAddressIndex;
|
||||
int silentAddressIndex;
|
||||
String? passphrase;
|
||||
DerivationType? derivationType;
|
||||
String? derivationPath;
|
||||
|
||||
static Future<ElectrumWalletSnapshot> load(String name, WalletType type, String password, BasedUtxoNetwork? network) async {
|
||||
static Future<ElectrumWalletSnapshot> load(
|
||||
String name, WalletType type, String password, BasedUtxoNetwork network) async {
|
||||
final path = await pathForWallet(name: name, type: type);
|
||||
final jsonSource = await read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||
final mnemonic = data['mnemonic'] as String;
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final xpub = data['xpub'] as String?;
|
||||
final passphrase = data['passphrase'] as String? ?? '';
|
||||
final addresses = addressesTmp
|
||||
.whereType<String>()
|
||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
|
||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
||||
.toList();
|
||||
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
|
||||
|
||||
final silentAddressesTmp = data['silent_addresses'] as List? ?? <Object>[];
|
||||
final silentAddresses = silentAddressesTmp
|
||||
.whereType<String>()
|
||||
.map((addr) => BitcoinSilentPaymentAddressRecord.fromJSON(addr, network: network))
|
||||
.toList();
|
||||
|
||||
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
|
||||
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
var silentAddressIndex = 0;
|
||||
|
||||
final derivationType = DerivationType
|
||||
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
||||
final derivationPath = data['derivationPath'] as String? ?? electrum_path;
|
||||
|
||||
try {
|
||||
regularAddressIndexByType = {
|
||||
|
@ -55,6 +82,7 @@ class ElectrumWalletSnapshot {
|
|||
SegwitAddresType.p2wpkh.toString():
|
||||
int.parse(data['change_address_index'] as String? ?? '0')
|
||||
};
|
||||
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
|
||||
} catch (_) {
|
||||
try {
|
||||
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
|
||||
|
@ -66,13 +94,18 @@ class ElectrumWalletSnapshot {
|
|||
name: name,
|
||||
type: type,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
mnemonic: mnemonic,
|
||||
xpub: xpub,
|
||||
addresses: addresses,
|
||||
balance: balance,
|
||||
regularAddressIndex: regularAddressIndexByType,
|
||||
changeAddressIndex: changeAddressIndexByType,
|
||||
addressPageType: data['address_page_type'] as String? ?? SegwitAddresType.p2wpkh.toString(),
|
||||
network: data['network_type'] == 'testnet' ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet,
|
||||
addressPageType: data['address_page_type'] as String?,
|
||||
derivationType: derivationType,
|
||||
derivationPath: derivationPath,
|
||||
silentAddresses: silentAddresses,
|
||||
silentAddressIndex: silentAddressIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
33
cw_bitcoin/lib/exceptions.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
|
||||
class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException {
|
||||
BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc);
|
||||
}
|
||||
|
||||
class BitcoinTransactionNoInputsException extends TransactionNoInputsException {}
|
||||
|
||||
class BitcoinTransactionNoFeeException extends TransactionNoFeeException {}
|
||||
|
||||
class BitcoinTransactionNoDustException extends TransactionNoDustException {}
|
||||
|
||||
class BitcoinTransactionNoDustOnChangeException extends TransactionNoDustOnChangeException {
|
||||
BitcoinTransactionNoDustOnChangeException(super.max, super.min);
|
||||
}
|
||||
|
||||
class BitcoinTransactionCommitFailed extends TransactionCommitFailed {
|
||||
BitcoinTransactionCommitFailed({super.errorMessage});
|
||||
}
|
||||
|
||||
class BitcoinTransactionCommitFailedDustChange extends TransactionCommitFailedDustChange {}
|
||||
|
||||
class BitcoinTransactionCommitFailedDustOutput extends TransactionCommitFailedDustOutput {}
|
||||
|
||||
class BitcoinTransactionCommitFailedDustOutputSendAll
|
||||
extends TransactionCommitFailedDustOutputSendAll {}
|
||||
|
||||
class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {}
|
||||
|
||||
class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {}
|
||||
|
||||
class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {}
|
|
@ -14,7 +14,7 @@ import 'package:cw_bitcoin/electrum_wallet.dart';
|
|||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/litecoin_network.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
part 'litecoin_wallet.g.dart';
|
||||
|
||||
|
@ -44,12 +44,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
currency: CryptoCurrency.ltc) {
|
||||
walletAddresses = LitecoinWalletAddresses(
|
||||
walletInfo,
|
||||
electrumClient: electrumClient,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes, network: networkType).derivePath("m/0'/1"),
|
||||
sideHd: accountHD.derive(1),
|
||||
network: network,
|
||||
);
|
||||
autorun((_) {
|
||||
|
@ -62,11 +61,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
String? passphrase,
|
||||
String? addressPageType,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex}) async {
|
||||
late Uint8List seedBytes;
|
||||
|
||||
switch (walletInfo.derivationInfo?.derivationType) {
|
||||
case DerivationType.bip39:
|
||||
seedBytes = await bip39.mnemonicToSeed(
|
||||
mnemonic,
|
||||
passphrase: passphrase ?? "",
|
||||
);
|
||||
break;
|
||||
case DerivationType.electrum:
|
||||
default:
|
||||
seedBytes = await mnemonicToSeedBytes(mnemonic);
|
||||
break;
|
||||
}
|
||||
return LitecoinWallet(
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
|
@ -74,7 +88,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: await mnemonicToSeedBytes(mnemonic),
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
addressPageType: addressPageType,
|
||||
|
@ -90,13 +104,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
final snp =
|
||||
await ElectrumWalletSnapshot.load(name, walletInfo.type, password, LitecoinNetwork.mainnet);
|
||||
return LitecoinWallet(
|
||||
mnemonic: snp.mnemonic,
|
||||
mnemonic: snp.mnemonic!,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic),
|
||||
seedBytes: await mnemonicToSeedBytes(snp.mnemonic!),
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
|
|
|
@ -15,7 +15,6 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
required super.mainHd,
|
||||
required super.sideHd,
|
||||
required super.network,
|
||||
required super.electrumClient,
|
||||
super.initialAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
|
|
|
@ -11,11 +11,12 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
||||
class LitecoinWalletService extends WalletService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials> {
|
||||
BitcoinRestoreWalletFromWIFCredentials,BitcoinNewWalletCredentials> {
|
||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
@ -27,8 +28,9 @@ class LitecoinWalletService extends WalletService<
|
|||
@override
|
||||
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final wallet = await LitecoinWalletBase.create(
|
||||
mnemonic: await generateMnemonic(),
|
||||
mnemonic: await generateElectrumMnemonic(),
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
await wallet.save();
|
||||
|
@ -92,6 +94,11 @@ class LitecoinWalletService extends WalletService<
|
|||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) {
|
||||
throw UnimplementedError("Restoring a Litecoin wallet from a hardware wallet is not yet supported!");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LitecoinWallet> restoreFromKeys(
|
||||
BitcoinRestoreWalletFromWIFCredentials credentials, {bool? isTestnet}) async =>
|
||||
|
@ -100,12 +107,13 @@ class LitecoinWalletService extends WalletService<
|
|||
@override
|
||||
Future<LitecoinWallet> restoreFromSeed(
|
||||
BitcoinRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
|
||||
if (!validateMnemonic(credentials.mnemonic)) {
|
||||
if (!validateMnemonic(credentials.mnemonic) && !bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw LitecoinMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = await LitecoinWalletBase.create(
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
|
||||
import 'package:cw_bitcoin/exceptions.dart';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_bitcoin/electrum.dart';
|
||||
|
@ -8,16 +8,29 @@ import 'package:cw_core/transaction_direction.dart';
|
|||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class PendingBitcoinTransaction with PendingTransaction {
|
||||
PendingBitcoinTransaction(this._tx, this.type,
|
||||
{required this.electrumClient, required this.amount, required this.fee, this.network})
|
||||
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||
PendingBitcoinTransaction(
|
||||
this._tx,
|
||||
this.type, {
|
||||
required this.electrumClient,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
required this.feeRate,
|
||||
this.network,
|
||||
required this.hasChange,
|
||||
this.isSendAll = false,
|
||||
this.hasTaprootInputs = false,
|
||||
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||
|
||||
final WalletType type;
|
||||
final BtcTransaction _tx;
|
||||
final ElectrumClient electrumClient;
|
||||
final int amount;
|
||||
final int fee;
|
||||
final String feeRate;
|
||||
final BasedUtxoNetwork? network;
|
||||
final bool hasChange;
|
||||
final bool isSendAll;
|
||||
final bool hasTaprootInputs;
|
||||
|
||||
@override
|
||||
String get id => _tx.txId();
|
||||
|
@ -31,14 +44,44 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
@override
|
||||
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
||||
|
||||
@override
|
||||
int? get outputCount => _tx.outputs.length;
|
||||
|
||||
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
final result = await electrumClient.broadcastTransaction(transactionRaw: hex, network: network);
|
||||
int? callId;
|
||||
|
||||
final result = await electrumClient.broadcastTransaction(
|
||||
transactionRaw: hex, network: network, idCallback: (id) => callId = id);
|
||||
|
||||
if (result.isEmpty) {
|
||||
throw BitcoinCommitTransactionException();
|
||||
if (callId != null) {
|
||||
final error = electrumClient.getErrorMessage(callId!);
|
||||
|
||||
if (error.contains("dust")) {
|
||||
if (hasChange) {
|
||||
throw BitcoinTransactionCommitFailedDustChange();
|
||||
} else if (!isSendAll) {
|
||||
throw BitcoinTransactionCommitFailedDustOutput();
|
||||
} else {
|
||||
throw BitcoinTransactionCommitFailedDustOutputSendAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (error.contains("bad-txns-vout-negative")) {
|
||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
||||
}
|
||||
|
||||
if (error.contains("non-BIP68-final")) {
|
||||
throw BitcoinTransactionCommitFailedBIP68Final();
|
||||
}
|
||||
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
||||
}
|
||||
|
||||
throw BitcoinTransactionCommitFailed();
|
||||
}
|
||||
|
||||
_listeners.forEach((listener) => listener(transactionInfo()));
|
||||
|
|
96
cw_bitcoin/lib/psbt_transaction_builder.dart
Normal file
|
@ -0,0 +1,96 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:ledger_bitcoin/psbt.dart';
|
||||
|
||||
class PSBTTransactionBuild {
|
||||
final PsbtV2 psbt = PsbtV2();
|
||||
|
||||
PSBTTransactionBuild(
|
||||
{required List<PSBTReadyUtxoWithAddress> inputs, required List<BitcoinBaseOutput> outputs, bool enableRBF = true}) {
|
||||
psbt.setGlobalTxVersion(2);
|
||||
psbt.setGlobalInputCount(inputs.length);
|
||||
psbt.setGlobalOutputCount(outputs.length);
|
||||
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
final input = inputs[i];
|
||||
|
||||
print(input.utxo.isP2tr());
|
||||
print(input.utxo.isSegwit());
|
||||
print(input.utxo.isP2shSegwit());
|
||||
|
||||
psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
|
||||
psbt.setInputOutputIndex(i, input.utxo.vout);
|
||||
psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);
|
||||
|
||||
|
||||
if (input.utxo.isSegwit()) {
|
||||
setInputSegwit(i, input);
|
||||
} else if (input.utxo.isP2shSegwit()) {
|
||||
setInputP2shSegwit(i, input);
|
||||
} else if (input.utxo.isP2tr()) {
|
||||
// ToDo: (Konsti) Handle Taproot Inputs
|
||||
} else {
|
||||
setInputP2pkh(i, input);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < outputs.length; i++) {
|
||||
final output = outputs[i];
|
||||
|
||||
if (output is BitcoinOutput) {
|
||||
psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes()));
|
||||
psbt.setOutputAmount(i, output.value.toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setInputP2pkh(int i, PSBTReadyUtxoWithAddress input) {
|
||||
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
|
||||
psbt.setInputBip32Derivation(
|
||||
i,
|
||||
Uint8List.fromList(hex.decode(input.ownerPublicKey)),
|
||||
input.ownerMasterFingerprint,
|
||||
BIPPath.fromString(input.ownerDerivationPath).toPathArray());
|
||||
}
|
||||
|
||||
void setInputSegwit(int i, PSBTReadyUtxoWithAddress input) {
|
||||
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
|
||||
psbt.setInputBip32Derivation(
|
||||
i,
|
||||
Uint8List.fromList(hex.decode(input.ownerPublicKey)),
|
||||
input.ownerMasterFingerprint,
|
||||
BIPPath.fromString(input.ownerDerivationPath).toPathArray());
|
||||
|
||||
psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
|
||||
Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
|
||||
}
|
||||
|
||||
void setInputP2shSegwit(int i, PSBTReadyUtxoWithAddress input) {
|
||||
psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
|
||||
psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)),
|
||||
input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray());
|
||||
|
||||
psbt.setInputRedeemScript(
|
||||
i, Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
|
||||
psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
|
||||
Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
|
||||
}
|
||||
}
|
||||
|
||||
class PSBTReadyUtxoWithAddress extends UtxoWithAddress {
|
||||
final String rawTx;
|
||||
final String ownerDerivationPath;
|
||||
final Uint8List ownerMasterFingerprint;
|
||||
final String ownerPublicKey;
|
||||
|
||||
PSBTReadyUtxoWithAddress({
|
||||
required super.utxo,
|
||||
required this.rawTx,
|
||||
required super.ownerDetails,
|
||||
required this.ownerDerivationPath,
|
||||
required this.ownerMasterFingerprint,
|
||||
required this.ownerPublicKey,
|
||||
});
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cw_bitcoin/address_to_output_script.dart';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin;
|
||||
|
||||
String scriptHash(String address, {required BasedUtxoNetwork network}) {
|
||||
final outputScript = addressToOutputScript(address: address, network: network);
|
||||
String scriptHash(String address, {required bitcoin.BasedUtxoNetwork network}) {
|
||||
final outputScript = addressToOutputScript(address, network);
|
||||
final parts = sha256.convert(outputScript).toString().split('');
|
||||
var res = '';
|
||||
|
||||
|
|
|
@ -5,29 +5,64 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
||||
import 'package:hex/hex.dart';
|
||||
|
||||
bitcoin.PaymentData generatePaymentData({required bitcoin.HDWallet hd, required int index}) =>
|
||||
PaymentData(pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey!)));
|
||||
bitcoin.PaymentData generatePaymentData({
|
||||
required bitcoin.HDWallet hd,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return PaymentData(pubkey: Uint8List.fromList(HEX.decode(pubKey)));
|
||||
}
|
||||
|
||||
ECPrivate generateECPrivate(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPrivate.fromWif(hd.derive(index).wif!, netVersion: network.wifNetVer);
|
||||
ECPrivate generateECPrivate({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final wif = hd.derive(index).wif!;
|
||||
return ECPrivate.fromWif(wif, netVersion: network.wifNetVer);
|
||||
}
|
||||
|
||||
String generateP2WPKHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhAddress().toAddress(network);
|
||||
String generateP2WPKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhAddress().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2SHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wpkhInP2sh().toAddress(network);
|
||||
String generateP2SHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wpkhInP2sh().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2WSHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2wshAddress().toAddress(network);
|
||||
String generateP2WSHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2wshAddress().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2PKHAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toP2pkhAddress().toAddress(network);
|
||||
String generateP2PKHAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toP2pkhAddress().toAddress(network);
|
||||
}
|
||||
|
||||
String generateP2TRAddress(
|
||||
{required bitcoin.HDWallet hd, required int index, required BasedUtxoNetwork network}) =>
|
||||
ECPublic.fromHex(hd.derive(index).pubKey!).toTaprootAddress().toAddress(network);
|
||||
String generateP2TRAddress({
|
||||
required bitcoin.HDWallet hd,
|
||||
required BasedUtxoNetwork network,
|
||||
required int index,
|
||||
}) {
|
||||
final pubKey = hd.derive(index).pubKey!;
|
||||
return ECPublic.fromHex(pubKey).toTaprootAddress().toAddress(network);
|
||||
}
|
||||
|
|
|
@ -21,18 +21,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.5.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
|
||||
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
version: "1.5.3"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -70,8 +70,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11
|
||||
ref: Add-Support-For-OP-Return-data
|
||||
resolved-ref: "57b78afb85bd2c30d3cdb9f7884f3878a62be442"
|
||||
url: "https://github.com/cake-tech/bitbox-flutter.git"
|
||||
source: git
|
||||
version: "1.0.1"
|
||||
|
@ -79,11 +79,11 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v1
|
||||
resolved-ref: "9611e9db77e92a8434e918cdfb620068f6fcb1aa"
|
||||
url: "https://github.com/cake-tech/bitcoin_base.git"
|
||||
ref: cake-update-v3
|
||||
resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
|
||||
url: "https://github.com/cake-tech/bitcoin_base"
|
||||
source: git
|
||||
version: "4.0.0"
|
||||
version: "4.2.1"
|
||||
bitcoin_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -96,11 +96,12 @@ packages:
|
|||
blockchain_utils:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: blockchain_utils
|
||||
sha256: "9701dfaa74caad4daae1785f1ec4445cf7fb94e45620bc3a4aca1b9b281dc6c9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
path: "."
|
||||
ref: cake-update-v1
|
||||
resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
|
||||
url: "https://github.com/cake-tech/blockchain_utils"
|
||||
source: git
|
||||
version: "2.1.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -153,10 +154,10 @@ packages:
|
|||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.8"
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -177,10 +178,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
|
||||
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.0"
|
||||
version: "8.9.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -197,6 +198,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -217,10 +226,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.1"
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -241,10 +250,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "2.7.0"
|
||||
cw_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -260,14 +269,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.4"
|
||||
decimal:
|
||||
dart_varuint_bitcoin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: decimal
|
||||
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
|
||||
name: dart_varuint_bitcoin
|
||||
sha256: "4f0ccc9733fb54148b9d3688eea822b7aaabf5cc00025998f8c09a1d45b31b4b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
version: "1.0.3"
|
||||
encrypt:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -288,10 +297,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.2"
|
||||
ffigen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffigen
|
||||
sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -317,10 +334,18 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_mobx
|
||||
sha256: "4a5d062ff85ed3759f4aac6410ff0ffae32e324b2e71ca722ae1b37b32e865f4"
|
||||
sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0+2"
|
||||
version: "2.2.1+1"
|
||||
flutter_reactive_ble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_reactive_ble
|
||||
sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -330,10 +355,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "4.0.0"
|
||||
functional_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: functional_data
|
||||
sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -378,10 +411,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.2.1"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -426,10 +459,59 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
ledger_bitcoin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab
|
||||
url: "https://github.com/cake-tech/ledger-bitcoin"
|
||||
source: git
|
||||
version: "0.0.2"
|
||||
ledger_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: ledger_flutter
|
||||
sha256: f1680060ed6ff78f275837e0024ccaf667715a59ba7aa29fa7354bc7752e71c8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
ledger_usb:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ledger_usb
|
||||
sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -442,42 +524,42 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.15"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.8.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.11.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5"
|
||||
mobx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobx
|
||||
sha256: "74ee54012dc7c1b3276eaa960a600a7418ef5f9997565deb8fca1fd88fb36b78"
|
||||
sha256: "63920b27b32ad1910adfe767ab1750e4c212e8923232a1f891597b362074ea5e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0+1"
|
||||
version: "2.3.3+2"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -506,34 +588,34 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.3"
|
||||
version: "1.9.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.2.4"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.4.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -578,10 +660,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.4"
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -590,14 +672,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
protobuf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: protobuf
|
||||
sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.1"
|
||||
version: "6.1.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -614,14 +704,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
rational:
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rational
|
||||
sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf
|
||||
name: quiver
|
||||
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "3.2.1"
|
||||
reactive_ble_mobile:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: reactive_ble_mobile
|
||||
sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.1"
|
||||
reactive_ble_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: reactive_ble_platform_interface
|
||||
sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.1"
|
||||
rxdart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -655,10 +761,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: socks5_proxy
|
||||
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
|
||||
sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5+dev.2"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -679,26 +785,35 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.10.0"
|
||||
sp_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "sp_v2.0.0"
|
||||
resolved-ref: "62c152b9086cd968019128845371072f7e1168de"
|
||||
url: "https://github.com/cake-tech/sp_scanner"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -727,10 +842,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
version: "0.6.1"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -748,13 +863,13 @@ packages:
|
|||
source: hosted
|
||||
version: "1.3.2"
|
||||
unorm_dart:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unorm_dart
|
||||
sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b"
|
||||
sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "0.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -763,30 +878,46 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
watcher:
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "13.0.0"
|
||||
watcher:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.1"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.5"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.9"
|
||||
version: "5.5.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -803,6 +934,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
yaml_edit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml_edit
|
||||
sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
sdks:
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.16.6"
|
||||
|
|
|
@ -26,24 +26,38 @@ dependencies:
|
|||
bitbox:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||
ref: master
|
||||
ref: Add-Support-For-OP-Return-data
|
||||
rxdart: ^0.27.5
|
||||
unorm_dart: ^0.2.0
|
||||
cryptography: ^2.0.5
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base.git
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v3
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v1
|
||||
blockchain_utils: ^1.6.0
|
||||
ledger_flutter: ^1.0.1
|
||||
ledger_bitcoin:
|
||||
git:
|
||||
url: https://github.com/cake-tech/ledger-bitcoin
|
||||
sp_scanner:
|
||||
git:
|
||||
url: https://github.com/cake-tech/sp_scanner
|
||||
ref: sp_v2.0.0
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.1.11
|
||||
build_runner: ^2.4.7
|
||||
build_resolvers: ^2.0.9
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^1.1.3
|
||||
|
||||
dependency_overrides:
|
||||
watcher: ^1.1.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
|
|
@ -4,15 +4,10 @@ import 'package:bitbox/bitbox.dart' as bitbox;
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
|
||||
import 'package:cw_bitcoin_cash/src/pending_bitcoin_cash_transaction.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
|
@ -34,7 +29,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required Uint8List seedBytes,
|
||||
String? addressPageType,
|
||||
BitcoinAddressType? addressPageType,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
|
@ -51,13 +46,13 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
currency: CryptoCurrency.bch) {
|
||||
walletAddresses = BitcoinCashWalletAddresses(
|
||||
walletInfo,
|
||||
electrumClient: electrumClient,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/1"),
|
||||
sideHd: accountHD.derive(1),
|
||||
network: network,
|
||||
initialAddressPageType: addressPageType,
|
||||
);
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
|
@ -84,7 +79,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
seedBytes: await Mnemonic.toSeed(mnemonic),
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
addressPageType: addressPageType,
|
||||
addressPageType: P2pkhAddressType.p2pkh,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -97,197 +92,41 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
final snp = await ElectrumWalletSnapshot.load(
|
||||
name, walletInfo.type, password, BitcoinCashNetwork.mainnet);
|
||||
return BitcoinCashWallet(
|
||||
mnemonic: snp.mnemonic,
|
||||
mnemonic: snp.mnemonic!,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialAddresses: snp.addresses.map((addr) {
|
||||
try {
|
||||
BitcoinCashAddress(addr.address);
|
||||
return BitcoinAddressRecord(
|
||||
addr.address,
|
||||
index: addr.index,
|
||||
isHidden: addr.isHidden,
|
||||
type: P2pkhAddressType.p2pkh,
|
||||
network: BitcoinCashNetwork.mainnet,
|
||||
);
|
||||
} catch (_) {
|
||||
return BitcoinAddressRecord(
|
||||
AddressUtils.getCashAddrFormat(addr.address),
|
||||
index: addr.index,
|
||||
isHidden: addr.isHidden,
|
||||
type: P2pkhAddressType.p2pkh,
|
||||
network: BitcoinCashNetwork.mainnet,
|
||||
);
|
||||
}
|
||||
}).toList(),
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: await Mnemonic.toSeed(snp.mnemonic),
|
||||
seedBytes: await Mnemonic.toSeed(snp.mnemonic!),
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
addressPageType: P2pkhAddressType.p2pkh,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingBitcoinCashTransaction> createTransaction(Object credentials) async {
|
||||
const minAmount = 546;
|
||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||
final inputs = <BitcoinUnspent>[];
|
||||
final outputs = transactionCredentials.outputs;
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
var allInputsAmount = 0;
|
||||
|
||||
if (unspentCoins.isEmpty) await updateUnspent();
|
||||
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
allInputsAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
|
||||
|
||||
final allAmountFee = transactionCredentials.feeRate != null
|
||||
? feeAmountWithFeeRate(transactionCredentials.feeRate!, inputs.length, outputs.length)
|
||||
: feeAmountForPriority(transactionCredentials.priority!, inputs.length, outputs.length);
|
||||
|
||||
final allAmount = allInputsAmount - allAmountFee;
|
||||
|
||||
var credentialsAmount = 0;
|
||||
var amount = 0;
|
||||
var fee = 0;
|
||||
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || item.formattedCryptoAmount! <= 0)) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
credentialsAmount = outputs.fold(0, (acc, value) => acc + value.formattedCryptoAmount!);
|
||||
|
||||
if (allAmount - credentialsAmount < minAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
amount = credentialsAmount;
|
||||
|
||||
if (transactionCredentials.feeRate != null) {
|
||||
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount,
|
||||
outputsCount: outputs.length + 1);
|
||||
} else {
|
||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
|
||||
outputsCount: outputs.length + 1);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
credentialsAmount = !output.sendAll ? output.formattedCryptoAmount! : 0;
|
||||
|
||||
if (credentialsAmount > allAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
amount = output.sendAll || allAmount - credentialsAmount < minAmount
|
||||
? allAmount
|
||||
: credentialsAmount;
|
||||
|
||||
if (output.sendAll || amount == allAmount) {
|
||||
fee = allAmountFee;
|
||||
} else if (transactionCredentials.feeRate != null) {
|
||||
fee = calculateEstimatedFeeWithFeeRate(transactionCredentials.feeRate!, amount);
|
||||
} else {
|
||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (fee == 0) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
final totalAmount = amount + fee;
|
||||
|
||||
if (totalAmount > balance[currency]!.confirmed || totalAmount > allInputsAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
final txb = bitbox.Bitbox.transactionBuilder(testnet: false);
|
||||
|
||||
final changeAddress = await walletAddresses.getChangeAddress();
|
||||
var leftAmount = totalAmount;
|
||||
var totalInputAmount = 0;
|
||||
|
||||
inputs.clear();
|
||||
|
||||
for (final utx in unspentCoins) {
|
||||
if (utx.isSending) {
|
||||
leftAmount = leftAmount - utx.value;
|
||||
totalInputAmount += utx.value;
|
||||
inputs.add(utx);
|
||||
|
||||
if (leftAmount <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs.isEmpty) throw BitcoinTransactionNoInputsException();
|
||||
|
||||
if (amount <= 0 || totalInputAmount < totalAmount) {
|
||||
throw BitcoinTransactionWrongBalanceException(currency);
|
||||
}
|
||||
|
||||
inputs.forEach((input) {
|
||||
txb.addInput(input.hash, input.vout);
|
||||
});
|
||||
|
||||
final String bchPrefix = "bitcoincash:";
|
||||
|
||||
outputs.forEach((item) {
|
||||
final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount;
|
||||
String outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address;
|
||||
|
||||
if (!outputAddress.startsWith(bchPrefix)) {
|
||||
outputAddress = "$bchPrefix$outputAddress";
|
||||
}
|
||||
|
||||
bool isP2sh = outputAddress.startsWith("p", bchPrefix.length);
|
||||
|
||||
if (isP2sh) {
|
||||
final p2sh = P2shAddress.fromAddress(
|
||||
address: outputAddress,
|
||||
network: BitcoinCashNetwork.mainnet,
|
||||
);
|
||||
|
||||
txb.addOutput(Uint8List.fromList(p2sh.toScriptPubKey().toBytes()), outputAmount!);
|
||||
return;
|
||||
}
|
||||
|
||||
txb.addOutput(outputAddress, outputAmount!);
|
||||
});
|
||||
|
||||
final estimatedSize = bitbox.BitcoinCash.getByteCount(inputs.length, outputs.length + 1);
|
||||
|
||||
var feeAmount = 0;
|
||||
|
||||
if (transactionCredentials.feeRate != null) {
|
||||
feeAmount = transactionCredentials.feeRate! * estimatedSize;
|
||||
} else {
|
||||
feeAmount = feeRate(transactionCredentials.priority!) * estimatedSize;
|
||||
}
|
||||
|
||||
final changeValue = totalInputAmount - amount - feeAmount;
|
||||
|
||||
if (changeValue > minAmount) {
|
||||
txb.addOutput(changeAddress, changeValue);
|
||||
}
|
||||
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
final input = inputs[i];
|
||||
final keyPair = generateKeyPair(
|
||||
hd: input.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
|
||||
index: input.bitcoinAddressRecord.index);
|
||||
txb.sign(i, keyPair, input.value);
|
||||
}
|
||||
|
||||
// Build the transaction
|
||||
final tx = txb.build();
|
||||
|
||||
return PendingBitcoinCashTransaction(tx, type,
|
||||
electrumClient: electrumClient, amount: amount, fee: fee);
|
||||
}
|
||||
|
||||
bitbox.ECPair generateKeyPair({required bitcoin.HDWallet hd, required int index}) =>
|
||||
bitbox.ECPair.fromWIF(hd.derive(index).wif!);
|
||||
|
||||
@override
|
||||
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount, int outputsCount,
|
||||
{int? size}) =>
|
||||
feeRate(priority) * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
|
||||
|
||||
int feeAmountWithFeeRate(int feeRate, int inputsCount, int outputsCount, {int? size}) =>
|
||||
feeRate * bitbox.BitcoinCash.getByteCount(inputsCount, outputsCount);
|
||||
|
||||
int calculateEstimatedFeeWithFeeRate(int feeRate, int? amount, {int? outputsCount, int? size}) {
|
||||
int inputsCount = 0;
|
||||
int totalValue = 0;
|
||||
|
@ -326,7 +165,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
@override
|
||||
String signMessage(String message, {String? address = null}) {
|
||||
Future<String> signMessage(String message, {String? address = null}) async {
|
||||
final index = address != null
|
||||
? walletAddresses.allAddresses
|
||||
.firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
|
||||
|
|
|
@ -15,10 +15,10 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
|||
required super.mainHd,
|
||||
required super.sideHd,
|
||||
required super.network,
|
||||
required super.electrumClient,
|
||||
super.initialAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
super.initialAddressPageType,
|
||||
}) : super(walletInfo);
|
||||
|
||||
@override
|
||||
|
|
|
@ -12,7 +12,7 @@ import 'package:collection/collection.dart';
|
|||
import 'package:hive/hive.dart';
|
||||
|
||||
class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredentials,
|
||||
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials> {
|
||||
BitcoinCashRestoreWalletFromSeedCredentials, BitcoinCashRestoreWalletFromWIFCredentials, BitcoinCashNewWalletCredentials> {
|
||||
BitcoinCashWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
|
@ -93,6 +93,11 @@ class BitcoinCashWalletService extends WalletService<BitcoinCashNewWalletCredent
|
|||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinCashWallet> restoreFromHardwareWallet(BitcoinCashNewWalletCredentials credentials) {
|
||||
throw UnimplementedError("Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<BitcoinCashWallet> restoreFromKeys(credentials, {bool? isTestnet}) {
|
||||
// TODO: implement restoreFromKeys
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
|
||||
import 'package:cw_bitcoin/exceptions.dart';
|
||||
import 'package:bitbox/bitbox.dart' as bitbox;
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_bitcoin/electrum.dart';
|
||||
|
@ -11,7 +11,9 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
|||
PendingBitcoinCashTransaction(this._tx, this.type,
|
||||
{required this.electrumClient,
|
||||
required this.amount,
|
||||
required this.fee})
|
||||
required this.fee,
|
||||
required this.hasChange,
|
||||
required this.isSendAll})
|
||||
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||
|
||||
final WalletType type;
|
||||
|
@ -19,6 +21,8 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
|||
final ElectrumClient electrumClient;
|
||||
final int amount;
|
||||
final int fee;
|
||||
final bool hasChange;
|
||||
final bool isSendAll;
|
||||
|
||||
@override
|
||||
String get id => _tx.getId();
|
||||
|
@ -36,18 +40,38 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
|||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
final result =
|
||||
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
|
||||
int? callId;
|
||||
|
||||
final result = await electrumClient.broadcastTransaction(
|
||||
transactionRaw: hex, idCallback: (id) => callId = id);
|
||||
|
||||
if (result.isEmpty) {
|
||||
throw BitcoinCommitTransactionException();
|
||||
if (callId != null) {
|
||||
final error = electrumClient.getErrorMessage(callId!);
|
||||
|
||||
if (error.contains("dust")) {
|
||||
if (hasChange) {
|
||||
throw BitcoinTransactionCommitFailedDustChange();
|
||||
} else if (!isSendAll) {
|
||||
throw BitcoinTransactionCommitFailedDustOutput();
|
||||
} else {
|
||||
throw BitcoinTransactionCommitFailedDustOutputSendAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (error.contains("bad-txns-vout-negative")) {
|
||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
||||
}
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
||||
}
|
||||
|
||||
throw BitcoinTransactionCommitFailed();
|
||||
}
|
||||
|
||||
_listeners?.forEach((listener) => listener(transactionInfo()));
|
||||
_listeners.forEach((listener) => listener(transactionInfo()));
|
||||
}
|
||||
|
||||
void addListener(
|
||||
void Function(ElectrumTransactionInfo transaction) listener) =>
|
||||
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
|
||||
_listeners.add(listener);
|
||||
|
||||
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
||||
|
|
|
@ -28,21 +28,26 @@ dependencies:
|
|||
bitbox:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitbox-flutter.git
|
||||
ref: master
|
||||
ref: Add-Support-For-OP-Return-data
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base.git
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v3
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v1
|
||||
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.1.11
|
||||
build_runner: ^2.4.7
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^1.1.3
|
||||
|
||||
dependency_overrides:
|
||||
watcher: ^1.1.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
|
|
@ -6,10 +6,8 @@ import 'package:cw_core/crypto_currency.dart';
|
|||
class AmountConverter {
|
||||
static const _moneroAmountLength = 12;
|
||||
static const _moneroAmountDivider = 1000000000000;
|
||||
static const _litecoinAmountDivider = 100000000;
|
||||
static const _ethereumAmountDivider = 1000000000000000000;
|
||||
static const _dashAmountDivider = 100000000;
|
||||
static const _bitcoinCashAmountDivider = 100000000;
|
||||
static const _wowneroAmountLength = 11;
|
||||
static const _wowneroAmountDivider = 100000000000;
|
||||
static const _bitcoinAmountDivider = 100000000;
|
||||
static const _bitcoinAmountLength = 8;
|
||||
static final _bitcoinAmountFormat = NumberFormat()
|
||||
|
@ -18,69 +16,16 @@ class AmountConverter {
|
|||
static final _moneroAmountFormat = NumberFormat()
|
||||
..maximumFractionDigits = _moneroAmountLength
|
||||
..minimumFractionDigits = 1;
|
||||
|
||||
static double amountIntToDouble(CryptoCurrency cryptoCurrency, int amount) {
|
||||
switch (cryptoCurrency) {
|
||||
case CryptoCurrency.xmr:
|
||||
return _moneroAmountToDouble(amount);
|
||||
case CryptoCurrency.btc:
|
||||
return _bitcoinAmountToDouble(amount);
|
||||
case CryptoCurrency.bch:
|
||||
return _bitcoinCashAmountToDouble(amount);
|
||||
case CryptoCurrency.dash:
|
||||
return _dashAmountToDouble(amount);
|
||||
case CryptoCurrency.eth:
|
||||
return _ethereumAmountToDouble(amount);
|
||||
case CryptoCurrency.ltc:
|
||||
return _litecoinAmountToDouble(amount);
|
||||
case CryptoCurrency.xhv:
|
||||
case CryptoCurrency.xag:
|
||||
case CryptoCurrency.xau:
|
||||
case CryptoCurrency.xaud:
|
||||
case CryptoCurrency.xbtc:
|
||||
case CryptoCurrency.xcad:
|
||||
case CryptoCurrency.xchf:
|
||||
case CryptoCurrency.xcny:
|
||||
case CryptoCurrency.xeur:
|
||||
case CryptoCurrency.xgbp:
|
||||
case CryptoCurrency.xjpy:
|
||||
case CryptoCurrency.xnok:
|
||||
case CryptoCurrency.xnzd:
|
||||
case CryptoCurrency.xusd:
|
||||
return _moneroAmountToDouble(amount);
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
static int amountStringToInt(CryptoCurrency cryptoCurrency, String amount) {
|
||||
switch (cryptoCurrency) {
|
||||
case CryptoCurrency.xmr:
|
||||
return _moneroParseAmount(amount);
|
||||
case CryptoCurrency.xhv:
|
||||
case CryptoCurrency.xag:
|
||||
case CryptoCurrency.xau:
|
||||
case CryptoCurrency.xaud:
|
||||
case CryptoCurrency.xbtc:
|
||||
case CryptoCurrency.xcad:
|
||||
case CryptoCurrency.xchf:
|
||||
case CryptoCurrency.xcny:
|
||||
case CryptoCurrency.xeur:
|
||||
case CryptoCurrency.xgbp:
|
||||
case CryptoCurrency.xjpy:
|
||||
case CryptoCurrency.xnok:
|
||||
case CryptoCurrency.xnzd:
|
||||
case CryptoCurrency.xusd:
|
||||
return _moneroParseAmount(amount);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static final _wowneroAmountFormat = NumberFormat()
|
||||
..maximumFractionDigits = _wowneroAmountLength
|
||||
..minimumFractionDigits = 1;
|
||||
|
||||
static String amountIntToString(CryptoCurrency cryptoCurrency, int amount) {
|
||||
switch (cryptoCurrency) {
|
||||
case CryptoCurrency.xmr:
|
||||
return _moneroAmountToString(amount);
|
||||
case CryptoCurrency.wow:
|
||||
return _wowneroAmountToString(amount);
|
||||
case CryptoCurrency.btc:
|
||||
case CryptoCurrency.bch:
|
||||
case CryptoCurrency.ltc:
|
||||
|
@ -110,41 +55,18 @@ class AmountConverter {
|
|||
static double cryptoAmountToDouble({required num amount, required num divider}) =>
|
||||
amount / divider;
|
||||
|
||||
static String _moneroAmountToString(int amount) => _moneroAmountFormat
|
||||
.format(cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider));
|
||||
|
||||
static String _bitcoinAmountToString(int amount) => _bitcoinAmountFormat
|
||||
.format(cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider));
|
||||
|
||||
static String _wowneroAmountToString(int amount) => _wowneroAmountFormat
|
||||
.format(cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider));
|
||||
|
||||
static Decimal cryptoAmountToDecimal({required int amount, required int divider}) =>
|
||||
(Decimal.fromInt(amount) / Decimal.fromInt(divider)).toDecimal();
|
||||
|
||||
static String _moneroAmountToString(int amount) => _moneroAmountFormat.format(
|
||||
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider));
|
||||
|
||||
|
||||
static String _moneroAmountToStringUsingDecimals(int amount) => _moneroAmountFormat.format(
|
||||
DecimalIntl(cryptoAmountToDecimal(amount: amount, divider: _moneroAmountDivider)));
|
||||
|
||||
|
||||
static double _moneroAmountToDouble(int amount) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider);
|
||||
|
||||
static int _moneroParseAmount(String amount) =>
|
||||
_moneroAmountFormat.parse(amount).toInt();
|
||||
|
||||
static String _bitcoinAmountToString(int amount) =>
|
||||
_bitcoinAmountFormat.format(
|
||||
cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider));
|
||||
|
||||
static double _bitcoinAmountToDouble(int amount) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider);
|
||||
|
||||
static int _doubleToBitcoinAmount(double amount) =>
|
||||
(amount * _bitcoinAmountDivider).toInt();
|
||||
|
||||
static double _bitcoinCashAmountToDouble(int amount) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: _bitcoinCashAmountDivider);
|
||||
|
||||
static double _dashAmountToDouble(int amount) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: _dashAmountDivider);
|
||||
|
||||
static double _ethereumAmountToDouble(num amount) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: _ethereumAmountDivider);
|
||||
|
||||
static double _litecoinAmountToDouble(int amount) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: _litecoinAmountDivider);
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.trx,
|
||||
CryptoCurrency.usdt,
|
||||
CryptoCurrency.usdterc20,
|
||||
CryptoCurrency.sol,
|
||||
CryptoCurrency.maticpoly,
|
||||
CryptoCurrency.xlm,
|
||||
CryptoCurrency.xrp,
|
||||
CryptoCurrency.xhv,
|
||||
|
@ -50,7 +52,6 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.usdttrc20,
|
||||
CryptoCurrency.hbar,
|
||||
CryptoCurrency.sc,
|
||||
CryptoCurrency.sol,
|
||||
CryptoCurrency.usdc,
|
||||
CryptoCurrency.usdcsol,
|
||||
CryptoCurrency.zaddr,
|
||||
|
@ -61,7 +62,6 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.dcr,
|
||||
CryptoCurrency.kmd,
|
||||
CryptoCurrency.mana,
|
||||
CryptoCurrency.maticpoly,
|
||||
CryptoCurrency.matic,
|
||||
CryptoCurrency.mkr,
|
||||
CryptoCurrency.near,
|
||||
|
@ -103,6 +103,9 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.kaspa,
|
||||
CryptoCurrency.digibyte,
|
||||
CryptoCurrency.usdtSol,
|
||||
CryptoCurrency.usdcTrc20,
|
||||
CryptoCurrency.tbtc,
|
||||
CryptoCurrency.wow,
|
||||
CryptoCurrency.zano,
|
||||
];
|
||||
|
||||
|
@ -218,8 +221,11 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
|
||||
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
|
||||
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||
static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 92, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12);
|
||||
|
||||
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||
static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
|
||||
static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11);
|
||||
static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 94, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12);
|
||||
|
||||
|
||||
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
|
||||
|
@ -253,16 +259,22 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
static CryptoCurrency fromString(String name, {CryptoCurrency? walletCurrency}) {
|
||||
try {
|
||||
return CryptoCurrency.all.firstWhere((element) =>
|
||||
element.title.toLowerCase() == name &&
|
||||
element.title.toLowerCase() == name.toLowerCase() &&
|
||||
(element.tag == null ||
|
||||
element.tag == walletCurrency?.title ||
|
||||
element.tag == walletCurrency?.tag));
|
||||
} catch (_) {}
|
||||
|
||||
// search by fullName if not found by title:
|
||||
try {
|
||||
return CryptoCurrency.all.firstWhere((element) => element.fullName?.toLowerCase() == name);
|
||||
} catch (_) {}
|
||||
|
||||
if (CryptoCurrency._nameCurrencyMap[name.toLowerCase()] == null) {
|
||||
final s = 'Unexpected token: $name for CryptoCurrency fromString';
|
||||
throw ArgumentError.value(name, 'name', s);
|
||||
}
|
||||
|
||||
return CryptoCurrency._nameCurrencyMap[name.toLowerCase()]!;
|
||||
}
|
||||
|
||||
|
@ -271,7 +283,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
final s = 'Unexpected token: $name for CryptoCurrency fromFullName';
|
||||
throw ArgumentError.value(name, 'Fullname', s);
|
||||
}
|
||||
return CryptoCurrency._fullNameCurrencyMap[name.toLowerCase()]!;
|
||||
return CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()]!;
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
CryptoCurrency currencyForWalletType(WalletType type) {
|
||||
CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
if (isTestnet == true) {
|
||||
return CryptoCurrency.tbtc;
|
||||
}
|
||||
return CryptoCurrency.btc;
|
||||
case WalletType.monero:
|
||||
return CryptoCurrency.xmr;
|
||||
|
@ -23,10 +26,15 @@ CryptoCurrency currencyForWalletType(WalletType type) {
|
|||
return CryptoCurrency.maticpoly;
|
||||
case WalletType.solana:
|
||||
return CryptoCurrency.sol;
|
||||
case WalletType.tron:
|
||||
return CryptoCurrency.trx;
|
||||
case WalletType.wownero:
|
||||
return CryptoCurrency.wow;
|
||||
case WalletType.zano:
|
||||
return CryptoCurrency.zano;
|
||||
default:
|
||||
case WalletType.none:
|
||||
throw Exception(
|
||||
|
||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||
}
|
||||
}
|
||||
|
|
39
cw_core/lib/exceptions.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class TransactionWrongBalanceException implements Exception {
|
||||
TransactionWrongBalanceException(this.currency, {this.amount});
|
||||
|
||||
final CryptoCurrency currency;
|
||||
final int? amount;
|
||||
}
|
||||
|
||||
class TransactionNoInputsException implements Exception {}
|
||||
|
||||
class TransactionNoFeeException implements Exception {}
|
||||
|
||||
class TransactionNoDustException implements Exception {}
|
||||
|
||||
class TransactionNoDustOnChangeException implements Exception {
|
||||
TransactionNoDustOnChangeException(this.max, this.min);
|
||||
|
||||
final String max;
|
||||
final String min;
|
||||
}
|
||||
|
||||
class TransactionCommitFailed implements Exception {
|
||||
final String? errorMessage;
|
||||
|
||||
TransactionCommitFailed({this.errorMessage});
|
||||
}
|
||||
|
||||
class TransactionCommitFailedDustChange implements Exception {}
|
||||
|
||||
class TransactionCommitFailedDustOutput implements Exception {}
|
||||
|
||||
class TransactionCommitFailedDustOutputSendAll implements Exception {}
|
||||
|
||||
class TransactionCommitFailedVoutNegative implements Exception {}
|
||||
|
||||
class TransactionCommitFailedBIP68Final implements Exception {}
|
||||
|
||||
class TransactionInputNotSupported implements Exception {}
|
|
@ -242,3 +242,136 @@ Future<int> getHavenCurrentHeight() async {
|
|||
throw Exception('Failed to load current blockchain height');
|
||||
}
|
||||
}
|
||||
|
||||
// Data taken from https://timechaincalendar.com/
|
||||
const bitcoinDates = {
|
||||
"2024-06": 846005,
|
||||
"2024-05": 841590,
|
||||
"2024-04": 837182,
|
||||
"2024-03": 832623,
|
||||
"2024-02": 828319,
|
||||
"2024-01": 823807,
|
||||
"2023-12": 819206,
|
||||
"2023-11": 814765,
|
||||
"2023-10": 810098,
|
||||
"2023-09": 805675,
|
||||
"2023-08": 801140,
|
||||
"2023-07": 796640,
|
||||
"2023-06": 792330,
|
||||
"2023-05": 787733,
|
||||
"2023-04": 783403,
|
||||
"2023-03": 778740,
|
||||
"2023-02": 774525,
|
||||
"2023-01": 769810,
|
||||
};
|
||||
|
||||
int getBitcoinHeightByDate({required DateTime date}) {
|
||||
String dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}';
|
||||
final closestKey = bitcoinDates.keys
|
||||
.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => bitcoinDates.keys.last);
|
||||
final beginningBlock = bitcoinDates[dateKey] ?? bitcoinDates[closestKey]!;
|
||||
|
||||
final startOfMonth = DateTime(date.year, date.month);
|
||||
final daysDifference = date.difference(startOfMonth).inDays;
|
||||
|
||||
// approximately 6 blocks per hour, 24 hours per day
|
||||
int estimatedBlocksSinceStartOfMonth = (daysDifference * 24 * 6);
|
||||
|
||||
return beginningBlock + estimatedBlocksSinceStartOfMonth;
|
||||
}
|
||||
|
||||
DateTime getDateByBitcoinHeight(int height) {
|
||||
final closestEntry = bitcoinDates.entries
|
||||
.lastWhere((entry) => entry.value >= height, orElse: () => bitcoinDates.entries.first);
|
||||
final beginningBlock = closestEntry.value;
|
||||
|
||||
final startOfMonth = formatMapKey(closestEntry.key);
|
||||
final blocksDifference = height - beginningBlock;
|
||||
final hoursDifference = blocksDifference / 5.5;
|
||||
|
||||
final estimatedDate = startOfMonth.add(Duration(hours: hoursDifference.ceil()));
|
||||
|
||||
if (estimatedDate.isAfter(DateTime.now())) {
|
||||
return DateTime.now();
|
||||
}
|
||||
|
||||
return estimatedDate;
|
||||
}
|
||||
|
||||
// TODO: enhance all of this global const lists
|
||||
const wowDates = {
|
||||
"2023-12": 583048,
|
||||
"2023-11": 575048,
|
||||
"2023-10": 566048,
|
||||
"2023-09": 558048,
|
||||
"2023-08": 549048,
|
||||
"2023-07": 540048,
|
||||
"2023-06": 532048,
|
||||
"2023-05": 523048,
|
||||
"2023-04": 514048,
|
||||
"2023-03": 505048,
|
||||
"2023-02": 497048,
|
||||
"2023-01": 488048,
|
||||
"2022-12": 479048,
|
||||
"2022-11": 471048,
|
||||
"2022-10": 462048,
|
||||
"2022-09": 453048,
|
||||
"2022-08": 444048,
|
||||
"2022-07": 435048,
|
||||
"2022-06": 427048,
|
||||
"2022-05": 418048,
|
||||
"2022-04": 410048,
|
||||
"2022-03": 401048,
|
||||
"2022-02": 393048,
|
||||
"2022-01": 384048,
|
||||
"2021-12": 375048,
|
||||
"2021-11": 367048,
|
||||
"2021-10": 358048,
|
||||
"2021-09": 349048,
|
||||
"2021-08": 340048,
|
||||
"2021-07": 331048,
|
||||
"2021-06": 322048,
|
||||
"2021-05": 313048,
|
||||
"2021-04": 305048,
|
||||
"2021-03": 295048,
|
||||
"2021-02": 287048,
|
||||
"2021-01": 279148,
|
||||
"2020-10": 252000,
|
||||
"2020-09": 243000,
|
||||
"2020-08": 234000,
|
||||
"2020-07": 225000,
|
||||
"2020-06": 217500,
|
||||
"2020-05": 208500,
|
||||
"2020-04": 199500,
|
||||
"2020-03": 190500,
|
||||
"2020-02": 183000,
|
||||
"2020-01": 174000,
|
||||
"2019-12": 165000,
|
||||
"2019-11": 156000,
|
||||
"2019-10": 147000,
|
||||
"2019-09": 138000,
|
||||
"2019-08": 129000,
|
||||
"2019-07": 120000,
|
||||
"2019-06": 112500,
|
||||
"2019-05": 103500,
|
||||
"2019-04": 94500,
|
||||
"2019-03": 85500,
|
||||
"2019-02": 79500,
|
||||
"2019-01": 73500,
|
||||
"2018-12": 67500,
|
||||
"2018-11": 61500,
|
||||
"2018-10": 52500,
|
||||
"2018-09": 45000,
|
||||
"2018-08": 36000,
|
||||
"2018-07": 27000,
|
||||
"2018-06": 18000,
|
||||
"2018-05": 9000,
|
||||
"2018-04": 1
|
||||
};
|
||||
|
||||
int getWowneroHeightByDate({required DateTime date}) {
|
||||
String closestKey =
|
||||
wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => '');
|
||||
|
||||
return wowDates[closestKey] ?? 0;
|
||||
}
|
28
cw_core/lib/hardware/device_connection_type.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
enum DeviceConnectionType {
|
||||
usb,
|
||||
ble;
|
||||
|
||||
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
|
||||
[bool isIOS = false]) {
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
if (isIOS) return [DeviceConnectionType.ble];
|
||||
return [DeviceConnectionType.ble, DeviceConnectionType.usb];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
String get iconString {
|
||||
switch (this) {
|
||||
case ble:
|
||||
return 'assets/images/bluetooth.png';
|
||||
case usb:
|
||||
return 'assets/images/usb.png';
|
||||
}
|
||||
}
|
||||
}
|
7
cw_core/lib/hardware/device_not_connected_exception.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
class DeviceNotConnectedException implements Exception {
|
||||
final String message;
|
||||
|
||||
DeviceNotConnectedException({
|
||||
this.message = '',
|
||||
});
|
||||
}
|
19
cw_core/lib/hardware/hardware_account_data.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
class HardwareAccountData {
|
||||
HardwareAccountData({
|
||||
required this.address,
|
||||
required this.accountIndex,
|
||||
required this.derivationPath,
|
||||
this.xpub,
|
||||
this.masterFingerprint,
|
||||
});
|
||||
|
||||
final String address;
|
||||
final int accountIndex;
|
||||
final String derivationPath;
|
||||
|
||||
// Bitcoin Specific
|
||||
final Uint8List? masterFingerprint;
|
||||
final String? xpub;
|
||||
}
|
|
@ -14,5 +14,9 @@ const ERC20_TOKEN_TYPE_ID = 12;
|
|||
const NANO_ACCOUNT_TYPE_ID = 13;
|
||||
const POW_NODE_TYPE_ID = 14;
|
||||
const DERIVATION_TYPE_TYPE_ID = 15;
|
||||
const SPL_TOKEN_TYPE_ID = 16;
|
||||
const ZANO_ASSET_TYPE_ID = 17;
|
||||
const SPL_TOKEN_TYPE_ID = 16;
|
||||
const DERIVATION_INFO_TYPE_ID = 17;
|
||||
const TRON_TOKEN_TYPE_ID = 18;
|
||||
const HARDWARE_WALLET_TYPE_TYPE_ID = 19;
|
||||
const ZANO_ASSET_TYPE_ID = 20;
|
||||
|
||||
|
|