Merge branch 'main' of https://github.com/cake-tech/cake_wallet into cw_linux_direct_input_password

 Conflicts:
	assets/text/Monerocom_Release_Notes.txt
	assets/text/Release_Notes.txt
	lib/di.dart
	lib/store/settings_store.dart
	lib/view_model/settings/other_settings_view_model.dart
	res/values/strings_ar.arb
	res/values/strings_bg.arb
	res/values/strings_cs.arb
	res/values/strings_de.arb
	res/values/strings_en.arb
	res/values/strings_es.arb
	res/values/strings_fr.arb
	res/values/strings_ha.arb
	res/values/strings_hi.arb
	res/values/strings_hr.arb
	res/values/strings_id.arb
	res/values/strings_it.arb
	res/values/strings_ja.arb
	res/values/strings_ko.arb
	res/values/strings_my.arb
	res/values/strings_nl.arb
	res/values/strings_pl.arb
	res/values/strings_pt.arb
	res/values/strings_ru.arb
	res/values/strings_th.arb
	res/values/strings_tl.arb
	res/values/strings_tr.arb
	res/values/strings_uk.arb
	res/values/strings_ur.arb
	res/values/strings_yo.arb
	res/values/strings_zh.arb
	scripts/android/app_env.sh
	scripts/ios/app_env.sh
	scripts/macos/app_env.sh
This commit is contained in:
OmarHatem 2024-04-16 01:57:52 +02:00
commit 76d1a7bdc6
130 changed files with 4395 additions and 879 deletions
.github
README.mdSECURITY.md
assets
cw_bitcoin
cw_bitcoin_cash
cw_core/lib
cw_evm/lib
cw_nano/lib
cw_solana/lib
lib
bitcoin
buy/moonpay
core
di.dart
entities
ethereum
exchange/provider
main.dart
nano
polygon
reactions
router.dartroutes.dart
solana
src
store
utils
view_model
pubspec_base.yaml
res/values

BIN
.github/assets/Logo_CakeWallet.png vendored Normal file

Binary file not shown.

After

(image error) Size: 156 KiB

48
.github/assets/NOTICE.txt vendored Normal file
View 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
View 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="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" 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="&lt;Path&gt;" 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="&lt;Group&gt;">
<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

(image error) Size: 11 KiB

BIN
.github/assets/devices.png vendored Normal file

Binary file not shown.

After

(image error) Size: 72 KiB

BIN
.github/assets/f-droid-badge.png vendored Normal file

Binary file not shown.

After

(image error) Size: 16 KiB

BIN
.github/assets/google-play-badge.png vendored Normal file

Binary file not shown.

After

(image error) Size: 22 KiB

1071
.github/assets/linux-badge.svg vendored Executable file

File diff suppressed because it is too large Load diff

After

(image error) Size: 67 KiB

51
.github/assets/mac-store-badge.svg vendored Executable file
View 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="&lt;Group&gt;">
<g id="_Group_2" data-name="&lt;Group&gt;">
<g id="_Group_3" data-name="&lt;Group&gt;">
<g id="_Group_4" data-name="&lt;Group&gt;">
<path id="_Path_" data-name="&lt;Path&gt;" 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="&lt;Path&gt;" 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="&lt;Group&gt;">
<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

(image error) Size: 12 KiB

View file

@ -139,7 +139,9 @@ 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

View file

@ -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
View 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.

View file

@ -3,4 +3,26 @@
useSSL: true
is_default: true
-
uri: node.perish.co:9076
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

View file

@ -1,2 +1,2 @@
Exchange flow enhancements and fixes
Generic enhancements and bug fixes
UI enhancements
Bug fixes

View file

@ -1,6 +1,7 @@
Exchange flow enhancements and fixes
Add MoonPay to Buy options
Add THORChain to Exchange providers
Improve Bitcoin fee calculations
Fixes and enhancements for Solana
Generic enhancements and bug fixes
Add Replace-By-Fee to boost pending Bitcoin transactions
Enable WalletConnect for Solana
WalletConnect Enhancements
Enhancements for ERC-20 tokens and Solana tokens
Enhancements for Nano wallet
UI enhancements
Bug fixes

View file

@ -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;

View file

@ -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');
}
@ -39,7 +43,10 @@ class BitcoinTransactionPriority extends TransactionPriority {
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 {

View file

@ -11,12 +11,11 @@ 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 {
@ -25,6 +24,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
required int height,
required int amount,
int? fee,
List<String>? inputAddresses,
List<String>? outputAddresses,
required TransactionDirection direction,
required bool isPending,
required DateTime date,
@ -32,6 +33,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.id = id;
this.height = height;
this.amount = amount;
this.inputAddresses = inputAddresses;
this.outputAddresses = outputAddresses;
this.fee = fee;
this.direction = direction;
this.date = date;
@ -100,6 +103,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 +113,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 +121,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 +144,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,
@ -187,6 +196,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
inputAddresses: data['inputAddresses'] as List<String>,
outputAddresses: data['outputAddresses'] as List<String>,
confirmations: data['confirmations'] as int);
}
@ -218,6 +229,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
direction: direction,
date: date,
isPending: isPending,
inputAddresses: inputAddresses,
outputAddresses: outputAddresses,
confirmations: info.confirmations);
}
@ -231,6 +244,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['isPending'] = isPending;
m['confirmations'] = confirmations;
m['fee'] = fee;
m['inputAddresses'] = inputAddresses;
m['outputAddresses'] = outputAddresses;
return m;
}
}

View file

@ -8,6 +8,7 @@ import 'package:cw_core/encryption_file_utils.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
import 'package:collection/collection.dart';
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_transaction_credentials.dart';
@ -198,11 +199,9 @@ abstract class ElectrumWalletBase
}
}
int _getDustAmount() {
return 546;
}
int get _dustAmount => 546;
bool _isBelowDust(int amount) => amount <= _getDustAmount() && network != BitcoinNetwork.testnet;
bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet;
Future<EstimatedTxResult> estimateSendAllTx(
List<BitcoinOutput> outputs,
@ -428,7 +427,7 @@ abstract class ElectrumWalletBase
}
// Estimate to user how much is needed to send to cover the fee
final maxAmountWithReturningChange = allInputsAmount - _getDustAmount() - fee - 1;
final maxAmountWithReturningChange = allInputsAmount - _dustAmount - fee - 1;
throw BitcoinTransactionNoDustOnChangeException(
bitcoinAmountToString(amount: maxAmountWithReturningChange),
bitcoinAmountToString(amount: estimatedSendAll.amount),
@ -542,6 +541,7 @@ abstract class ElectrumWalletBase
network: network,
memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none,
enableRBF: true,
);
} else {
txb = BitcoinTransactionBuilder(
@ -551,9 +551,12 @@ abstract class ElectrumWalletBase
network: network,
memo: estimatedTx.memo,
outputOrdering: BitcoinOrdering.none,
enableRBF: true,
);
}
bool hasTaprootInputs = false;
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
final key = estimatedTx.privateKeys
.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey);
@ -563,21 +566,25 @@ abstract class ElectrumWalletBase
}
if (utxo.utxo.isP2tr()) {
hasTaprootInputs = true;
return key.signTapRoot(txDigest, sighash: sighash);
} else {
return key.signInput(txDigest, sigHash: sighash);
}
});
return PendingBitcoinTransaction(transaction, type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
feeRate: feeRateInt.toString(),
network: network,
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll)
..addListener((transaction) async {
return PendingBitcoinTransaction(
transaction,
type,
electrumClient: electrumClient,
amount: estimatedTx.amount,
fee: estimatedTx.fee,
feeRate: feeRateInt.toString(),
network: network,
hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs,
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
await updateBalance();
});
@ -798,8 +805,180 @@ abstract class ElectrumWalletBase
}
}
Future<ElectrumTransactionBundle> getTransactionExpanded(
{required String hash, required int height}) async {
Future<bool> canReplaceByFee(String hash) async {
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
final confirmations = verboseTransaction['confirmations'] as int? ?? 0;
final transactionHex = verboseTransaction['hex'] as String?;
if (confirmations > 0) return false;
if (transactionHex == null) {
return false;
}
final original = bitcoin.Transaction.fromHex(transactionHex);
return original.ins
.any((element) => element.sequence != null && element.sequence! < 4294967293);
}
Future<bool> isChangeSufficientForFee(String txId, int newFee) async {
final bundle = await getTransactionExpanded(hash: txId);
final outputs = bundle.originalTransaction.outputs;
final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden);
// look for a change address in the outputs
final changeOutput = outputs.firstWhereOrNull((output) => changeAddresses.any(
(element) => element.address == addressFromOutputScript(output.scriptPubKey, network)));
var allInputsAmount = 0;
for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) {
final input = bundle.originalTransaction.inputs[i];
final inputTransaction = bundle.ins[i];
final vout = input.txIndex;
final outTransaction = inputTransaction.outputs[vout];
allInputsAmount += outTransaction.amount.toInt();
}
int totalOutAmount = bundle.originalTransaction.outputs
.fold<int>(0, (previousValue, element) => previousValue + element.amount.toInt());
var currentFee = allInputsAmount - totalOutAmount;
int remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee;
return changeOutput != null && changeOutput.amount.toInt() - remainingFee >= 0;
}
Future<PendingBitcoinTransaction> replaceByFee(String hash, int newFee) async {
try {
final bundle = await getTransactionExpanded(hash: hash);
final utxos = <UtxoWithAddress>[];
List<ECPrivate> privateKeys = [];
var allInputsAmount = 0;
// Add inputs
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
final input = bundle.originalTransaction.inputs[i];
final inputTransaction = bundle.ins[i];
final vout = input.txIndex;
final outTransaction = inputTransaction.outputs[vout];
final address = addressFromOutputScript(outTransaction.scriptPubKey, network);
allInputsAmount += outTransaction.amount.toInt();
final addressRecord =
walletAddresses.allAddresses.firstWhere((element) => element.address == address);
final btcAddress = addressTypeFromStr(addressRecord.address, network);
final privkey = generateECPrivate(
hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
index: addressRecord.index,
network: network);
privateKeys.add(privkey);
utxos.add(
UtxoWithAddress(
utxo: BitcoinUtxo(
txHash: input.txId,
value: outTransaction.amount,
vout: vout,
scriptType: _getScriptType(btcAddress),
),
ownerDetails:
UtxoAddressDetails(publicKey: privkey.getPublic().toHex(), address: btcAddress),
),
);
}
int totalOutAmount = bundle.originalTransaction.outputs
.fold<int>(0, (previousValue, element) => previousValue + element.amount.toInt());
var currentFee = allInputsAmount - totalOutAmount;
int remainingFee = newFee - currentFee;
final outputs = <BitcoinOutput>[];
// Add outputs and deduct the fees from it
for (int i = bundle.originalTransaction.outputs.length - 1; i >= 0; i--) {
final out = bundle.originalTransaction.outputs[i];
final address = addressFromOutputScript(out.scriptPubKey, network);
final btcAddress = addressTypeFromStr(address, network);
int newAmount;
if (out.amount.toInt() >= remainingFee) {
newAmount = out.amount.toInt() - remainingFee;
remainingFee = 0;
// if new amount of output is less than dust amount, then don't add this output as well
if (newAmount <= _dustAmount) {
continue;
}
} else {
remainingFee -= out.amount.toInt();
continue;
}
outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(newAmount)));
}
final changeAddresses = walletAddresses.allAddresses.where((element) => element.isHidden);
// look for a change address in the outputs
final changeOutput = outputs.firstWhereOrNull((output) =>
changeAddresses.any((element) => element.address == output.address.toAddress(network)));
// deduct the change amount from the output amount
if (changeOutput != null) {
totalOutAmount -= changeOutput.value.toInt();
}
final txb = BitcoinTransactionBuilder(
utxos: utxos,
outputs: outputs,
fee: BigInt.from(newFee),
network: network,
enableRBF: true,
);
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
final key =
privateKeys.firstWhereOrNull((element) => element.getPublic().toHex() == publicKey);
if (key == null) {
throw Exception("Cannot find private key");
}
if (utxo.utxo.isP2tr()) {
return key.signTapRoot(txDigest, sighash: sighash);
} else {
return key.signInput(txDigest, sigHash: sighash);
}
});
return PendingBitcoinTransaction(
transaction,
type,
electrumClient: electrumClient,
amount: totalOutAmount,
fee: newFee,
network: network,
hasChange: changeOutput != null,
feeRate: newFee.toString(),
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
await updateBalance();
});
} catch (e) {
throw e;
}
}
Future<ElectrumTransactionBundle> getTransactionExpanded({required String hash}) async {
String transactionHex;
int? time;
int confirmations = 0;
@ -830,8 +1009,12 @@ abstract class ElectrumWalletBase
ins.add(tx);
}
return ElectrumTransactionBundle(original,
ins: ins, time: time, confirmations: confirmations, height: height);
return ElectrumTransactionBundle(
original,
ins: ins,
time: time,
confirmations: confirmations,
);
}
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
@ -841,7 +1024,7 @@ abstract class ElectrumWalletBase
bool? retryOnFailure}) async {
try {
return ElectrumTransactionInfo.fromElectrumBundle(
await getTransactionExpanded(hash: hash, height: height), walletInfo.type, network,
await getTransactionExpanded(hash: hash), walletInfo.type, network,
addresses: myAddresses, height: height);
} catch (e) {
if (e is FormatException && retryOnFailure == true) {

View file

@ -241,6 +241,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final index = _addresses.indexOf(addressRecord);
_addresses.remove(addressRecord);
_addresses.insert(index, addressRecord);
updateAddressesByMatch();
}
@action

View file

@ -8,15 +8,18 @@ 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,
required this.feeRate,
this.network,
required this.hasChange,
required this.isSendAll})
: _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;
@ -27,6 +30,7 @@ class PendingBitcoinTransaction with PendingTransaction {
final BasedUtxoNetwork? network;
final bool hasChange;
final bool isSendAll;
final bool hasTaprootInputs;
@override
String get id => _tx.txId();

View file

@ -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"

View file

@ -26,7 +26,7 @@ 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

View file

@ -28,7 +28,7 @@ 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

31
cw_core/lib/n2_node.dart Normal file
View file

@ -0,0 +1,31 @@
class N2Node {
N2Node({
this.weight,
this.uptime,
this.score,
this.account,
this.alias,
});
String? uptime;
double? weight;
int? score;
String? account;
String? alias;
factory N2Node.fromJson(Map<String, dynamic> json) => N2Node(
weight: double.tryParse((json['weight'] as num?).toString()),
uptime: json['uptime'] as String?,
score: json['score'] as int?,
account: json['rep_address'] as String?,
alias: json['alias'] as String?,
);
Map<String, dynamic> toJson() => <String, dynamic>{
'uptime': uptime,
'weight': weight,
'score': score,
'rep_address': account,
'alias': alias,
};
}

View file

@ -21,6 +21,7 @@ class Node extends HiveObject with Keyable {
this.trusted = false,
this.socksProxyAddress,
String? uri,
String? path,
WalletType? type,
}) {
if (uri != null) {
@ -29,10 +30,14 @@ class Node extends HiveObject with Keyable {
if (type != null) {
this.type = type;
}
if (path != null) {
this.path = path;
}
}
Node.fromMap(Map<String, Object?> map)
: uriRaw = map['uri'] as String? ?? '',
path = map['path'] as String? ?? '',
login = map['login'] as String?,
password = map['password'] as String?,
useSSL = map['useSSL'] as bool?,
@ -63,6 +68,9 @@ class Node extends HiveObject with Keyable {
@HiveField(6)
String? socksProxyAddress;
@HiveField(7, defaultValue: '')
String? path;
bool get isSSL => useSSL ?? false;
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
@ -79,9 +87,9 @@ class Node extends HiveObject with Keyable {
case WalletType.nano:
case WalletType.banano:
if (isSSL) {
return Uri.https(uriRaw, '');
return Uri.https(uriRaw, path ?? '');
} else {
return Uri.http(uriRaw, '');
return Uri.http(uriRaw, path ?? '');
}
case WalletType.ethereum:
case WalletType.polygon:
@ -103,7 +111,8 @@ class Node extends HiveObject with Keyable {
other.typeRaw == typeRaw &&
other.useSSL == useSSL &&
other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress);
other.socksProxyAddress == socksProxyAddress &&
other.path == path);
@override
int get hashCode =>
@ -113,7 +122,8 @@ class Node extends HiveObject with Keyable {
typeRaw.hashCode ^
useSSL.hashCode ^
trusted.hashCode ^
socksProxyAddress.hashCode;
socksProxyAddress.hashCode ^
path.hashCode;
@override
dynamic get keyIndex {

View file

@ -16,6 +16,8 @@ abstract class TransactionInfo extends Object with Keyable {
void changeFiatAmount(String amount);
String? to;
String? from;
List<String>? inputAddresses;
List<String>? outputAddresses;
@override
dynamic get keyIndex => id;

View file

@ -41,5 +41,6 @@ abstract class WalletAddresses {
}
}
bool containsAddress(String address) => allAddressesMap.containsKey(address);
bool containsAddress(String address) =>
addressesMap.containsKey(address) || allAddressesMap.containsKey(address);
}

View file

@ -67,6 +67,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
int calculateEstimatedFee(TransactionPriority priority, int? amount);
// void fetchTransactionsAsync(
// void Function(TransactionType transaction) onTransactionLoaded,
// {void Function() onFinished});

View file

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:cw_core/node.dart';
@ -9,6 +10,7 @@ import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/pending_evm_chain_transaction.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:flutter/services.dart';
import 'package:http/http.dart';
@ -211,26 +213,61 @@ abstract class EVMChainClient {
return EVMChainERC20Balance(balance, exponent: exponent);
}
Future<Erc20Token?> getErc20Token(String contractAddress) async {
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async {
try {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final name = await erc20.name();
final symbol = await erc20.symbol();
final decimal = await erc20.decimals();
final uri = Uri.https(
'deep-index.moralis.io',
'/api/v2.2/erc20/metadata',
{
"chain": chainName,
"addresses": contractAddress,
},
);
final response = await httpClient.get(
uri,
headers: {
"Accept": "application/json",
"X-API-Key": secrets.moralisApiKey,
},
);
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
final name = decodedResponse['name'] ?? '';
final symbol = decodedResponse['symbol'] ?? '';
final decimal = decodedResponse['decimals'] ?? '0';
final iconPath = decodedResponse['logo'] ?? '';
return Erc20Token(
name: name,
symbol: symbol,
contractAddress: contractAddress,
decimal: decimal.toInt(),
decimal: int.tryParse(decimal) ?? 0,
iconPath: iconPath,
);
} catch (e) {
try {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final name = await erc20.name();
final symbol = await erc20.symbol();
final decimal = await erc20.decimals();
return Erc20Token(
name: name,
symbol: symbol,
contractAddress: contractAddress,
decimal: decimal.toInt(),
);
} catch (_) {}
return null;
}
}
Uint8List hexToBytes(String hexString) {
return Uint8List.fromList(hex.HEX.decode(hexString.startsWith('0x') ? hexString.substring(2) : hexString));
return Uint8List.fromList(
hex.HEX.decode(hexString.startsWith('0x') ? hexString.substring(2) : hexString));
}
void stop() {

View file

@ -445,11 +445,16 @@ abstract class EVMChainWalletBase
Future<void> addErc20Token(Erc20Token token) async {
String? iconPath;
try {
iconPath = CryptoCurrency.all
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
if (token.iconPath == null || token.iconPath!.isEmpty) {
try {
iconPath = CryptoCurrency.all
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
} else {
iconPath = token.iconPath;
}
final newToken = createNewErc20TokenObject(token, iconPath);
@ -472,8 +477,8 @@ abstract class EVMChainWalletBase
_updateBalance();
}
Future<Erc20Token?> getErc20Token(String contractAddress) async =>
await _client.getErc20Token(contractAddress);
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async =>
await _client.getErc20Token(contractAddress, chainName);
void _onNewTransaction() {
_updateBalance();

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_core/n2_node.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_transaction_model.dart';
import 'package:http/http.dart' as http;
@ -16,6 +17,8 @@ class NanoClient {
"nano-app": "cake-wallet"
};
static const String N2_REPS_ENDPOINT = "https://rpc.nano.to";
NanoClient() {
SharedPreferences.getInstance().then((value) => prefs = value);
}
@ -418,7 +421,7 @@ class NanoClient {
body: jsonEncode({
"action": "account_history",
"account": address,
"count": "250", // TODO: pick a number
"count": "100",
// "raw": true,
}));
final data = await jsonDecode(response.body);
@ -434,4 +437,37 @@ class NanoClient {
return [];
}
}
Future<List<N2Node>> getN2Reps() async {
final response = await http.post(
Uri.parse(N2_REPS_ENDPOINT),
headers: CAKE_HEADERS,
body: jsonEncode({"action": "reps"}),
);
try {
final List<N2Node> nodes = (json.decode(response.body) as List<dynamic>)
.map((dynamic e) => N2Node.fromJson(e as Map<String, dynamic>))
.toList();
return nodes;
} catch (error) {
return [];
}
}
Future<int> getRepScore(String rep) async {
final response = await http.post(
Uri.parse(N2_REPS_ENDPOINT),
headers: CAKE_HEADERS,
body: jsonEncode({
"action": "rep_info",
"account": rep,
}),
);
try {
final N2Node node = N2Node.fromJson(json.decode(response.body) as Map<String, dynamic>);
return node.score ?? 100;
} catch (error) {
return 100;
}
}
}

View file

@ -13,6 +13,7 @@ import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/nano_account.dart';
import 'package:cw_core/n2_node.dart';
import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_transaction_credentials.dart';
@ -73,9 +74,11 @@ abstract class NanoWalletBase
String? _privateKey;
String? _publicAddress;
String? _hexSeed;
Timer? _receiveTimer;
String? _representativeAddress;
Timer? _receiveTimer;
int repScore = 100;
bool get isRepOk => repScore >= 90;
late final NanoClient _client;
bool _isTransactionUpdating;
@ -442,6 +445,8 @@ abstract class NanoWalletBase
_representativeAddress = await _client.getRepFromPrefs();
throw Exception("Failed to get representative address $e");
}
repScore = await _client.getRepScore(_representativeAddress!);
}
Future<void> regenerateAddress() async {
@ -478,6 +483,10 @@ abstract class NanoWalletBase
}
}
Future<List<N2Node>> getN2Reps() async {
return _client.getN2Reps();
}
Future<void>? updateBalance() async => await _updateBalance();
@override

View file

@ -533,4 +533,21 @@ class SolanaWalletClient {
throw Exception(e);
}
}
Future<String?> getIconImageFromTokenUri(String uri) async {
try {
final response = await httpClient.get(Uri.parse(uri));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300) {
return jsonResponse['image'];
} else {
return null;
}
} catch (e) {
print('Error occurred while fetching token image: \n${e.toString()}');
return null;
}
}
}

View file

@ -472,11 +472,17 @@ abstract class SolanaWalletBase
return null;
}
String? iconPath;
try {
iconPath = await _client.getIconImageFromTokenUri(token.uri);
} catch (_) {}
return SPLToken.fromMetadata(
name: token.name,
mint: token.mint,
symbol: token.symbol,
mintAddress: mintAddress,
iconPath: iconPath,
);
} catch (e) {
return null;

View file

@ -55,6 +55,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
required String mint,
required String symbol,
required String mintAddress,
String? iconPath
}) {
return SPLToken(
name: name,
@ -62,7 +63,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin {
mintAddress: mintAddress,
decimal: 0,
mint: mint,
iconPath: '',
iconPath: iconPath,
);
}

View file

@ -74,22 +74,26 @@ class CWBitcoin extends Bitcoin {
@override
Object createBitcoinTransactionCredentials(List<Output> outputs,
{required TransactionPriority priority, int? feeRate}) =>
BitcoinTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
.toList(),
priority: priority as BitcoinTransactionPriority,
feeRate: feeRate);
{required TransactionPriority priority, int? feeRate}) {
final bitcoinFeeRate =
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
return BitcoinTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
.toList(),
priority: priority as BitcoinTransactionPriority,
feeRate: bitcoinFeeRate
);
}
@override
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
@ -172,8 +176,9 @@ class CWBitcoin extends Bitcoin {
int formatterStringDoubleToBitcoinAmount(String amount) => stringDoubleToBitcoinAmount(amount);
@override
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate) =>
(priority as BitcoinTransactionPriority).labelWithRate(rate);
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate,
{int? customRate}) =>
(priority as BitcoinTransactionPriority).labelWithRate(rate, customRate);
@override
List<BitcoinUnspent> getUnspents(Object wallet) {
@ -199,6 +204,9 @@ class CWBitcoin extends Bitcoin {
@override
TransactionPriority getBitcoinTransactionPriorityMedium() => BitcoinTransactionPriority.medium;
@override
TransactionPriority getBitcoinTransactionPriorityCustom() => BitcoinTransactionPriority.custom;
@override
TransactionPriority getLitecoinTransactionPriorityMedium() => LitecoinTransactionPriority.medium;
@ -239,4 +247,48 @@ class CWBitcoin extends Bitcoin {
return SegwitAddresType.p2wpkh;
}
}
@override
bool hasTaprootInput(PendingTransaction pendingTransaction) {
return (pendingTransaction as PendingBitcoinTransaction).hasTaprootInputs;
}
@override
Future<PendingBitcoinTransaction> replaceByFee(
Object wallet, String transactionHash, String fee) async {
final bitcoinWallet = wallet as ElectrumWallet;
return await bitcoinWallet.replaceByFee(transactionHash, int.parse(fee));
}
@override
Future<bool> canReplaceByFee(Object wallet, String transactionHash) async {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.canReplaceByFee(transactionHash);
}
@override
Future<bool> isChangeSufficientForFee(Object wallet, String txId, String newFee) async {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.isChangeSufficientForFee(txId, int.parse(newFee));
}
@override
int getFeeAmountForPriority(
Object wallet, TransactionPriority priority, int inputsCount, int outputsCount,
{int? size}) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.feeAmountForPriority(
priority as BitcoinTransactionPriority, inputsCount, outputsCount);
}
@override
int getFeeAmountWithFeeRate(Object wallet, int feeRate, int inputsCount, int outputsCount,
{int? size}) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.feeAmountWithFeeRate(
feeRate,
inputsCount,
outputsCount,
);
}
}

View file

@ -155,7 +155,7 @@ class MoonPayProvider extends BuyProvider {
'baseCurrencyAmount': amount ?? '0',
'currencyCode': currencyCode,
'walletAddress': walletAddress,
'lockAmount': 'true',
'lockAmount': 'false',
'showAllCurrencies': 'false',
'showWalletAddressForm': 'false',
'enabledPaymentMethods':
@ -256,44 +256,44 @@ class MoonPayProvider extends BuyProvider {
@override
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
// try {
late final Uri uri;
if (isBuyAction ?? true) {
uri = await requestBuyMoonPayUrl(
currency: wallet.currency,
walletAddress: wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
} else {
uri = await requestSellMoonPayUrl(
currency: wallet.currency,
refundWalletAddress: wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
}
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
try {
late final Uri uri;
if (isBuyAction ?? true) {
uri = await requestBuyMoonPayUrl(
currency: wallet.currency,
walletAddress: wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
uri = await requestSellMoonPayUrl(
currency: wallet.currency,
refundWalletAddress: wallet.walletAddresses.address,
settingsStore: _settingsStore,
);
}
} else {
throw Exception('Could not launch URL');
if (await canLaunchUrl(uri)) {
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
} else {
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
throw Exception('Could not launch URL');
}
} catch (e) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: 'MoonPay',
alertContent: 'The MoonPay service is currently unavailable: $e',
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
// } catch (e) {
// await showDialog<void>(
// context: context,
// builder: (BuildContext context) {
// return AlertWithOneAction(
// alertTitle: 'MoonPay',
// alertContent: 'The MoonPay service is currently unavailable: $e',
// buttonText: S.of(context).ok,
// buttonAction: () => Navigator.of(context).pop(),
// );
// },
// );
// }
}
String _normalizeCurrency(CryptoCurrency currency) {

View file

@ -14,4 +14,13 @@ class FailureState extends ExecutionState {
FailureState(this.error);
final String error;
}
class AwaitingConfirmationState extends ExecutionState {
AwaitingConfirmationState({this.title, this.message, this.onConfirm, this.onCancel});
final String? title;
final String? message;
final Function()? onConfirm;
final Function()? onCancel;
}

View file

@ -8,3 +8,8 @@ class NodeAddressValidator extends TextValidator {
pattern:
'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$|^[0-9a-zA-Z.\-]+\$');
}
class NodePathValidator extends TextValidator {
NodePathValidator()
: super(errorMessage: S.current.error_text_node_address, pattern: '^([/0-9a-zA-Z.\-]+)?\$');
}

View file

@ -2,8 +2,8 @@ import 'solana_chain_service.dart';
enum SolanaChainId {
mainnet,
testnet,
devnet,
// testnet,
// devnet,
}
extension SolanaChainIdX on SolanaChainId {
@ -13,13 +13,16 @@ extension SolanaChainIdX on SolanaChainId {
switch (this) {
case SolanaChainId.mainnet:
name = '4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ';
// solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp
break;
case SolanaChainId.testnet:
name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K';
break;
case SolanaChainId.devnet:
name = '';
break;
// case SolanaChainId.devnet:
// name = '8E9rvCKLFQia2Y35HXjjpWzj8weVo44K';
// // solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1
// break;
// case SolanaChainId.testnet:
// name = '';
// // solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z
// break;
}
return '${SolanaChainServiceImpl.namespace}:$name';

View file

@ -43,7 +43,7 @@ class SolanaChainServiceImpl implements ChainService {
SolanaClient(
rpcUrl: rpcUrl,
websocketUrl: Uri.parse(webSocketUrl),
timeout: const Duration(minutes: 2),
timeout: const Duration(minutes: 5),
) {
for (final String event in getEvents()) {
wallet.registerEventEmitter(chainId: getChainId(), event: event);
@ -72,7 +72,7 @@ class SolanaChainServiceImpl implements ChainService {
@override
List<String> getEvents() {
return [''];
return ['chainChanged', 'accountsChanged'];
}
Future<String?> requestAuthorization(String? text) async {
@ -100,8 +100,7 @@ class SolanaChainServiceImpl implements ChainService {
Future<String> solanaSignTransaction(String topic, dynamic parameters) async {
log('received solana sign transaction request $parameters');
final solanaSignTx =
SolanaSignTransaction.fromJson(parameters as Map<String, dynamic>);
final solanaSignTx = SolanaSignTransaction.fromJson(parameters as Map<String, dynamic>);
final String? authError = await requestAuthorization('Confirm request to sign transaction?');
@ -122,10 +121,13 @@ class SolanaChainServiceImpl implements ChainService {
return '';
}
String signature = sign.signatures.first.toBase58();
String signature = await solanaClient.sendAndConfirmTransaction(
message: message,
signers: [ownerKeyPair!],
commitment: Commitment.confirmed,
);
print(signature);
print(signature.runtimeType);
bottomSheetService.queueBottomSheet(
isModalDismissible: true,

View file

@ -1,10 +1,12 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_id.dart';
import 'package:cake_wallet/core/wallet_connect/chain_service/eth/evm_chain_service.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart';
import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart';
@ -19,6 +21,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:walletconnect_flutter_v2/walletconnect_flutter_v2.dart';
import 'chain_service/solana/solana_chain_id.dart';
@ -32,6 +35,7 @@ class Web3WalletService = Web3WalletServiceBase with _$Web3WalletService;
abstract class Web3WalletServiceBase with Store {
final AppStore appStore;
final SharedPreferences sharedPreferences;
final BottomSheetService _bottomSheetHandler;
final WalletConnectKeyService walletKeyService;
@ -52,7 +56,8 @@ abstract class Web3WalletServiceBase with Store {
@observable
ObservableList<StoredCacao> auth;
Web3WalletServiceBase(this._bottomSheetHandler, this.walletKeyService, this.appStore)
Web3WalletServiceBase(
this._bottomSheetHandler, this.walletKeyService, this.appStore, this.sharedPreferences)
: pairings = ObservableList<PairingInfo>(),
sessions = ObservableList<SessionData>(),
auth = ObservableList<StoredCacao>(),
@ -133,13 +138,27 @@ abstract class Web3WalletServiceBase with Store {
if (appStore.wallet!.type == WalletType.solana) {
for (final cId in SolanaChainId.values) {
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
final rpcUri = node.uri;
final webSocketUri = 'wss://${node.uriRaw}/ws${node.uri.path}';
Uri? rpcUri;
String webSocketUrl;
bool isModifiedNodeUri = false;
if (node.uriRaw == 'rpc.ankr.com') {
isModifiedNodeUri = true;
//A better way to handle this instead of adding this to the general secrets?
String ankrApiKey = secrets.ankrApiKey;
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
} else {
webSocketUrl = 'wss://${node.uriRaw}';
}
SolanaChainServiceImpl(
reference: cId,
rpcUrl: rpcUri,
webSocketUrl: webSocketUri,
rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
webSocketUrl: webSocketUrl,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,
wallet: _web3Wallet,
@ -177,13 +196,6 @@ abstract class Web3WalletServiceBase with Store {
_refreshPairings();
}
@action
void _refreshPairings() {
pairings.clear();
final allPairings = _web3Wallet.pairings.getAll();
pairings.addAll(allPairings);
}
Future<void> _onSessionProposalError(SessionProposalErrorEvent? args) async {
log(args.toString());
}
@ -246,14 +258,37 @@ abstract class Web3WalletServiceBase with Store {
}
}
@action
void _refreshPairings() {
print('Refreshing pairings');
pairings.clear();
final allPairings = _web3Wallet.pairings.getAll();
final keyForWallet = getKeyForStoringTopicsForWallet();
final currentTopicsForWallet = getPairingTopicsForWallet(keyForWallet);
final filteredPairings =
allPairings.where((pairing) => currentTopicsForWallet.contains(pairing.topic)).toList();
pairings.addAll(filteredPairings);
}
void _onPairingCreate(PairingEvent? args) {
log('Pairing Create Event: $args');
}
@action
void _onSessionConnect(SessionConnect? args) {
Future<void> _onSessionConnect(SessionConnect? args) async {
if (args != null) {
log('Session Connected $args');
await savePairingTopicToLocalStorage(args.session.pairingTopic);
sessions.add(args.session);
_refreshPairings();
}
}
@ -321,4 +356,53 @@ abstract class Web3WalletServiceBase with Store {
List<SessionData> getSessionsForPairingInfo(PairingInfo pairing) {
return sessions.where((element) => element.pairingTopic == pairing.topic).toList();
}
String getKeyForStoringTopicsForWallet() {
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
final keyForPairingTopic =
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
return keyForPairingTopic;
}
List<String> getPairingTopicsForWallet(String key) {
// Get the JSON-encoded string from shared preferences
final jsonString = sharedPreferences.getString(key);
// If the string is null, return an empty list
if (jsonString == null) {
return [];
}
// Decode the JSON string to a list of strings
final List<dynamic> jsonList = jsonDecode(jsonString) as List<dynamic>;
// Cast each item to a string
return jsonList.map((item) => item as String).toList();
}
Future<void> savePairingTopicToLocalStorage(String pairingTopic) async {
// Get key specific to the current wallet
final key = getKeyForStoringTopicsForWallet();
// Get all pairing topics attached to this key
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
print(pairingTopicsForWallet);
bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic);
print('Is Pairing Topic Saved: $isPairingTopicAlreadySaved');
if (!isPairingTopicAlreadySaved) {
// Update the list with the most recent pairing topic
pairingTopicsForWallet.add(pairingTopic);
// Convert the list of updated pairing topics to a JSON-encoded string
final jsonString = jsonEncode(pairingTopicsForWallet);
// Save the encoded string to shared preferences
await sharedPreferences.setString(key, jsonString);
}
}
}

View file

@ -14,6 +14,8 @@ import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/nano/nano.dart';
@ -493,6 +495,7 @@ Future<void> setup({
getIt.get<BottomSheetService>(),
getIt.get<WalletConnectKeyService>(),
appStore,
getIt.get<SharedPreferences>()
);
web3WalletService.create();
return web3WalletService;
@ -921,7 +924,8 @@ Future<void> setup({
transactionInfo: transactionInfo,
transactionDescriptionBox: _transactionDescriptionBox,
wallet: wallet,
settingsStore: getIt.get<SettingsStore>());
settingsStore: getIt.get<SettingsStore>(),
sendViewModel: getIt.get<SendViewModel>());
});
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
@ -1144,6 +1148,11 @@ Future<void> setup({
getIt.registerFactory(() => IoniaAccountCardsPage(getIt.get<IoniaAccountViewModel>()));
getIt.registerFactoryParam<RBFDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => RBFDetailsPage(
transactionDetailsViewModel:
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
getIt.registerFactory(() => AnonPayApi(
useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
wallet: getIt.get<AppStore>().wallet!));

View file

@ -1,6 +1,7 @@
import 'dart:io' show Directory, File, Platform;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/core/secure_storage.dart';
@ -211,6 +212,14 @@ Future<void> defaultSettingsMigration(
await changeDefaultBitcoinNode(nodes, sharedPreferences);
break;
case 30:
await disableServiceStatusFiatDisabled(sharedPreferences);
break;
case 31:
await updateNanoNodeList(nodes: nodes);
break;
default:
break;
}
@ -225,6 +234,44 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
}
Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
final nodeList = await loadDefaultNanoNodes();
var listOfNewEndpoints = <String>[
"app.natrium.io",
"rainstorm.city",
"node.somenano.com",
"nanoslo.0x.no",
"www.bitrequest.app",
];
// add new nodes:
for (final node in nodeList) {
if (listOfNewEndpoints.contains(node.uriRaw)) {
await nodes.add(node);
}
}
// update the nautilus node:
final nautilusNode =
nodes.values.firstWhereOrNull((element) => element.uriRaw == "node.perish.co");
if (nautilusNode != null) {
nautilusNode.uriRaw = "node.nautilus.io";
nautilusNode.path = "/api";
nautilusNode.useSSL = true;
await nautilusNode.save();
}
}
Future<void> disableServiceStatusFiatDisabled(SharedPreferences sharedPreferences) async {
final currentFiat = await sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? -1;
if (currentFiat == -1 || currentFiat == FiatApiMode.enabled.raw) {
return;
}
if (currentFiat == FiatApiMode.disabled.raw || currentFiat == FiatApiMode.torOnly.raw) {
await sharedPreferences.setBool(PreferencesKey.disableBulletinKey, true);
}
}
Future<void> _updateMoneroPriority(SharedPreferences sharedPreferences) async {
final currentPriority =
await sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority) ??

View file

@ -42,8 +42,10 @@ class PreferencesKey {
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowRepWarning = 'should_show_rep_warning';
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
static const syncModeKey = 'sync_mode';
static const syncAllKey = 'sync_all';
@ -74,4 +76,7 @@ class PreferencesKey {
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
static const isNewInstall = 'is_new_install';
static const serviceStatusShaKey = 'service_status_sha_key';
static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list';
static String walletConnectPairingTopicsListForWallet(String publicKey) =>
'${PreferencesKey.walletConnectPairingTopicsList}_${publicKey}';
}

View file

@ -132,7 +132,7 @@ class CWEthereum extends Ethereum {
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
final ethereumWallet = wallet as EthereumWallet;
return await ethereumWallet.getErc20Token(contractAddress);
return await ethereumWallet.getErc20Token(contractAddress, 'eth');
}
@override

View file

@ -32,7 +32,17 @@ class TrocadorExchangeProvider extends ExchangeProvider {
'Exolix',
'Godex',
'Exch',
'CoinCraddle'
'CoinCraddle',
'Alfacash',
'LocalMonero',
'XChange',
'NeroSwap',
'Changee',
'BitcoinVN',
'EasyBit',
'WizardSwap',
'Quantex',
'SwapSpace',
];
static const List<CryptoCurrency> _notSupported = [

View file

@ -151,25 +151,26 @@ Future<void> initializeAppConfigs() async {
final unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
powNodes: powNodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 29);
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
powNodes: powNodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
tradesSource: trades,
ordersSource: orders,
unspentCoinsInfoSource: unspentCoinsInfoSource,
// fiatConvertationService: fiatConvertationService,
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 31,
);
}
Future<void> initialSetup(
{required SharedPreferences sharedPreferences,
{required SharedPreferences sharedPreferences,
required Box<Node> nodes,
required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource,

View file

@ -186,6 +186,16 @@ class CWNano extends Nano {
String getRepresentative(Object wallet) {
return (wallet as NanoWallet).representative;
}
@override
Future<List<N2Node>> getN2Reps(Object wallet) async {
return (wallet as NanoWallet).getN2Reps();
}
@override
bool isRepOk(Object wallet) {
return (wallet as NanoWallet).isRepOk;
}
}
class CWNanoUtil extends NanoUtil {

View file

@ -130,7 +130,7 @@ class CWPolygon extends Polygon {
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
final polygonWallet = wallet as PolygonWallet;
return await polygonWallet.getErc20Token(contractAddress);
return await polygonWallet.getErc20Token(contractAddress, 'polygon');
}
@override

View file

@ -16,6 +16,7 @@ bool isWalletConnectCompatibleChain(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.solana:
return true;
default:
return false;

View file

@ -54,6 +54,7 @@ import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart
import 'package:cake_wallet/src/screens/support/support_page.dart';
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
@ -269,6 +270,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) =>
getIt.get<TransactionDetailsPage>(param1: settings.arguments as TransactionInfo));
case Routes.bumpFeePage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) =>
getIt.get<RBFDetailsPage>(param1: settings.arguments as TransactionInfo));
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));

View file

@ -12,6 +12,7 @@ class Routes {
static const dashboard = '/dashboard';
static const send = '/send';
static const transactionDetails = '/transaction_info';
static const bumpFeePage = '/bump_fee_page';
static const receive = '/receive';
static const newSubaddress = '/new_subaddress';
static const walletEdit = '/walletEdit';

View file

@ -87,6 +87,7 @@ class CWSolana extends Solana {
decimal: token.decimals,
mint: token.name.toUpperCase(),
enabled: token.enabled,
iconPath: token.iconPath,
);
await (wallet as SolanaWallet).addSPLToken(splToken);

View file

@ -51,14 +51,25 @@ class DashboardPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
return Scaffold(
body: Observer(
builder: (_) {
final dashboardPageView = _DashboardPageView(
balancePage: balancePage,
bottomSheetService: bottomSheetService,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
final dashboardPageView = RefreshIndicator(
displacement: screenHeight * 0.1,
onRefresh: () async => await dashboardViewModel.refreshDashboard(),
child: SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Container(
height: screenHeight,
child: _DashboardPageView(
balancePage: balancePage,
bottomSheetService: bottomSheetService,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
),
),
),
);
if (DeviceInfo.instance.isDesktop) {
@ -106,10 +117,10 @@ class _DashboardPageView extends BasePage {
Widget leading(BuildContext context) {
return Observer(
builder: (context) {
if (dashboardViewModel.isEnabledBulletinAction) {
return ServicesUpdatesWidget(dashboardViewModel.getServicesStatus());
}
return const SizedBox();
return ServicesUpdatesWidget(
dashboardViewModel.getServicesStatus(),
enabled: dashboardViewModel.isEnabledBulletinAction,
);
},
);
}

View file

@ -107,7 +107,10 @@ class DesktopSidebarWrapper extends BasePage {
: unselectedIconPath,
),
SideMenuItem(
widget: ServicesUpdatesWidget(dashboardViewModel.getServicesStatus()),
widget: ServicesUpdatesWidget(
dashboardViewModel.getServicesStatus(),
enabled: dashboardViewModel.isEnabledBulletinAction,
),
isSelected: desktopSidebarViewModel.currentPage == SidebarItem.status,
onTap: () {},
),

View file

@ -59,6 +59,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
final TextEditingController _tokenNameController = TextEditingController();
final TextEditingController _tokenSymbolController = TextEditingController();
final TextEditingController _tokenDecimalController = TextEditingController();
final TextEditingController _tokenIconPathController = TextEditingController();
final FocusNode _contractAddressFocusNode = FocusNode();
final FocusNode _tokenNameFocusNode = FocusNode();
@ -83,6 +84,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
_tokenNameController.text = widget.token!.name;
_tokenSymbolController.text = widget.token!.title;
_tokenDecimalController.text = widget.token!.decimals.toString();
_tokenIconPathController.text = widget.token?.iconPath ?? '';
}
if (widget.initialContractAddress != null) {
@ -200,6 +202,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
name: _tokenNameController.text,
title: _tokenSymbolController.text.toUpperCase(),
decimals: int.parse(_tokenDecimalController.text),
iconPath: _tokenIconPathController.text,
),
contractAddress: _contractAddressController.text,
);
@ -228,6 +231,8 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (token != null) {
if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.title;
if (_tokenIconPathController.text.isEmpty)
_tokenIconPathController.text = token.iconPath ?? '';
if (_tokenDecimalController.text.isEmpty)
_tokenDecimalController.text = token.decimals.toString();
}
@ -305,10 +310,15 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
if (text?.isEmpty ?? true) {
return S.of(context).field_required;
}
if (int.tryParse(text!) == null) {
return S.of(context).invalid_input;
}
if (int.tryParse(text) == 0) {
return S.current.decimals_cannot_be_zero;
}
return null;
},
),

View file

@ -129,25 +129,29 @@ class HomeSettingsPage extends BasePage {
'token': token,
});
},
leading: CakeImageWidget(
imageUrl: token.iconPath,
height: 40,
width: 40,
displayOnError: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
token.title.substring(0, min(token.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
leading: Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(shape: BoxShape.circle),
child: CakeImageWidget(
imageUrl: token.iconPath,
height: 40,
width: 40,
displayOnError: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
token.title.substring(0, min(token.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
),
),
),
),
),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(30),

View file

@ -163,12 +163,7 @@ class AddressPage extends BasePage {
if (addressListViewModel.hasAddressList) {
return SelectButton(
text: addressListViewModel.buttonTitle,
onTap: () async => dashboardViewModel.isAutoGenerateSubaddressesEnabled &&
(WalletType.monero == addressListViewModel.wallet.type ||
WalletType.haven == addressListViewModel.wallet.type)
? await showPopUp<void>(
context: context, builder: (_) => getIt.get<MoneroAccountListPage>())
: Navigator.of(context).pushNamed(Routes.receive),
onTap: () async => Navigator.of(context).pushNamed(Routes.receive),
textColor: Theme.of(context).extension<SyncIndicatorTheme>()!.textColor,
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
@ -176,17 +171,11 @@ class AddressPage extends BasePage {
textSize: 14,
height: 50,
);
} else if (dashboardViewModel.isAutoGenerateSubaddressesEnabled ||
addressListViewModel.isElectrumWallet) {
return Text(S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor));
} else {
}
else {
return const SizedBox();
}
})
}),
],
),
));

View file

@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/src/widgets/introducing_card.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
@ -183,6 +184,22 @@ class CryptoBalanceWidget extends StatelessWidget {
return Container();
},
),
Observer(builder: (_) {
if (!dashboardViewModel.showRepWarning) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
title: S.current.rep_warning,
subTitle: S.current.rep_warning_sub,
onTap: () => Navigator.of(context).pushNamed(Routes.changeRep),
onClose: () {
dashboardViewModel.settingsStore.shouldShowRepWarning = false;
},
),
);
}),
Observer(
builder: (_) {
return ListView.separated(
@ -323,7 +340,7 @@ class BalanceRowWidget extends StatelessWidget {
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
],
@ -334,24 +351,28 @@ class BalanceRowWidget extends StatelessWidget {
child: Center(
child: Column(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
displayOnError: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(shape: BoxShape.circle),
child: CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
displayOnError: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
),
),
),
),
const SizedBox(height: 10),
Text(
@ -410,9 +431,7 @@ class BalanceRowWidget extends StatelessWidget {
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
color: Theme.of(context).extension<BalancePageTheme>()!.balanceAmountColor,
height: 1,
),
maxLines: 1,

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_ro
import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart';
import 'package:cake_wallet/themes/extensions/placeholder_theme.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
@ -20,6 +21,7 @@ import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
import 'package:intl/intl.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart';
class TransactionsPage extends StatelessWidget {
TransactionsPage({required this.dashboardViewModel});
@ -46,11 +48,17 @@ class TransactionsPage extends StatelessWidget {
return Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
child: DashBoardRoundedCardWidget(
onTap: () => Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [
'',
Uri.parse(
'https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/')
]),
onTap: () {
try {
final uri = Uri.parse(
"https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/");
if (DeviceInfo.instance.isMobile) {
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['', uri]);
} else {
launchUrl(uri);
}
} catch (_) {}
},
title: S.of(context).syncing_wallet_alert_title,
subTitle: S.of(context).syncing_wallet_alert_content,
),

View file

@ -5,10 +5,12 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/address_theme.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/n2_node.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -21,9 +23,7 @@ class NanoChangeRepPage extends BasePage {
: _wallet = wallet,
_settingsStore = settingsStore,
_addressController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
_addressController.text = nano!.getRepresentative(wallet);
}
_formKey = GlobalKey<FormState>() {}
final TextEditingController _addressController;
final WalletBase _wallet;
@ -34,105 +34,314 @@ class NanoChangeRepPage extends BasePage {
@override
String get title => S.current.change_rep;
N2Node getCurrentRepNode(List<N2Node> nodes) {
final currentRepAccount = nano!.getRepresentative(_wallet);
final currentNode = nodes.firstWhere(
(node) => node.account == currentRepAccount,
orElse: () => N2Node(
account: currentRepAccount,
alias: currentRepAccount,
score: 0,
uptime: "???",
weight: 0,
),
);
return currentNode;
}
@override
Widget body(BuildContext context) {
return Form(
key: _formKey,
child: Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Container(
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: AddressTextField(
controller: _addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
_addressController.text = paymentRequest.address;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
],
buttonColor: Theme.of(context).extension<AddressTheme>()!.actionButtonColor,
validator: AddressValidator(type: CryptoCurrency.nano),
child: FutureBuilder(
future: nano!.getN2Reps(_wallet),
builder: (context, snapshot) {
if (snapshot.data == null) {
return SizedBox();
}
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
topSectionPadding: EdgeInsets.only(bottom: 24),
topSection: Column(
children: [
Row(
children: <Widget>[
Expanded(
child: AddressTextField(
controller: _addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
_addressController.text = paymentRequest.address;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
],
buttonColor:
Theme.of(context).extension<AddressTheme>()!.actionButtonColor,
validator: AddressValidator(type: CryptoCurrency.nano),
),
)
],
),
Column(
children: [
Container(
margin: EdgeInsets.only(top: 12),
child: Text(
S.current.nano_current_rep,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
)
],
_buildSingleRepresentative(
context,
getCurrentRepNode(snapshot.data as List<N2Node>),
isList: false,
),
Divider(height: 20),
Container(
margin: EdgeInsets.only(top: 12),
child: Text(
S.current.nano_pick_new_rep,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
],
),
],
),
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
child: Column(
children: _getRepresentativeWidgets(context, snapshot.data as List<N2Node>),
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () => _onSubmit(context),
text: S.of(context).change,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
)),
],
)),
),
);
},
),
);
}
Future<void> _onSubmit(BuildContext context) async {
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
return;
}
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_rep,
alertContent: S.of(context).change_rep_message,
rightButtonText: S.of(context).change,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
try {
_settingsStore.defaultNanoRep = _addressController.text;
await nano!.changeRep(_wallet, _addressController.text);
// reset this flag whenever we successfully change reps:
_settingsStore.shouldShowRepWarning = true;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).successful,
alertContent: S.of(context).change_rep_successful,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
throw e;
}
}
}
List<Widget> _getRepresentativeWidgets(BuildContext context, List<N2Node>? list) {
if (list == null) {
return [];
}
final List<Widget> ret = [];
for (final N2Node node in list) {
if (node.alias != null && node.alias!.trim().isNotEmpty) {
ret.add(_buildSingleRepresentative(context, node));
}
}
return ret;
}
Widget _buildSingleRepresentative(BuildContext context, N2Node rep, {bool isList = true}) {
return Column(
children: <Widget>[
if (isList)
Divider(
height: 2,
),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
),
onPressed: () async {
if (!isList) {
return;
}
_addressController.text = rep.account!;
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsetsDirectional.only(start: 24),
width: MediaQuery.of(context).size.width * 0.50,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_sanitizeAlias(rep.alias),
style: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontWeight: FontWeight.w700,
fontSize: 18,
),
),
Container(
margin: const EdgeInsets.only(top: 7),
child: RichText(
text: TextSpan(
text: "${S.current.voting_weight}: ${rep.weight.toString()}%",
style: TextStyle(
color:
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
fontWeight: FontWeight.w700,
fontSize: 14.0,
),
),
),
),
Container(
margin: const EdgeInsets.only(top: 4),
child: RichText(
text: TextSpan(
text: '',
children: [
TextSpan(
text: "${S.current.uptime}: ",
style: TextStyle(
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
fontWeight: FontWeight.w700,
fontSize: 14,
),
),
TextSpan(
text: rep.uptime,
style: TextStyle(
color: Theme.of(context)
.extension<CakeTextTheme>()!
.secondaryTextColor,
fontWeight: FontWeight.w900,
fontSize: 14,
),
),
],
),
),
),
],
),
),
Container(
margin: const EdgeInsetsDirectional.only(end: 24, start: 14),
child: Stack(
children: <Widget>[
Icon(
Icons.verified,
color: Theme.of(context).primaryColor,
size: 50,
),
Positioned.fill(
child: Container(
margin: EdgeInsets.all(13),
color: Theme.of(context).primaryColor,
),
),
Container(
alignment: const AlignmentDirectional(-0.03, 0.03),
width: 50,
height: 50,
child: Text(
(rep.score).toString(),
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontSize: 13,
fontWeight: FontWeight.w800,
),
),
),
],
),
),
],
),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: LoadingPrimaryButton(
onPressed: () async {
if (_formKey.currentState != null &&
!_formKey.currentState!.validate()) {
return;
}
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).change_rep,
alertContent: S.of(context).change_rep_message,
rightButtonText: S.of(context).change,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.pop(context, true),
actionLeftButton: () => Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
try {
_settingsStore.defaultNanoRep = _addressController.text;
await nano!.changeRep(_wallet, _addressController.text);
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).successful,
alertContent: S.of(context).change_rep_successful,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
} catch (e) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.pop(context));
});
throw e;
}
}
},
text: S.of(context).change,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
)),
],
)),
),
),
],
);
}
String _sanitizeAlias(String? alias) {
if (alias != null) {
return alias.replaceAll(RegExp(r'[^a-zA-Z_.!?_;:-]'), '');
}
return '';
}
}

View file

@ -18,6 +18,7 @@ class NodeCreateOrEditPage extends BasePage {
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_pathController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
@ -49,6 +50,8 @@ class NodeCreateOrEditPage extends BasePage {
_addressController.addListener(
() => nodeCreateOrEditViewModel.address = _addressController.text);
_pathController.addListener(
() => nodeCreateOrEditViewModel.path = _pathController.text);
_portController.addListener(
() => nodeCreateOrEditViewModel.port = _portController.text);
_loginController.addListener(
@ -59,6 +62,7 @@ class NodeCreateOrEditPage extends BasePage {
final GlobalKey<FormState> _formKey;
final TextEditingController _addressController;
final TextEditingController _pathController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;

View file

@ -16,13 +16,15 @@ class NodeForm extends StatelessWidget {
required this.formKey,
this.editingNode,
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
_pathController = TextEditingController(text: editingNode?.path.toString()),
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
_loginController = TextEditingController(text: editingNode?.login),
_passwordController = TextEditingController(text: editingNode?.password),
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress) {
if (editingNode != null) {
nodeViewModel
..setAddress((editingNode!.uri.host.toString()))
..setPath((editingNode!.path.toString()))
..setPort((editingNode!.uri.port.toString()))
..setPassword((editingNode!.password ?? ''))
..setLogin((editingNode!.login ?? ''))
@ -57,10 +59,12 @@ class NodeForm extends StatelessWidget {
});
_addressController.addListener(() => nodeViewModel.address = _addressController.text);
_pathController.addListener(() => nodeViewModel.path = _pathController.text);
_portController.addListener(() => nodeViewModel.port = _portController.text);
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
_socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
_socksAddressController
.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
}
final NodeCreateOrEditViewModel nodeViewModel;
@ -68,6 +72,7 @@ class NodeForm extends StatelessWidget {
final Node? editingNode;
final TextEditingController _addressController;
final TextEditingController _pathController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
@ -91,6 +96,18 @@ class NodeForm extends StatelessWidget {
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _pathController,
hintText: "/path",
validator: NodePathValidator(),
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
@ -103,6 +120,26 @@ class NodeForm extends StatelessWidget {
],
),
SizedBox(height: 10.0),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.useSSL,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) => nodeViewModel.useSSL = value,
caption: S.of(context).use_ssl,
),
)
],
),
),
SizedBox(height: 10.0),
if (nodeViewModel.hasAuthCredentials) ...[
Row(
children: <Widget>[
@ -123,25 +160,6 @@ class NodeForm extends StatelessWidget {
))
],
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
Observer(
builder: (_) => StandardCheckbox(
value: nodeViewModel.useSSL,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) => nodeViewModel.useSSL = value,
caption: S.of(context).use_ssl,
),
)
],
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
@ -163,44 +181,44 @@ class NodeForm extends StatelessWidget {
),
Observer(
builder: (_) => Column(
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.useSocksProxy,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) {
if (!value) {
_socksAddressController.text = '';
}
nodeViewModel.useSocksProxy = value;
},
caption: 'SOCKS Proxy',
),
],
),
),
if (nodeViewModel.useSocksProxy) ...[
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.useSocksProxy,
gradientBackground: true,
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (value) {
if (!value) {
_socksAddressController.text = '';
}
nodeViewModel.useSocksProxy = value;
},
caption: 'SOCKS Proxy',
),
],
),
),
if (nodeViewModel.useSocksProxy) ...[
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _socksAddressController,
hintText: '[<ip>:]<port>',
validator: SocksProxyNodeAddressValidator(),
))
],
),
]
],
)),
],
),
]
],
)),
]
],
),

View file

@ -99,12 +99,7 @@ class ReceivePage extends BasePage {
@override
Widget body(BuildContext context) {
final isElectrumWallet = addressListViewModel.isElectrumWallet;
return (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven ||
addressListViewModel.type == WalletType.nano ||
isElectrumWallet)
? KeyboardActions(
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
@ -213,32 +208,6 @@ class ReceivePage extends BasePage {
})),
],
),
))
: Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Column(
children: [
Expanded(
flex: 7,
child: QRWidget(
formKey: _formKey,
heroTag: _heroTag,
addressListViewModel: addressListViewModel,
amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController,
isLight: currentTheme.type == ThemeType.light),
),
Expanded(
flex: 2,
child: SizedBox(),
),
Text(S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor)),
],
),
);
));
}
}

View file

@ -1,5 +1,6 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@ -81,41 +82,45 @@ class AddressCell extends StatelessWidget {
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: name.isNotEmpty ? MainAxisAlignment.spaceBetween : MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
if (isChange)
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Container(
height: 20,
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: textColor),
alignment: Alignment.center,
child: Text(
S.of(context).unspent_change,
style: TextStyle(
color: backgroundColor,
fontSize: 10,
fontWeight: FontWeight.w600,
Row(
children: [
if (isChange)
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Container(
height: 20,
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: textColor),
alignment: Alignment.center,
child: Text(
S.of(context).unspent_change,
style: TextStyle(
color: backgroundColor,
fontSize: 10,
fontWeight: FontWeight.w600,
),
),
),
),
),
),
if (name.isNotEmpty)
Text(
'$name - ',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: textColor,
),
),
if (name.isNotEmpty)
Text(
'$name',
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: textColor,
),
),
],
),
Flexible(
child: AutoSizeText(
formattedAddress,
responsiveLayoutUtil.shouldRenderTabletUI ? address : formattedAddress,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(

View file

@ -100,7 +100,10 @@ class SendPage extends BasePage {
AppBarStyle get appBarStyle => AppBarStyle.transparent;
double _sendCardHeight(BuildContext context) {
final double initialHeight = sendViewModel.hasCoinControl ? 500 : 465;
double initialHeight = 450;
if (sendViewModel.hasCoinControl) {
initialHeight += 35;
}
if (!responsiveLayoutUtil.shouldRenderMobileUI) {
return initialHeight - 66;
@ -190,7 +193,7 @@ class SendPage extends BasePage {
},
)),
Padding(
padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
padding: EdgeInsets.only(left: 24, right: 24, bottom: 10),
child: Container(
height: 10,
child: Observer(
@ -456,7 +459,7 @@ class SendPage extends BasePage {
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : '';
final newContactMessage = newContactAddress != null
? '\n${S.of(context).add_contact_to_address_book}' : '';
? '\n${S.of(_dialogContext).add_contact_to_address_book}' : '';
final alertContent =
"$successMessage$waitMessage$newContactMessage";

View file

@ -1,16 +1,17 @@
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -456,7 +457,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
Observer(
builder: (_) => GestureDetector(
onTap: sendViewModel.hasFeesPriority
? () => _setTransactionPriority(context)
? () => pickTransactionPriority(context)
: () {},
child: Container(
padding: EdgeInsets.only(top: 24),
@ -669,22 +670,41 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
_effectsInstalled = true;
}
Future<void> _setTransactionPriority(BuildContext context) async {
Future<void> pickTransactionPriority(BuildContext context) async {
final items = priorityForWalletType(sendViewModel.walletType);
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
final customItemIndex = sendViewModel.getCustomPriorityIndex(items);
final isBitcoinWallet = sendViewModel.walletType == WalletType.bitcoin;
double? customFeeRate = isBitcoinWallet ? sendViewModel.customBitcoinFeeRate.toDouble() : null;
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: items,
displayItem: sendViewModel.displayFeeRate,
selectedAtIndex: selectedItem,
title: S.of(context).please_select,
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (TransactionPriority priority) =>
sendViewModel.setTransactionPriority(priority),
),
builder: (BuildContext context) {
int selectedIdx = selectedItem;
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Picker(
items: items,
displayItem: (TransactionPriority priority) =>
sendViewModel.displayFeeRate(priority, customFeeRate?.round()),
selectedAtIndex: selectedIdx,
customItemIndex: customItemIndex,
title: S.of(context).please_select,
headerEnabled: !isBitcoinWallet,
closeOnItemSelected: !isBitcoinWallet,
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: customFeeRate,
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
onItemSelected: (TransactionPriority priority) {
sendViewModel.setTransactionPriority(priority);
setState(() => selectedIdx = items.indexOf(priority));
},
);
},
);
},
);
if (isBitcoinWallet) sendViewModel.customBitcoinFeeRate = customFeeRate!.round();
}
void _presentPicker(BuildContext context) {

View file

@ -2,11 +2,12 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -27,13 +28,23 @@ class OtherSettingsPage extends BasePage {
child: Column(
children: [
if (_otherSettingsViewModel.displayTransactionPriority)
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
_otherSettingsViewModel.walletType == WalletType.bitcoin ?
SettingsPriorityPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayBitcoinPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
customItemIndex: _otherSettingsViewModel.customPriorityItemIndex,
onItemSelected: _otherSettingsViewModel.onDisplayBitcoinPrioritySelected,
customValue: _otherSettingsViewModel.customBitcoinFeeRate,
) :
SettingsPickerCell(
title: S.current.settings_fee_priority,
items: priorityForWalletType(_otherSettingsViewModel.walletType),
displayItem: _otherSettingsViewModel.getDisplayPriority,
selectedItem: _otherSettingsViewModel.transactionPriority,
onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
),
if (_otherSettingsViewModel.changeRepresentativeEnabled)
SettingsCellWithArrow(
title: S.current.change_rep,

View file

@ -55,7 +55,9 @@ class PrivacyPage extends BasePage {
}),
if (_privacySettingsViewModel.isAutoGenerateSubaddressesVisible)
SettingsSwitcherCell(
title: S.current.auto_generate_subaddresses,
title: _privacySettingsViewModel.isMoneroWallet
? S.current.auto_generate_subaddresses
: S.current.auto_generate_addresses,
value: _privacySettingsViewModel.isAutoGenerateSubaddressesEnabled,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setAutoGenerateSubaddresses(value);

View file

@ -0,0 +1,78 @@
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
class SettingsPriorityPickerCell<ItemType> extends StandardListRow {
SettingsPriorityPickerCell(
{required String title,
required this.selectedItem,
required this.items,
this.displayItem,
this.images,
this.searchHintText,
this.isGridView = false,
this.matchingCriteria,
this.customValue,
this.customItemIndex,
this.onItemSelected})
: super(
title: title,
isSelected: false,
onTap: (BuildContext context) async {
var selectedAtIndex = items.indexOf(selectedItem);
double sliderValue = customValue ?? 0.0;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Picker(
items: items,
displayItem: (ItemType item) => displayItem!(item, sliderValue.round()),
selectedAtIndex: selectedAtIndex,
customItemIndex: customItemIndex,
headerEnabled: false,
closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: sliderValue,
onSliderChanged: (double newValue) =>
setState(() => sliderValue = newValue),
onItemSelected: (ItemType priority) {
setState(() => selectedAtIndex = items.indexOf(priority));
onItemSelected?.call(priority, sliderValue);
},
);
},
);
},
);
onItemSelected?.call(items[selectedAtIndex], sliderValue);
});
final ItemType selectedItem;
final List<ItemType> items;
final void Function(ItemType item, double customValue)? onItemSelected;
final String Function(ItemType item, int value)? displayItem;
final List<Image>? images;
final String? searchHintText;
final bool isGridView;
final bool Function(ItemType, String)? matchingCriteria;
double? customValue;
int? customItemIndex;
@override
Widget buildTrailing(BuildContext context) {
return Text(
displayItem?.call(selectedItem,customValue?.round() ?? 0) ?? selectedItem.toString(),
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
),
);
}
}

View file

@ -0,0 +1,23 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
class StandardPickerListItem<T> extends TransactionDetailsListItem {
StandardPickerListItem(
{required String title,
required String value,
required this.items,
required this.displayItem,
required this.onSliderChanged,
required this.onItemSelected,
required this.selectedIdx,
required this.customItemIndex,
required this.customValue})
: super(title: title, value: value);
final List<T> items;
final String Function(T item, double sliderValue) displayItem;
final Function(double) onSliderChanged;
final Function(T) onItemSelected;
final int selectedIdx;
final int customItemIndex;
double customValue;
}

View file

@ -0,0 +1,199 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/standard_expandable_list.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/standard_picker_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class RBFDetailsPage extends BasePage {
RBFDetailsPage({required this.transactionDetailsViewModel});
@override
String get title => S.current.bump_fee;
final TransactionDetailsViewModel transactionDetailsViewModel;
bool _effectsInstalled = false;
@override
Widget body(BuildContext context) {
_setEffects(context);
return Column(
children: [
Expanded(
child: SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.RBFListItems.length,
itemBuilder: (__, index) {
final item = transactionDetailsViewModel.RBFListItems[index];
if (item is StandartListItem) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
},
child: ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is StandardExpandableListItem) {
return StandardExpandableList(
title: '${item.title}: ${item.expandableItems.length}',
expandableItems: item.expandableItems,
);
}
if (item is StandardPickerListItem) {
return StandardPickerList(
title: item.title,
value: item.value,
items: item.items,
displayItem: item.displayItem,
onSliderChanged: item.onSliderChanged,
onItemSelected: item.onItemSelected,
selectedIdx: item.selectedIdx,
customItemIndex: item.customItemIndex,
customValue: item.customValue,
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
return Container();
}),
),
Padding(
padding: const EdgeInsets.all(24),
child: Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
transactionDetailsViewModel
.replaceByFee(transactionDetailsViewModel.newFee.toString());
},
text: S.of(context).send,
isLoading:
transactionDetailsViewModel.sendViewModel.state is IsExecutingState,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
))),
],
);
}
void _setEffects(BuildContext context) {
if (_effectsInstalled) {
return;
}
reaction((_) => transactionDetailsViewModel.sendViewModel.state, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
});
}
if (state is AwaitingConfirmationState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithTwoActions(
alertTitle: state.title ?? '',
alertContent: state.message ?? '',
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
state.onConfirm?.call();
Navigator.of(popupContext).pop();
},
actionLeftButton: () {
state.onCancel?.call();
Navigator.of(popupContext).pop();
});
});
});
}
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return ConfirmSendingAlert(
alertTitle: S.of(popupContext).confirm_sending,
amount: S.of(popupContext).send_amount,
amountValue: transactionDetailsViewModel
.sendViewModel.pendingTransaction!.amountFormatted,
fee: S.of(popupContext).send_fee,
feeValue:
transactionDetailsViewModel.sendViewModel.pendingTransaction!.feeFormatted,
rightButtonText: S.of(popupContext).send,
leftButtonText: S.of(popupContext).cancel,
actionRightButton: () async {
Navigator.of(popupContext).pop();
await transactionDetailsViewModel.sendViewModel.commitTransaction();
// transactionStatePopup();
},
actionLeftButton: () => Navigator.of(popupContext).pop(),
feeFiatAmount:
transactionDetailsViewModel.pendingTransactionFeeFiatAmountFormatted,
fiatAmountValue:
transactionDetailsViewModel.pendingTransactionFiatAmountValueFormatted,
outputs: transactionDetailsViewModel.sendViewModel.outputs);
});
});
}
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(popupContext).sending,
alertContent: S.of(popupContext).transaction_sent,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
}
});
}
});
_effectsInstalled = true;
}
}

View file

@ -1,15 +1,18 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class TransactionDetailsPage extends BasePage {
TransactionDetailsPage({required this.transactionDetailsViewModel});
@ -21,41 +24,62 @@ class TransactionDetailsPage extends BasePage {
@override
Widget body(BuildContext context) {
return SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length,
itemBuilder: (__, index) {
final item = transactionDetailsViewModel.items[index];
return Column(
children: [
Expanded(
child: SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length,
itemBuilder: (__, index) {
final item = transactionDetailsViewModel.items[index];
if (item is StandartListItem) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context,
S.of(context).transaction_details_copied(item.title));
},
child:
ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is StandartListItem) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
},
child: ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is BlockExplorerListItem) {
return GestureDetector(
onTap: item.onTap,
child:
ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is BlockExplorerListItem) {
return GestureDetector(
onTap: item.onTap,
child: ListRow(title: '${item.title}:', value: item.value),
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
return Container();
});
return Container();
}),
),
Observer(
builder: (_) {
if (transactionDetailsViewModel.canReplaceByFee) {
return Padding(
padding: const EdgeInsets.all(24),
child: SelectButton(
text: S.of(context).bump_fee,
onTap: () async {
Navigator.of(context).pushNamed(Routes.bumpFeePage,
arguments: transactionDetailsViewModel.transactionInfo);
},
),
);
}
return const SizedBox();
},
),
],
);
}
}

View file

@ -0,0 +1,7 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
class StandardExpandableListItem<T> extends TransactionDetailsListItem {
StandardExpandableListItem({required String title, required this.expandableItems})
: super(title: title, value: '');
final List<T> expandableItems;
}

View file

@ -30,7 +30,7 @@ class PairingItemWidget extends StatelessWidget {
leading: CakeImageWidget(
imageUrl: metadata.icons.isNotEmpty ? metadata.icons[0]: null,
displayOnError: CircleAvatar(
backgroundImage: AssetImage('assets/images/default_icon.png'),
backgroundImage: AssetImage('assets/images/walletconnect_logo.png'),
),
),
title: Text(

View file

@ -18,7 +18,7 @@ class CakeImageWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
try {
if (imageUrl == null) return _displayOnError!;
if (imageUrl == null || imageUrl!.isEmpty) return _displayOnError!;
if (imageUrl!.contains('assets/images')) {
return Image.asset(

View file

@ -31,7 +31,6 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
height: 24.0,

View file

@ -4,15 +4,15 @@ import 'package:flutter/material.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
class DashBoardRoundedCardWidget extends StatelessWidget {
DashBoardRoundedCardWidget({
required this.onTap,
required this.title,
required this.subTitle,
this.onClose,
});
final VoidCallback onTap;
final VoidCallback? onClose;
final String title;
final String subTitle;
@ -26,7 +26,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
child: Stack(
children: [
Container(
padding: EdgeInsets.all(20),
padding: EdgeInsets.fromLTRB(20, 20, 40, 20),
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
@ -35,32 +35,40 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
),
),
child:
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontSize: 24,
fontWeight: FontWeight.w900,
),
),
SizedBox(height: 5),
Text(
subTitle,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato'),
)
],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontSize: 24,
fontWeight: FontWeight.w900,
),
),
SizedBox(height: 5),
Text(
subTitle,
style: TextStyle(
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato'),
)
],
),
),
if (onClose != null)
Positioned(
top: 10,
right: 10,
child: IconButton(
icon: Icon(Icons.close),
onPressed: onClose,
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
),
),
],
),
);
}
}

View file

@ -24,6 +24,13 @@ class Picker<Item> extends StatefulWidget {
this.isGridView = false,
this.isSeparated = true,
this.hintText,
this.headerEnabled = true,
this.closeOnItemSelected = true,
this.sliderValue,
this.customItemIndex,
this.isWrapped = true,
this.borderColor,
this.onSliderChanged,
this.matchingCriteria,
}) : assert(hintText == null ||
matchingCriteria !=
@ -40,6 +47,13 @@ class Picker<Item> extends StatefulWidget {
final bool isGridView;
final bool isSeparated;
final String? hintText;
final bool headerEnabled;
final bool closeOnItemSelected;
final double? sliderValue;
final int? customItemIndex;
final bool isWrapped;
final Color? borderColor;
final Function(double)? onSliderChanged;
final bool Function(Item, String)? matchingCriteria;
@override
@ -124,8 +138,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
containerHeight = height * 0.75;
}
return PickerWrapperWidget(
hasTitle: widget.title?.isNotEmpty ?? false,
final content = Column (
children: [
if (widget.title?.isNotEmpty ?? false)
Container(
@ -144,61 +157,71 @@ class _PickerState<Item> extends State<Picker<Item>> {
),
Padding(
padding: EdgeInsets.symmetric(horizontal: padding),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container(
color: Theme.of(context).dialogTheme.backgroundColor,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: containerHeight,
maxWidth: ResponsiveLayoutUtilBase.kPopupWidth,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.hintText != null)
Padding(
padding: const EdgeInsets.all(16),
child: SearchBarWidget(searchController: searchController),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: widget.borderColor ?? Colors.transparent,
),
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(30)),
child: Container(
color: Theme.of(context).dialogTheme.backgroundColor,
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: containerHeight,
maxWidth: ResponsiveLayoutUtilBase.kPopupWidth,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.hintText != null)
Padding(
padding: const EdgeInsets.all(16),
child: SearchBarWidget(
searchController: searchController, hintText: widget.hintText),
),
Divider(
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
height: 1,
),
Divider(
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
height: 1,
),
if (widget.selectedAtIndex != -1) buildSelectedItem(widget.selectedAtIndex),
Flexible(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
filteredItems.length > 3
? Scrollbar(
controller: controller,
child: itemsList(),
)
: itemsList(),
(widget.description?.isNotEmpty ?? false)
? Positioned(
bottom: padding,
left: padding,
right: padding,
child: Text(
widget.description!,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
decoration: TextDecoration.none,
color:
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
if (widget.selectedAtIndex != -1 && widget.headerEnabled)
buildSelectedItem(widget.selectedAtIndex),
Flexible(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
filteredItems.length > 3
? Scrollbar(
controller: controller,
child: itemsList(),
)
: itemsList(),
(widget.description?.isNotEmpty ?? false)
? Positioned(
bottom: padding,
left: padding,
right: padding,
child: Text(
widget.description!,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'Lato',
decoration: TextDecoration.none,
color:
Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
),
)
: Offstage(),
],
)
: Offstage(),
],
),
),
),
],
],
),
),
),
),
@ -206,9 +229,23 @@ class _PickerState<Item> extends State<Picker<Item>> {
)
],
);
if (widget.isWrapped) {
return PickerWrapperWidget(
hasTitle: widget.title?.isNotEmpty ?? false,
children: [content],
);
} else {
return content;
}
}
Widget itemsList() {
final itemCount = !widget.headerEnabled
? items.length
: filteredItems.isEmpty
? 0
: filteredItems.length;
return Container(
color: Theme.of(context).extension<PickerTheme>()!.dividerColor,
child: widget.isGridView
@ -216,13 +253,16 @@ class _PickerState<Item> extends State<Picker<Item>> {
padding: EdgeInsets.zero,
controller: controller,
shrinkWrap: true,
itemCount: filteredItems.isEmpty ? 0 : filteredItems.length,
itemCount: itemCount,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 2,
childAspectRatio: 3,
),
itemBuilder: (context, index) => buildItem(index),
itemBuilder: (context, index) =>
!widget.headerEnabled && widget.selectedAtIndex == index
? buildSelectedItem(index)
: buildItem(index),
)
: ListView.separated(
padding: EdgeInsets.zero,
@ -234,83 +274,97 @@ class _PickerState<Item> extends State<Picker<Item>> {
height: 1,
)
: const SizedBox(),
itemCount: filteredItems.isEmpty ? 0 : filteredItems.length,
itemBuilder: (context, index) => buildItem(index),
itemCount: itemCount,
itemBuilder: (context, index) =>
!widget.headerEnabled && widget.selectedAtIndex == index
? buildSelectedItem(index)
: buildItem(index),
),
);
}
Widget buildItem(int index) {
final item = filteredItems[index];
final item = widget.headerEnabled ? filteredItems[index] : items[index];
final tag = item is Currency ? item.tag : null;
final icon = _getItemIcon(item);
final image = images.isNotEmpty ? filteredImages[index] : icon;
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
final itemContent = Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: widget.mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image ?? Offstage(),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: image != null ? 12 : 0),
child: Row(
children: [
Flexible(
child: Text(
widget.displayItem?.call(item) ?? item.toString(),
softWrap: true,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
),
if (tag != null)
Align(
alignment: Alignment.topCenter,
child: Container(
width: 35.0,
height: 18.0,
child: Center(
child: Text(
tag,
style: TextStyle(
fontSize: 7.0,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
),
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
//border: Border.all(color: ),
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
),
),
),
],
),
),
),
],
);
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
if (widget.closeOnItemSelected) Navigator.of(context).pop();
onItemSelected(item!);
},
child: Container(
height: 55,
height: isCustomItem ? 95 : 55,
color: Theme.of(context).dialogTheme.backgroundColor,
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: widget.mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image ?? Offstage(),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: image != null ? 12 : 0),
child: Row(
children: [
Flexible(
child: Text(
widget.displayItem?.call(item) ?? item.toString(),
softWrap: true,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
),
if (tag != null)
Align(
alignment: Alignment.topCenter,
child: Container(
width: 35.0,
height: 18.0,
child: Center(
child: Text(
tag,
style: TextStyle(
fontSize: 7.0,
fontFamily: 'Lato',
color:
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
),
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
//border: Border.all(color: ),
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
),
),
),
],
),
),
),
],
),
child: isCustomItem
? Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
itemContent,
buildSlider(index: index, isActivated: widget.selectedAtIndex == index)
],
)
: itemContent,
),
);
}
@ -323,69 +377,80 @@ class _PickerState<Item> extends State<Picker<Item>> {
final image = images.isNotEmpty ? images[index] : icon;
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
height: 55,
color: Theme.of(context).dialogTheme.backgroundColor,
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: widget.mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image ?? Offstage(),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: image != null ? 12 : 0),
child: Row(
children: [
Flexible(
child: Text(
widget.displayItem?.call(item) ?? item.toString(),
softWrap: true,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w700,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex;
final itemContent = Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: widget.mainAxisAlignment,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image ?? Offstage(),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: image != null ? 12 : 0),
child: Row(
children: [
Flexible(
child: Text(
widget.displayItem?.call(item) ?? item.toString(),
softWrap: true,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w700,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
),
if (tag != null)
Align(
alignment: Alignment.topCenter,
child: Container(
width: 35.0,
height: 18.0,
child: Center(
child: Text(
tag,
style: TextStyle(
fontSize: 7.0,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
),
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
//border: Border.all(color: ),
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
),
),
if (tag != null)
Align(
alignment: Alignment.topCenter,
child: Container(
width: 35.0,
height: 18.0,
child: Center(
child: Text(
tag,
style: TextStyle(
fontSize: 7.0,
fontFamily: 'Lato',
color:
Theme.of(context).extension<CakeScrollbarTheme>()!.thumbColor,
),
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
//border: Border.all(color: ),
color: Theme.of(context).extension<CakeScrollbarTheme>()!.trackColor,
),
),
),
],
),
),
),
],
),
Icon(Icons.check_circle, color: Theme.of(context).primaryColor),
],
),
),
Icon(Icons.check_circle, color: Theme.of(context).primaryColor),
],
);
return GestureDetector(
onTap: () {
if (widget.closeOnItemSelected) Navigator.of(context).pop();
},
child: Container(
height: isCustomItem ? 95 : 55,
color: Theme.of(context).dialogTheme.backgroundColor,
padding: EdgeInsets.symmetric(horizontal: 24),
child: isCustomItem
? Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
itemContent,
buildSlider(index: index, isActivated: widget.selectedAtIndex == index)
],
)
: itemContent,
),
);
}
@ -418,4 +483,20 @@ class _PickerState<Item> extends State<Picker<Item>> {
return null;
}
Widget buildSlider({required int index, required bool isActivated}) {
return Row(
children: <Widget>[
Expanded(
child: Slider(
value: widget.sliderValue ?? 1,
onChanged: isActivated ? widget.onSliderChanged : null,
min: 1,
max: 100,
divisions: 100,
),
),
],
);
}
}

View file

@ -2,16 +2,21 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ScrollableWithBottomSection extends StatefulWidget {
ScrollableWithBottomSection(
{required this.content,
required this.bottomSection,
this.contentPadding,
this.bottomSectionPadding});
ScrollableWithBottomSection({
required this.content,
required this.bottomSection,
this.topSection,
this.contentPadding,
this.bottomSectionPadding,
this.topSectionPadding,
});
final Widget content;
final Widget bottomSection;
final Widget? topSection;
final EdgeInsets? contentPadding;
final EdgeInsets? bottomSectionPadding;
final EdgeInsets? topSectionPadding;
@override
ScrollableWithBottomSectionState createState() => ScrollableWithBottomSectionState();
@ -22,6 +27,12 @@ class ScrollableWithBottomSectionState extends State<ScrollableWithBottomSection
Widget build(BuildContext context) {
return Column(
children: [
if (widget.topSection != null)
Padding(
padding: widget.topSectionPadding?.copyWith(top: 10) ??
EdgeInsets.only(top: 10, bottom: 20, right: 20, left: 20),
child: widget.topSection,
),
Expanded(
child: SingleChildScrollView(
child: Padding(

View file

@ -19,7 +19,7 @@ class SearchBarWidget extends StatelessWidget {
controller: searchController,
style: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
decoration: InputDecoration(
hintText: hintText ?? S.of(context).search_currency,
hintText: hintText ?? S.of(context).search,
hintStyle: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
prefixIcon: Image.asset("assets/images/search_icon.png",
color: Theme.of(context).extension<PickerTheme>()!.searchIconColor),

View file

@ -1,10 +1,13 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/service_status.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/service_status_tile.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -12,8 +15,9 @@ import 'package:url_launcher/url_launcher.dart';
class ServicesUpdatesWidget extends StatefulWidget {
final Future<ServicesResponse> servicesResponse;
final bool enabled;
const ServicesUpdatesWidget(this.servicesResponse, {super.key});
const ServicesUpdatesWidget(this.servicesResponse, {super.key, required this.enabled});
@override
State<ServicesUpdatesWidget> createState() => _ServicesUpdatesWidgetState();
@ -24,6 +28,27 @@ class _ServicesUpdatesWidgetState extends State<ServicesUpdatesWidget> {
@override
Widget build(BuildContext context) {
if (!widget.enabled) {
return InkWell(
onTap: () async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.service_health_disabled,
alertContent: S.current.service_health_disabled_message,
buttonText: S.current.ok,
buttonAction: () => Navigator.of(context).pop(),
);
});
},
child: SvgPicture.asset(
"assets/images/notification_icon.svg",
color: Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
width: 30,
),
);
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: FutureBuilder<ServicesResponse>(

View file

@ -0,0 +1,58 @@
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
import 'package:flutter/material.dart';
class StandardExpandableList<T> extends StatelessWidget {
StandardExpandableList({
required this.title,
required this.expandableItems,
this.decoration,
});
final String title;
final List<T> expandableItems;
final Decoration? decoration;
@override
Widget build(BuildContext context) {
return Container(
decoration: decoration ??
BoxDecoration(
color: Theme.of(context).colorScheme.background,
),
child: Theme(
data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
child: ExpansionTile(
iconColor: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
collapsedIconColor:
Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
title: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
),
textAlign: TextAlign.left,
),
children: expandableItems.map((item) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, bottom: 8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
item.toString(),
maxLines: 1,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
),
),
);
}).toList(),
),
),
);
}
}

View file

@ -0,0 +1,81 @@
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
import 'package:flutter/material.dart';
class StandardPickerList<T> extends StatefulWidget {
StandardPickerList({
Key? key,
required this.title,
required this.value,
required this.items,
required this.displayItem,
required this.onSliderChanged,
required this.onItemSelected,
required this.selectedIdx,
required this.customItemIndex,
required this.customValue,
}) : super(key: key);
final String title;
final List<T> items;
final int customItemIndex;
final String Function(T item, double sliderValue) displayItem;
final Function(double) onSliderChanged;
final Function(T) onItemSelected;
final String value;
final int selectedIdx;
final double customValue;
@override
_StandardPickerListState<T> createState() => _StandardPickerListState<T>();
}
class _StandardPickerListState<T> extends State<StandardPickerList<T>> {
late String value;
late int selectedIdx;
late double customValue;
@override
void initState() {
super.initState();
value = widget.value;
selectedIdx = widget.selectedIdx;
customValue = widget.customValue;
}
@override
Widget build(BuildContext context) {
String adaptedDisplayItem(T item) => widget.displayItem(item, customValue);
return Column(
children: [
ListRow(title: '${widget.title}:', value: value),
Padding(
padding: const EdgeInsets.only(left: 24, right: 24, top: 0, bottom: 24),
child: Picker(
items: widget.items,
displayItem: adaptedDisplayItem,
selectedAtIndex: selectedIdx,
customItemIndex: widget.customItemIndex,
headerEnabled: false,
closeOnItemSelected: false,
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: customValue,
isWrapped: false,
borderColor: Theme.of(context).extension<PickerTheme>()!.dividerColor,
onSliderChanged: (newValue) {
setState(() => customValue = newValue);
value = widget.onSliderChanged(newValue).toString();
},
onItemSelected: (T item) {
setState(() => selectedIdx = widget.items.indexOf(item));
value = widget.onItemSelected(item).toString();
},
),
),
],
);
}
}

View file

@ -79,6 +79,7 @@ abstract class SettingsStoreBase with Store {
required Map<WalletType, Node> nodes,
required Map<WalletType, Node> powNodes,
required this.shouldShowYatPopup,
required this.shouldShowRepWarning,
required this.isBitcoinBuyEnabled,
required this.actionlistDisplayMode,
required this.pinTimeOutDuration,
@ -105,6 +106,7 @@ abstract class SettingsStoreBase with Store {
required this.lookupsUnstoppableDomains,
required this.lookupsOpenAlias,
required this.lookupsENS,
required this.customBitcoinFeeRate,
TransactionPriority? initialBitcoinTransactionPriority,
TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialHavenTransactionPriority,
@ -224,6 +226,9 @@ abstract class SettingsStoreBase with Store {
(bool shouldShowYatPopup) =>
sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup));
reaction((_) => shouldShowRepWarning,
(bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val));
defaultBuyProviders.observe((change) {
final String key = 'buyProvider_${change.key.toString()}';
if (change.newValue != null) {
@ -504,6 +509,11 @@ abstract class SettingsStoreBase with Store {
(PinCodeRequiredDuration pinCodeInterval) => secureStorage.write(
key: SecureKey.pinTimeOutDuration, value: pinCodeInterval.value.toString()));
reaction(
(_) => customBitcoinFeeRate,
(int customBitcoinFeeRate) =>
_sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, customBitcoinFeeRate));
this.nodes.observe((change) {
if (change.newValue != null && change.key != null) {
_saveCurrentNode(change.newValue!, change.key!);
@ -531,6 +541,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool shouldShowYatPopup;
@observable
bool shouldShowRepWarning;
@observable
bool shouldShowMarketPlaceInDashboard;
@ -691,6 +704,9 @@ abstract class SettingsStoreBase with Store {
String deviceName;
@observable
int customBitcoinFeeRate;
final SecureStorage _secureStorage;
final SharedPreferences _sharedPreferences;
final BackgroundTasks _backgroundTasks;
@ -835,6 +851,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
// If no value
if (pinLength == null || pinLength == 0) {
@ -869,6 +886,8 @@ abstract class SettingsStoreBase with Store {
final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
final shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? true;
final generateSubaddresses =
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
@ -1025,74 +1044,77 @@ abstract class SettingsStoreBase with Store {
'';
return SettingsStore(
secureStorage: secureStorage,
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
initialFiatCurrency: currentFiatCurrency,
initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialMoneroSeedType: moneroSeedType,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
initialDisableBulletin: disableBulletin,
initialWalletListOrder: walletListOrder,
initialWalletListAscending: walletListAscending,
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset,
initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
seedPhraseLength: seedPhraseWordCount,
initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
defaultNanoRep: defaultNanoRep,
defaultBananoRep: defaultBananoRep,
lookupsTwitter: lookupsTwitter,
lookupsMastodon: lookupsMastodon,
lookupsYatService: lookupsYatService,
lookupsUnstoppableDomains: lookupsUnstoppableDomains,
lookupsOpenAlias: lookupsOpenAlias,
lookupsENS: lookupsENS,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority,
initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
initialShouldRequireTOTP2FAForSendsToInternalWallets:
shouldRequireTOTP2FAForSendsToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
shouldRequireTOTP2FAForExchangesToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToExternalWallets:
shouldRequireTOTP2FAForExchangesToExternalWallets,
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
initialPolygonTransactionPriority: polygonTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup);
secureStorage: secureStorage,
sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
nodes: nodes,
powNodes: powNodes,
appVersion: packageInfo.version,
deviceName: deviceName,
isBitcoinBuyEnabled: isBitcoinBuyEnabled,
initialFiatCurrency: currentFiatCurrency,
initialBalanceDisplayMode: currentBalanceDisplayMode,
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialMoneroSeedType: moneroSeedType,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
initialDisableBulletin: disableBulletin,
initialWalletListOrder: walletListOrder,
initialWalletListAscending: walletListAscending,
initialFiatMode: currentFiatApiMode,
initialAllowBiometricalAuthentication: allowBiometricalAuthentication,
initialCake2FAPresetOptions: selectedCake2FAPreset,
initialUseTOTP2FA: useTOTP2FA,
initialTotpSecretKey: totpSecretKey,
initialFailedTokenTrial: tokenTrialNumber,
initialExchangeStatus: exchangeStatus,
initialTheme: savedTheme,
actionlistDisplayMode: actionListDisplayMode,
initialPinLength: pinLength,
pinTimeOutDuration: pinCodeTimeOutDuration,
seedPhraseLength: seedPhraseWordCount,
initialLanguageCode: savedLanguageCode,
sortBalanceBy: sortBalanceBy,
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
defaultNanoRep: defaultNanoRep,
defaultBananoRep: defaultBananoRep,
lookupsTwitter: lookupsTwitter,
lookupsMastodon: lookupsMastodon,
lookupsYatService: lookupsYatService,
lookupsUnstoppableDomains: lookupsUnstoppableDomains,
lookupsOpenAlias: lookupsOpenAlias,
lookupsENS: lookupsENS,
customBitcoinFeeRate: customBitcoinFeeRate,
initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority,
initialLitecoinTransactionPriority: litecoinTransactionPriority,
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
initialShouldRequireTOTP2FAForSendsToInternalWallets:
shouldRequireTOTP2FAForSendsToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToInternalWallets:
shouldRequireTOTP2FAForExchangesToInternalWallets,
initialShouldRequireTOTP2FAForExchangesToExternalWallets:
shouldRequireTOTP2FAForExchangesToExternalWallets,
initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts,
initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
initialPolygonTransactionPriority: polygonTransactionPriority,
backgroundTasks: backgroundTasks,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup,
shouldShowRepWarning: shouldShowRepWarning,
);
}
Future<void> reload({required Box<Node> nodeSource}) async {
@ -1160,7 +1182,8 @@ abstract class SettingsStoreBase with Store {
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy;
disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell;
disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
disableBulletin =
sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
walletListOrder =
WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
@ -1187,6 +1210,8 @@ abstract class SettingsStoreBase with Store {
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
shouldShowYatPopup =
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? shouldShowRepWarning;
sortBalanceBy = SortBalanceBy
.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
@ -1201,7 +1226,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true;
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);

View file

@ -46,6 +46,10 @@ abstract class ResponsiveLayoutUtilBase with Store, WidgetsBindingObserver {
(orientation == Orientation.portrait && screenWidth < screenHeight) ||
(orientation == Orientation.landscape && screenWidth < screenHeight);
}
bool get shouldRenderTabletUI {
return screenWidth > _kMobileThreshold && screenWidth < kDesktopMaxDashBoardWidthConstraint;
}
}
_ResponsiveLayoutUtil _singletonResponsiveLayoutUtil = _ResponsiveLayoutUtil();

View file

@ -11,6 +11,7 @@ import 'package:cake_wallet/entities/service_status.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/store/anonpay/anonpay_transactions_store.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/orders_store.dart';
@ -370,6 +371,18 @@ abstract class DashboardViewModelBase with Store {
@computed
bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
bool get showRepWarning {
if (wallet.type != WalletType.nano) {
return false;
}
if (!settingsStore.shouldShowRepWarning) {
return false;
}
return !nano!.isRepOk(wallet);
}
Future<void> reconnect() async {
final node = appStore.settingsStore.getCurrentNode(wallet.type);
await wallet.connectToNode(node: node);
@ -534,4 +547,8 @@ abstract class DashboardViewModelBase with Store {
return ServicesResponse([], false, '');
}
}
Future<void> refreshDashboard() async {
reconnect();
}
}

View file

@ -54,6 +54,7 @@ abstract class HomeSettingsViewModelBase with Store {
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress,
iconPath: token.iconPath,
);
await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token);
@ -65,6 +66,7 @@ abstract class HomeSettingsViewModelBase with Store {
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress,
iconPath: token.iconPath,
);
await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken);
}

View file

@ -12,16 +12,15 @@ import 'package:permission_handler/permission_handler.dart';
part 'node_create_or_edit_view_model.g.dart';
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase
with _$NodeCreateOrEditViewModel;
class NodeCreateOrEditViewModel = NodeCreateOrEditViewModelBase with _$NodeCreateOrEditViewModel;
abstract class NodeCreateOrEditViewModelBase with Store {
NodeCreateOrEditViewModelBase(
this._nodeSource, this._walletType, this._settingsStore)
NodeCreateOrEditViewModelBase(this._nodeSource, this._walletType, this._settingsStore)
: state = InitialExecutionState(),
connectionState = InitialExecutionState(),
useSSL = false,
address = '',
path = '',
port = '',
login = '',
password = '',
@ -35,6 +34,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@observable
String address;
@observable
String path;
@observable
String port;
@ -84,6 +86,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@action
void reset() {
address = '';
path = '';
port = '';
login = '';
password = '';
@ -99,6 +102,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@action
void setAddress(String val) => address = val;
@action
void setPath(String val) => path = val;
@action
void setLogin(String val) => login = val;
@ -121,6 +127,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
final node = Node(
uri: uri,
path: path,
type: _walletType,
login: login,
password: password,
@ -151,6 +158,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
Future<void> connect() async {
final node = Node(
uri: uri,
path: path,
type: _walletType,
login: login,
password: password,
@ -183,7 +191,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
Future<void> scanQRCodeForNewNode(BuildContext context) async {
try {
bool isCameraPermissionGranted =
await PermissionHandler.checkPermission(Permission.camera, context);
await PermissionHandler.checkPermission(Permission.camera, context);
if (!isCameraPermissionGranted) return;
String code = await presentQRScanner();
@ -198,7 +206,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
}
final userInfo = uri.userInfo.split(':');
if (userInfo.length < 2) {
throw Exception('Unexpected scan QR code value: Value is invalid');
}
@ -207,8 +215,11 @@ abstract class NodeCreateOrEditViewModelBase with Store {
final rpcPassword = userInfo[1];
final ipAddress = uri.host;
final port = uri.port.toString();
final path = uri.path;
setAddress(ipAddress);
setPath(path);
setPassword(rpcPassword);
setLogin(rpcUser);
setPort(port);

View file

@ -121,11 +121,19 @@ abstract class OutputBase with Store {
return solana!.getEstimateFees(_wallet) ?? 0.0;
}
final fee = _wallet.calculateEstimatedFee(
int? fee = _wallet.calculateEstimatedFee(
_settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
if (_wallet.type == WalletType.bitcoin ||
_wallet.type == WalletType.litecoin ||
if (_wallet.type == WalletType.bitcoin) {
if (_settingsStore.priority[_wallet.type] == bitcoin!.getBitcoinTransactionPriorityCustom()) {
fee = bitcoin!.getFeeAmountWithFeeRate(
_settingsStore.customBitcoinFeeRate, formattedCryptoAmount, 1, 1);
}
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
}
if (_wallet.type == WalletType.litecoin ||
_wallet.type == WalletType.bitcoinCash) {
return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
}

View file

@ -39,6 +39,7 @@ import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:collection/collection.dart';
part 'send_view_model.g.dart';
@ -68,9 +69,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
_settingsStore = appStore.settingsStore,
fiatFromSettings = appStore.settingsStore.fiatCurrency,
super(appStore: appStore) {
if (wallet.type == WalletType.bitcoin &&
_settingsStore.priority[wallet.type] == bitcoinTransactionPriorityCustom) {
setTransactionPriority(bitcoinTransactionPriorityMedium);
}
final priority = _settingsStore.priority[wallet.type];
final priorities = priorityForWalletType(wallet.type);
if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[wallet.type] = priorities.first;
}
@ -152,6 +156,21 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return priority;
}
int? getCustomPriorityIndex(List<TransactionPriority> priorities) {
if (wallet.type == WalletType.bitcoin) {
final customItem = priorities
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
return customItem != null ? priorities.indexOf(customItem) : null;
}
return null;
}
@computed
int get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate;
void set customBitcoinFeeRate(int value) => _settingsStore.customBitcoinFeeRate = value;
CryptoCurrency get currency => wallet.currency;
Validator<String> get amountValidator =>
@ -308,13 +327,42 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
pendingTransaction = await wallet.createTransaction(_credentials());
if (provider is ThorChainExchangeProvider) {
final outputCount = pendingTransaction?.outputCount ?? 0;
if (outputCount > 10) throw Exception("ThorChain does not support more than 10 outputs");
if (outputCount > 10) {
throw Exception("ThorChain does not support more than 10 outputs");
}
if (_hasTaprootInput(pendingTransaction)) {
throw Exception("ThorChain does not support Taproot addresses");
}
}
state = ExecutedSuccessfullyState();
return pendingTransaction;
} catch (e) {
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
}
return null;
}
@action
Future<void> replaceByFee(String txId, String newFee) async {
state = IsExecutingState();
final isSufficient = await bitcoin!.isChangeSufficientForFee(wallet, txId, newFee);
if (!isSufficient) {
state = AwaitingConfirmationState(
title: S.current.confirm_fee_deduction,
message: S.current.confirm_fee_deduction_content,
onConfirm: () async {
pendingTransaction = await bitcoin!.replaceByFee(wallet, txId, newFee);
state = ExecutedSuccessfullyState();
},
onCancel: () {
state = FailureState('Insufficient change for fee');
});
} else {
pendingTransaction = await bitcoin!.replaceByFee(wallet, txId, newFee);
state = ExecutedSuccessfullyState();
}
}
@action
@ -374,7 +422,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
return bitcoin!.createBitcoinTransactionCredentials(outputs, priority: priority!);
return bitcoin!.createBitcoinTransactionCredentials(outputs,
priority: priority!, feeRate: customBitcoinFeeRate);
case WalletType.monero:
return monero!
@ -400,9 +449,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
}
String displayFeeRate(dynamic priority) {
String displayFeeRate(dynamic priority, int? customValue) {
final _priority = priority as TransactionPriority;
if (walletType == WalletType.bitcoin) {
final rate = bitcoin!.getFeeRate(wallet, _priority);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue);
}
if (isElectrumWallet) {
final rate = bitcoin!.getFeeRate(wallet, _priority);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate);
@ -414,6 +468,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
bool _isEqualCurrency(String currency) =>
wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase());
TransactionPriority get bitcoinTransactionPriorityCustom =>
bitcoin!.getBitcoinTransactionPriorityCustom();
TransactionPriority get bitcoinTransactionPriorityMedium =>
bitcoin!.getBitcoinTransactionPriorityMedium();
@action
void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
@ -443,7 +503,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
address = output.address;
}
if (address.isNotEmpty && !contactAddresses.contains(address)) {
if (address.isNotEmpty &&
!contactAddresses.contains(address) &&
selectedCryptoCurrency.raw != -1) {
return ContactRecord(
contactListViewModel.contactSource,
Contact(
@ -512,4 +574,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return errorMessage;
}
bool _hasTaprootInput(PendingTransaction? pendingTransaction) {
if (walletType == WalletType.bitcoin && pendingTransaction != null) {
return bitcoin!.hasTaprootInput(pendingTransaction);
}
return false;
}
}

View file

@ -11,6 +11,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:collection/collection.dart';
part 'other_settings_view_model.g.dart';
@ -77,6 +78,8 @@ abstract class OtherSettingsViewModelBase with Store {
ProviderType get sellProviderType =>
_settingsStore.defaultSellProviders[walletType] ?? ProviderType.askEachTime;
String getDisplayPriority(dynamic priority) {
final _priority = priority as TransactionPriority;
@ -90,6 +93,19 @@ abstract class OtherSettingsViewModelBase with Store {
return priority.toString();
}
String getDisplayBitcoinPriority(dynamic priority, int customValue) {
final _priority = priority as TransactionPriority;
if (_wallet.type == WalletType.bitcoin ||
_wallet.type == WalletType.litecoin ||
_wallet.type == WalletType.bitcoinCash) {
final rate = bitcoin!.getFeeRate(_wallet, _priority);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue);
}
return priority.toString();
}
String getBuyProviderType(dynamic buyProviderType) {
final _buyProviderType = buyProviderType as ProviderType;
return _buyProviderType == ProviderType.askEachTime
@ -105,7 +121,24 @@ abstract class OtherSettingsViewModelBase with Store {
}
void onDisplayPrioritySelected(TransactionPriority priority) =>
_settingsStore.priority[_wallet.type] = priority;
_settingsStore.priority[walletType] = priority;
void onDisplayBitcoinPrioritySelected(TransactionPriority priority, double customValue) {
if (_wallet.type == WalletType.bitcoin) {
_settingsStore.customBitcoinFeeRate = customValue.round();
}
_settingsStore.priority[_wallet.type] = priority;
}
@computed
double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble();
int? get customPriorityItemIndex {
final priorities = priorityForWalletType(walletType);
final customItem = priorities
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
return customItem != null ? priorities.indexOf(customItem) : null;
}
@action
ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) =>

View file

@ -44,6 +44,8 @@ abstract class PrivacySettingsViewModelBase with Store {
_wallet.type == WalletType.litecoin ||
_wallet.type == WalletType.bitcoinCash;
bool get isMoneroWallet => _wallet.type == WalletType.monero;
@computed
bool get shouldSaveRecipientAddress => _settingsStore.shouldSaveRecipientAddress;

View file

@ -1,20 +1,27 @@
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/blockexplorer_list_item.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_expandable_list_item.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:collection/collection.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:intl/src/intl/date_format.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/monero/monero.dart';
part 'transaction_details_view_model.g.dart';
@ -26,8 +33,11 @@ abstract class TransactionDetailsViewModelBase with Store {
{required this.transactionInfo,
required this.transactionDescriptionBox,
required this.wallet,
required this.settingsStore})
required this.settingsStore,
required this.sendViewModel})
: items = [],
RBFListItems = [],
newFee = 0,
isRecipientAddressShown = false,
showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
final dateFormat = DateFormatter.withCurrentLocal();
@ -38,6 +48,10 @@ abstract class TransactionDetailsViewModelBase with Store {
_addMoneroListItems(tx, dateFormat);
break;
case WalletType.bitcoin:
_addElectrumListItems(tx, dateFormat);
_addBumpFeesListItems(tx);
_checkForRBF();
break;
case WalletType.litecoin:
case WalletType.bitcoinCash:
_addElectrumListItems(tx, dateFormat);
@ -109,10 +123,20 @@ abstract class TransactionDetailsViewModelBase with Store {
final Box<TransactionDescription> transactionDescriptionBox;
final SettingsStore settingsStore;
final WalletBase wallet;
final SendViewModel sendViewModel;
final List<TransactionDetailsListItem> items;
final List<TransactionDetailsListItem> RBFListItems;
bool showRecipientAddress;
bool isRecipientAddressShown;
int newFee;
TransactionPriority? transactionPriority;
@observable
bool _canReplaceByFee = false;
@computed
bool get canReplaceByFee => _canReplaceByFee /*&& transactionInfo.confirmations <= 0*/;
String _explorerUrl(WalletType type, String txId) {
switch (type) {
@ -305,4 +329,88 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addBumpFeesListItems(TransactionInfo tx) {
transactionPriority = bitcoin!.getBitcoinTransactionPriorityMedium();
newFee = bitcoin!.getFeeAmountForPriority(
wallet,
bitcoin!.getBitcoinTransactionPriorityMedium(),
transactionInfo.inputAddresses?.length ?? 1,
transactionInfo.outputAddresses?.length ?? 1);
RBFListItems.add(StandartListItem(
title: S.current.old_fee,
value: tx.feeFormatted() ?? '0.0'));
final priorities = priorityForWalletType(wallet.type);
final selectedItem = priorities.indexOf(sendViewModel.transactionPriority);
final customItem = priorities.firstWhereOrNull(
(element) => element == sendViewModel.bitcoinTransactionPriorityCustom);
final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null;
RBFListItems.add(StandardPickerListItem(
title: S.current.estimated_new_fee,
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}',
items: priorityForWalletType(wallet.type),
customValue: settingsStore.customBitcoinFeeRate.toDouble(),
selectedIdx: selectedItem,
customItemIndex: customItemIndex ?? 0,
displayItem: (dynamic priority, double sliderValue) =>
sendViewModel.displayFeeRate(priority, sliderValue.round()),
onSliderChanged: (double newValue) =>
setNewFee(value: newValue, priority: transactionPriority!),
onItemSelected: (dynamic item) {
transactionPriority = item as TransactionPriority;
return setNewFee(priority: transactionPriority!);
}));
if (transactionInfo.inputAddresses != null) {
RBFListItems.add(StandardExpandableListItem(
title: S.current.inputs, expandableItems: transactionInfo.inputAddresses!));
}
if (transactionInfo.outputAddresses != null) {
RBFListItems.add(StandardExpandableListItem(
title: S.current.outputs, expandableItems: transactionInfo.outputAddresses!));
}
}
@action
Future<void> _checkForRBF() async {
if (wallet.type == WalletType.bitcoin &&
transactionInfo.direction == TransactionDirection.outgoing) {
if (await bitcoin!.canReplaceByFee(wallet, transactionInfo.id)) {
_canReplaceByFee = true;
}
}
}
String setNewFee({double? value, required TransactionPriority priority}) {
newFee = priority == bitcoin!.getBitcoinTransactionPriorityCustom() && value != null
? bitcoin!.getFeeAmountWithFeeRate(
wallet,
value.round(),
transactionInfo.inputAddresses?.length ?? 1,
transactionInfo.outputAddresses?.length ?? 1)
: bitcoin!.getFeeAmountForPriority(
wallet,
priority,
transactionInfo.inputAddresses?.length ?? 1,
transactionInfo.outputAddresses?.length ?? 1);
return bitcoin!.formatterBitcoinAmountToString(amount: newFee);
}
void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo.id, newFee);
@computed
String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled
? ''
: sendViewModel.pendingTransactionFiatAmount + ' ' + sendViewModel.fiat.title;
@computed
String get pendingTransactionFeeFiatAmountFormatted => sendViewModel.isFiatDisabled
? ''
: sendViewModel.pendingTransactionFeeFiatAmount + ' ' + sendViewModel.fiat.title;
}

View file

@ -213,10 +213,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return S.current.addresses;
}
if (isAutoGenerateSubaddressEnabled) {
return hasAccounts ? S.current.accounts : S.current.account;
}
return hasAccounts ? S.current.accounts_subaddresses : S.current.addresses;
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/transaction_direction.dart';
@ -103,7 +104,15 @@ abstract class WalletKeysViewModelBase with Store {
if (_appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin ||
_appStore.wallet!.type == WalletType.bitcoinCash) {
// final keys = bitcoin!.getWalletKeys(_appStore.wallet!);
items.addAll([
// if (keys['wif'] != null)
// StandartListItem(title: "WIF", value: keys['wif']!),
// if (keys['privateKey'] != null)
// StandartListItem(title: S.current.private_key, value: keys['privateKey']!),
// if (keys['publicKey'] != null)
// StandartListItem(title: S.current.public_key, value: keys['publicKey']!),
StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
]);
}

View file

@ -97,7 +97,7 @@ dependencies:
# ref: main
socks5_proxy: ^1.0.4
flutter_svg: ^2.0.9
polyseed: ^0.0.2
polyseed: ^0.0.4
nostr_tools: ^1.0.9
solana: ^0.30.1
bitcoin_base:

View file

@ -59,6 +59,7 @@
"auth_store_incorrect_password": "PIN خطأ",
"authenticated": "تم المصادقة",
"authentication": "المصادقة",
"auto_generate_addresses": "تلقائي توليد العناوين",
"auto_generate_subaddresses": "تلقائي توليد subddresses",
"automatic": "تلقائي",
"available_balance": "الرصيد المتوفر",
@ -79,6 +80,7 @@
"bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.",
"Blocks_remaining": "بلوك متبقي ${status}",
"bright_theme": "مشرق",
"bump_fee": "رسوم عثرة",
"buy": "اشتري",
"buy_alert_content": ".ﺎﻬﻴﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ Monero ﻭﺃ Litecoin ﻭﺃ Ethereum ﻭﺃ Bitcoin ﺔﻈﻔﺤﻣ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .",
"buy_bitcoin": "شراء Bitcoin",
@ -133,6 +135,8 @@
"confirm": "تأكيد",
"confirm_delete_template": "سيؤدي هذا الإجراء إلى حذف هذا القالب. هل ترغب في الاستمرار؟",
"confirm_delete_wallet": "سيؤدي هذا الإجراء إلى حذف هذه المحفظة. هل ترغب في الاستمرار؟",
"confirm_fee_deduction": "تأكيد خصم الرسوم",
"confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟",
"confirm_sending": "تأكيد الإرسال",
"confirmations": "التأكيدات",
"confirmed": "رصيد مؤكد",
@ -172,6 +176,7 @@
"debit_card": "بطاقة ائتمان",
"debit_card_terms": "يخضع تخزين واستخدام رقم بطاقة الدفع الخاصة بك (وبيانات الاعتماد المقابلة لرقم بطاقة الدفع الخاصة بك) في هذه المحفظة الرقمية لشروط وأحكام اتفاقية حامل البطاقة المعمول بها مع جهة إصدار بطاقة الدفع ، كما هو معمول به من وقت لآخر.",
"decimal_places_error": "عدد كبير جدًا من المنازل العشرية",
"decimals_cannot_be_zero": "الرمز العشري لا يمكن أن يكون الصفر.",
"default_buy_provider": "مزود شراء الافتراضي",
"default_sell_provider": "ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ",
"delete": "حذف",
@ -212,6 +217,7 @@
"edit_token": "تحرير الرمز المميز",
"electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل",
"email_address": "عنوان البريد الالكترونى",
"enable_replace_by_fee": "تمكين الاستبدال",
"enabled": "ممكنة",
"enter_amount": "أدخل المبلغ",
"enter_backup_password": "أدخل كلمة المرور الاحتياطية هنا",
@ -249,6 +255,7 @@
"errorGettingCredentials": "ﺩﺎﻤﺘﻋﻻﺍ ﺕﺎﻧﺎﻴﺑ ﻰﻠﻋ ﻝﻮﺼﺤﻟﺍ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ :ﻞﺸﻓ",
"errorSigningTransaction": "ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ ءﺎﻨﺛﺃ ﺄﻄﺧ ﺙﺪﺣ",
"estimated": "مُقدَّر",
"estimated_new_fee": "رسوم جديدة مقدرة",
"etherscan_history": "Etherscan تاريخ",
"event": "ﺙﺪﺣ",
"events": "ﺙﺍﺪﺣﻷﺍ",
@ -315,6 +322,7 @@
"in_store": "في المتجر",
"incoming": "الواردة",
"incorrect_seed": "النص الذي تم إدخاله غير صالح.",
"inputs": "المدخلات",
"introducing_cake_pay": "نقدم لكم Cake Pay!",
"invalid_input": "مدخل غير صالح",
"invalid_password": "رمز مرور خاطئ",
@ -351,6 +359,8 @@
"moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}",
"more_options": "المزيد من الخيارات",
"name": "ﻢﺳﺍ",
"nano_current_rep": "الممثل الحالي",
"nano_pick_new_rep": "اختر ممثلًا جديدًا",
"narrow": "ضيق",
"new_first_wallet_text": "حافظ بسهولة على أمان العملة المشفرة",
"new_node_testing": "تجربة العقدة الجديدة",
@ -383,6 +393,7 @@
"offer_expires_in": "ينتهي العرض في:",
"offline": "غير متصل على الانترنت",
"ok": "حسناً",
"old_fee": "الرسوم القديمة",
"onion_link": "رابط البصل",
"online": "متصل",
"onramper_option_description": "شراء بسرعة التشفير مع العديد من طرق الدفع. متوفر في معظم البلدان. ينتشر وتختلف الرسوم.",
@ -399,6 +410,7 @@
"outdated_electrum_wallet_description": "محافظ Bitcoin الجديدة التي تم إنشاؤها في Cake الآن سييد مكونة من 24 كلمة. من الضروري أن تقوم بإنشاء محفظة Bitcoin جديدة وتحويل جميع أموالك إلى المحفظة الجديدة المكونة من 24 كلمة ، والتوقف عن استخدام محافظ سييد مكونة من 12 كلمة. يرجى القيام بذلك على الفور لتأمين أموالك.",
"outdated_electrum_wallet_receive_warning": "إذا كانت هذه المحفظة تحتوي على سييد مكونة من 12 كلمة وتم إنشاؤها في Cake ، فلا تقم بإيداع Bitcoin في هذه المحفظة. قد يتم فقد أي BTC تم تحويله إلى هذه المحفظة. قم بإنشاء محفظة جديدة مكونة من 24 كلمة (انقر فوق القائمة في الجزء العلوي الأيمن ، وحدد محافظ ، واختر إنشاء محفظة جديدة ، ثم حدد Bitcoin) وقم على الفور بنقل BTC الخاص بك هناك. محافظ BTC الجديدة (24 كلمة) من Cake آمنة",
"outgoing": "الصادره",
"outputs": "المخرجات",
"overwrite_amount": "تغير المبلغ",
"pairingInvalidEvent": "ﺢﻟﺎﺻ ﺮﻴﻏ ﺙﺪﺣ ﻥﺍﺮﻗﺇ",
"password": "كلمة المرور",
@ -457,6 +469,8 @@
"remove_node": "إزالة العقدة",
"remove_node_message": "هل أنت متأكد أنك تريد إزالة العقدة المحددة؟",
"rename": "إعادة تسمية",
"rep_warning": "تحذير تمثيلي",
"rep_warning_sub": "لا يبدو أن ممثلك في وضع جيد. اضغط هنا لاختيار واحدة جديدة",
"repeat_wallet_password": "كرر كلمة مرور المحفظة",
"repeated_password_is_incorrect": "كلمة المرور المتكررة غير صحيحة. يرجى تكرار كلمة مرور المحفظة مرة أخرى.",
"require_for_adding_contacts": "تتطلب إضافة جهات اتصال",
@ -573,6 +587,8 @@
"send_your_wallet": "محفظتك",
"sending": "يتم الإرسال",
"sent": "تم الأرسال",
"service_health_disabled": "تم تعطيل نشرة صحة الخدمة",
"service_health_disabled_message": "هذه هي صفحة نشرة صحة الخدمة ، يمكنك تمكين هذه الصفحة ضمن الإعدادات -> الخصوصية",
"settings": "إعدادات",
"settings_all": "الكل",
"settings_allow_biometrical_authentication": "السماح بالمصادقة البيومترية",
@ -737,6 +753,7 @@
"unspent_coins_details_title": "تفاصيل العملات الغير المنفقة",
"unspent_coins_title": "العملات الغير المنفقة",
"unsupported_asset": ".ﻡﻮﻋﺪﻣ ﻞﺻﺃ ﻉﻮﻧ ﻦﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .ﻞﺻﻷﺍ ﺍﺬﻬﻟ ءﺍﺮﺟﻹﺍ ﺍﺬﻫ ﻢﻋﺪﻧ ﻻ ﻦﺤﻧ",
"uptime": "مدة التشغيل",
"upto": "حتى ${value}",
"use": "التبديل إلى",
"use_card_info_three": "استخدم البطاقة الرقمية عبر الإنترنت أو مع طرق الدفع غير التلامسية.",
@ -753,6 +770,7 @@
"view_key_private": "مفتاح العرض (خاص)",
"view_key_public": "مفتاح العرض (عام)",
"view_transaction_on": "عرض العملية على",
"voting_weight": "وزن التصويت",
"waitFewSecondForTxUpdate": "ﺕﻼﻣﺎﻌﻤﻟﺍ ﻞﺠﺳ ﻲﻓ ﺔﻠﻣﺎﻌﻤﻟﺍ ﺲﻜﻌﻨﺗ ﻰﺘﺣ ﻥﺍﻮﺛ ﻊﻀﺒﻟ ﺭﺎﻈﺘﻧﻻﺍ ﻰﺟﺮﻳ",
"wallet_keys": "سييد المحفظة / المفاتيح",
"wallet_list_create_new_wallet": "إنشاء محفظة جديدة",

View file

@ -59,6 +59,7 @@
"auth_store_incorrect_password": "Грешен PIN",
"authenticated": "Удостоверено",
"authentication": "Удостоверяване",
"auto_generate_addresses": "Автоматично генериране на адреси",
"auto_generate_subaddresses": "Автоматично генериране на подадреси",
"automatic": "Автоматично",
"available_balance": "Наличен баланс",
@ -79,6 +80,7 @@
"bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.",
"Blocks_remaining": "${status} оставащи блока",
"bright_theme": "Ярко",
"bump_fee": "Такса за бум",
"buy": "Купуване",
"buy_alert_content": "В момента поддържаме само закупуването на Bitcoin, Ethereum, Litecoin и Monero. Моля, създайте или превключете към своя портфейл Bitcoin, Ethereum, Litecoin или Monero.",
"buy_bitcoin": "Купуване на Bitcoin",
@ -133,6 +135,8 @@
"confirm": "Потвърждаване",
"confirm_delete_template": "Този шаблон ще бъде изтрит. Искате ли да продължите?",
"confirm_delete_wallet": "Този портфейл ще бъде изтрит. Искате ли да продължите?",
"confirm_fee_deduction": "Потвърдете приспадането на таксите",
"confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?",
"confirm_sending": "Потвърждаване на изпращането",
"confirmations": "потвърждения",
"confirmed": "Потвърден баланс",
@ -172,6 +176,7 @@
"debit_card": "Дебитна карта",
"debit_card_terms": "Съхранението и използването на данните от вашата платежна карта в този дигитален портфейл подлежат на условията на съответното съгласие за картодържец от издателя на картата.",
"decimal_places_error": "Твърде много знаци след десетичната запетая",
"decimals_cannot_be_zero": "Десетичната точка не може да бъде нула.",
"default_buy_provider": "Доставчик по подразбиране купува",
"default_sell_provider": "Доставчик за продажба по подразбиране",
"delete": "Изтрий",
@ -212,6 +217,7 @@
"edit_token": "Редактиране на токена",
"electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят",
"email_address": "Имейл адрес",
"enable_replace_by_fee": "Активиране на замяна по забрана",
"enabled": "Активирано",
"enter_amount": "Въведете сума",
"enter_backup_password": "Въведете парола за възстановяване",
@ -249,6 +255,7 @@
"errorGettingCredentials": "Неуспешно: Грешка при получаване на идентификационни данни",
"errorSigningTransaction": "Възникна грешка при подписване на транзакция",
"estimated": "Изчислено",
"estimated_new_fee": "Прогнозна нова такса",
"etherscan_history": "История на Etherscan",
"event": "Събитие",
"events": "събития",
@ -315,6 +322,7 @@
"in_store": "In Store",
"incoming": "Входящи",
"incorrect_seed": "Въведеният текст е невалиден.",
"inputs": "Входове",
"introducing_cake_pay": "Запознайте се с Cake Pay!",
"invalid_input": "Невалиден вход",
"invalid_password": "Невалидна парола",
@ -351,6 +359,8 @@
"moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}",
"more_options": "Още настройки",
"name": "Име",
"nano_current_rep": "Настоящ представител",
"nano_pick_new_rep": "Изберете нов представител",
"narrow": "Тесен",
"new_first_wallet_text": "Лесно пазете криптовалутата си в безопасност",
"new_node_testing": "Тестване на нов node",
@ -383,6 +393,7 @@
"offer_expires_in": "Предложението изтича след: ",
"offline": "Офлайн",
"ok": "Ок",
"old_fee": "Стара такса",
"onion_link": "Лукова връзка",
"online": "Онлайн",
"onramper_option_description": "Бързо купувайте криптовалута с много методи за плащане. Предлага се в повечето страни. Разпространенията и таксите варират.",
@ -399,6 +410,7 @@
"outdated_electrum_wallet_description": "Нови Bitcoin портфейли, създадени в Cake, сега имат seed от 24 думи. Трябва да създадете нов Bitcoin адрес и да прехвърлите всичките си средства в него и веднага да спрете използването на стари портфейли. Моля, напревете това незабавно, за да подсигурите средствата си.",
"outdated_electrum_wallet_receive_warning": "Ако този адрес има seed от 12 думи и е създаден чрез Cake, НЕ добавяйте Bitcoin в него. Всякакъв Bitcoin, изпратен на този адрес, може да бъде загубен завинаги. Създайте нов портфейл от 24 думи (натиснете менюто горе, вдясно, изберете Портфейли, изберете Създаване на нов портфейл, след това изберете Bitcoin) и НЕЗАБАВНО преместете своя Bitcoin там. Нови (такива с 24 думи) Bitcoin портфейли от Cake са надеждни",
"outgoing": "Изходящи",
"outputs": "Изходи",
"overwrite_amount": "Промени сума",
"pairingInvalidEvent": "Невалидно събитие при сдвояване",
"password": "Парола",
@ -457,6 +469,8 @@
"remove_node": "Премахни node",
"remove_node_message": "Сигурни ли сте, че искате да премахнете избрания node?",
"rename": "Промяна на името",
"rep_warning": "Представително предупреждение",
"rep_warning_sub": "Вашият представител изглежда не е в добро състояние. Докоснете тук, за да изберете нов",
"repeat_wallet_password": "Повторете паролата на портфейла",
"repeated_password_is_incorrect": "Многократната парола е неправилна. Моля, повторете отново паролата за портфейла.",
"require_for_adding_contacts": "Изисква се за добавяне на контакти",
@ -573,6 +587,8 @@
"send_your_wallet": "Вашият портфейл",
"sending": "Изпращане",
"sent": "Изпратени",
"service_health_disabled": "Service Health Bulletin е деактивиран",
"service_health_disabled_message": "Това е страницата на Bulletin на Service Health, можете да активирате тази страница в Настройки -> Поверителност",
"settings": "Настройки",
"settings_all": "Всичко",
"settings_allow_biometrical_authentication": "Позволяване на биометрично удостоверяване.",
@ -737,6 +753,7 @@
"unspent_coins_details_title": "Подробности за неизползваните монети",
"unspent_coins_title": "Неизползвани монети",
"unsupported_asset": "Не поддържаме това действие за този актив. Моля, създайте или преминете към портфейл от поддържан тип актив.",
"uptime": "Време за работа",
"upto": "до ${value}",
"use": "Смяна на ",
"use_card_info_three": "Използвайте дигиталната карта онлайн или чрез безконтактен метод на плащане.",
@ -753,6 +770,7 @@
"view_key_private": "View key (таен)",
"view_key_public": "View key (публичен)",
"view_transaction_on": "Вижте транзакция на ",
"voting_weight": "Тегло на гласуване",
"waitFewSecondForTxUpdate": "Моля, изчакайте няколко секунди, докато транзакцията се отрази в историята на транзакциите",
"wallet_keys": "Seed/keys на портфейла",
"wallet_list_create_new_wallet": "Създаване на нов портфейл",

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